Compare commits
71
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d83fccb4f | ||
|
|
4dde694191 | ||
|
|
7d17d68971 | ||
|
|
345f1cd9f6 | ||
|
|
8d5f2a7570 | ||
|
|
5f20161cf3 | ||
|
|
05838b4688 | ||
|
|
223d1a2da1 | ||
|
|
7f7ac2141e | ||
|
|
db1604cd60 | ||
|
|
cc1fd25295 | ||
|
|
ee99e42be4 | ||
|
|
0ef167c907 | ||
|
|
248a780fd3 | ||
|
|
80f630be21 | ||
|
|
353801cde4 | ||
|
|
c8a3159df4 | ||
|
|
48fdfebab6 | ||
|
|
6eb194ddd7 | ||
|
|
7e278ef797 | ||
|
|
6a5ea68e9e | ||
|
|
31454ebde5 | ||
|
|
5e4eb851dd | ||
|
|
dd11d33deb | ||
|
|
c4bf37e0b0 | ||
|
|
e9dbd28e2a | ||
|
|
92230a5963 | ||
|
|
48b3aa20ae | ||
|
|
f1372d6e14 | ||
|
|
63087cd74b | ||
|
|
9df69dcd14 | ||
|
|
95a0638043 | ||
|
|
1790a9d77d | ||
|
|
1b16ded6a5 | ||
|
|
ca83f87a66 | ||
|
|
2817466c55 | ||
|
|
e389e44a35 | ||
|
|
b1fdc94193 | ||
|
|
61781984db | ||
|
|
857efecf91 | ||
|
|
99837b0d36 | ||
|
|
b34c7bd463 | ||
|
|
f3467f56c1 | ||
|
|
5be5335661 | ||
|
|
af29bd3193 | ||
|
|
f72ca1ac6f | ||
|
|
4d45fe11d1 | ||
|
|
4a25ab2a04 | ||
|
|
95200ebb50 | ||
|
|
8e94512568 | ||
|
|
d40d148feb | ||
|
|
f9856029d5 | ||
|
|
a3a254550b | ||
|
|
533dedf059 | ||
|
|
54c26b7340 | ||
|
|
f5436d1dab | ||
|
|
3550eeafcd | ||
|
|
316c2c9448 | ||
|
|
98b4465f7d | ||
|
|
8342e1240b | ||
|
|
0b6ab25367 | ||
|
|
742886578d | ||
|
|
2f8e8c7396 | ||
|
|
069e885d83 | ||
|
|
bfb15f77bb | ||
|
|
c6a7b922f8 | ||
|
|
d06c61f310 | ||
|
|
91a2f46297 | ||
|
|
520d4b288f | ||
|
|
4b6542dd17 | ||
|
|
c1a77fd6fa |
No files matched your search
@@ -1,15 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
|
||||
[Makefile*]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[*.{c,h}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
@@ -1,8 +0,0 @@
|
||||
# GDB may have ./.gdbinit loading disabled by default. In that case you can
|
||||
# follow the instructions it prints. They boil down to adding the following to
|
||||
# your home directory's ~/.gdbinit file:
|
||||
#
|
||||
# add-auto-load-safe-path /path/to/qemu/.gdbinit
|
||||
|
||||
# Load QEMU-specific sub-commands and settings
|
||||
source scripts/qemu-gdb.py
|
||||
+14
-33
@@ -6,12 +6,18 @@
|
||||
/config.status
|
||||
/config-temp
|
||||
/trace-events-all
|
||||
/trace/generated-tracers.h
|
||||
/trace/generated-tracers.c
|
||||
/trace/generated-tracers-dtrace.h
|
||||
/trace/generated-tracers.dtrace
|
||||
/trace/generated-events.h
|
||||
/trace/generated-events.c
|
||||
/trace/generated-helpers-wrappers.h
|
||||
/trace/generated-helpers.h
|
||||
/trace/generated-helpers.c
|
||||
/trace/generated-tcg-tracers.h
|
||||
/trace/generated-ust-provider.h
|
||||
/trace/generated-ust.c
|
||||
/ui/shader/texture-blit-frag.h
|
||||
/ui/shader/texture-blit-vert.h
|
||||
*-timestamp
|
||||
@@ -33,8 +39,9 @@
|
||||
/qmp-introspect.[ch]
|
||||
/qmp-marshal.c
|
||||
/qemu-doc.html
|
||||
/qemu-tech.html
|
||||
/qemu-doc.info
|
||||
/qemu-doc.txt
|
||||
/qemu-tech.info
|
||||
/qemu-img
|
||||
/qemu-nbd
|
||||
/qemu-options.def
|
||||
@@ -46,16 +53,14 @@
|
||||
/qemu-bridge-helper
|
||||
/qemu-monitor.texi
|
||||
/qemu-monitor-info.texi
|
||||
/qemu-version.h
|
||||
/qemu-version.h.tmp
|
||||
/module_block.h
|
||||
/qmp-commands.txt
|
||||
/vscclient
|
||||
/vhost-user-scsi
|
||||
/fsdev/virtfs-proxy-helper
|
||||
*.[1-9]
|
||||
*.a
|
||||
*.aux
|
||||
*.cp
|
||||
*.dvi
|
||||
*.exe
|
||||
*.msi
|
||||
*.dll
|
||||
@@ -77,6 +82,10 @@
|
||||
*.d
|
||||
!/scripts/qemu-guest-agent/fsfreeze-hook.d
|
||||
*.o
|
||||
*.lo
|
||||
*.la
|
||||
*.pc
|
||||
.libs
|
||||
.sdk
|
||||
*.gcda
|
||||
*.gcno
|
||||
@@ -100,37 +109,9 @@
|
||||
/pc-bios/optionrom/kvmvapic.img
|
||||
/pc-bios/s390-ccw/s390-ccw.elf
|
||||
/pc-bios/s390-ccw/s390-ccw.img
|
||||
/docs/interop/qemu-ga-qapi.texi
|
||||
/docs/interop/qemu-ga-ref.html
|
||||
/docs/interop/qemu-ga-ref.info*
|
||||
/docs/interop/qemu-ga-ref.txt
|
||||
/docs/interop/qemu-qmp-qapi.texi
|
||||
/docs/interop/qemu-qmp-ref.html
|
||||
/docs/interop/qemu-qmp-ref.info*
|
||||
/docs/interop/qemu-qmp-ref.txt
|
||||
/docs/version.texi
|
||||
*.tps
|
||||
.stgit-*
|
||||
cscope.*
|
||||
tags
|
||||
TAGS
|
||||
docker-src.*
|
||||
*~
|
||||
*.ast_raw
|
||||
*.depend_raw
|
||||
trace.h
|
||||
trace.c
|
||||
trace-ust.h
|
||||
trace-ust.h
|
||||
trace-dtrace.h
|
||||
trace-dtrace.dtrace
|
||||
trace-root.h
|
||||
trace-root.c
|
||||
trace-ust-root.h
|
||||
trace-ust-root.h
|
||||
trace-ust-all.h
|
||||
trace-ust-all.c
|
||||
trace-dtrace-root.h
|
||||
trace-dtrace-root.dtrace
|
||||
trace-ust-all.h
|
||||
trace-ust-all.c
|
||||
@@ -31,9 +31,3 @@
|
||||
[submodule "roms/u-boot"]
|
||||
path = roms/u-boot
|
||||
url = git://git.qemu-project.org/u-boot.git
|
||||
[submodule "roms/skiboot"]
|
||||
path = roms/skiboot
|
||||
url = git://git.qemu.org/skiboot.git
|
||||
[submodule "roms/QemuMacDrivers"]
|
||||
path = roms/QemuMacDrivers
|
||||
url = git://git.qemu.org/QemuMacDrivers.git
|
||||
@@ -1,49 +0,0 @@
|
||||
language: c
|
||||
git:
|
||||
submodules: false
|
||||
env:
|
||||
global:
|
||||
- LC_ALL=C
|
||||
matrix:
|
||||
- IMAGE=debian-amd64
|
||||
TARGET_LIST=x86_64-softmmu,x86_64-linux-user
|
||||
- IMAGE=debian-win32-cross
|
||||
TARGET_LIST=arm-softmmu,i386-softmmu,lm32-softmmu
|
||||
- IMAGE=debian-win64-cross
|
||||
TARGET_LIST=aarch64-softmmu,sparc64-softmmu,x86_64-softmmu
|
||||
- IMAGE=debian-armel-cross
|
||||
TARGET_LIST=arm-softmmu,arm-linux-user,armeb-linux-user
|
||||
- IMAGE=debian-armhf-cross
|
||||
TARGET_LIST=arm-softmmu,arm-linux-user,armeb-linux-user
|
||||
- IMAGE=debian-arm64-cross
|
||||
TARGET_LIST=aarch64-softmmu,aarch64-linux-user
|
||||
- IMAGE=debian-s390x-cross
|
||||
TARGET_LIST=s390x-softmmu,s390x-linux-user
|
||||
- IMAGE=debian-mips-cross
|
||||
TARGET_LIST=mips-softmmu,mipsel-linux-user
|
||||
- IMAGE=debian-mips64el-cross
|
||||
TARGET_LIST=mips64el-softmmu,mips64el-linux-user
|
||||
- IMAGE=debian-powerpc-cross
|
||||
TARGET_LIST=ppc-softmmu,ppcemb-softmmu,ppc-linux-user
|
||||
- IMAGE=debian-ppc64el-cross
|
||||
TARGET_LIST=ppc64-softmmu,ppc64-linux-user,ppc64abi32-linux-user
|
||||
build:
|
||||
pre_ci:
|
||||
- make docker-image-${IMAGE} V=1
|
||||
pre_ci_boot:
|
||||
image_name: qemu
|
||||
image_tag: ${IMAGE}
|
||||
pull: false
|
||||
options: "-e HOME=/root"
|
||||
ci:
|
||||
- unset CC
|
||||
# some targets require newer up to date packages, for example TARGET_LIST matching
|
||||
# aarch64*-softmmu|arm*-softmmu|ppc*-softmmu|microblaze*-softmmu|mips64el-softmmu)
|
||||
# see the configure script:
|
||||
# error_exit "DTC (libfdt) version >= 1.4.2 not present. Your options:"
|
||||
# " (1) Preferred: Install the DTC (libfdt) devel package"
|
||||
# " (2) Fetch the DTC submodule, using:"
|
||||
# " git submodule update --init dtc"
|
||||
- dpkg --compare-versions `dpkg-query --showformat='${Version}' --show libfdt-dev` ge 1.4.2 || git submodule update --init dtc
|
||||
- ./configure ${QEMU_CONFIGURE_OPTS} --target-list=${TARGET_LIST}
|
||||
- make -j$(($(getconf _NPROCESSORS_ONLN) + 1))
|
||||
+4
-101
@@ -4,11 +4,11 @@ python:
|
||||
- "2.4"
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
cache: ccache
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# Build dependencies
|
||||
- libaio-dev
|
||||
- libattr1-dev
|
||||
- libbrlapi-dev
|
||||
@@ -46,7 +46,6 @@ notifications:
|
||||
env:
|
||||
global:
|
||||
- TEST_CMD="make check"
|
||||
- MAKEFLAGS="-j3"
|
||||
matrix:
|
||||
- CONFIG=""
|
||||
- CONFIG="--enable-debug --enable-debug-tcg --enable-trace-backends=log"
|
||||
@@ -65,12 +64,9 @@ before_install:
|
||||
before_script:
|
||||
- ./configure ${CONFIG}
|
||||
script:
|
||||
- make ${MAKEFLAGS} && ${TEST_CMD}
|
||||
- make -j3 && ${TEST_CMD}
|
||||
matrix:
|
||||
include:
|
||||
# Test with CLang for compile portability
|
||||
- env: CONFIG=""
|
||||
compiler: clang
|
||||
# gprof/gcov are GCC features
|
||||
- env: CONFIG="--enable-gprof --enable-gcov --disable-pie"
|
||||
compiler: gcc
|
||||
@@ -87,14 +83,13 @@ matrix:
|
||||
- env: CONFIG="--enable-trace-backends=ust"
|
||||
TEST_CMD=""
|
||||
compiler: gcc
|
||||
- env: CONFIG="--disable-tcg"
|
||||
- env: CONFIG="--with-coroutine=gthread"
|
||||
TEST_CMD=""
|
||||
compiler: gcc
|
||||
- env: CONFIG=""
|
||||
os: osx
|
||||
compiler: clang
|
||||
# Plain Trusty System Build
|
||||
- env: CONFIG="--disable-linux-user"
|
||||
- env: CONFIG=""
|
||||
sudo: required
|
||||
addons:
|
||||
dist: trusty
|
||||
@@ -104,95 +99,3 @@ matrix:
|
||||
- sudo apt-get build-dep -qq qemu
|
||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
||||
- git submodule update --init --recursive
|
||||
# Plain Trusty Linux User Build
|
||||
- env: CONFIG="--disable-system"
|
||||
sudo: required
|
||||
addons:
|
||||
dist: trusty
|
||||
compiler: gcc
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get build-dep -qq qemu
|
||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
||||
- git submodule update --init --recursive
|
||||
# Trusty System build with latest stable clang
|
||||
- sudo: required
|
||||
addons:
|
||||
dist: trusty
|
||||
language: generic
|
||||
compiler: none
|
||||
env:
|
||||
- COMPILER_NAME=clang CXX=clang++-3.9 CC=clang-3.9
|
||||
- CONFIG="--disable-linux-user --cc=clang-3.9 --cxx=clang++-3.9"
|
||||
before_install:
|
||||
- wget -nv -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
- sudo apt-add-repository -y 'deb http://llvm.org/apt/trusty llvm-toolchain-trusty-3.9 main'
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq -y clang-3.9
|
||||
- sudo apt-get build-dep -qq qemu
|
||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
||||
- git submodule update --init --recursive
|
||||
before_script:
|
||||
- ./configure ${CONFIG} || cat config.log
|
||||
# Trusty Linux User build with latest stable clang
|
||||
- sudo: required
|
||||
addons:
|
||||
dist: trusty
|
||||
language: generic
|
||||
compiler: none
|
||||
env:
|
||||
- COMPILER_NAME=clang CXX=clang++-3.9 CC=clang-3.9
|
||||
- CONFIG="--disable-system --cc=clang-3.9 --cxx=clang++-3.9"
|
||||
before_install:
|
||||
- wget -nv -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
- sudo apt-add-repository -y 'deb http://llvm.org/apt/trusty llvm-toolchain-trusty-3.9 main'
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq -y clang-3.9
|
||||
- sudo apt-get build-dep -qq qemu
|
||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
||||
- git submodule update --init --recursive
|
||||
before_script:
|
||||
- ./configure ${CONFIG} || cat config.log
|
||||
# Using newer GCC with sanitizers
|
||||
- addons:
|
||||
apt:
|
||||
sources:
|
||||
# PPAs for newer toolchains
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
# Extra toolchains
|
||||
- gcc-5
|
||||
- g++-5
|
||||
# Build dependencies
|
||||
- libaio-dev
|
||||
- libattr1-dev
|
||||
- libbrlapi-dev
|
||||
- libcap-ng-dev
|
||||
- libgnutls-dev
|
||||
- libgtk-3-dev
|
||||
- libiscsi-dev
|
||||
- liblttng-ust-dev
|
||||
- libnfs-dev
|
||||
- libncurses5-dev
|
||||
- libnss3-dev
|
||||
- libpixman-1-dev
|
||||
- libpng12-dev
|
||||
- librados-dev
|
||||
- libsdl1.2-dev
|
||||
- libseccomp-dev
|
||||
- libspice-protocol-dev
|
||||
- libspice-server-dev
|
||||
- libssh2-1-dev
|
||||
- liburcu-dev
|
||||
- libusb-1.0-0-dev
|
||||
- libvte-2.90-dev
|
||||
- sparse
|
||||
- uuid-dev
|
||||
language: generic
|
||||
compiler: none
|
||||
env:
|
||||
- COMPILER_NAME=gcc CXX=g++-5 CC=gcc-5
|
||||
- CONFIG="--cc=gcc-5 --cxx=g++-5 --disable-pie --disable-linux-user"
|
||||
- TEST_CMD=""
|
||||
before_script:
|
||||
- ./configure ${CONFIG} --extra-cflags="-g3 -O0 -fsanitize=thread -fuse-ld=gold" || cat config.log
|
||||
+1
-43
@@ -9,7 +9,7 @@ patches before submitting.
|
||||
Of course, the most important aspect in any coding style is whitespace.
|
||||
Crusty old coders who have trouble spotting the glasses on their noses
|
||||
can tell the difference between a tab and eight spaces from a distance
|
||||
of approximately fifteen parsecs. Many a flamewar has been fought and
|
||||
of approximately fifteen parsecs. Many a flamewar have been fought and
|
||||
lost on this issue.
|
||||
|
||||
QEMU indents are four spaces. Tabs are never used, except in Makefiles
|
||||
@@ -116,45 +116,3 @@ if (a == 1) {
|
||||
Rationale: Yoda conditions (as in 'if (1 == a)') are awkward to read.
|
||||
Besides, good compilers already warn users when '==' is mis-typed as '=',
|
||||
even when the constant is on the right.
|
||||
|
||||
7. Comment style
|
||||
|
||||
We use traditional C-style /* */ comments and avoid // comments.
|
||||
|
||||
Rationale: The // form is valid in C99, so this is purely a matter of
|
||||
consistency of style. The checkpatch script will warn you about this.
|
||||
|
||||
8. trace-events style
|
||||
|
||||
8.1 0x prefix
|
||||
|
||||
In trace-events files, use a '0x' prefix to specify hex numbers, as in:
|
||||
|
||||
some_trace(unsigned x, uint64_t y) "x 0x%x y 0x" PRIx64
|
||||
|
||||
An exception is made for groups of numbers that are hexadecimal by
|
||||
convention and separated by the symbols '.', '/', ':', or ' ' (such as
|
||||
PCI bus id):
|
||||
|
||||
another_trace(int cssid, int ssid, int dev_num) "bus id: %x.%x.%04x"
|
||||
|
||||
However, you can use '0x' for such groups if you want. Anyway, be sure that
|
||||
it is obvious that numbers are in hex, ex.:
|
||||
|
||||
data_dump(uint8_t c1, uint8_t c2, uint8_t c3) "bytes (in hex): %02x %02x %02x"
|
||||
|
||||
Rationale: hex numbers are hard to read in logs when there is no 0x prefix,
|
||||
especially when (occasionally) the representation doesn't contain any letters
|
||||
and especially in one line with other decimal numbers. Number groups are allowed
|
||||
to not use '0x' because for some things notations like %x.%x.%x are used not
|
||||
only in Qemu. Also dumping raw data bytes with '0x' is less readable.
|
||||
|
||||
8.2 '#' printf flag
|
||||
|
||||
Do not use printf flag '#', like '%#x'.
|
||||
|
||||
Rationale: there are two ways to add a '0x' prefix to printed number: '0x%...'
|
||||
and '%#...'. For consistency the only one way should be used. Arguments for
|
||||
'0x%' are:
|
||||
- it is more popular
|
||||
- '%#' omits the 0x for the value 0 which makes output inconsistent
|
||||
@@ -1,28 +1,10 @@
|
||||
1. Preprocessor
|
||||
|
||||
1.1. Variadic macros
|
||||
|
||||
For variadic macros, stick with this C99-like syntax:
|
||||
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("IRQ: " fmt, ## __VA_ARGS__); } while (0)
|
||||
|
||||
1.2. Include directives
|
||||
|
||||
Order include directives as follows:
|
||||
|
||||
#include "qemu/osdep.h" /* Always first... */
|
||||
#include <...> /* then system headers... */
|
||||
#include "..." /* and finally QEMU headers. */
|
||||
|
||||
The "qemu/osdep.h" header contains preprocessor macros that affect the behavior
|
||||
of core system headers like <stdint.h>. It must be the first include so that
|
||||
core system headers included by external libraries get the preprocessor macros
|
||||
that QEMU depends on.
|
||||
|
||||
Do not include "qemu/osdep.h" from header files since the .c file will have
|
||||
already included it.
|
||||
|
||||
2. C types
|
||||
|
||||
It should be common sense to use the right type, but we have collected
|
||||
|
||||
+147
-395
File diff suppressed because it is too large.
Load diff
@@ -26,7 +26,6 @@ endif
|
||||
|
||||
CONFIG_SOFTMMU := $(if $(filter %-softmmu,$(TARGET_DIRS)),y)
|
||||
CONFIG_USER_ONLY := $(if $(filter %-user,$(TARGET_DIRS)),y)
|
||||
CONFIG_XEN := $(CONFIG_XEN_BACKEND)
|
||||
CONFIG_ALL=y
|
||||
-include config-all-devices.mak
|
||||
-include config-all-disas.mak
|
||||
@@ -51,153 +50,39 @@ endif
|
||||
|
||||
include $(SRC_PATH)/rules.mak
|
||||
|
||||
GENERATED_FILES = qemu-version.h config-host.h qemu-options.def
|
||||
GENERATED_FILES += qmp-commands.h qapi-types.h qapi-visit.h qapi-event.h
|
||||
GENERATED_FILES += qmp-marshal.c qapi-types.c qapi-visit.c qapi-event.c
|
||||
GENERATED_FILES += qmp-introspect.h
|
||||
GENERATED_FILES += qmp-introspect.c
|
||||
GENERATED_HEADERS = qemu-version.h config-host.h qemu-options.def
|
||||
GENERATED_HEADERS += qmp-commands.h qapi-types.h qapi-visit.h qapi-event.h
|
||||
GENERATED_SOURCES += qmp-marshal.c qapi-types.c qapi-visit.c qapi-event.c
|
||||
GENERATED_HEADERS += qmp-introspect.h
|
||||
GENERATED_SOURCES += qmp-introspect.c
|
||||
|
||||
GENERATED_FILES += trace/generated-tcg-tracers.h
|
||||
GENERATED_HEADERS += trace/generated-events.h
|
||||
GENERATED_SOURCES += trace/generated-events.c
|
||||
|
||||
GENERATED_FILES += trace/generated-helpers-wrappers.h
|
||||
GENERATED_FILES += trace/generated-helpers.h
|
||||
GENERATED_FILES += trace/generated-helpers.c
|
||||
|
||||
ifdef CONFIG_TRACE_UST
|
||||
GENERATED_FILES += trace-ust-all.h
|
||||
GENERATED_FILES += trace-ust-all.c
|
||||
GENERATED_HEADERS += trace/generated-tracers.h
|
||||
ifeq ($(findstring dtrace,$(TRACE_BACKENDS)),dtrace)
|
||||
GENERATED_HEADERS += trace/generated-tracers-dtrace.h
|
||||
endif
|
||||
GENERATED_SOURCES += trace/generated-tracers.c
|
||||
|
||||
GENERATED_FILES += module_block.h
|
||||
GENERATED_HEADERS += trace/generated-tcg-tracers.h
|
||||
|
||||
TRACE_HEADERS = trace-root.h $(trace-events-subdirs:%=%/trace.h)
|
||||
TRACE_SOURCES = trace-root.c $(trace-events-subdirs:%=%/trace.c)
|
||||
TRACE_DTRACE =
|
||||
ifdef CONFIG_TRACE_DTRACE
|
||||
TRACE_HEADERS += trace-dtrace-root.h $(trace-events-subdirs:%=%/trace-dtrace.h)
|
||||
TRACE_DTRACE += trace-dtrace-root.dtrace $(trace-events-subdirs:%=%/trace-dtrace.dtrace)
|
||||
GENERATED_HEADERS += trace/generated-helpers-wrappers.h
|
||||
GENERATED_HEADERS += trace/generated-helpers.h
|
||||
GENERATED_SOURCES += trace/generated-helpers.c
|
||||
|
||||
ifeq ($(findstring ust,$(TRACE_BACKENDS)),ust)
|
||||
GENERATED_HEADERS += trace/generated-ust-provider.h
|
||||
GENERATED_SOURCES += trace/generated-ust.c
|
||||
endif
|
||||
ifdef CONFIG_TRACE_UST
|
||||
TRACE_HEADERS += trace-ust-root.h $(trace-events-subdirs:%=%/trace-ust.h)
|
||||
endif
|
||||
|
||||
GENERATED_FILES += $(TRACE_HEADERS)
|
||||
GENERATED_FILES += $(TRACE_SOURCES)
|
||||
GENERATED_FILES += $(BUILD_DIR)/trace-events-all
|
||||
|
||||
trace-group-name = $(shell dirname $1 | sed -e 's/[^a-zA-Z0-9]/_/g')
|
||||
|
||||
tracetool-y = $(SRC_PATH)/scripts/tracetool.py
|
||||
tracetool-y += $(shell find $(SRC_PATH)/scripts/tracetool -name "*.py")
|
||||
|
||||
%/trace.h: %/trace.h-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
%/trace.h-timestamp: $(SRC_PATH)/%/trace-events $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=$(call trace-group-name,$@) \
|
||||
--format=h \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
%/trace.c: %/trace.c-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
%/trace.c-timestamp: $(SRC_PATH)/%/trace-events $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=$(call trace-group-name,$@) \
|
||||
--format=c \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
%/trace-ust.h: %/trace-ust.h-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
%/trace-ust.h-timestamp: $(SRC_PATH)/%/trace-events $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=$(call trace-group-name,$@) \
|
||||
--format=ust-events-h \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
%/trace-dtrace.dtrace: %/trace-dtrace.dtrace-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
%/trace-dtrace.dtrace-timestamp: $(SRC_PATH)/%/trace-events $(BUILD_DIR)/config-host.mak $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=$(call trace-group-name,$@) \
|
||||
--format=d \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
%/trace-dtrace.h: %/trace-dtrace.dtrace $(tracetool-y)
|
||||
$(call quiet-command,dtrace -o $@ -h -s $<, "GEN","$@")
|
||||
|
||||
%/trace-dtrace.o: %/trace-dtrace.dtrace $(tracetool-y)
|
||||
|
||||
|
||||
trace-root.h: trace-root.h-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
trace-root.h-timestamp: $(SRC_PATH)/trace-events $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=root \
|
||||
--format=h \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
trace-root.c: trace-root.c-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
trace-root.c-timestamp: $(SRC_PATH)/trace-events $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=root \
|
||||
--format=c \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
trace-ust-root.h: trace-ust-root.h-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
trace-ust-root.h-timestamp: $(SRC_PATH)/trace-events $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=root \
|
||||
--format=ust-events-h \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
trace-ust-all.h: trace-ust-all.h-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
trace-ust-all.h-timestamp: $(trace-events-files) $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=all \
|
||||
--format=ust-events-h \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$(trace-events-files) > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
trace-ust-all.c: trace-ust-all.c-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
trace-ust-all.c-timestamp: $(trace-events-files) $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=all \
|
||||
--format=ust-events-c \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$(trace-events-files) > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
trace-dtrace-root.dtrace: trace-dtrace-root.dtrace-timestamp
|
||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
||||
trace-dtrace-root.dtrace-timestamp: $(SRC_PATH)/trace-events $(BUILD_DIR)/config-host.mak $(tracetool-y)
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=root \
|
||||
--format=d \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
||||
|
||||
trace-dtrace-root.h: trace-dtrace-root.dtrace
|
||||
$(call quiet-command,dtrace -o $@ -h -s $<, "GEN","$@")
|
||||
|
||||
trace-dtrace-root.o: trace-dtrace-root.dtrace
|
||||
|
||||
# Don't try to regenerate Makefile or configure
|
||||
# We don't generate any of them
|
||||
Makefile: ;
|
||||
configure: ;
|
||||
|
||||
.PHONY: all clean cscope distclean html info install install-doc \
|
||||
pdf txt recurse-all speed test dist msi FORCE
|
||||
.PHONY: all clean cscope distclean dvi html info install install-doc \
|
||||
pdf recurse-all speed test dist msi FORCE
|
||||
|
||||
$(call set-vpath, $(SRC_PATH))
|
||||
|
||||
@@ -206,9 +91,8 @@ LIBS+=-lz $(LIBS_TOOLS)
|
||||
HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
|
||||
|
||||
ifdef BUILD_DOCS
|
||||
DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
|
||||
DOCS+=docs/interop/qemu-qmp-ref.html docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7
|
||||
DOCS+=docs/interop/qemu-ga-ref.html docs/interop/qemu-ga-ref.txt docs/interop/qemu-ga-ref.7
|
||||
DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
|
||||
DOCS+=qmp-commands.txt
|
||||
ifdef CONFIG_VIRTFS
|
||||
DOCS+=fsdev/virtfs-proxy-helper.1
|
||||
endif
|
||||
@@ -222,20 +106,20 @@ SUBDIR_DEVICES_MAK_DEP=$(patsubst %, %-config-devices.mak.d, $(TARGET_DIRS))
|
||||
|
||||
ifeq ($(SUBDIR_DEVICES_MAK),)
|
||||
config-all-devices.mak:
|
||||
$(call quiet-command,echo '# no devices' > $@,"GEN","$@")
|
||||
$(call quiet-command,echo '# no devices' > $@," GEN $@")
|
||||
else
|
||||
config-all-devices.mak: $(SUBDIR_DEVICES_MAK)
|
||||
$(call quiet-command, sed -n \
|
||||
's|^\([^=]*\)=\(.*\)$$|\1:=$$(findstring y,$$(\1)\2)|p' \
|
||||
$(SUBDIR_DEVICES_MAK) | sort -u > $@, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
endif
|
||||
|
||||
-include $(SUBDIR_DEVICES_MAK_DEP)
|
||||
|
||||
%/config-devices.mak: default-configs/%.mak $(SRC_PATH)/scripts/make_device_config.sh
|
||||
$(call quiet-command, \
|
||||
$(SHELL) $(SRC_PATH)/scripts/make_device_config.sh $< $*-config-devices.mak.d $@ > $@.tmp,"GEN","$@.tmp")
|
||||
$(SHELL) $(SRC_PATH)/scripts/make_device_config.sh $< $*-config-devices.mak.d $@ > $@.tmp, " GEN $@.tmp")
|
||||
$(call quiet-command, if test -f $@; then \
|
||||
if cmp -s $@.old $@; then \
|
||||
mv $@.tmp $@; \
|
||||
@@ -252,7 +136,7 @@ endif
|
||||
else \
|
||||
mv $@.tmp $@; \
|
||||
cp -p $@ $@.old; \
|
||||
fi,"GEN","$@");
|
||||
fi, " GEN $@");
|
||||
|
||||
defconfig:
|
||||
rm -f config-all-devices.mak $(SUBDIR_DEVICES_MAK)
|
||||
@@ -263,13 +147,10 @@ endif
|
||||
|
||||
dummy := $(call unnest-vars,, \
|
||||
stub-obj-y \
|
||||
chardev-obj-y \
|
||||
util-obj-y \
|
||||
qga-obj-y \
|
||||
ivshmem-client-obj-y \
|
||||
ivshmem-server-obj-y \
|
||||
libvhost-user-obj-y \
|
||||
vhost-user-scsi-obj-y \
|
||||
qga-vss-dll-obj-y \
|
||||
block-obj-y \
|
||||
block-obj-m \
|
||||
@@ -278,10 +159,11 @@ dummy := $(call unnest-vars,, \
|
||||
qom-obj-y \
|
||||
io-obj-y \
|
||||
common-obj-y \
|
||||
common-obj-m \
|
||||
trace-obj-y)
|
||||
common-obj-m)
|
||||
|
||||
ifneq ($(wildcard config-host.mak),)
|
||||
include $(SRC_PATH)/tests/Makefile.include
|
||||
endif
|
||||
|
||||
all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all modules
|
||||
|
||||
@@ -303,16 +185,12 @@ qemu-version.h: FORCE
|
||||
printf '""\n'; \
|
||||
fi; \
|
||||
fi) > $@.tmp)
|
||||
$(call quiet-command, if ! cmp -s $@ $@.tmp; then \
|
||||
mv $@.tmp $@; \
|
||||
else \
|
||||
rm $@.tmp; \
|
||||
fi)
|
||||
$(call quiet-command, cmp -s $@ $@.tmp || mv $@.tmp $@)
|
||||
|
||||
config-host.h: config-host.h-timestamp
|
||||
config-host.h-timestamp: config-host.mak
|
||||
qemu-options.def: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@")
|
||||
|
||||
SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
|
||||
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
||||
@@ -344,8 +222,7 @@ subdir-dtc:dtc/libfdt dtc/tests
|
||||
dtc/%:
|
||||
mkdir -p $@
|
||||
|
||||
$(SUBDIR_RULES): libqemuutil.a libqemustub.a $(common-obj-y) $(chardev-obj-y) \
|
||||
$(qom-obj-y) $(crypto-aes-obj-$(CONFIG_USER_ONLY))
|
||||
$(SUBDIR_RULES): libqemuutil.a libqemustub.a $(common-obj-y) $(qom-obj-y) $(crypto-aes-obj-$(CONFIG_USER_ONLY))
|
||||
|
||||
ROMSUBDIR_RULES=$(patsubst %,romsubdir-%, $(ROMS))
|
||||
# Only keep -O and -g cflags
|
||||
@@ -356,34 +233,37 @@ ALL_SUBDIRS=$(TARGET_DIRS) $(patsubst %,pc-bios/%, $(ROMS))
|
||||
|
||||
recurse-all: $(SUBDIR_RULES) $(ROMSUBDIR_RULES)
|
||||
|
||||
$(BUILD_DIR)/version.o: $(SRC_PATH)/version.rc config-host.h
|
||||
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<,"RC","version.o")
|
||||
$(BUILD_DIR)/version.o: $(SRC_PATH)/version.rc config-host.h | $(BUILD_DIR)/version.lo
|
||||
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<," RC version.o")
|
||||
$(BUILD_DIR)/version.lo: $(SRC_PATH)/version.rc config-host.h
|
||||
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<," RC version.lo")
|
||||
|
||||
Makefile: $(version-obj-y)
|
||||
Makefile: $(version-obj-y) $(version-lobj-y)
|
||||
|
||||
######################################################################
|
||||
# Build libraries
|
||||
|
||||
libqemustub.a: $(stub-obj-y)
|
||||
libqemuutil.a: $(util-obj-y) $(trace-obj-y)
|
||||
libqemuutil.a: $(util-obj-y)
|
||||
|
||||
block-modules = $(foreach o,$(block-obj-m),"$(basename $(subst /,-,$o))",) NULL
|
||||
util/module.o-cflags = -D'CONFIG_BLOCK_MODULES=$(block-modules)'
|
||||
|
||||
######################################################################
|
||||
|
||||
COMMON_LDADDS = libqemuutil.a libqemustub.a
|
||||
|
||||
qemu-img.o: qemu-img-cmds.h
|
||||
|
||||
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
|
||||
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
|
||||
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
|
||||
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a
|
||||
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a
|
||||
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a
|
||||
|
||||
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
|
||||
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o libqemuutil.a libqemustub.a
|
||||
|
||||
fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS)
|
||||
fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o libqemuutil.a libqemustub.a
|
||||
fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
|
||||
|
||||
qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@")
|
||||
|
||||
qemu-ga$(EXESUF): LIBS = $(LIBS_QGA)
|
||||
qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
|
||||
@@ -396,17 +276,17 @@ qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h :\
|
||||
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
|
||||
$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h :\
|
||||
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
|
||||
$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c :\
|
||||
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
|
||||
$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
|
||||
qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
|
||||
$(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
|
||||
@@ -418,32 +298,32 @@ qapi-types.c qapi-types.h :\
|
||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
|
||||
$(gen-out-type) -o "." -b $<, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
qapi-visit.c qapi-visit.h :\
|
||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
|
||||
$(gen-out-type) -o "." -b $<, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
qapi-event.c qapi-event.h :\
|
||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
|
||||
$(gen-out-type) -o "." $<, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
qmp-commands.h qmp-marshal.c :\
|
||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
|
||||
$(gen-out-type) -o "." $<, \
|
||||
"GEN","$@")
|
||||
$(gen-out-type) -o "." -m $<, \
|
||||
" GEN $@")
|
||||
qmp-introspect.h qmp-introspect.c :\
|
||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
|
||||
$(gen-out-type) -o "." $<, \
|
||||
"GEN","$@")
|
||||
" GEN $@")
|
||||
|
||||
QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
|
||||
$(qga-obj-y): $(QGALIB_GEN)
|
||||
$(qga-obj-y) qemu-ga.o: $(QGALIB_GEN)
|
||||
|
||||
qemu-ga$(EXESUF): $(qga-obj-y) $(COMMON_LDADDS)
|
||||
qemu-ga$(EXESUF): $(qga-obj-y) libqemuutil.a libqemustub.a
|
||||
$(call LINK, $^)
|
||||
|
||||
ifdef QEMU_GA_MSI_ENABLED
|
||||
@@ -457,7 +337,7 @@ $(QEMU_GA_MSI): config-host.mak
|
||||
|
||||
$(QEMU_GA_MSI): $(SRC_PATH)/qga/installer/qemu-ga.wxs
|
||||
$(call quiet-command,QEMU_GA_VERSION="$(QEMU_GA_VERSION)" QEMU_GA_MANUFACTURER="$(QEMU_GA_MANUFACTURER)" QEMU_GA_DISTRO="$(QEMU_GA_DISTRO)" BUILD_DIR="$(BUILD_DIR)" \
|
||||
wixl -o $@ $(QEMU_GA_MSI_ARCH) $(QEMU_GA_MSI_WITH_VSS) $(QEMU_GA_MSI_MINGW_DLL_PATH) $<,"WIXL","$@")
|
||||
wixl -o $@ $(QEMU_GA_MSI_ARCH) $(QEMU_GA_MSI_WITH_VSS) $(QEMU_GA_MSI_MINGW_DLL_PATH) $<, " WIXL $@")
|
||||
else
|
||||
msi:
|
||||
@echo "MSI build not configured or dependency resolution failed (reconfigure with --enable-guest-agent-msi option)"
|
||||
@@ -468,34 +348,27 @@ ifneq ($(EXESUF),)
|
||||
qemu-ga: qemu-ga$(EXESUF) $(QGA_VSS_PROVIDER) $(QEMU_GA_MSI)
|
||||
endif
|
||||
|
||||
ifdef CONFIG_IVSHMEM
|
||||
ivshmem-client$(EXESUF): $(ivshmem-client-obj-y) $(COMMON_LDADDS)
|
||||
ivshmem-client$(EXESUF): $(ivshmem-client-obj-y) libqemuutil.a libqemustub.a
|
||||
$(call LINK, $^)
|
||||
ivshmem-server$(EXESUF): $(ivshmem-server-obj-y) $(COMMON_LDADDS)
|
||||
ivshmem-server$(EXESUF): $(ivshmem-server-obj-y) libqemuutil.a libqemustub.a
|
||||
$(call LINK, $^)
|
||||
endif
|
||||
vhost-user-scsi$(EXESUF): $(vhost-user-scsi-obj-y)
|
||||
$(call LINK, $^)
|
||||
|
||||
module_block.h: $(SRC_PATH)/scripts/modules/module_block.py config-host.mak
|
||||
$(call quiet-command,$(PYTHON) $< $@ \
|
||||
$(addprefix $(SRC_PATH)/,$(patsubst %.mo,%.c,$(block-obj-m))), \
|
||||
"GEN","$@")
|
||||
|
||||
clean:
|
||||
# avoid old build problems by removing potentially incorrect old files
|
||||
rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
|
||||
rm -f qemu-options.def
|
||||
rm -f *.msi
|
||||
find . \( -name '*.so' -o -name '*.dll' -o -name '*.mo' -o -name '*.[oda]' \) -type f -exec rm {} +
|
||||
find . \( -name '*.l[oa]' -o -name '*.so' -o -name '*.dll' -o -name '*.mo' -o -name '*.[oda]' \) -type f -exec rm {} +
|
||||
rm -f $(filter-out %.tlb,$(TOOLS)) $(HELPERS-y) qemu-ga TAGS cscope.* *.pod *~ */*~
|
||||
rm -f fsdev/*.pod
|
||||
rm -rf .libs */.libs
|
||||
rm -f qemu-img-cmds.h
|
||||
rm -f ui/shader/*-vert.h ui/shader/*-frag.h
|
||||
@# May not be present in GENERATED_FILES
|
||||
@# May not be present in GENERATED_HEADERS
|
||||
rm -f trace/generated-tracers-dtrace.dtrace*
|
||||
rm -f trace/generated-tracers-dtrace.h*
|
||||
rm -f $(foreach f,$(GENERATED_FILES),$(f) $(f)-timestamp)
|
||||
rm -f $(foreach f,$(GENERATED_HEADERS),$(f) $(f)-timestamp)
|
||||
rm -f $(foreach f,$(GENERATED_SOURCES),$(f) $(f)-timestamp)
|
||||
rm -rf qapi-generated
|
||||
rm -rf qga/qapi-generated
|
||||
for d in $(ALL_SUBDIRS); do \
|
||||
@@ -516,18 +389,13 @@ distclean: clean
|
||||
rm -f config-all-devices.mak config-all-disas.mak config.status
|
||||
rm -f po/*.mo tests/qemu-iotests/common.env
|
||||
rm -f roms/seabios/config.mak roms/vgabios/config.mak
|
||||
rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.cps
|
||||
rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.cps qemu-doc.dvi
|
||||
rm -f qemu-doc.fn qemu-doc.fns qemu-doc.info qemu-doc.ky qemu-doc.kys
|
||||
rm -f qemu-doc.log qemu-doc.pdf qemu-doc.pg qemu-doc.toc qemu-doc.tp
|
||||
rm -f qemu-doc.vr qemu-doc.txt
|
||||
rm -f qemu-doc.vr
|
||||
rm -f config.log
|
||||
rm -f linux-headers/asm
|
||||
rm -f docs/version.texi
|
||||
rm -f docs/interop/qemu-ga-qapi.texi docs/interop/qemu-qmp-qapi.texi
|
||||
rm -f docs/interop/qemu-qmp-ref.7 docs/interop/qemu-ga-ref.7
|
||||
rm -f docs/interop/qemu-qmp-ref.txt docs/interop/qemu-ga-ref.txt
|
||||
rm -f docs/interop/qemu-qmp-ref.pdf docs/interop/qemu-ga-ref.pdf
|
||||
rm -f docs/interop/qemu-qmp-ref.html docs/interop/qemu-ga-ref.html
|
||||
rm -f qemu-tech.info qemu-tech.aux qemu-tech.cp qemu-tech.dvi qemu-tech.fn qemu-tech.info qemu-tech.ky qemu-tech.log qemu-tech.pdf qemu-tech.pg qemu-tech.toc qemu-tech.tp qemu-tech.vr
|
||||
for d in $(TARGET_DIRS); do \
|
||||
rm -rf $$d || exit 1 ; \
|
||||
done
|
||||
@@ -553,26 +421,21 @@ efi-e1000e.rom efi-vmxnet3.rom \
|
||||
qemu-icon.bmp qemu_logo_no_text.svg \
|
||||
bamboo.dtb petalogix-s3adsp1800.dtb petalogix-ml605.dtb \
|
||||
multiboot.bin linuxboot.bin linuxboot_dma.bin kvmvapic.bin \
|
||||
s390-ccw.img s390-netboot.img \
|
||||
spapr-rtas.bin slof.bin skiboot.lid \
|
||||
s390-ccw.img \
|
||||
spapr-rtas.bin slof.bin \
|
||||
palcode-clipper \
|
||||
u-boot.e500 \
|
||||
qemu_vga.ndrv
|
||||
u-boot.e500
|
||||
else
|
||||
BLOBS=
|
||||
endif
|
||||
|
||||
install-doc: $(DOCS)
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) qemu-doc.html "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) qemu-doc.txt "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) docs/interop/qemu-qmp-ref.html "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) docs/interop/qemu-qmp-ref.txt "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) qemu-doc.html qemu-tech.html "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) qmp-commands.txt "$(DESTDIR)$(qemu_docdir)"
|
||||
ifdef CONFIG_POSIX
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
||||
$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man7"
|
||||
$(INSTALL_DATA) docs/interop/qemu-qmp-ref.7 "$(DESTDIR)$(mandir)/man7"
|
||||
ifneq ($(TOOLS),)
|
||||
$(INSTALL_DATA) qemu-img.1 "$(DESTDIR)$(mandir)/man1"
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8"
|
||||
@@ -580,9 +443,6 @@ ifneq ($(TOOLS),)
|
||||
endif
|
||||
ifneq (,$(findstring qemu-ga,$(TOOLS)))
|
||||
$(INSTALL_DATA) qemu-ga.8 "$(DESTDIR)$(mandir)/man8"
|
||||
$(INSTALL_DATA) docs/interop/qemu-ga-ref.html "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) docs/interop/qemu-ga-ref.txt "$(DESTDIR)$(qemu_docdir)"
|
||||
$(INSTALL_DATA) docs/interop/qemu-ga-ref.7 "$(DESTDIR)$(mandir)/man7"
|
||||
endif
|
||||
endif
|
||||
ifdef CONFIG_VIRTFS
|
||||
@@ -601,7 +461,8 @@ endif
|
||||
endif
|
||||
|
||||
|
||||
install: all $(if $(BUILD_DOCS),install-doc) install-datadir install-localstatedir
|
||||
install: all $(if $(BUILD_DOCS),install-doc) \
|
||||
install-datadir install-localstatedir
|
||||
ifneq ($(TOOLS),)
|
||||
$(call install-prog,$(subst qemu-ga,qemu-ga$(EXESUF),$(TOOLS)),$(DESTDIR)$(bindir))
|
||||
endif
|
||||
@@ -657,89 +518,90 @@ ui/shader/%-vert.h: $(SRC_PATH)/ui/shader/%.vert $(SRC_PATH)/scripts/shaderinclu
|
||||
@mkdir -p $(dir $@)
|
||||
$(call quiet-command,\
|
||||
perl $(SRC_PATH)/scripts/shaderinclude.pl $< > $@,\
|
||||
"VERT","$@")
|
||||
" VERT $@")
|
||||
|
||||
ui/shader/%-frag.h: $(SRC_PATH)/ui/shader/%.frag $(SRC_PATH)/scripts/shaderinclude.pl
|
||||
@mkdir -p $(dir $@)
|
||||
$(call quiet-command,\
|
||||
perl $(SRC_PATH)/scripts/shaderinclude.pl $< > $@,\
|
||||
"FRAG","$@")
|
||||
" FRAG $@")
|
||||
|
||||
ui/console-gl.o: $(SRC_PATH)/ui/console-gl.c \
|
||||
ui/shader/texture-blit-vert.h ui/shader/texture-blit-frag.h
|
||||
|
||||
# documentation
|
||||
MAKEINFO=makeinfo
|
||||
MAKEINFOINCLUDES= -I docs -I $(<D) -I $(@D)
|
||||
MAKEINFOFLAGS=--no-split --number-sections $(MAKEINFOINCLUDES)
|
||||
TEXI2PODFLAGS=$(MAKEINFOINCLUDES) "-DVERSION=$(VERSION)"
|
||||
TEXI2PDFFLAGS=$(if $(V),,--quiet) -I $(SRC_PATH) $(MAKEINFOINCLUDES)
|
||||
MAKEINFOFLAGS=--no-headers --no-split --number-sections
|
||||
TEXIFLAG=$(if $(V),,--quiet)
|
||||
%.dvi: %.texi
|
||||
$(call quiet-command,texi2dvi $(TEXIFLAG) -I . $<," GEN $@")
|
||||
|
||||
docs/version.texi: $(SRC_PATH)/VERSION
|
||||
$(call quiet-command,echo "@set VERSION $(VERSION)" > $@,"GEN","$@")
|
||||
%.html: %.texi
|
||||
$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --html $< -o $@, \
|
||||
" GEN $@")
|
||||
|
||||
%.html: %.texi docs/version.texi
|
||||
$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
|
||||
--html $< -o $@,"GEN","$@")
|
||||
%.info: %.texi
|
||||
$(call quiet-command,$(MAKEINFO) $< -o $@," GEN $@")
|
||||
|
||||
%.info: %.texi docs/version.texi
|
||||
$(call quiet-command,$(MAKEINFO) $(MAKEINFOFLAGS) $< -o $@,"GEN","$@")
|
||||
|
||||
%.txt: %.texi docs/version.texi
|
||||
$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
|
||||
--plaintext $< -o $@,"GEN","$@")
|
||||
|
||||
%.pdf: %.texi docs/version.texi
|
||||
$(call quiet-command,texi2pdf $(TEXI2PDFFLAGS) $< -o $@,"GEN","$@")
|
||||
%.pdf: %.texi
|
||||
$(call quiet-command,texi2pdf $(TEXIFLAG) -I . $<," GEN $@")
|
||||
|
||||
qemu-options.texi: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@")
|
||||
|
||||
qemu-monitor.texi: $(SRC_PATH)/hmp-commands.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@")
|
||||
|
||||
qemu-monitor-info.texi: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@")
|
||||
|
||||
qmp-commands.txt: $(SRC_PATH)/qmp-commands.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -q < $< > $@," GEN $@")
|
||||
|
||||
qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
||||
|
||||
docs/interop/qemu-qmp-qapi.texi docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
|
||||
|
||||
docs/interop/qemu-qmp-qapi.texi: $(qapi-modules)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
|
||||
|
||||
docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/qga/qapi-schema.json
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@")
|
||||
|
||||
qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi qemu-monitor-info.texi
|
||||
$(call quiet-command, \
|
||||
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu.pod && \
|
||||
$(POD2MAN) --section=1 --center=" " --release=" " qemu.pod > $@, \
|
||||
" GEN $@")
|
||||
qemu.1: qemu-option-trace.texi
|
||||
|
||||
qemu-img.1: qemu-img.texi qemu-option-trace.texi qemu-img-cmds.texi
|
||||
$(call quiet-command, \
|
||||
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-img.pod && \
|
||||
$(POD2MAN) --section=1 --center=" " --release=" " qemu-img.pod > $@, \
|
||||
" GEN $@")
|
||||
|
||||
fsdev/virtfs-proxy-helper.1: fsdev/virtfs-proxy-helper.texi
|
||||
$(call quiet-command, \
|
||||
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< fsdev/virtfs-proxy-helper.pod && \
|
||||
$(POD2MAN) --section=1 --center=" " --release=" " fsdev/virtfs-proxy-helper.pod > $@, \
|
||||
" GEN $@")
|
||||
|
||||
qemu-nbd.8: qemu-nbd.texi qemu-option-trace.texi
|
||||
$(call quiet-command, \
|
||||
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-nbd.pod && \
|
||||
$(POD2MAN) --section=8 --center=" " --release=" " qemu-nbd.pod > $@, \
|
||||
" GEN $@")
|
||||
|
||||
qemu-ga.8: qemu-ga.texi
|
||||
$(call quiet-command, \
|
||||
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-ga.pod && \
|
||||
$(POD2MAN) --section=8 --center=" " --release=" " qemu-ga.pod > $@, \
|
||||
" GEN $@")
|
||||
|
||||
html: qemu-doc.html docs/interop/qemu-qmp-ref.html docs/interop/qemu-ga-ref.html
|
||||
info: qemu-doc.info docs/interop/qemu-qmp-ref.info docs/interop/qemu-ga-ref.info
|
||||
pdf: qemu-doc.pdf docs/interop/qemu-qmp-ref.pdf docs/interop/qemu-ga-ref.pdf
|
||||
txt: qemu-doc.txt docs/interop/qemu-qmp-ref.txt docs/interop/qemu-ga-ref.txt
|
||||
dvi: qemu-doc.dvi qemu-tech.dvi
|
||||
html: qemu-doc.html qemu-tech.html
|
||||
info: qemu-doc.info qemu-tech.info
|
||||
pdf: qemu-doc.pdf qemu-tech.pdf
|
||||
|
||||
qemu-doc.html qemu-doc.info qemu-doc.pdf qemu-doc.txt: \
|
||||
qemu-doc.dvi qemu-doc.html qemu-doc.info qemu-doc.pdf: \
|
||||
qemu-img.texi qemu-nbd.texi qemu-options.texi qemu-option-trace.texi \
|
||||
qemu-monitor.texi qemu-img-cmds.texi qemu-ga.texi \
|
||||
qemu-monitor-info.texi
|
||||
|
||||
docs/interop/qemu-ga-ref.dvi docs/interop/qemu-ga-ref.html \
|
||||
docs/interop/qemu-ga-ref.info docs/interop/qemu-ga-ref.pdf \
|
||||
docs/interop/qemu-ga-ref.txt docs/interop/qemu-ga-ref.7: \
|
||||
docs/interop/qemu-ga-ref.texi docs/interop/qemu-ga-qapi.texi
|
||||
|
||||
docs/interop/qemu-qmp-ref.dvi docs/interop/qemu-qmp-ref.html \
|
||||
docs/interop/qemu-qmp-ref.info docs/interop/qemu-qmp-ref.pdf \
|
||||
docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7: \
|
||||
docs/interop/qemu-qmp-ref.texi docs/interop/qemu-qmp-qapi.texi
|
||||
|
||||
|
||||
ifdef CONFIG_WIN32
|
||||
|
||||
INSTALLER = qemu-setup-$(VERSION)$(EXESUF)
|
||||
@@ -798,55 +660,12 @@ endif # CONFIG_WIN
|
||||
|
||||
# Add a dependency on the generated files, so that they are always
|
||||
# rebuilt before other object files
|
||||
ifneq ($(wildcard config-host.mak),)
|
||||
ifneq ($(filter-out $(UNCHECKED_GOALS),$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
||||
Makefile: $(GENERATED_FILES)
|
||||
Makefile: $(GENERATED_HEADERS)
|
||||
endif
|
||||
endif
|
||||
|
||||
.SECONDARY: $(TRACE_HEADERS) $(TRACE_HEADERS:%=%-timestamp) \
|
||||
$(TRACE_SOURCES) $(TRACE_SOURCES:%=%-timestamp) \
|
||||
$(TRACE_DTRACE) $(TRACE_DTRACE:%=%-timestamp)
|
||||
|
||||
# Include automatically generated dependency files
|
||||
# Dependencies in Makefile.objs files come from our recursive subdir rules
|
||||
-include $(wildcard *.d tests/*.d)
|
||||
|
||||
include $(SRC_PATH)/tests/docker/Makefile.include
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo 'Generic targets:'
|
||||
@echo ' all - Build all'
|
||||
@echo ' dir/file.o - Build specified target only'
|
||||
@echo ' install - Install QEMU, documentation and tools'
|
||||
@echo ' ctags/TAGS - Generate tags file for editors'
|
||||
@echo ' cscope - Generate cscope index'
|
||||
@echo ''
|
||||
@$(if $(TARGET_DIRS), \
|
||||
echo 'Architecture specific targets:'; \
|
||||
$(foreach t, $(TARGET_DIRS), \
|
||||
printf " %-30s - Build for %s\\n" $(patsubst %,subdir-%,$(t)) $(t);) \
|
||||
echo '')
|
||||
@echo 'Cleaning targets:'
|
||||
@echo ' clean - Remove most generated files but keep the config'
|
||||
@echo ' distclean - Remove all generated files'
|
||||
@echo ' dist - Build a distributable tarball'
|
||||
@echo ''
|
||||
@echo 'Test targets:'
|
||||
@echo ' check - Run all tests (check-help for details)'
|
||||
@echo ' docker - Help about targets running tests inside Docker containers'
|
||||
@echo ''
|
||||
@echo 'Documentation targets:'
|
||||
@echo ' html info pdf txt'
|
||||
@echo ' - Build documentation in specified format'
|
||||
@echo ''
|
||||
ifdef CONFIG_WIN32
|
||||
@echo 'Windows targets:'
|
||||
@echo ' installer - Build NSIS-based installer for QEMU'
|
||||
ifdef QEMU_GA_MSI_ENABLED
|
||||
@echo ' msi - Build MSI-based installer for qemu-ga'
|
||||
endif
|
||||
@echo ''
|
||||
endif
|
||||
@echo ' make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build'
|
||||
+56
-73
@@ -4,16 +4,17 @@ stub-obj-y = stubs/ crypto/
|
||||
util-obj-y = util/ qobject/ qapi/
|
||||
util-obj-y += qmp-introspect.o qapi-types.o qapi-visit.o qapi-event.o
|
||||
|
||||
chardev-obj-y = chardev/
|
||||
|
||||
#######################################################################
|
||||
# block-obj-y is code used by both qemu system emulation and qemu-img
|
||||
|
||||
block-obj-y = async.o thread-pool.o
|
||||
block-obj-y += nbd/
|
||||
block-obj-y += block.o blockjob.o
|
||||
block-obj-y += main-loop.o iohandler.o qemu-timer.o
|
||||
block-obj-$(CONFIG_POSIX) += aio-posix.o
|
||||
block-obj-$(CONFIG_WIN32) += aio-win32.o
|
||||
block-obj-y += block/
|
||||
block-obj-y += qemu-io-cmds.o
|
||||
block-obj-$(CONFIG_REPLICATION) += replication.o
|
||||
|
||||
block-obj-m = block/
|
||||
|
||||
@@ -40,7 +41,7 @@ io-obj-y = io/
|
||||
|
||||
ifeq ($(CONFIG_SOFTMMU),y)
|
||||
common-obj-y = blockdev.o blockdev-nbd.o block/
|
||||
common-obj-y += bootdevice.o iothread.o
|
||||
common-obj-y += iothread.o
|
||||
common-obj-y += net/
|
||||
common-obj-y += qdev-monitor.o device-hotplug.o
|
||||
common-obj-$(CONFIG_WIN32) += os-win32.o
|
||||
@@ -49,9 +50,14 @@ common-obj-$(CONFIG_POSIX) += os-posix.o
|
||||
common-obj-$(CONFIG_LINUX) += fsdev/
|
||||
|
||||
common-obj-y += migration/
|
||||
common-obj-y += qemu-char.o #aio.o
|
||||
common-obj-y += page_cache.o
|
||||
|
||||
common-obj-$(CONFIG_SPICE) += spice-qemu-char.o
|
||||
|
||||
common-obj-y += audio/
|
||||
common-obj-y += hw/
|
||||
common-obj-y += accel.o
|
||||
|
||||
common-obj-y += replay/
|
||||
|
||||
@@ -67,7 +73,6 @@ common-obj-y += tpm.o
|
||||
common-obj-$(CONFIG_SLIRP) += slirp/
|
||||
|
||||
common-obj-y += backends/
|
||||
common-obj-y += chardev/
|
||||
|
||||
common-obj-$(CONFIG_SECCOMP) += qemu-seccomp.o
|
||||
|
||||
@@ -83,7 +88,7 @@ endif
|
||||
|
||||
#######################################################################
|
||||
# Target-independent parts used in system and user emulation
|
||||
common-obj-y += cpus-common.o
|
||||
common-obj-y += tcg-runtime.o
|
||||
common-obj-y += hw/
|
||||
common-obj-y += qom/
|
||||
common-obj-y += disas/
|
||||
@@ -91,6 +96,7 @@ common-obj-y += disas/
|
||||
######################################################################
|
||||
# Resource file for Windows executables
|
||||
version-obj-$(CONFIG_WIN32) += $(BUILD_DIR)/version.o
|
||||
version-lobj-$(CONFIG_WIN32) += $(BUILD_DIR)/version.lo
|
||||
|
||||
######################################################################
|
||||
# tracing
|
||||
@@ -107,72 +113,49 @@ qga-vss-dll-obj-y = qga/
|
||||
|
||||
######################################################################
|
||||
# contrib
|
||||
ivshmem-client-obj-$(CONFIG_IVSHMEM) = contrib/ivshmem-client/
|
||||
ivshmem-server-obj-$(CONFIG_IVSHMEM) = contrib/ivshmem-server/
|
||||
libvhost-user-obj-y = contrib/libvhost-user/
|
||||
vhost-user-scsi.o-cflags := $(LIBISCSI_CFLAGS)
|
||||
vhost-user-scsi.o-libs := $(LIBISCSI_LIBS)
|
||||
vhost-user-scsi-obj-y = contrib/vhost-user-scsi/
|
||||
vhost-user-scsi-obj-y += contrib/libvhost-user/libvhost-user.o
|
||||
ivshmem-client-obj-y = contrib/ivshmem-client/
|
||||
ivshmem-server-obj-y = contrib/ivshmem-server/
|
||||
|
||||
|
||||
######################################################################
|
||||
trace-events-subdirs =
|
||||
trace-events-subdirs += util
|
||||
trace-events-subdirs += crypto
|
||||
trace-events-subdirs += io
|
||||
trace-events-subdirs += migration
|
||||
trace-events-subdirs += block
|
||||
trace-events-subdirs += chardev
|
||||
trace-events-subdirs += hw/block
|
||||
trace-events-subdirs += hw/block/dataplane
|
||||
trace-events-subdirs += hw/char
|
||||
trace-events-subdirs += hw/intc
|
||||
trace-events-subdirs += hw/net
|
||||
trace-events-subdirs += hw/virtio
|
||||
trace-events-subdirs += hw/audio
|
||||
trace-events-subdirs += hw/misc
|
||||
trace-events-subdirs += hw/usb
|
||||
trace-events-subdirs += hw/scsi
|
||||
trace-events-subdirs += hw/nvram
|
||||
trace-events-subdirs += hw/display
|
||||
trace-events-subdirs += hw/input
|
||||
trace-events-subdirs += hw/timer
|
||||
trace-events-subdirs += hw/dma
|
||||
trace-events-subdirs += hw/sparc
|
||||
trace-events-subdirs += hw/sd
|
||||
trace-events-subdirs += hw/isa
|
||||
trace-events-subdirs += hw/mem
|
||||
trace-events-subdirs += hw/i386
|
||||
trace-events-subdirs += hw/i386/xen
|
||||
trace-events-subdirs += hw/9pfs
|
||||
trace-events-subdirs += hw/ppc
|
||||
trace-events-subdirs += hw/pci
|
||||
trace-events-subdirs += hw/s390x
|
||||
trace-events-subdirs += hw/vfio
|
||||
trace-events-subdirs += hw/acpi
|
||||
trace-events-subdirs += hw/arm
|
||||
trace-events-subdirs += hw/alpha
|
||||
trace-events-subdirs += hw/xen
|
||||
trace-events-subdirs += ui
|
||||
trace-events-subdirs += audio
|
||||
trace-events-subdirs += net
|
||||
trace-events-subdirs += target/arm
|
||||
trace-events-subdirs += target/i386
|
||||
trace-events-subdirs += target/mips
|
||||
trace-events-subdirs += target/sparc
|
||||
trace-events-subdirs += target/s390x
|
||||
trace-events-subdirs += target/ppc
|
||||
trace-events-subdirs += qom
|
||||
trace-events-subdirs += linux-user
|
||||
trace-events-subdirs += qapi
|
||||
trace-events-subdirs += accel/tcg
|
||||
trace-events-subdirs += accel/kvm
|
||||
trace-events-subdirs += nbd
|
||||
|
||||
trace-events-files = $(SRC_PATH)/trace-events $(trace-events-subdirs:%=$(SRC_PATH)/%/trace-events)
|
||||
|
||||
trace-obj-y = trace-root.o
|
||||
trace-obj-y += $(trace-events-subdirs:%=%/trace.o)
|
||||
trace-obj-$(CONFIG_TRACE_UST) += trace-ust-all.o
|
||||
trace-obj-$(CONFIG_TRACE_DTRACE) += trace-dtrace-root.o
|
||||
trace-obj-$(CONFIG_TRACE_DTRACE) += $(trace-events-subdirs:%=%/trace-dtrace.o)
|
||||
trace-events-y = trace-events
|
||||
trace-events-y += util/trace-events
|
||||
trace-events-y += crypto/trace-events
|
||||
trace-events-y += io/trace-events
|
||||
trace-events-y += migration/trace-events
|
||||
trace-events-y += block/trace-events
|
||||
trace-events-y += hw/block/trace-events
|
||||
trace-events-y += hw/char/trace-events
|
||||
trace-events-y += hw/intc/trace-events
|
||||
trace-events-y += hw/net/trace-events
|
||||
trace-events-y += hw/virtio/trace-events
|
||||
trace-events-y += hw/audio/trace-events
|
||||
trace-events-y += hw/misc/trace-events
|
||||
trace-events-y += hw/usb/trace-events
|
||||
trace-events-y += hw/scsi/trace-events
|
||||
trace-events-y += hw/nvram/trace-events
|
||||
trace-events-y += hw/display/trace-events
|
||||
trace-events-y += hw/input/trace-events
|
||||
trace-events-y += hw/timer/trace-events
|
||||
trace-events-y += hw/dma/trace-events
|
||||
trace-events-y += hw/sparc/trace-events
|
||||
trace-events-y += hw/sd/trace-events
|
||||
trace-events-y += hw/isa/trace-events
|
||||
trace-events-y += hw/i386/trace-events
|
||||
trace-events-y += hw/9pfs/trace-events
|
||||
trace-events-y += hw/ppc/trace-events
|
||||
trace-events-y += hw/pci/trace-events
|
||||
trace-events-y += hw/s390x/trace-events
|
||||
trace-events-y += hw/vfio/trace-events
|
||||
trace-events-y += hw/acpi/trace-events
|
||||
trace-events-y += hw/arm/trace-events
|
||||
trace-events-y += hw/alpha/trace-events
|
||||
trace-events-y += ui/trace-events
|
||||
trace-events-y += audio/trace-events
|
||||
trace-events-y += net/trace-events
|
||||
trace-events-y += target-i386/trace-events
|
||||
trace-events-y += target-sparc/trace-events
|
||||
trace-events-y += target-s390x/trace-events
|
||||
trace-events-y += target-ppc/trace-events
|
||||
trace-events-y += qom/trace-events
|
||||
trace-events-y += linux-user/trace-events
|
||||
+43
-43
@@ -11,7 +11,7 @@ $(call set-vpath, $(SRC_PATH):$(BUILD_DIR))
|
||||
ifdef CONFIG_LINUX
|
||||
QEMU_CFLAGS += -I../linux-headers
|
||||
endif
|
||||
QEMU_CFLAGS += -I.. -I$(SRC_PATH)/target/$(TARGET_BASE_ARCH) -DNEED_CPU_H
|
||||
QEMU_CFLAGS += -I.. -I$(SRC_PATH)/target-$(TARGET_BASE_ARCH) -DNEED_CPU_H
|
||||
|
||||
QEMU_CFLAGS+=-I$(SRC_PATH)/include
|
||||
|
||||
@@ -26,7 +26,7 @@ ifneq (,$(findstring -mwindows,$(libs_softmmu)))
|
||||
# Terminate program name with a 'w' because the linker builds a windows executable.
|
||||
QEMU_PROGW=qemu-system-$(TARGET_NAME)w$(EXESUF)
|
||||
$(QEMU_PROG): $(QEMU_PROGW)
|
||||
$(call quiet-command,$(OBJCOPY) --subsystem console $(QEMU_PROGW) $(QEMU_PROG),"GEN","$(TARGET_DIR)$(QEMU_PROG)")
|
||||
$(call quiet-command,$(OBJCOPY) --subsystem console $(QEMU_PROGW) $(QEMU_PROG)," GEN $(TARGET_DIR)$(QEMU_PROG)")
|
||||
QEMU_PROG_BUILD = $(QEMU_PROGW)
|
||||
else
|
||||
QEMU_PROG_BUILD = $(QEMU_PROG)
|
||||
@@ -48,41 +48,34 @@ else
|
||||
TARGET_TYPE=system
|
||||
endif
|
||||
|
||||
tracetool-y = $(SRC_PATH)/scripts/tracetool.py
|
||||
tracetool-y += $(shell find $(SRC_PATH)/scripts/tracetool -name "*.py")
|
||||
|
||||
$(QEMU_PROG).stp-installed: $(BUILD_DIR)/trace-events-all $(tracetool-y)
|
||||
$(QEMU_PROG).stp-installed: $(BUILD_DIR)/trace-events-all
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=all \
|
||||
--format=stap \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
--binary=$(bindir)/$(QEMU_PROG) \
|
||||
--target-name=$(TARGET_NAME) \
|
||||
--target-type=$(TARGET_TYPE) \
|
||||
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG).stp-installed")
|
||||
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG).stp-installed")
|
||||
|
||||
$(QEMU_PROG).stp: $(BUILD_DIR)/trace-events-all $(tracetool-y)
|
||||
$(QEMU_PROG).stp: $(BUILD_DIR)/trace-events-all
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=all \
|
||||
--format=stap \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
--binary=$(realpath .)/$(QEMU_PROG) \
|
||||
--target-name=$(TARGET_NAME) \
|
||||
--target-type=$(TARGET_TYPE) \
|
||||
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG).stp")
|
||||
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG).stp")
|
||||
|
||||
$(QEMU_PROG)-simpletrace.stp: $(BUILD_DIR)/trace-events-all $(tracetool-y)
|
||||
$(QEMU_PROG)-simpletrace.stp: $(BUILD_DIR)/trace-events-all
|
||||
$(call quiet-command,$(TRACETOOL) \
|
||||
--group=all \
|
||||
--format=simpletrace-stap \
|
||||
--backends=$(TRACE_BACKENDS) \
|
||||
--probe-prefix=qemu.$(TARGET_TYPE).$(TARGET_NAME) \
|
||||
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG)-simpletrace.stp")
|
||||
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG)-simpletrace.stp")
|
||||
|
||||
else
|
||||
stap:
|
||||
endif
|
||||
.PHONY: stap
|
||||
|
||||
all: $(PROGS) stap
|
||||
|
||||
@@ -91,17 +84,18 @@ all: $(PROGS) stap
|
||||
|
||||
#########################################################
|
||||
# cpu emulator library
|
||||
obj-y += exec.o
|
||||
obj-y += accel/
|
||||
obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/optimize.o
|
||||
obj-$(CONFIG_TCG) += tcg/tcg-common.o tcg/tcg-runtime.o
|
||||
obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o
|
||||
obj-y = exec.o translate-all.o cpu-exec.o
|
||||
obj-y += translate-common.o
|
||||
obj-y += cpu-exec-common.o
|
||||
obj-y += tcg/tcg.o tcg/tcg-op.o tcg/optimize.o
|
||||
obj-$(CONFIG_TCG_INTERPRETER) += tci.o
|
||||
obj-y += tcg/tcg-common.o
|
||||
obj-$(CONFIG_TCG_INTERPRETER) += disas/tci.o
|
||||
obj-y += fpu/softfloat.o
|
||||
obj-y += target/$(TARGET_BASE_ARCH)/
|
||||
obj-y += target-$(TARGET_BASE_ARCH)/
|
||||
obj-y += disas.o
|
||||
obj-$(call notempty,$(TARGET_XML_FILES)) += gdbstub-xml.o
|
||||
obj-$(call lnot,$(CONFIG_HAX)) += hax-stub.o
|
||||
obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
|
||||
|
||||
obj-$(CONFIG_LIBDECNUMBER) += libdecnumber/decContext.o
|
||||
obj-$(CONFIG_LIBDECNUMBER) += libdecnumber/decNumber.o
|
||||
@@ -119,7 +113,7 @@ QEMU_CFLAGS+=-I$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR) \
|
||||
-I$(SRC_PATH)/linux-user
|
||||
|
||||
obj-y += linux-user/
|
||||
obj-y += gdbstub.o thunk.o user-exec.o user-exec-stub.o
|
||||
obj-y += gdbstub.o thunk.o user-exec.o
|
||||
|
||||
endif #CONFIG_LINUX_USER
|
||||
|
||||
@@ -132,7 +126,7 @@ QEMU_CFLAGS+=-I$(SRC_PATH)/bsd-user -I$(SRC_PATH)/bsd-user/$(TARGET_ABI_DIR) \
|
||||
-I$(SRC_PATH)/bsd-user/$(HOST_VARIANT_DIR)
|
||||
|
||||
obj-y += bsd-user/
|
||||
obj-y += gdbstub.o user-exec.o user-exec-stub.o
|
||||
obj-y += gdbstub.o user-exec.o
|
||||
|
||||
endif #CONFIG_BSD_USER
|
||||
|
||||
@@ -140,14 +134,21 @@ endif #CONFIG_BSD_USER
|
||||
# System emulator target
|
||||
ifdef CONFIG_SOFTMMU
|
||||
obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o numa.o
|
||||
obj-y += qtest.o
|
||||
obj-y += qtest.o bootdevice.o
|
||||
obj-y += hw/
|
||||
obj-y += memory.o
|
||||
obj-$(CONFIG_KVM) += kvm-all.o
|
||||
obj-y += memory.o cputlb.o
|
||||
obj-y += memory_mapping.o
|
||||
obj-y += dump.o
|
||||
obj-y += migration/ram.o
|
||||
obj-y += migration/ram.o migration/savevm.o
|
||||
LIBS := $(libs_softmmu) $(LIBS)
|
||||
|
||||
# xen support
|
||||
obj-$(CONFIG_XEN) += xen-common.o
|
||||
obj-$(CONFIG_XEN_I386) += xen-hvm.o xen-mapcache.o
|
||||
obj-$(call lnot,$(CONFIG_XEN)) += xen-common-stub.o
|
||||
obj-$(call lnot,$(CONFIG_XEN_I386)) += xen-hvm-stub.o
|
||||
|
||||
# Hardware support
|
||||
ifeq ($(TARGET_NAME), sparc64)
|
||||
obj-y += hw/sparc64/
|
||||
@@ -155,7 +156,7 @@ else
|
||||
obj-y += hw/$(TARGET_BASE_ARCH)/
|
||||
endif
|
||||
|
||||
GENERATED_FILES += hmp-commands.h hmp-commands-info.h
|
||||
GENERATED_HEADERS += hmp-commands.h hmp-commands-info.h qmp-commands-old.h
|
||||
|
||||
endif # CONFIG_SOFTMMU
|
||||
|
||||
@@ -168,14 +169,12 @@ all-obj-y := $(obj-y)
|
||||
target-obj-y :=
|
||||
block-obj-y :=
|
||||
common-obj-y :=
|
||||
chardev-obj-y :=
|
||||
include $(SRC_PATH)/Makefile.objs
|
||||
dummy := $(call unnest-vars,,target-obj-y)
|
||||
target-obj-y-save := $(target-obj-y)
|
||||
dummy := $(call unnest-vars,.., \
|
||||
block-obj-y \
|
||||
block-obj-m \
|
||||
chardev-obj-y \
|
||||
crypto-obj-y \
|
||||
crypto-aes-obj-y \
|
||||
qom-obj-y \
|
||||
@@ -186,36 +185,37 @@ target-obj-y := $(target-obj-y-save)
|
||||
all-obj-y += $(common-obj-y)
|
||||
all-obj-y += $(target-obj-y)
|
||||
all-obj-y += $(qom-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y) $(chardev-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y)
|
||||
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(io-obj-y)
|
||||
|
||||
$(QEMU_PROG_BUILD): config-devices.mak
|
||||
|
||||
COMMON_LDADDS = ../libqemuutil.a ../libqemustub.a
|
||||
|
||||
# build either PROG or PROGW
|
||||
$(QEMU_PROG_BUILD): $(all-obj-y) $(COMMON_LDADDS)
|
||||
$(QEMU_PROG_BUILD): $(all-obj-y) ../libqemuutil.a ../libqemustub.a
|
||||
$(call LINK, $(filter-out %.mak, $^))
|
||||
ifdef CONFIG_DARWIN
|
||||
$(call quiet-command,Rez -append $(SRC_PATH)/pc-bios/qemu.rsrc -o $@,"REZ","$(TARGET_DIR)$@")
|
||||
$(call quiet-command,SetFile -a C $@,"SETFILE","$(TARGET_DIR)$@")
|
||||
$(call quiet-command,Rez -append $(SRC_PATH)/pc-bios/qemu.rsrc -o $@," REZ $(TARGET_DIR)$@")
|
||||
$(call quiet-command,SetFile -a C $@," SETFILE $(TARGET_DIR)$@")
|
||||
endif
|
||||
|
||||
gdbstub-xml.c: $(TARGET_XML_FILES) $(SRC_PATH)/scripts/feature_to_c.sh
|
||||
$(call quiet-command,rm -f $@ && $(SHELL) $(SRC_PATH)/scripts/feature_to_c.sh $@ $(TARGET_XML_FILES),"GEN","$(TARGET_DIR)$@")
|
||||
$(call quiet-command,rm -f $@ && $(SHELL) $(SRC_PATH)/scripts/feature_to_c.sh $@ $(TARGET_XML_FILES)," GEN $(TARGET_DIR)$@")
|
||||
|
||||
hmp-commands.h: $(SRC_PATH)/hmp-commands.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$(TARGET_DIR)$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@")
|
||||
|
||||
hmp-commands-info.h: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$(TARGET_DIR)$@")
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@")
|
||||
|
||||
clean: clean-target
|
||||
qmp-commands-old.h: $(SRC_PATH)/qmp-commands.hx $(SRC_PATH)/scripts/hxtool
|
||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@")
|
||||
|
||||
clean:
|
||||
rm -f *.a *~ $(PROGS)
|
||||
rm -f $(shell find . -name '*.[od]')
|
||||
rm -f hmp-commands.h gdbstub-xml.c
|
||||
rm -f hmp-commands.h qmp-commands-old.h gdbstub-xml.c
|
||||
ifdef CONFIG_TRACE_SYSTEMTAP
|
||||
rm -f *.stp
|
||||
endif
|
||||
@@ -230,5 +230,5 @@ ifdef CONFIG_TRACE_SYSTEMTAP
|
||||
$(INSTALL_DATA) $(QEMU_PROG)-simpletrace.stp "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset/$(QEMU_PROG)-simpletrace.stp"
|
||||
endif
|
||||
|
||||
GENERATED_FILES += config-target.h
|
||||
Makefile: $(GENERATED_FILES)
|
||||
GENERATED_HEADERS += config-target.h
|
||||
Makefile: $(GENERATED_HEADERS)
|
||||
@@ -42,10 +42,11 @@ of other UNIX targets. The simple steps to build QEMU are:
|
||||
../configure
|
||||
make
|
||||
|
||||
Complete details of the process for building and configuring QEMU for
|
||||
all supported host platforms can be found in the qemu-tech.html file.
|
||||
Additional information can also be found online via the QEMU website:
|
||||
|
||||
http://qemu-project.org/Hosts/Linux
|
||||
http://qemu-project.org/Hosts/Mac
|
||||
http://qemu-project.org/Hosts/W32
|
||||
|
||||
|
||||
|
||||
+35
-13
@@ -33,7 +33,16 @@
|
||||
#include "sysemu/qtest.h"
|
||||
#include "hw/xen/xen.h"
|
||||
#include "qom/object.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/boards.h"
|
||||
|
||||
int tcg_tb_size;
|
||||
static bool tcg_allowed = true;
|
||||
|
||||
static int tcg_init(MachineState *ms)
|
||||
{
|
||||
tcg_exec_init(tcg_tb_size * 1024 * 1024);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const TypeInfo accel_type = {
|
||||
.name = TYPE_ACCEL,
|
||||
@@ -70,20 +79,19 @@ static int accel_init_machine(AccelClass *acc, MachineState *ms)
|
||||
|
||||
void configure_accelerator(MachineState *ms)
|
||||
{
|
||||
const char *accel, *p;
|
||||
const char *p;
|
||||
char buf[10];
|
||||
int ret;
|
||||
bool accel_initialised = false;
|
||||
bool init_failed = false;
|
||||
AccelClass *acc = NULL;
|
||||
|
||||
accel = qemu_opt_get(qemu_get_machine_opts(), "accel");
|
||||
if (accel == NULL) {
|
||||
p = qemu_opt_get(qemu_get_machine_opts(), "accel");
|
||||
if (p == NULL) {
|
||||
/* Use the default "accelerator", tcg */
|
||||
accel = "tcg";
|
||||
p = "tcg";
|
||||
}
|
||||
|
||||
p = accel;
|
||||
while (!accel_initialised && *p != '\0') {
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
@@ -91,6 +99,7 @@ void configure_accelerator(MachineState *ms)
|
||||
p = get_opt_name(buf, sizeof(buf), p, ':');
|
||||
acc = accel_find(buf);
|
||||
if (!acc) {
|
||||
fprintf(stderr, "\"%s\" accelerator not found.\n", buf);
|
||||
continue;
|
||||
}
|
||||
if (acc->available && !acc->available()) {
|
||||
@@ -101,8 +110,9 @@ void configure_accelerator(MachineState *ms)
|
||||
ret = accel_init_machine(acc, ms);
|
||||
if (ret < 0) {
|
||||
init_failed = true;
|
||||
error_report("failed to initialize %s: %s",
|
||||
acc->name, strerror(-ret));
|
||||
fprintf(stderr, "failed to initialize %s: %s\n",
|
||||
acc->name,
|
||||
strerror(-ret));
|
||||
} else {
|
||||
accel_initialised = true;
|
||||
}
|
||||
@@ -110,25 +120,37 @@ void configure_accelerator(MachineState *ms)
|
||||
|
||||
if (!accel_initialised) {
|
||||
if (!init_failed) {
|
||||
error_report("-machine accel=%s: No accelerator found", accel);
|
||||
fprintf(stderr, "No accelerator found!\n");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (init_failed) {
|
||||
error_report("Back to %s accelerator", acc->name);
|
||||
fprintf(stderr, "Back to %s accelerator.\n", acc->name);
|
||||
}
|
||||
}
|
||||
|
||||
void accel_register_compat_props(AccelState *accel)
|
||||
|
||||
static void tcg_accel_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
AccelClass *class = ACCEL_GET_CLASS(accel);
|
||||
register_compat_props_array(class->global_props);
|
||||
AccelClass *ac = ACCEL_CLASS(oc);
|
||||
ac->name = "tcg";
|
||||
ac->init_machine = tcg_init;
|
||||
ac->allowed = &tcg_allowed;
|
||||
}
|
||||
|
||||
#define TYPE_TCG_ACCEL ACCEL_CLASS_NAME("tcg")
|
||||
|
||||
static const TypeInfo tcg_accel_type = {
|
||||
.name = TYPE_TCG_ACCEL,
|
||||
.parent = TYPE_ACCEL,
|
||||
.class_init = tcg_accel_class_init,
|
||||
};
|
||||
|
||||
static void register_accel_types(void)
|
||||
{
|
||||
type_register_static(&accel_type);
|
||||
type_register_static(&tcg_accel_type);
|
||||
}
|
||||
|
||||
type_init(register_accel_types);
|
||||
@@ -1,4 +0,0 @@
|
||||
obj-$(CONFIG_SOFTMMU) += accel.o
|
||||
obj-y += kvm/
|
||||
obj-$(CONFIG_TCG) += tcg/
|
||||
obj-y += stubs/
|
||||
@@ -1 +0,0 @@
|
||||
obj-$(CONFIG_KVM) += kvm-all.o
|
||||
@@ -1,15 +0,0 @@
|
||||
# Trace events for debugging and performance instrumentation
|
||||
|
||||
# kvm-all.c
|
||||
kvm_ioctl(int type, void *arg) "type 0x%x, arg %p"
|
||||
kvm_vm_ioctl(int type, void *arg) "type 0x%x, arg %p"
|
||||
kvm_vcpu_ioctl(int cpu_index, int type, void *arg) "cpu_index %d, type 0x%x, arg %p"
|
||||
kvm_run_exit(int cpu_index, uint32_t reason) "cpu_index %d, reason %d"
|
||||
kvm_device_ioctl(int fd, int type, void *arg) "dev fd %d, type 0x%x, arg %p"
|
||||
kvm_failed_reg_get(uint64_t id, const char *msg) "Warning: Unable to retrieve ONEREG %" PRIu64 " from KVM: %s"
|
||||
kvm_failed_reg_set(uint64_t id, const char *msg) "Warning: Unable to set ONEREG %" PRIu64 " to KVM: %s"
|
||||
kvm_irqchip_commit_routes(void) ""
|
||||
kvm_irqchip_add_msi_route(char *name, int vector, int virq) "dev %s vector %d virq %d"
|
||||
kvm_irqchip_update_msi_route(int virq) "Updating MSI route virq=%d"
|
||||
kvm_irqchip_release_virq(int virq) "virq %d"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
|
||||
obj-$(call lnot,$(CONFIG_TCG)) += tcg-stub.o
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* QEMU TCG accelerator stub
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2013
|
||||
*
|
||||
* Author: Paolo Bonzini <pbonzini@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "cpu.h"
|
||||
#include "tcg/tcg.h"
|
||||
#include "exec/cpu-common.h"
|
||||
#include "exec/exec-all.h"
|
||||
|
||||
void tb_flush(CPUState *cpu)
|
||||
{
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
obj-$(CONFIG_SOFTMMU) += tcg-all.o
|
||||
obj-$(CONFIG_SOFTMMU) += cputlb.o
|
||||
obj-y += cpu-exec.o cpu-exec-common.o translate-all.o
|
||||
-1063
File diff suppressed because it is too large.
Load diff
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* QEMU System Emulator, accelerator interfaces
|
||||
*
|
||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
||||
* Copyright (c) 2014 Red Hat Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "sysemu/accel.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "qom/object.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qom/cpu.h"
|
||||
#include "sysemu/cpus.h"
|
||||
#include "qemu/main-loop.h"
|
||||
|
||||
unsigned long tcg_tb_size;
|
||||
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
/* mask must never be zero, except for A20 change call */
|
||||
static void tcg_handle_interrupt(CPUState *cpu, int mask)
|
||||
{
|
||||
int old_mask;
|
||||
g_assert(qemu_mutex_iothread_locked());
|
||||
|
||||
old_mask = cpu->interrupt_request;
|
||||
cpu->interrupt_request |= mask;
|
||||
|
||||
/*
|
||||
* If called from iothread context, wake the target cpu in
|
||||
* case its halted.
|
||||
*/
|
||||
if (!qemu_cpu_is_self(cpu)) {
|
||||
qemu_cpu_kick(cpu);
|
||||
} else {
|
||||
cpu->icount_decr.u16.high = -1;
|
||||
if (use_icount &&
|
||||
!cpu->can_do_io
|
||||
&& (mask & ~old_mask) != 0) {
|
||||
cpu_abort(cpu, "Raised interrupt while not in I/O function");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int tcg_init(MachineState *ms)
|
||||
{
|
||||
tcg_exec_init(tcg_tb_size * 1024 * 1024);
|
||||
cpu_interrupt_handler = tcg_handle_interrupt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tcg_accel_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
AccelClass *ac = ACCEL_CLASS(oc);
|
||||
ac->name = "tcg";
|
||||
ac->init_machine = tcg_init;
|
||||
ac->allowed = &tcg_allowed;
|
||||
}
|
||||
|
||||
#define TYPE_TCG_ACCEL ACCEL_CLASS_NAME("tcg")
|
||||
|
||||
static const TypeInfo tcg_accel_type = {
|
||||
.name = TYPE_TCG_ACCEL,
|
||||
.parent = TYPE_ACCEL,
|
||||
.class_init = tcg_accel_class_init,
|
||||
};
|
||||
|
||||
static void register_accel_types(void)
|
||||
{
|
||||
type_register_static(&tcg_accel_type);
|
||||
}
|
||||
|
||||
type_init(register_accel_types);
|
||||
@@ -1,10 +0,0 @@
|
||||
# Trace events for debugging and performance instrumentation
|
||||
|
||||
# TCG related tracing (mostly disabled by default)
|
||||
# cpu-exec.c
|
||||
disable exec_tb(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
|
||||
disable exec_tb_nocache(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
|
||||
disable exec_tb_exit(void *last_tb, unsigned int flags) "tb:%p flags=0x%x"
|
||||
|
||||
# translate-all.c
|
||||
translate_block(void *tb, uintptr_t pc, uint8_t *tb_code) "tb:%p, pc:0x%"PRIxPTR", tb_code:%p"
|
||||
+107
-335
@@ -16,10 +16,8 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "block/block.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "trace.h"
|
||||
#ifdef CONFIG_EPOLL_CREATE1
|
||||
#include <sys/epoll.h>
|
||||
#endif
|
||||
@@ -29,9 +27,6 @@ struct AioHandler
|
||||
GPollFD pfd;
|
||||
IOHandler *io_read;
|
||||
IOHandler *io_write;
|
||||
AioPollFn *io_poll;
|
||||
IOHandler *io_poll_begin;
|
||||
IOHandler *io_poll_end;
|
||||
int deleted;
|
||||
void *opaque;
|
||||
bool is_external;
|
||||
@@ -66,7 +61,7 @@ static bool aio_epoll_try_enable(AioContext *ctx)
|
||||
AioHandler *node;
|
||||
struct epoll_event event;
|
||||
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
int r;
|
||||
if (node->deleted || !node->pfd.events) {
|
||||
continue;
|
||||
@@ -86,22 +81,29 @@ static void aio_epoll_update(AioContext *ctx, AioHandler *node, bool is_new)
|
||||
{
|
||||
struct epoll_event event;
|
||||
int r;
|
||||
int ctl;
|
||||
|
||||
if (!ctx->epoll_enabled) {
|
||||
return;
|
||||
}
|
||||
if (!node->pfd.events) {
|
||||
ctl = EPOLL_CTL_DEL;
|
||||
r = epoll_ctl(ctx->epollfd, EPOLL_CTL_DEL, node->pfd.fd, &event);
|
||||
if (r) {
|
||||
aio_epoll_disable(ctx);
|
||||
}
|
||||
} else {
|
||||
event.data.ptr = node;
|
||||
event.events = epoll_events_from_pfd(node->pfd.events);
|
||||
ctl = is_new ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;
|
||||
}
|
||||
|
||||
r = epoll_ctl(ctx->epollfd, ctl, node->pfd.fd, &event);
|
||||
if (r) {
|
||||
aio_epoll_disable(ctx);
|
||||
if (is_new) {
|
||||
r = epoll_ctl(ctx->epollfd, EPOLL_CTL_ADD, node->pfd.fd, &event);
|
||||
if (r) {
|
||||
aio_epoll_disable(ctx);
|
||||
}
|
||||
} else {
|
||||
r = epoll_ctl(ctx->epollfd, EPOLL_CTL_MOD, node->pfd.fd, &event);
|
||||
if (r) {
|
||||
aio_epoll_disable(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,61 +207,45 @@ void aio_set_fd_handler(AioContext *ctx,
|
||||
bool is_external,
|
||||
IOHandler *io_read,
|
||||
IOHandler *io_write,
|
||||
AioPollFn *io_poll,
|
||||
void *opaque)
|
||||
{
|
||||
AioHandler *node;
|
||||
bool is_new = false;
|
||||
bool deleted = false;
|
||||
|
||||
qemu_lockcnt_lock(&ctx->list_lock);
|
||||
|
||||
node = find_aio_handler(ctx, fd);
|
||||
|
||||
/* Are we deleting the fd handler? */
|
||||
if (!io_read && !io_write && !io_poll) {
|
||||
if (node == NULL) {
|
||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
||||
return;
|
||||
}
|
||||
if (!io_read && !io_write) {
|
||||
if (node) {
|
||||
g_source_remove_poll(&ctx->source, &node->pfd);
|
||||
|
||||
g_source_remove_poll(&ctx->source, &node->pfd);
|
||||
|
||||
/* If the lock is held, just mark the node as deleted */
|
||||
if (qemu_lockcnt_count(&ctx->list_lock)) {
|
||||
node->deleted = 1;
|
||||
node->pfd.revents = 0;
|
||||
} else {
|
||||
/* Otherwise, delete it for real. We can't just mark it as
|
||||
* deleted because deleted nodes are only cleaned up while
|
||||
* no one is walking the handlers list.
|
||||
*/
|
||||
QLIST_REMOVE(node, node);
|
||||
deleted = true;
|
||||
}
|
||||
|
||||
if (!node->io_poll) {
|
||||
ctx->poll_disable_cnt--;
|
||||
/* If the lock is held, just mark the node as deleted */
|
||||
if (ctx->walking_handlers) {
|
||||
node->deleted = 1;
|
||||
node->pfd.revents = 0;
|
||||
} else {
|
||||
/* Otherwise, delete it for real. We can't just mark it as
|
||||
* deleted because deleted nodes are only cleaned up after
|
||||
* releasing the walking_handlers lock.
|
||||
*/
|
||||
QLIST_REMOVE(node, node);
|
||||
deleted = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (node == NULL) {
|
||||
/* Alloc and insert if it's not already there */
|
||||
node = g_new0(AioHandler, 1);
|
||||
node->pfd.fd = fd;
|
||||
QLIST_INSERT_HEAD_RCU(&ctx->aio_handlers, node, node);
|
||||
QLIST_INSERT_HEAD(&ctx->aio_handlers, node, node);
|
||||
|
||||
g_source_add_poll(&ctx->source, &node->pfd);
|
||||
is_new = true;
|
||||
|
||||
ctx->poll_disable_cnt += !io_poll;
|
||||
} else {
|
||||
ctx->poll_disable_cnt += !io_poll - !node->io_poll;
|
||||
}
|
||||
|
||||
/* Update handler with latest information */
|
||||
node->io_read = io_read;
|
||||
node->io_write = io_write;
|
||||
node->io_poll = io_poll;
|
||||
node->opaque = opaque;
|
||||
node->is_external = is_external;
|
||||
|
||||
@@ -268,127 +254,72 @@ void aio_set_fd_handler(AioContext *ctx,
|
||||
}
|
||||
|
||||
aio_epoll_update(ctx, node, is_new);
|
||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
||||
aio_notify(ctx);
|
||||
|
||||
if (deleted) {
|
||||
g_free(node);
|
||||
}
|
||||
}
|
||||
|
||||
void aio_set_fd_poll(AioContext *ctx, int fd,
|
||||
IOHandler *io_poll_begin,
|
||||
IOHandler *io_poll_end)
|
||||
{
|
||||
AioHandler *node = find_aio_handler(ctx, fd);
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node->io_poll_begin = io_poll_begin;
|
||||
node->io_poll_end = io_poll_end;
|
||||
}
|
||||
|
||||
void aio_set_event_notifier(AioContext *ctx,
|
||||
EventNotifier *notifier,
|
||||
bool is_external,
|
||||
EventNotifierHandler *io_read,
|
||||
AioPollFn *io_poll)
|
||||
EventNotifierHandler *io_read)
|
||||
{
|
||||
aio_set_fd_handler(ctx, event_notifier_get_fd(notifier), is_external,
|
||||
(IOHandler *)io_read, NULL, io_poll, notifier);
|
||||
aio_set_fd_handler(ctx, event_notifier_get_fd(notifier),
|
||||
is_external, (IOHandler *)io_read, NULL, notifier);
|
||||
}
|
||||
|
||||
void aio_set_event_notifier_poll(AioContext *ctx,
|
||||
EventNotifier *notifier,
|
||||
EventNotifierHandler *io_poll_begin,
|
||||
EventNotifierHandler *io_poll_end)
|
||||
{
|
||||
aio_set_fd_poll(ctx, event_notifier_get_fd(notifier),
|
||||
(IOHandler *)io_poll_begin,
|
||||
(IOHandler *)io_poll_end);
|
||||
}
|
||||
|
||||
static void poll_set_started(AioContext *ctx, bool started)
|
||||
{
|
||||
AioHandler *node;
|
||||
|
||||
if (started == ctx->poll_started) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->poll_started = started;
|
||||
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
IOHandler *fn;
|
||||
|
||||
if (node->deleted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (started) {
|
||||
fn = node->io_poll_begin;
|
||||
} else {
|
||||
fn = node->io_poll_end;
|
||||
}
|
||||
|
||||
if (fn) {
|
||||
fn(node->opaque);
|
||||
}
|
||||
}
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
}
|
||||
|
||||
|
||||
bool aio_prepare(AioContext *ctx)
|
||||
{
|
||||
/* Poll mode cannot be used with glib's event loop, disable it. */
|
||||
poll_set_started(ctx, false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool aio_pending(AioContext *ctx)
|
||||
{
|
||||
AioHandler *node;
|
||||
bool result = false;
|
||||
|
||||
/*
|
||||
* We have to walk very carefully in case aio_set_fd_handler is
|
||||
* called while we're walking.
|
||||
*/
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
int revents;
|
||||
|
||||
revents = node->pfd.revents & node->pfd.events;
|
||||
if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read &&
|
||||
aio_node_check(ctx, node->is_external)) {
|
||||
result = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
if (revents & (G_IO_OUT | G_IO_ERR) && node->io_write &&
|
||||
aio_node_check(ctx, node->is_external)) {
|
||||
result = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool aio_dispatch_handlers(AioContext *ctx)
|
||||
bool aio_dispatch(AioContext *ctx)
|
||||
{
|
||||
AioHandler *node, *tmp;
|
||||
AioHandler *node;
|
||||
bool progress = false;
|
||||
|
||||
QLIST_FOREACH_SAFE_RCU(node, &ctx->aio_handlers, node, tmp) {
|
||||
/*
|
||||
* If there are callbacks left that have been queued, we need to call them.
|
||||
* Do not call select in this case, because it is possible that the caller
|
||||
* does not need a complete flush (as is the case for aio_poll loops).
|
||||
*/
|
||||
if (aio_bh_poll(ctx)) {
|
||||
progress = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to walk very carefully in case aio_set_fd_handler is
|
||||
* called while we're walking.
|
||||
*/
|
||||
node = QLIST_FIRST(&ctx->aio_handlers);
|
||||
while (node) {
|
||||
AioHandler *tmp;
|
||||
int revents;
|
||||
|
||||
ctx->walking_handlers++;
|
||||
|
||||
revents = node->pfd.revents & node->pfd.events;
|
||||
node->pfd.revents = 0;
|
||||
|
||||
@@ -411,28 +342,23 @@ static bool aio_dispatch_handlers(AioContext *ctx)
|
||||
progress = true;
|
||||
}
|
||||
|
||||
if (node->deleted) {
|
||||
if (qemu_lockcnt_dec_if_lock(&ctx->list_lock)) {
|
||||
QLIST_REMOVE(node, node);
|
||||
g_free(node);
|
||||
qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
|
||||
}
|
||||
tmp = node;
|
||||
node = QLIST_NEXT(node, node);
|
||||
|
||||
ctx->walking_handlers--;
|
||||
|
||||
if (!ctx->walking_handlers && tmp->deleted) {
|
||||
QLIST_REMOVE(tmp, node);
|
||||
g_free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run our timers */
|
||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
void aio_dispatch(AioContext *ctx)
|
||||
{
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
aio_bh_poll(ctx);
|
||||
aio_dispatch_handlers(ctx);
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
|
||||
timerlistgroup_run_timers(&ctx->tlg);
|
||||
}
|
||||
|
||||
/* These thread-local variables are used only in a small part of aio_poll
|
||||
* around the call to the poll() system call. In particular they are not
|
||||
* used while aio_poll is performing callbacks, which makes it much easier
|
||||
@@ -479,101 +405,15 @@ static void add_pollfd(AioHandler *node)
|
||||
npfd++;
|
||||
}
|
||||
|
||||
static bool run_poll_handlers_once(AioContext *ctx)
|
||||
{
|
||||
bool progress = false;
|
||||
AioHandler *node;
|
||||
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
if (!node->deleted && node->io_poll &&
|
||||
aio_node_check(ctx, node->is_external) &&
|
||||
node->io_poll(node->opaque)) {
|
||||
progress = true;
|
||||
}
|
||||
|
||||
/* Caller handles freeing deleted nodes. Don't do it here. */
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
/* run_poll_handlers:
|
||||
* @ctx: the AioContext
|
||||
* @max_ns: maximum time to poll for, in nanoseconds
|
||||
*
|
||||
* Polls for a given time.
|
||||
*
|
||||
* Note that ctx->notify_me must be non-zero so this function can detect
|
||||
* aio_notify().
|
||||
*
|
||||
* Note that the caller must have incremented ctx->list_lock.
|
||||
*
|
||||
* Returns: true if progress was made, false otherwise
|
||||
*/
|
||||
static bool run_poll_handlers(AioContext *ctx, int64_t max_ns)
|
||||
{
|
||||
bool progress;
|
||||
int64_t end_time;
|
||||
|
||||
assert(ctx->notify_me);
|
||||
assert(qemu_lockcnt_count(&ctx->list_lock) > 0);
|
||||
assert(ctx->poll_disable_cnt == 0);
|
||||
|
||||
trace_run_poll_handlers_begin(ctx, max_ns);
|
||||
|
||||
end_time = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + max_ns;
|
||||
|
||||
do {
|
||||
progress = run_poll_handlers_once(ctx);
|
||||
} while (!progress && qemu_clock_get_ns(QEMU_CLOCK_REALTIME) < end_time);
|
||||
|
||||
trace_run_poll_handlers_end(ctx, progress);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
/* try_poll_mode:
|
||||
* @ctx: the AioContext
|
||||
* @blocking: busy polling is only attempted when blocking is true
|
||||
*
|
||||
* ctx->notify_me must be non-zero so this function can detect aio_notify().
|
||||
*
|
||||
* Note that the caller must have incremented ctx->list_lock.
|
||||
*
|
||||
* Returns: true if progress was made, false otherwise
|
||||
*/
|
||||
static bool try_poll_mode(AioContext *ctx, bool blocking)
|
||||
{
|
||||
if (blocking && ctx->poll_max_ns && ctx->poll_disable_cnt == 0) {
|
||||
/* See qemu_soonest_timeout() uint64_t hack */
|
||||
int64_t max_ns = MIN((uint64_t)aio_compute_timeout(ctx),
|
||||
(uint64_t)ctx->poll_ns);
|
||||
|
||||
if (max_ns) {
|
||||
poll_set_started(ctx, true);
|
||||
|
||||
if (run_poll_handlers(ctx, max_ns)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
poll_set_started(ctx, false);
|
||||
|
||||
/* Even if we don't run busy polling, try polling once in case it can make
|
||||
* progress and the caller will be able to avoid ppoll(2)/epoll_wait(2).
|
||||
*/
|
||||
return run_poll_handlers_once(ctx);
|
||||
}
|
||||
|
||||
bool aio_poll(AioContext *ctx, bool blocking)
|
||||
{
|
||||
AioHandler *node;
|
||||
int i;
|
||||
int ret = 0;
|
||||
int i, ret;
|
||||
bool progress;
|
||||
int64_t timeout;
|
||||
int64_t start = 0;
|
||||
|
||||
aio_context_acquire(ctx);
|
||||
progress = false;
|
||||
|
||||
/* aio_notify can avoid the expensive event_notifier_set if
|
||||
* everything (file descriptors, bottom halves, timers) will
|
||||
@@ -586,86 +426,41 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||
atomic_add(&ctx->notify_me, 2);
|
||||
}
|
||||
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
ctx->walking_handlers++;
|
||||
|
||||
if (ctx->poll_max_ns) {
|
||||
start = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
||||
}
|
||||
assert(npfd == 0);
|
||||
|
||||
progress = try_poll_mode(ctx, blocking);
|
||||
if (!progress) {
|
||||
assert(npfd == 0);
|
||||
|
||||
/* fill pollfds */
|
||||
|
||||
if (!aio_epoll_enabled(ctx)) {
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
if (!node->deleted && node->pfd.events
|
||||
&& aio_node_check(ctx, node->is_external)) {
|
||||
add_pollfd(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeout = blocking ? aio_compute_timeout(ctx) : 0;
|
||||
|
||||
/* wait until next event */
|
||||
if (aio_epoll_check_poll(ctx, pollfds, npfd, timeout)) {
|
||||
AioHandler epoll_handler;
|
||||
|
||||
epoll_handler.pfd.fd = ctx->epollfd;
|
||||
epoll_handler.pfd.events = G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR;
|
||||
npfd = 0;
|
||||
add_pollfd(&epoll_handler);
|
||||
ret = aio_epoll(ctx, pollfds, npfd, timeout);
|
||||
} else {
|
||||
ret = qemu_poll_ns(pollfds, npfd, timeout);
|
||||
/* fill pollfds */
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
if (!node->deleted && node->pfd.events
|
||||
&& !aio_epoll_enabled(ctx)
|
||||
&& aio_node_check(ctx, node->is_external)) {
|
||||
add_pollfd(node);
|
||||
}
|
||||
}
|
||||
|
||||
timeout = blocking ? aio_compute_timeout(ctx) : 0;
|
||||
|
||||
/* wait until next event */
|
||||
if (timeout) {
|
||||
aio_context_release(ctx);
|
||||
}
|
||||
if (aio_epoll_check_poll(ctx, pollfds, npfd, timeout)) {
|
||||
AioHandler epoll_handler;
|
||||
|
||||
epoll_handler.pfd.fd = ctx->epollfd;
|
||||
epoll_handler.pfd.events = G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR;
|
||||
npfd = 0;
|
||||
add_pollfd(&epoll_handler);
|
||||
ret = aio_epoll(ctx, pollfds, npfd, timeout);
|
||||
} else {
|
||||
ret = qemu_poll_ns(pollfds, npfd, timeout);
|
||||
}
|
||||
if (blocking) {
|
||||
atomic_sub(&ctx->notify_me, 2);
|
||||
}
|
||||
|
||||
/* Adjust polling time */
|
||||
if (ctx->poll_max_ns) {
|
||||
int64_t block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start;
|
||||
|
||||
if (block_ns <= ctx->poll_ns) {
|
||||
/* This is the sweet spot, no adjustment needed */
|
||||
} else if (block_ns > ctx->poll_max_ns) {
|
||||
/* We'd have to poll for too long, poll less */
|
||||
int64_t old = ctx->poll_ns;
|
||||
|
||||
if (ctx->poll_shrink) {
|
||||
ctx->poll_ns /= ctx->poll_shrink;
|
||||
} else {
|
||||
ctx->poll_ns = 0;
|
||||
}
|
||||
|
||||
trace_poll_shrink(ctx, old, ctx->poll_ns);
|
||||
} else if (ctx->poll_ns < ctx->poll_max_ns &&
|
||||
block_ns < ctx->poll_max_ns) {
|
||||
/* There is room to grow, poll longer */
|
||||
int64_t old = ctx->poll_ns;
|
||||
int64_t grow = ctx->poll_grow;
|
||||
|
||||
if (grow == 0) {
|
||||
grow = 2;
|
||||
}
|
||||
|
||||
if (ctx->poll_ns) {
|
||||
ctx->poll_ns *= grow;
|
||||
} else {
|
||||
ctx->poll_ns = 4000; /* start polling at 4 microseconds */
|
||||
}
|
||||
|
||||
if (ctx->poll_ns > ctx->poll_max_ns) {
|
||||
ctx->poll_ns = ctx->poll_max_ns;
|
||||
}
|
||||
|
||||
trace_poll_grow(ctx, old, ctx->poll_ns);
|
||||
}
|
||||
if (timeout) {
|
||||
aio_context_acquire(ctx);
|
||||
}
|
||||
|
||||
aio_notify_accept(ctx);
|
||||
@@ -678,29 +473,20 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||
}
|
||||
|
||||
npfd = 0;
|
||||
ctx->walking_handlers--;
|
||||
|
||||
progress |= aio_bh_poll(ctx);
|
||||
|
||||
if (ret > 0) {
|
||||
progress |= aio_dispatch_handlers(ctx);
|
||||
/* Run dispatch even if there were no readable fds to run timers */
|
||||
if (aio_dispatch(ctx)) {
|
||||
progress = true;
|
||||
}
|
||||
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
|
||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||
aio_context_release(ctx);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
void aio_context_setup(AioContext *ctx)
|
||||
{
|
||||
/* TODO remove this in final patch submission */
|
||||
if (getenv("QEMU_AIO_POLL_MAX_NS")) {
|
||||
fprintf(stderr, "The QEMU_AIO_POLL_MAX_NS environment variable has "
|
||||
"been replaced with -object iothread,poll-max-ns=NUM\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EPOLL_CREATE1
|
||||
assert(!ctx->epollfd);
|
||||
ctx->epollfd = epoll_create1(EPOLL_CLOEXEC);
|
||||
@@ -712,17 +498,3 @@ void aio_context_setup(AioContext *ctx)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
|
||||
int64_t grow, int64_t shrink, Error **errp)
|
||||
{
|
||||
/* No thread synchronization here, it doesn't matter if an incorrect value
|
||||
* is used once.
|
||||
*/
|
||||
ctx->poll_max_ns = max_ns;
|
||||
ctx->poll_ns = 0;
|
||||
ctx->poll_grow = grow;
|
||||
ctx->poll_shrink = shrink;
|
||||
|
||||
aio_notify(ctx);
|
||||
}
|
||||
@@ -20,8 +20,6 @@
|
||||
#include "block/block.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
|
||||
struct AioHandler {
|
||||
EventNotifier *e;
|
||||
@@ -40,13 +38,11 @@ void aio_set_fd_handler(AioContext *ctx,
|
||||
bool is_external,
|
||||
IOHandler *io_read,
|
||||
IOHandler *io_write,
|
||||
AioPollFn *io_poll,
|
||||
void *opaque)
|
||||
{
|
||||
/* fd is a SOCKET in our case */
|
||||
AioHandler *node;
|
||||
|
||||
qemu_lockcnt_lock(&ctx->list_lock);
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
if (node->pfd.fd == fd && !node->deleted) {
|
||||
break;
|
||||
@@ -56,14 +52,14 @@ void aio_set_fd_handler(AioContext *ctx,
|
||||
/* Are we deleting the fd handler? */
|
||||
if (!io_read && !io_write) {
|
||||
if (node) {
|
||||
/* If aio_poll is in progress, just mark the node as deleted */
|
||||
if (qemu_lockcnt_count(&ctx->list_lock)) {
|
||||
/* If the lock is held, just mark the node as deleted */
|
||||
if (ctx->walking_handlers) {
|
||||
node->deleted = 1;
|
||||
node->pfd.revents = 0;
|
||||
} else {
|
||||
/* Otherwise, delete it for real. We can't just mark it as
|
||||
* deleted because deleted nodes are only cleaned up after
|
||||
* releasing the list_lock.
|
||||
* releasing the walking_handlers lock.
|
||||
*/
|
||||
QLIST_REMOVE(node, node);
|
||||
g_free(node);
|
||||
@@ -71,13 +67,12 @@ void aio_set_fd_handler(AioContext *ctx,
|
||||
}
|
||||
} else {
|
||||
HANDLE event;
|
||||
long bitmask = 0;
|
||||
|
||||
if (node == NULL) {
|
||||
/* Alloc and insert if it's not already there */
|
||||
node = g_new0(AioHandler, 1);
|
||||
node->pfd.fd = fd;
|
||||
QLIST_INSERT_HEAD_RCU(&ctx->aio_handlers, node, node);
|
||||
QLIST_INSERT_HEAD(&ctx->aio_handlers, node, node);
|
||||
}
|
||||
|
||||
node->pfd.events = 0;
|
||||
@@ -96,38 +91,22 @@ void aio_set_fd_handler(AioContext *ctx,
|
||||
node->io_write = io_write;
|
||||
node->is_external = is_external;
|
||||
|
||||
if (io_read) {
|
||||
bitmask |= FD_READ | FD_ACCEPT | FD_CLOSE;
|
||||
}
|
||||
|
||||
if (io_write) {
|
||||
bitmask |= FD_WRITE | FD_CONNECT;
|
||||
}
|
||||
|
||||
event = event_notifier_get_handle(&ctx->notifier);
|
||||
WSAEventSelect(node->pfd.fd, event, bitmask);
|
||||
WSAEventSelect(node->pfd.fd, event,
|
||||
FD_READ | FD_ACCEPT | FD_CLOSE |
|
||||
FD_CONNECT | FD_WRITE | FD_OOB);
|
||||
}
|
||||
|
||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
||||
aio_notify(ctx);
|
||||
}
|
||||
|
||||
void aio_set_fd_poll(AioContext *ctx, int fd,
|
||||
IOHandler *io_poll_begin,
|
||||
IOHandler *io_poll_end)
|
||||
{
|
||||
/* Not implemented */
|
||||
}
|
||||
|
||||
void aio_set_event_notifier(AioContext *ctx,
|
||||
EventNotifier *e,
|
||||
bool is_external,
|
||||
EventNotifierHandler *io_notify,
|
||||
AioPollFn *io_poll)
|
||||
EventNotifierHandler *io_notify)
|
||||
{
|
||||
AioHandler *node;
|
||||
|
||||
qemu_lockcnt_lock(&ctx->list_lock);
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
if (node->e == e && !node->deleted) {
|
||||
break;
|
||||
@@ -139,14 +118,14 @@ void aio_set_event_notifier(AioContext *ctx,
|
||||
if (node) {
|
||||
g_source_remove_poll(&ctx->source, &node->pfd);
|
||||
|
||||
/* aio_poll is in progress, just mark the node as deleted */
|
||||
if (qemu_lockcnt_count(&ctx->list_lock)) {
|
||||
/* If the lock is held, just mark the node as deleted */
|
||||
if (ctx->walking_handlers) {
|
||||
node->deleted = 1;
|
||||
node->pfd.revents = 0;
|
||||
} else {
|
||||
/* Otherwise, delete it for real. We can't just mark it as
|
||||
* deleted because deleted nodes are only cleaned up after
|
||||
* releasing the list_lock.
|
||||
* releasing the walking_handlers lock.
|
||||
*/
|
||||
QLIST_REMOVE(node, node);
|
||||
g_free(node);
|
||||
@@ -160,7 +139,7 @@ void aio_set_event_notifier(AioContext *ctx,
|
||||
node->pfd.fd = (uintptr_t)event_notifier_get_handle(e);
|
||||
node->pfd.events = G_IO_IN;
|
||||
node->is_external = is_external;
|
||||
QLIST_INSERT_HEAD_RCU(&ctx->aio_handlers, node, node);
|
||||
QLIST_INSERT_HEAD(&ctx->aio_handlers, node, node);
|
||||
|
||||
g_source_add_poll(&ctx->source, &node->pfd);
|
||||
}
|
||||
@@ -168,18 +147,9 @@ void aio_set_event_notifier(AioContext *ctx,
|
||||
node->io_notify = io_notify;
|
||||
}
|
||||
|
||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
||||
aio_notify(ctx);
|
||||
}
|
||||
|
||||
void aio_set_event_notifier_poll(AioContext *ctx,
|
||||
EventNotifier *notifier,
|
||||
EventNotifierHandler *io_poll_begin,
|
||||
EventNotifierHandler *io_poll_end)
|
||||
{
|
||||
/* Not implemented */
|
||||
}
|
||||
|
||||
bool aio_prepare(AioContext *ctx)
|
||||
{
|
||||
static struct timeval tv0;
|
||||
@@ -187,16 +157,10 @@ bool aio_prepare(AioContext *ctx)
|
||||
bool have_select_revents = false;
|
||||
fd_set rfds, wfds;
|
||||
|
||||
/*
|
||||
* We have to walk very carefully in case aio_set_fd_handler is
|
||||
* called while we're walking.
|
||||
*/
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
|
||||
/* fill fd sets */
|
||||
FD_ZERO(&rfds);
|
||||
FD_ZERO(&wfds);
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
if (node->io_read) {
|
||||
FD_SET ((SOCKET)node->pfd.fd, &rfds);
|
||||
}
|
||||
@@ -206,7 +170,7 @@ bool aio_prepare(AioContext *ctx)
|
||||
}
|
||||
|
||||
if (select(0, &rfds, &wfds, NULL, &tv0) > 0) {
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
node->pfd.revents = 0;
|
||||
if (FD_ISSET(node->pfd.fd, &rfds)) {
|
||||
node->pfd.revents |= G_IO_IN;
|
||||
@@ -220,53 +184,45 @@ bool aio_prepare(AioContext *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
return have_select_revents;
|
||||
}
|
||||
|
||||
bool aio_pending(AioContext *ctx)
|
||||
{
|
||||
AioHandler *node;
|
||||
bool result = false;
|
||||
|
||||
/*
|
||||
* We have to walk very carefully in case aio_set_fd_handler is
|
||||
* called while we're walking.
|
||||
*/
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
if (node->pfd.revents && node->io_notify) {
|
||||
result = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((node->pfd.revents & G_IO_IN) && node->io_read) {
|
||||
result = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
if ((node->pfd.revents & G_IO_OUT) && node->io_write) {
|
||||
result = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool aio_dispatch_handlers(AioContext *ctx, HANDLE event)
|
||||
{
|
||||
AioHandler *node;
|
||||
bool progress = false;
|
||||
AioHandler *tmp;
|
||||
|
||||
/*
|
||||
* We have to walk very carefully in case aio_set_fd_handler is
|
||||
* called while we're walking.
|
||||
*/
|
||||
QLIST_FOREACH_SAFE_RCU(node, &ctx->aio_handlers, node, tmp) {
|
||||
node = QLIST_FIRST(&ctx->aio_handlers);
|
||||
while (node) {
|
||||
AioHandler *tmp;
|
||||
int revents = node->pfd.revents;
|
||||
|
||||
ctx->walking_handlers++;
|
||||
|
||||
if (!node->deleted &&
|
||||
(revents || event_notifier_get_handle(node->e) == event) &&
|
||||
node->io_notify) {
|
||||
@@ -301,25 +257,28 @@ static bool aio_dispatch_handlers(AioContext *ctx, HANDLE event)
|
||||
}
|
||||
}
|
||||
|
||||
if (node->deleted) {
|
||||
if (qemu_lockcnt_dec_if_lock(&ctx->list_lock)) {
|
||||
QLIST_REMOVE(node, node);
|
||||
g_free(node);
|
||||
qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
|
||||
}
|
||||
tmp = node;
|
||||
node = QLIST_NEXT(node, node);
|
||||
|
||||
ctx->walking_handlers--;
|
||||
|
||||
if (!ctx->walking_handlers && tmp->deleted) {
|
||||
QLIST_REMOVE(tmp, node);
|
||||
g_free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
void aio_dispatch(AioContext *ctx)
|
||||
bool aio_dispatch(AioContext *ctx)
|
||||
{
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
aio_bh_poll(ctx);
|
||||
aio_dispatch_handlers(ctx, INVALID_HANDLE_VALUE);
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
timerlistgroup_run_timers(&ctx->tlg);
|
||||
bool progress;
|
||||
|
||||
progress = aio_bh_poll(ctx);
|
||||
progress |= aio_dispatch_handlers(ctx, INVALID_HANDLE_VALUE);
|
||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool aio_poll(AioContext *ctx, bool blocking)
|
||||
@@ -330,6 +289,7 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||
int count;
|
||||
int timeout;
|
||||
|
||||
aio_context_acquire(ctx);
|
||||
progress = false;
|
||||
|
||||
/* aio_notify can avoid the expensive event_notifier_set if
|
||||
@@ -343,18 +303,20 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||
atomic_add(&ctx->notify_me, 2);
|
||||
}
|
||||
|
||||
qemu_lockcnt_inc(&ctx->list_lock);
|
||||
have_select_revents = aio_prepare(ctx);
|
||||
|
||||
ctx->walking_handlers++;
|
||||
|
||||
/* fill fd sets */
|
||||
count = 0;
|
||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||
if (!node->deleted && node->io_notify
|
||||
&& aio_node_check(ctx, node->is_external)) {
|
||||
events[count++] = event_notifier_get_handle(node->e);
|
||||
}
|
||||
}
|
||||
|
||||
ctx->walking_handlers--;
|
||||
first = true;
|
||||
|
||||
/* ctx->notifier is always registered. */
|
||||
@@ -370,11 +332,17 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||
|
||||
timeout = blocking && !have_select_revents
|
||||
? qemu_timeout_ns_to_ms(aio_compute_timeout(ctx)) : 0;
|
||||
if (timeout) {
|
||||
aio_context_release(ctx);
|
||||
}
|
||||
ret = WaitForMultipleObjects(count, events, FALSE, timeout);
|
||||
if (blocking) {
|
||||
assert(first);
|
||||
atomic_sub(&ctx->notify_me, 2);
|
||||
}
|
||||
if (timeout) {
|
||||
aio_context_acquire(ctx);
|
||||
}
|
||||
|
||||
if (first) {
|
||||
aio_notify_accept(ctx);
|
||||
@@ -397,18 +365,12 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||
progress |= aio_dispatch_handlers(ctx, event);
|
||||
} while (count > 0);
|
||||
|
||||
qemu_lockcnt_dec(&ctx->list_lock);
|
||||
|
||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||
|
||||
aio_context_release(ctx);
|
||||
return progress;
|
||||
}
|
||||
|
||||
void aio_context_setup(AioContext *ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
|
||||
int64_t grow, int64_t shrink, Error **errp)
|
||||
{
|
||||
error_setg(errp, "AioContext polling is not implemented on Windows");
|
||||
}
|
||||
+192
-3
@@ -27,7 +27,8 @@
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "sysemu/arch_init.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/audio/soundhw.h"
|
||||
#include "hw/audio/audio.h"
|
||||
#include "hw/smbios/smbios.h"
|
||||
#include "qemu/config-file.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qmp-commands.h"
|
||||
@@ -63,8 +64,6 @@ int graphic_depth = 32;
|
||||
#define QEMU_ARCH QEMU_ARCH_MIPS
|
||||
#elif defined(TARGET_MOXIE)
|
||||
#define QEMU_ARCH QEMU_ARCH_MOXIE
|
||||
#elif defined(TARGET_NIOS2)
|
||||
#define QEMU_ARCH QEMU_ARCH_NIOS2
|
||||
#elif defined(TARGET_OPENRISC)
|
||||
#define QEMU_ARCH QEMU_ARCH_OPENRISC
|
||||
#elif defined(TARGET_PPC)
|
||||
@@ -85,6 +84,196 @@ int graphic_depth = 32;
|
||||
|
||||
const uint32_t arch_type = QEMU_ARCH;
|
||||
|
||||
static struct defconfig_file {
|
||||
const char *filename;
|
||||
/* Indicates it is an user config file (disabled by -no-user-config) */
|
||||
bool userconfig;
|
||||
} default_config_files[] = {
|
||||
{ CONFIG_QEMU_CONFDIR "/qemu.conf", true },
|
||||
{ NULL }, /* end of list */
|
||||
};
|
||||
|
||||
int qemu_read_default_config_files(bool userconfig)
|
||||
{
|
||||
int ret;
|
||||
struct defconfig_file *f;
|
||||
|
||||
for (f = default_config_files; f->filename; f++) {
|
||||
if (!userconfig && f->userconfig) {
|
||||
continue;
|
||||
}
|
||||
ret = qemu_read_config_file(f->filename);
|
||||
if (ret < 0 && ret != -ENOENT) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct soundhw {
|
||||
const char *name;
|
||||
const char *descr;
|
||||
int enabled;
|
||||
int isa;
|
||||
union {
|
||||
int (*init_isa) (ISABus *bus);
|
||||
int (*init_pci) (PCIBus *bus);
|
||||
} init;
|
||||
};
|
||||
|
||||
static struct soundhw soundhw[9];
|
||||
static int soundhw_count;
|
||||
|
||||
void isa_register_soundhw(const char *name, const char *descr,
|
||||
int (*init_isa)(ISABus *bus))
|
||||
{
|
||||
assert(soundhw_count < ARRAY_SIZE(soundhw) - 1);
|
||||
soundhw[soundhw_count].name = name;
|
||||
soundhw[soundhw_count].descr = descr;
|
||||
soundhw[soundhw_count].isa = 1;
|
||||
soundhw[soundhw_count].init.init_isa = init_isa;
|
||||
soundhw_count++;
|
||||
}
|
||||
|
||||
void pci_register_soundhw(const char *name, const char *descr,
|
||||
int (*init_pci)(PCIBus *bus))
|
||||
{
|
||||
assert(soundhw_count < ARRAY_SIZE(soundhw) - 1);
|
||||
soundhw[soundhw_count].name = name;
|
||||
soundhw[soundhw_count].descr = descr;
|
||||
soundhw[soundhw_count].isa = 0;
|
||||
soundhw[soundhw_count].init.init_pci = init_pci;
|
||||
soundhw_count++;
|
||||
}
|
||||
|
||||
void select_soundhw(const char *optarg)
|
||||
{
|
||||
struct soundhw *c;
|
||||
|
||||
if (is_help_option(optarg)) {
|
||||
show_valid_cards:
|
||||
|
||||
if (soundhw_count) {
|
||||
printf("Valid sound card names (comma separated):\n");
|
||||
for (c = soundhw; c->name; ++c) {
|
||||
printf ("%-11s %s\n", c->name, c->descr);
|
||||
}
|
||||
printf("\n-soundhw all will enable all of the above\n");
|
||||
} else {
|
||||
printf("Machine has no user-selectable audio hardware "
|
||||
"(it may or may not have always-present audio hardware).\n");
|
||||
}
|
||||
exit(!is_help_option(optarg));
|
||||
}
|
||||
else {
|
||||
size_t l;
|
||||
const char *p;
|
||||
char *e;
|
||||
int bad_card = 0;
|
||||
|
||||
if (!strcmp(optarg, "all")) {
|
||||
for (c = soundhw; c->name; ++c) {
|
||||
c->enabled = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
p = optarg;
|
||||
while (*p) {
|
||||
e = strchr(p, ',');
|
||||
l = !e ? strlen(p) : (size_t) (e - p);
|
||||
|
||||
for (c = soundhw; c->name; ++c) {
|
||||
if (!strncmp(c->name, p, l) && !c->name[l]) {
|
||||
c->enabled = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!c->name) {
|
||||
if (l > 80) {
|
||||
error_report("Unknown sound card name (too big to show)");
|
||||
}
|
||||
else {
|
||||
error_report("Unknown sound card name `%.*s'",
|
||||
(int) l, p);
|
||||
}
|
||||
bad_card = 1;
|
||||
}
|
||||
p += l + (e != NULL);
|
||||
}
|
||||
|
||||
if (bad_card) {
|
||||
goto show_valid_cards;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void audio_init(void)
|
||||
{
|
||||
struct soundhw *c;
|
||||
ISABus *isa_bus = (ISABus *) object_resolve_path_type("", TYPE_ISA_BUS, NULL);
|
||||
PCIBus *pci_bus = (PCIBus *) object_resolve_path_type("", TYPE_PCI_BUS, NULL);
|
||||
|
||||
for (c = soundhw; c->name; ++c) {
|
||||
if (c->enabled) {
|
||||
if (c->isa) {
|
||||
if (!isa_bus) {
|
||||
error_report("ISA bus not available for %s", c->name);
|
||||
exit(1);
|
||||
}
|
||||
c->init.init_isa(isa_bus);
|
||||
} else {
|
||||
if (!pci_bus) {
|
||||
error_report("PCI bus not available for %s", c->name);
|
||||
exit(1);
|
||||
}
|
||||
c->init.init_pci(pci_bus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int qemu_uuid_parse(const char *str, uint8_t *uuid)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (strlen(str) != 36) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = sscanf(str, UUID_FMT, &uuid[0], &uuid[1], &uuid[2], &uuid[3],
|
||||
&uuid[4], &uuid[5], &uuid[6], &uuid[7], &uuid[8], &uuid[9],
|
||||
&uuid[10], &uuid[11], &uuid[12], &uuid[13], &uuid[14],
|
||||
&uuid[15]);
|
||||
|
||||
if (ret != 16) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void do_acpitable_option(const QemuOpts *opts)
|
||||
{
|
||||
#ifdef TARGET_I386
|
||||
Error *err = NULL;
|
||||
|
||||
acpi_table_add(opts, &err);
|
||||
if (err) {
|
||||
error_reportf_err(err, "Wrong acpi table provided: ");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void do_smbios_option(QemuOpts *opts)
|
||||
{
|
||||
#ifdef TARGET_I386
|
||||
smbios_entry_add(opts);
|
||||
#endif
|
||||
}
|
||||
|
||||
int kvm_available(void)
|
||||
{
|
||||
#ifdef CONFIG_KVM
|
||||
|
||||
+46
-143
@@ -1,8 +1,7 @@
|
||||
/*
|
||||
* Data plane event loop
|
||||
* QEMU System Emulator
|
||||
*
|
||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
||||
* Copyright (c) 2009-2017 QEMU contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,8 +30,6 @@
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/atomic.h"
|
||||
#include "block/raw-aio.h"
|
||||
#include "qemu/coroutine_int.h"
|
||||
#include "trace.h"
|
||||
|
||||
/***********************************************************/
|
||||
/* bottom halves (can be seen as timers which expire ASAP) */
|
||||
@@ -47,26 +44,6 @@ struct QEMUBH {
|
||||
bool deleted;
|
||||
};
|
||||
|
||||
void aio_bh_schedule_oneshot(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
||||
{
|
||||
QEMUBH *bh;
|
||||
bh = g_new(QEMUBH, 1);
|
||||
*bh = (QEMUBH){
|
||||
.ctx = ctx,
|
||||
.cb = cb,
|
||||
.opaque = opaque,
|
||||
};
|
||||
qemu_lockcnt_lock(&ctx->list_lock);
|
||||
bh->next = ctx->first_bh;
|
||||
bh->scheduled = 1;
|
||||
bh->deleted = 1;
|
||||
/* Make sure that the members are ready before putting bh into list */
|
||||
smp_wmb();
|
||||
ctx->first_bh = bh;
|
||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
||||
aio_notify(ctx);
|
||||
}
|
||||
|
||||
QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
||||
{
|
||||
QEMUBH *bh;
|
||||
@@ -76,12 +53,12 @@ QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
||||
.cb = cb,
|
||||
.opaque = opaque,
|
||||
};
|
||||
qemu_lockcnt_lock(&ctx->list_lock);
|
||||
qemu_mutex_lock(&ctx->bh_lock);
|
||||
bh->next = ctx->first_bh;
|
||||
/* Make sure that the members are ready before putting bh into list */
|
||||
smp_wmb();
|
||||
ctx->first_bh = bh;
|
||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
||||
qemu_mutex_unlock(&ctx->bh_lock);
|
||||
return bh;
|
||||
}
|
||||
|
||||
@@ -90,56 +67,53 @@ void aio_bh_call(QEMUBH *bh)
|
||||
bh->cb(bh->opaque);
|
||||
}
|
||||
|
||||
/* Multiple occurrences of aio_bh_poll cannot be called concurrently.
|
||||
* The count in ctx->list_lock is incremented before the call, and is
|
||||
* not affected by the call.
|
||||
*/
|
||||
/* Multiple occurrences of aio_bh_poll cannot be called concurrently */
|
||||
int aio_bh_poll(AioContext *ctx)
|
||||
{
|
||||
QEMUBH *bh, **bhp, *next;
|
||||
int ret;
|
||||
bool deleted = false;
|
||||
|
||||
ctx->walking_bh++;
|
||||
|
||||
ret = 0;
|
||||
for (bh = atomic_rcu_read(&ctx->first_bh); bh; bh = next) {
|
||||
next = atomic_rcu_read(&bh->next);
|
||||
for (bh = ctx->first_bh; bh; bh = next) {
|
||||
/* Make sure that fetching bh happens before accessing its members */
|
||||
smp_read_barrier_depends();
|
||||
next = bh->next;
|
||||
/* The atomic_xchg is paired with the one in qemu_bh_schedule. The
|
||||
* implicit memory barrier ensures that the callback sees all writes
|
||||
* done by the scheduling thread. It also ensures that the scheduling
|
||||
* thread sees the zero before bh->cb has run, and thus will call
|
||||
* aio_notify again if necessary.
|
||||
*/
|
||||
if (atomic_xchg(&bh->scheduled, 0)) {
|
||||
/* Idle BHs don't count as progress */
|
||||
if (!bh->idle) {
|
||||
if (!bh->deleted && atomic_xchg(&bh->scheduled, 0)) {
|
||||
/* Idle BHs and the notify BH don't count as progress */
|
||||
if (!bh->idle && bh != ctx->notify_dummy_bh) {
|
||||
ret = 1;
|
||||
}
|
||||
bh->idle = 0;
|
||||
aio_bh_call(bh);
|
||||
}
|
||||
if (bh->deleted) {
|
||||
deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->walking_bh--;
|
||||
|
||||
/* remove deleted bhs */
|
||||
if (!deleted) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (qemu_lockcnt_dec_if_lock(&ctx->list_lock)) {
|
||||
if (!ctx->walking_bh) {
|
||||
qemu_mutex_lock(&ctx->bh_lock);
|
||||
bhp = &ctx->first_bh;
|
||||
while (*bhp) {
|
||||
bh = *bhp;
|
||||
if (bh->deleted && !bh->scheduled) {
|
||||
if (bh->deleted) {
|
||||
*bhp = bh->next;
|
||||
g_free(bh);
|
||||
} else {
|
||||
bhp = &bh->next;
|
||||
}
|
||||
}
|
||||
qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
|
||||
qemu_mutex_unlock(&ctx->bh_lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -193,9 +167,8 @@ aio_compute_timeout(AioContext *ctx)
|
||||
int timeout = -1;
|
||||
QEMUBH *bh;
|
||||
|
||||
for (bh = atomic_rcu_read(&ctx->first_bh); bh;
|
||||
bh = atomic_rcu_read(&bh->next)) {
|
||||
if (bh->scheduled) {
|
||||
for (bh = ctx->first_bh; bh; bh = bh->next) {
|
||||
if (!bh->deleted && bh->scheduled) {
|
||||
if (bh->idle) {
|
||||
/* idle bottom halves will be polled at least
|
||||
* every 10ms */
|
||||
@@ -243,7 +216,7 @@ aio_ctx_check(GSource *source)
|
||||
aio_notify_accept(ctx);
|
||||
|
||||
for (bh = ctx->first_bh; bh; bh = bh->next) {
|
||||
if (bh->scheduled) {
|
||||
if (!bh->deleted && bh->scheduled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -267,6 +240,7 @@ aio_ctx_finalize(GSource *source)
|
||||
{
|
||||
AioContext *ctx = (AioContext *) source;
|
||||
|
||||
qemu_bh_delete(ctx->notify_dummy_bh);
|
||||
thread_pool_free(ctx->thread_pool);
|
||||
|
||||
#ifdef CONFIG_LINUX_AIO
|
||||
@@ -277,11 +251,7 @@ aio_ctx_finalize(GSource *source)
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(QSLIST_EMPTY(&ctx->scheduled_coroutines));
|
||||
qemu_bh_delete(ctx->co_schedule_bh);
|
||||
|
||||
qemu_lockcnt_lock(&ctx->list_lock);
|
||||
assert(!qemu_lockcnt_count(&ctx->list_lock));
|
||||
qemu_mutex_lock(&ctx->bh_lock);
|
||||
while (ctx->first_bh) {
|
||||
QEMUBH *next = ctx->first_bh->next;
|
||||
|
||||
@@ -291,12 +261,12 @@ aio_ctx_finalize(GSource *source)
|
||||
g_free(ctx->first_bh);
|
||||
ctx->first_bh = next;
|
||||
}
|
||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
||||
qemu_mutex_unlock(&ctx->bh_lock);
|
||||
|
||||
aio_set_event_notifier(ctx, &ctx->notifier, false, NULL, NULL);
|
||||
aio_set_event_notifier(ctx, &ctx->notifier, false, NULL);
|
||||
event_notifier_cleanup(&ctx->notifier);
|
||||
qemu_rec_mutex_destroy(&ctx->lock);
|
||||
qemu_lockcnt_destroy(&ctx->list_lock);
|
||||
rfifolock_destroy(&ctx->lock);
|
||||
qemu_mutex_destroy(&ctx->bh_lock);
|
||||
timerlistgroup_deinit(&ctx->tlg);
|
||||
}
|
||||
|
||||
@@ -351,46 +321,26 @@ void aio_notify_accept(AioContext *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
static void aio_timerlist_notify(void *opaque, QEMUClockType type)
|
||||
static void aio_timerlist_notify(void *opaque)
|
||||
{
|
||||
aio_notify(opaque);
|
||||
}
|
||||
|
||||
static void event_notifier_dummy_cb(EventNotifier *e)
|
||||
{
|
||||
}
|
||||
|
||||
/* Returns true if aio_notify() was called (e.g. a BH was scheduled) */
|
||||
static bool event_notifier_poll(void *opaque)
|
||||
{
|
||||
EventNotifier *e = opaque;
|
||||
AioContext *ctx = container_of(e, AioContext, notifier);
|
||||
|
||||
return atomic_read(&ctx->notified);
|
||||
}
|
||||
|
||||
static void co_schedule_bh_cb(void *opaque)
|
||||
static void aio_rfifolock_cb(void *opaque)
|
||||
{
|
||||
AioContext *ctx = opaque;
|
||||
QSLIST_HEAD(, Coroutine) straight, reversed;
|
||||
|
||||
QSLIST_MOVE_ATOMIC(&reversed, &ctx->scheduled_coroutines);
|
||||
QSLIST_INIT(&straight);
|
||||
/* Kick owner thread in case they are blocked in aio_poll() */
|
||||
qemu_bh_schedule(ctx->notify_dummy_bh);
|
||||
}
|
||||
|
||||
while (!QSLIST_EMPTY(&reversed)) {
|
||||
Coroutine *co = QSLIST_FIRST(&reversed);
|
||||
QSLIST_REMOVE_HEAD(&reversed, co_scheduled_next);
|
||||
QSLIST_INSERT_HEAD(&straight, co, co_scheduled_next);
|
||||
}
|
||||
static void notify_dummy_bh(void *opaque)
|
||||
{
|
||||
/* Do nothing, we were invoked just to force the event loop to iterate */
|
||||
}
|
||||
|
||||
while (!QSLIST_EMPTY(&straight)) {
|
||||
Coroutine *co = QSLIST_FIRST(&straight);
|
||||
QSLIST_REMOVE_HEAD(&straight, co_scheduled_next);
|
||||
trace_aio_co_schedule_bh_cb(ctx, co);
|
||||
aio_context_acquire(ctx);
|
||||
qemu_coroutine_enter(co);
|
||||
aio_context_release(ctx);
|
||||
}
|
||||
static void event_notifier_dummy_cb(EventNotifier *e)
|
||||
{
|
||||
}
|
||||
|
||||
AioContext *aio_context_new(Error **errp)
|
||||
@@ -407,27 +357,19 @@ AioContext *aio_context_new(Error **errp)
|
||||
goto fail;
|
||||
}
|
||||
g_source_set_can_recurse(&ctx->source, true);
|
||||
qemu_lockcnt_init(&ctx->list_lock);
|
||||
|
||||
ctx->co_schedule_bh = aio_bh_new(ctx, co_schedule_bh_cb, ctx);
|
||||
QSLIST_INIT(&ctx->scheduled_coroutines);
|
||||
|
||||
aio_set_event_notifier(ctx, &ctx->notifier,
|
||||
false,
|
||||
(EventNotifierHandler *)
|
||||
event_notifier_dummy_cb,
|
||||
event_notifier_poll);
|
||||
event_notifier_dummy_cb);
|
||||
#ifdef CONFIG_LINUX_AIO
|
||||
ctx->linux_aio = NULL;
|
||||
#endif
|
||||
ctx->thread_pool = NULL;
|
||||
qemu_rec_mutex_init(&ctx->lock);
|
||||
qemu_mutex_init(&ctx->bh_lock);
|
||||
rfifolock_init(&ctx->lock, aio_rfifolock_cb, ctx);
|
||||
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
||||
|
||||
ctx->poll_ns = 0;
|
||||
ctx->poll_max_ns = 0;
|
||||
ctx->poll_grow = 0;
|
||||
ctx->poll_shrink = 0;
|
||||
ctx->notify_dummy_bh = aio_bh_new(ctx, notify_dummy_bh, NULL);
|
||||
|
||||
return ctx;
|
||||
fail:
|
||||
@@ -435,45 +377,6 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void aio_co_schedule(AioContext *ctx, Coroutine *co)
|
||||
{
|
||||
trace_aio_co_schedule(ctx, co);
|
||||
QSLIST_INSERT_HEAD_ATOMIC(&ctx->scheduled_coroutines,
|
||||
co, co_scheduled_next);
|
||||
qemu_bh_schedule(ctx->co_schedule_bh);
|
||||
}
|
||||
|
||||
void aio_co_wake(struct Coroutine *co)
|
||||
{
|
||||
AioContext *ctx;
|
||||
|
||||
/* Read coroutine before co->ctx. Matches smp_wmb in
|
||||
* qemu_coroutine_enter.
|
||||
*/
|
||||
smp_read_barrier_depends();
|
||||
ctx = atomic_read(&co->ctx);
|
||||
|
||||
aio_co_enter(ctx, co);
|
||||
}
|
||||
|
||||
void aio_co_enter(AioContext *ctx, struct Coroutine *co)
|
||||
{
|
||||
if (ctx != qemu_get_current_aio_context()) {
|
||||
aio_co_schedule(ctx, co);
|
||||
return;
|
||||
}
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
Coroutine *self = qemu_coroutine_self();
|
||||
assert(self != co);
|
||||
QSIMPLEQ_INSERT_TAIL(&self->co_queue_wakeup, co, co_queue_next);
|
||||
} else {
|
||||
aio_context_acquire(ctx);
|
||||
qemu_aio_coroutine_enter(ctx, co);
|
||||
aio_context_release(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void aio_context_ref(AioContext *ctx)
|
||||
{
|
||||
g_source_ref(&ctx->source);
|
||||
@@ -486,10 +389,10 @@ void aio_context_unref(AioContext *ctx)
|
||||
|
||||
void aio_context_acquire(AioContext *ctx)
|
||||
{
|
||||
qemu_rec_mutex_lock(&ctx->lock);
|
||||
rfifolock_lock(&ctx->lock);
|
||||
}
|
||||
|
||||
void aio_context_release(AioContext *ctx)
|
||||
{
|
||||
qemu_rec_mutex_unlock(&ctx->lock);
|
||||
rfifolock_unlock(&ctx->lock);
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Atomic helper templates
|
||||
* Included from tcg-runtime.c and cputlb.c.
|
||||
*
|
||||
* Copyright (c) 2016 Red Hat, Inc
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if DATA_SIZE == 16
|
||||
# define SUFFIX o
|
||||
# define DATA_TYPE Int128
|
||||
# define BSWAP bswap128
|
||||
#elif DATA_SIZE == 8
|
||||
# define SUFFIX q
|
||||
# define DATA_TYPE uint64_t
|
||||
# define BSWAP bswap64
|
||||
#elif DATA_SIZE == 4
|
||||
# define SUFFIX l
|
||||
# define DATA_TYPE uint32_t
|
||||
# define BSWAP bswap32
|
||||
#elif DATA_SIZE == 2
|
||||
# define SUFFIX w
|
||||
# define DATA_TYPE uint16_t
|
||||
# define BSWAP bswap16
|
||||
#elif DATA_SIZE == 1
|
||||
# define SUFFIX b
|
||||
# define DATA_TYPE uint8_t
|
||||
# define BSWAP
|
||||
#else
|
||||
# error unsupported data size
|
||||
#endif
|
||||
|
||||
#if DATA_SIZE >= 4
|
||||
# define ABI_TYPE DATA_TYPE
|
||||
#else
|
||||
# define ABI_TYPE uint32_t
|
||||
#endif
|
||||
|
||||
/* Define host-endian atomic operations. Note that END is used within
|
||||
the ATOMIC_NAME macro, and redefined below. */
|
||||
#if DATA_SIZE == 1
|
||||
# define END
|
||||
#elif defined(HOST_WORDS_BIGENDIAN)
|
||||
# define END _be
|
||||
#else
|
||||
# define END _le
|
||||
#endif
|
||||
|
||||
ABI_TYPE ATOMIC_NAME(cmpxchg)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE cmpv, ABI_TYPE newv EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
return atomic_cmpxchg__nocheck(haddr, cmpv, newv);
|
||||
}
|
||||
|
||||
#if DATA_SIZE >= 16
|
||||
ABI_TYPE ATOMIC_NAME(ld)(CPUArchState *env, target_ulong addr EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE val, *haddr = ATOMIC_MMU_LOOKUP;
|
||||
__atomic_load(haddr, &val, __ATOMIC_RELAXED);
|
||||
return val;
|
||||
}
|
||||
|
||||
void ATOMIC_NAME(st)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE val EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
__atomic_store(haddr, &val, __ATOMIC_RELAXED);
|
||||
}
|
||||
#else
|
||||
ABI_TYPE ATOMIC_NAME(xchg)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE val EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
return atomic_xchg__nocheck(haddr, val);
|
||||
}
|
||||
|
||||
#define GEN_ATOMIC_HELPER(X) \
|
||||
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, target_ulong addr, \
|
||||
ABI_TYPE val EXTRA_ARGS) \
|
||||
{ \
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP; \
|
||||
return atomic_##X(haddr, val); \
|
||||
} \
|
||||
|
||||
GEN_ATOMIC_HELPER(fetch_add)
|
||||
GEN_ATOMIC_HELPER(fetch_and)
|
||||
GEN_ATOMIC_HELPER(fetch_or)
|
||||
GEN_ATOMIC_HELPER(fetch_xor)
|
||||
GEN_ATOMIC_HELPER(add_fetch)
|
||||
GEN_ATOMIC_HELPER(and_fetch)
|
||||
GEN_ATOMIC_HELPER(or_fetch)
|
||||
GEN_ATOMIC_HELPER(xor_fetch)
|
||||
|
||||
#undef GEN_ATOMIC_HELPER
|
||||
#endif /* DATA SIZE >= 16 */
|
||||
|
||||
#undef END
|
||||
|
||||
#if DATA_SIZE > 1
|
||||
|
||||
/* Define reverse-host-endian atomic operations. Note that END is used
|
||||
within the ATOMIC_NAME macro. */
|
||||
#ifdef HOST_WORDS_BIGENDIAN
|
||||
# define END _le
|
||||
#else
|
||||
# define END _be
|
||||
#endif
|
||||
|
||||
ABI_TYPE ATOMIC_NAME(cmpxchg)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE cmpv, ABI_TYPE newv EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
return BSWAP(atomic_cmpxchg__nocheck(haddr, BSWAP(cmpv), BSWAP(newv)));
|
||||
}
|
||||
|
||||
#if DATA_SIZE >= 16
|
||||
ABI_TYPE ATOMIC_NAME(ld)(CPUArchState *env, target_ulong addr EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE val, *haddr = ATOMIC_MMU_LOOKUP;
|
||||
__atomic_load(haddr, &val, __ATOMIC_RELAXED);
|
||||
return BSWAP(val);
|
||||
}
|
||||
|
||||
void ATOMIC_NAME(st)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE val EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
val = BSWAP(val);
|
||||
__atomic_store(haddr, &val, __ATOMIC_RELAXED);
|
||||
}
|
||||
#else
|
||||
ABI_TYPE ATOMIC_NAME(xchg)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE val EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
return BSWAP(atomic_xchg__nocheck(haddr, BSWAP(val)));
|
||||
}
|
||||
|
||||
#define GEN_ATOMIC_HELPER(X) \
|
||||
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, target_ulong addr, \
|
||||
ABI_TYPE val EXTRA_ARGS) \
|
||||
{ \
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP; \
|
||||
return BSWAP(atomic_##X(haddr, BSWAP(val))); \
|
||||
}
|
||||
|
||||
GEN_ATOMIC_HELPER(fetch_and)
|
||||
GEN_ATOMIC_HELPER(fetch_or)
|
||||
GEN_ATOMIC_HELPER(fetch_xor)
|
||||
GEN_ATOMIC_HELPER(and_fetch)
|
||||
GEN_ATOMIC_HELPER(or_fetch)
|
||||
GEN_ATOMIC_HELPER(xor_fetch)
|
||||
|
||||
#undef GEN_ATOMIC_HELPER
|
||||
|
||||
/* Note that for addition, we need to use a separate cmpxchg loop instead
|
||||
of bswaps for the reverse-host-endian helpers. */
|
||||
ABI_TYPE ATOMIC_NAME(fetch_add)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE val EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
DATA_TYPE ldo, ldn, ret, sto;
|
||||
|
||||
ldo = atomic_read__nocheck(haddr);
|
||||
while (1) {
|
||||
ret = BSWAP(ldo);
|
||||
sto = BSWAP(ret + val);
|
||||
ldn = atomic_cmpxchg__nocheck(haddr, ldo, sto);
|
||||
if (ldn == ldo) {
|
||||
return ret;
|
||||
}
|
||||
ldo = ldn;
|
||||
}
|
||||
}
|
||||
|
||||
ABI_TYPE ATOMIC_NAME(add_fetch)(CPUArchState *env, target_ulong addr,
|
||||
ABI_TYPE val EXTRA_ARGS)
|
||||
{
|
||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
||||
DATA_TYPE ldo, ldn, ret, sto;
|
||||
|
||||
ldo = atomic_read__nocheck(haddr);
|
||||
while (1) {
|
||||
ret = BSWAP(ldo) + val;
|
||||
sto = BSWAP(ret);
|
||||
ldn = atomic_cmpxchg__nocheck(haddr, ldo, sto);
|
||||
if (ldn == ldo) {
|
||||
return ret;
|
||||
}
|
||||
ldo = ldn;
|
||||
}
|
||||
}
|
||||
#endif /* DATA_SIZE >= 16 */
|
||||
|
||||
#undef END
|
||||
#endif /* DATA_SIZE > 1 */
|
||||
|
||||
#undef BSWAP
|
||||
#undef ABI_TYPE
|
||||
#undef DATA_TYPE
|
||||
#undef SUFFIX
|
||||
#undef DATA_SIZE
|
||||
+3
-10
@@ -28,7 +28,6 @@
|
||||
#include "qemu/timer.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
#define AUDIO_CAP "audio"
|
||||
#include "audio_int.h"
|
||||
@@ -1113,7 +1112,7 @@ static int audio_is_timer_needed (void)
|
||||
static void audio_reset_timer (AudioState *s)
|
||||
{
|
||||
if (audio_is_timer_needed ()) {
|
||||
timer_mod_anticipate_ns(s->ts,
|
||||
timer_mod (s->ts,
|
||||
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks);
|
||||
}
|
||||
else {
|
||||
@@ -1388,7 +1387,6 @@ static void audio_run_out (AudioState *s)
|
||||
|
||||
prev_rpos = hw->rpos;
|
||||
played = hw->pcm_ops->run_out (hw, live);
|
||||
replay_audio_out(&played);
|
||||
if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) {
|
||||
dolog ("hw->rpos=%d hw->samples=%d played=%d\n",
|
||||
hw->rpos, hw->samples, played);
|
||||
@@ -1452,12 +1450,9 @@ static void audio_run_in (AudioState *s)
|
||||
|
||||
while ((hw = audio_pcm_hw_find_any_enabled_in (hw))) {
|
||||
SWVoiceIn *sw;
|
||||
int captured = 0, min;
|
||||
int captured, min;
|
||||
|
||||
if (replay_mode != REPLAY_MODE_PLAY) {
|
||||
captured = hw->pcm_ops->run_in(hw);
|
||||
}
|
||||
replay_audio_in(&captured, hw->conv_buf, &hw->wpos, hw->samples);
|
||||
captured = hw->pcm_ops->run_in (hw);
|
||||
|
||||
min = audio_pcm_hw_find_min_in (hw);
|
||||
hw->total_samples_captured += captured - min;
|
||||
@@ -2028,8 +2023,6 @@ void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
|
||||
sw = sw1;
|
||||
}
|
||||
QLIST_REMOVE (cap, entries);
|
||||
g_free (cap->hw.mix_buf);
|
||||
g_free (cap->buf);
|
||||
g_free (cap);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -166,9 +166,4 @@ int wav_start_capture (CaptureState *s, const char *path, int freq,
|
||||
bool audio_is_cleaning_up(void);
|
||||
void audio_cleanup(void);
|
||||
|
||||
void audio_sample_to_uint64(void *samples, int pos,
|
||||
uint64_t *left, uint64_t *right);
|
||||
void audio_sample_from_uint64(void *samples, int pos,
|
||||
uint64_t left, uint64_t right);
|
||||
|
||||
#endif /* QEMU_AUDIO_H */
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/bswap.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "audio.h"
|
||||
|
||||
#define AUDIO_CAP "mixeng"
|
||||
@@ -268,37 +267,6 @@ f_sample *mixeng_clip[2][2][2][3] = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void audio_sample_to_uint64(void *samples, int pos,
|
||||
uint64_t *left, uint64_t *right)
|
||||
{
|
||||
struct st_sample *sample = samples;
|
||||
sample += pos;
|
||||
#ifdef FLOAT_MIXENG
|
||||
error_report(
|
||||
"Coreaudio and floating point samples are not supported by replay yet");
|
||||
abort();
|
||||
#else
|
||||
*left = sample->l;
|
||||
*right = sample->r;
|
||||
#endif
|
||||
}
|
||||
|
||||
void audio_sample_from_uint64(void *samples, int pos,
|
||||
uint64_t left, uint64_t right)
|
||||
{
|
||||
struct st_sample *sample = samples;
|
||||
sample += pos;
|
||||
#ifdef FLOAT_MIXENG
|
||||
error_report(
|
||||
"Coreaudio and floating point samples are not supported by replay yet");
|
||||
abort();
|
||||
#else
|
||||
sample->l = left;
|
||||
sample->r = right;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* August 21, 1998
|
||||
* Copyright 1998 Fabrice Bellard.
|
||||
|
||||
@@ -71,12 +71,6 @@ void NAME (void *opaque, struct st_sample *ibuf, struct st_sample *obuf,
|
||||
while (rate->ipos <= (rate->opos >> 32)) {
|
||||
ilast = *ibuf++;
|
||||
rate->ipos++;
|
||||
|
||||
/* if ipos overflow, there is a infinite loop */
|
||||
if (rate->ipos == 0xffffffff) {
|
||||
rate->ipos = 1;
|
||||
rate->opos = rate->opos & 0xffffffff;
|
||||
}
|
||||
/* See if we finished the input buffer yet */
|
||||
if (ibuf >= iend) {
|
||||
goto the_end;
|
||||
|
||||
@@ -38,14 +38,10 @@
|
||||
#define AUDIO_CAP "sdl"
|
||||
#include "audio_int.h"
|
||||
|
||||
#define USE_SEMAPHORE (SDL_MAJOR_VERSION < 2)
|
||||
|
||||
typedef struct SDLVoiceOut {
|
||||
HWVoiceOut hw;
|
||||
int live;
|
||||
#if USE_SEMAPHORE
|
||||
int rpos;
|
||||
#endif
|
||||
int decr;
|
||||
} SDLVoiceOut;
|
||||
|
||||
@@ -57,10 +53,8 @@ static struct {
|
||||
|
||||
static struct SDLAudioState {
|
||||
int exit;
|
||||
#if USE_SEMAPHORE
|
||||
SDL_mutex *mutex;
|
||||
SDL_sem *sem;
|
||||
#endif
|
||||
int initialized;
|
||||
bool driver_created;
|
||||
} glob_sdl;
|
||||
@@ -79,45 +73,31 @@ static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...)
|
||||
|
||||
static int sdl_lock (SDLAudioState *s, const char *forfn)
|
||||
{
|
||||
#if USE_SEMAPHORE
|
||||
if (SDL_LockMutex (s->mutex)) {
|
||||
sdl_logerr ("SDL_LockMutex for %s failed\n", forfn);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
SDL_LockAudio();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdl_unlock (SDLAudioState *s, const char *forfn)
|
||||
{
|
||||
#if USE_SEMAPHORE
|
||||
if (SDL_UnlockMutex (s->mutex)) {
|
||||
sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
SDL_UnlockAudio();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdl_post (SDLAudioState *s, const char *forfn)
|
||||
{
|
||||
#if USE_SEMAPHORE
|
||||
if (SDL_SemPost (s->sem)) {
|
||||
sdl_logerr ("SDL_SemPost for %s failed\n", forfn);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if USE_SEMAPHORE
|
||||
static int sdl_wait (SDLAudioState *s, const char *forfn)
|
||||
{
|
||||
if (SDL_SemWait (s->sem)) {
|
||||
@@ -126,7 +106,6 @@ static int sdl_wait (SDLAudioState *s, const char *forfn)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn)
|
||||
{
|
||||
@@ -267,7 +246,6 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
||||
int to_mix, decr;
|
||||
|
||||
/* dolog ("in callback samples=%d\n", samples); */
|
||||
#if USE_SEMAPHORE
|
||||
sdl_wait (s, "sdl_callback");
|
||||
if (s->exit) {
|
||||
return;
|
||||
@@ -286,11 +264,6 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
||||
if (!sdl->live) {
|
||||
goto again;
|
||||
}
|
||||
#else
|
||||
if (s->exit || !sdl->live) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* dolog ("in callback live=%d\n", live); */
|
||||
to_mix = audio_MIN (samples, sdl->live);
|
||||
@@ -301,11 +274,7 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
||||
|
||||
/* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */
|
||||
hw->clip (buf, src, chunk);
|
||||
#if USE_SEMAPHORE
|
||||
sdl->rpos = (sdl->rpos + chunk) % hw->samples;
|
||||
#else
|
||||
hw->rpos = (hw->rpos + chunk) % hw->samples;
|
||||
#endif
|
||||
to_mix -= chunk;
|
||||
buf += chunk << hw->info.shift;
|
||||
}
|
||||
@@ -313,21 +282,12 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
||||
sdl->live -= decr;
|
||||
sdl->decr += decr;
|
||||
|
||||
#if USE_SEMAPHORE
|
||||
again:
|
||||
if (sdl_unlock (s, "sdl_callback")) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/* dolog ("done len=%d\n", len); */
|
||||
|
||||
#if (SDL_MAJOR_VERSION >= 2)
|
||||
/* SDL2 does not clear the remaining buffer for us, so do it on our own */
|
||||
if (samples) {
|
||||
memset(buf, 0, samples << hw->info.shift);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static int sdl_write_out (SWVoiceOut *sw, void *buf, int len)
|
||||
@@ -355,12 +315,8 @@ static int sdl_run_out (HWVoiceOut *hw, int live)
|
||||
decr = audio_MIN (sdl->decr, live);
|
||||
sdl->decr -= decr;
|
||||
|
||||
#if USE_SEMAPHORE
|
||||
sdl->live = live - decr;
|
||||
hw->rpos = sdl->rpos;
|
||||
#else
|
||||
sdl->live = live;
|
||||
#endif
|
||||
|
||||
if (sdl->live > 0) {
|
||||
sdl_unlock_and_post (s, "sdl_run_out");
|
||||
@@ -449,7 +405,6 @@ static void *sdl_audio_init (void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if USE_SEMAPHORE
|
||||
s->mutex = SDL_CreateMutex ();
|
||||
if (!s->mutex) {
|
||||
sdl_logerr ("Failed to create SDL mutex\n");
|
||||
@@ -464,7 +419,6 @@ static void *sdl_audio_init (void)
|
||||
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
s->driver_created = true;
|
||||
return s;
|
||||
@@ -474,10 +428,8 @@ static void sdl_audio_fini (void *opaque)
|
||||
{
|
||||
SDLAudioState *s = opaque;
|
||||
sdl_close (s);
|
||||
#if USE_SEMAPHORE
|
||||
SDL_DestroySemaphore (s->sem);
|
||||
SDL_DestroyMutex (s->mutex);
|
||||
#endif
|
||||
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
||||
s->driver_created = false;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
# See docs/devel/tracing.txt for syntax documentation.
|
||||
# See docs/tracing.txt for syntax documentation.
|
||||
|
||||
# audio/alsaaudio.c
|
||||
alsa_revents(int revents) "revents = %d"
|
||||
alsa_pollout(int i, int fd) "i = %d fd = %d"
|
||||
alsa_set_handler(int events, int index, int fd, int err) "events=0x%x index=%d fd=%d err=%d"
|
||||
alsa_set_handler(int events, int index, int fd, int err) "events=%#x index=%d fd=%d err=%d"
|
||||
alsa_wrote_zero(int len) "Failed to write %d frames (wrote zero)"
|
||||
alsa_read_zero(long len) "Failed to read %ld frames (read zero)"
|
||||
alsa_xrun_out(void) "Recovering from playback xrun"
|
||||
@@ -13,5 +13,5 @@ alsa_resume_in(void) "Resuming suspended input stream"
|
||||
alsa_no_frames(int state) "No frames available and ALSA state is %d"
|
||||
|
||||
# audio/ossaudio.c
|
||||
oss_version(int version) "OSS version = 0x%x"
|
||||
oss_version(int version) "OSS version = %#x"
|
||||
oss_invalid_available_size(int size, int bufsize) "Invalid available size, size=%d bufsize=%d"
|
||||
@@ -88,7 +88,6 @@ static void wav_capture_destroy (void *opaque)
|
||||
WAVState *wav = opaque;
|
||||
|
||||
AUD_del_capture (wav->cap, wav);
|
||||
g_free (wav);
|
||||
}
|
||||
|
||||
static void wav_capture_info (void *opaque)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
common-obj-y += rng.o rng-egd.o
|
||||
common-obj-$(CONFIG_POSIX) += rng-random.o
|
||||
|
||||
common-obj-y += msmouse.o testdev.o
|
||||
common-obj-$(CONFIG_BRLAPI) += baum.o
|
||||
baum.o-cflags := $(SDL_CFLAGS)
|
||||
|
||||
common-obj-$(CONFIG_TPM) += tpm.o
|
||||
|
||||
common-obj-y += hostmem.o hostmem-ram.o
|
||||
common-obj-$(CONFIG_LINUX) += hostmem-file.o
|
||||
|
||||
common-obj-y += cryptodev.o
|
||||
common-obj-y += cryptodev-builtin.o
|
||||
+182
-210
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* QEMU Baum Braille Device
|
||||
*
|
||||
* Copyright (c) 2008, 2010-2011, 2016 Samuel Thibault
|
||||
* Copyright (c) 2008 Samuel Thibault
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,13 +24,15 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu-common.h"
|
||||
#include "chardev/char.h"
|
||||
#include "sysemu/char.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "hw/usb.h"
|
||||
#include "ui/console.h"
|
||||
#include <brlapi.h>
|
||||
#include <brlapi_constants.h>
|
||||
#include <brlapi_keycodes.h>
|
||||
#ifdef CONFIG_SDL
|
||||
#include <SDL_syswm.h>
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define DPRINTF(fmt, ...) \
|
||||
@@ -85,12 +87,11 @@
|
||||
#define BUF_SIZE 256
|
||||
|
||||
typedef struct {
|
||||
Chardev parent;
|
||||
CharDriverState *chr;
|
||||
|
||||
brlapi_handle_t *brlapi;
|
||||
int brlapi_fd;
|
||||
unsigned int x, y;
|
||||
bool deferred_init;
|
||||
|
||||
uint8_t in_buf[BUF_SIZE];
|
||||
uint8_t in_buf_used;
|
||||
@@ -98,17 +99,11 @@ typedef struct {
|
||||
uint8_t out_buf_used, out_buf_ptr;
|
||||
|
||||
QEMUTimer *cellCount_timer;
|
||||
} BaumChardev;
|
||||
|
||||
#define TYPE_CHARDEV_BRAILLE "chardev-braille"
|
||||
#define BAUM_CHARDEV(obj) OBJECT_CHECK(BaumChardev, (obj), TYPE_CHARDEV_BRAILLE)
|
||||
} BaumDriverState;
|
||||
|
||||
/* Let's assume NABCC by default */
|
||||
enum way {
|
||||
DOTS2ASCII,
|
||||
ASCII2DOTS
|
||||
};
|
||||
static const uint8_t nabcc_translation[2][256] = {
|
||||
static const uint8_t nabcc_translation[256] = {
|
||||
[0] = ' ',
|
||||
#ifndef BRLAPI_DOTS
|
||||
#define BRLAPI_DOTS(d1,d2,d3,d4,d5,d6,d7,d8) \
|
||||
((d1?BRLAPI_DOT1:0)|\
|
||||
@@ -120,145 +115,111 @@ static const uint8_t nabcc_translation[2][256] = {
|
||||
(d7?BRLAPI_DOT7:0)|\
|
||||
(d8?BRLAPI_DOT8:0))
|
||||
#endif
|
||||
#define DO(dots, ascii) \
|
||||
[DOTS2ASCII][dots] = ascii, \
|
||||
[ASCII2DOTS][ascii] = dots
|
||||
DO(0, ' '),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 0, 0, 0), 'a'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 0, 0, 0), 'b'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 0, 0, 0), 'c'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 0, 0, 0), 'd'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 0, 0, 0), 'e'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 0, 0, 0), 'f'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 0, 0, 0), 'g'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 0, 0, 0), 'h'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 0, 0, 0), 'i'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 0, 0, 0), 'j'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 0, 0, 0), 'k'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 0, 0, 0), 'l'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 0, 0, 0), 'm'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 0, 0, 0), 'n'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 0, 0, 0), 'o'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 0, 0, 0), 'p'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 0, 0, 0), 'q'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 0, 0, 0), 'r'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 0, 0, 0), 's'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 0, 0, 0), 't'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 1, 0, 0), 'u'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 1, 0, 0), 'v'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 1, 0, 0), 'w'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 1, 0, 0), 'x'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 1, 0, 0), 'y'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 1, 0, 0), 'z'),
|
||||
[BRLAPI_DOTS(1,0,0,0,0,0,0,0)] = 'a',
|
||||
[BRLAPI_DOTS(1,1,0,0,0,0,0,0)] = 'b',
|
||||
[BRLAPI_DOTS(1,0,0,1,0,0,0,0)] = 'c',
|
||||
[BRLAPI_DOTS(1,0,0,1,1,0,0,0)] = 'd',
|
||||
[BRLAPI_DOTS(1,0,0,0,1,0,0,0)] = 'e',
|
||||
[BRLAPI_DOTS(1,1,0,1,0,0,0,0)] = 'f',
|
||||
[BRLAPI_DOTS(1,1,0,1,1,0,0,0)] = 'g',
|
||||
[BRLAPI_DOTS(1,1,0,0,1,0,0,0)] = 'h',
|
||||
[BRLAPI_DOTS(0,1,0,1,0,0,0,0)] = 'i',
|
||||
[BRLAPI_DOTS(0,1,0,1,1,0,0,0)] = 'j',
|
||||
[BRLAPI_DOTS(1,0,1,0,0,0,0,0)] = 'k',
|
||||
[BRLAPI_DOTS(1,1,1,0,0,0,0,0)] = 'l',
|
||||
[BRLAPI_DOTS(1,0,1,1,0,0,0,0)] = 'm',
|
||||
[BRLAPI_DOTS(1,0,1,1,1,0,0,0)] = 'n',
|
||||
[BRLAPI_DOTS(1,0,1,0,1,0,0,0)] = 'o',
|
||||
[BRLAPI_DOTS(1,1,1,1,0,0,0,0)] = 'p',
|
||||
[BRLAPI_DOTS(1,1,1,1,1,0,0,0)] = 'q',
|
||||
[BRLAPI_DOTS(1,1,1,0,1,0,0,0)] = 'r',
|
||||
[BRLAPI_DOTS(0,1,1,1,0,0,0,0)] = 's',
|
||||
[BRLAPI_DOTS(0,1,1,1,1,0,0,0)] = 't',
|
||||
[BRLAPI_DOTS(1,0,1,0,0,1,0,0)] = 'u',
|
||||
[BRLAPI_DOTS(1,1,1,0,0,1,0,0)] = 'v',
|
||||
[BRLAPI_DOTS(0,1,0,1,1,1,0,0)] = 'w',
|
||||
[BRLAPI_DOTS(1,0,1,1,0,1,0,0)] = 'x',
|
||||
[BRLAPI_DOTS(1,0,1,1,1,1,0,0)] = 'y',
|
||||
[BRLAPI_DOTS(1,0,1,0,1,1,0,0)] = 'z',
|
||||
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 0, 1, 0), 'A'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 0, 1, 0), 'B'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 0, 1, 0), 'C'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 0, 1, 0), 'D'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 0, 1, 0), 'E'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 0, 1, 0), 'F'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 0, 1, 0), 'G'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 0, 1, 0), 'H'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 0, 1, 0), 'I'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 0, 1, 0), 'J'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 0, 1, 0), 'K'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 0, 1, 0), 'L'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 0, 1, 0), 'M'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 0, 1, 0), 'N'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 0, 1, 0), 'O'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 0, 1, 0), 'P'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 0, 1, 0), 'Q'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 0, 1, 0), 'R'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 0, 1, 0), 'S'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 0, 1, 0), 'T'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 1, 1, 0), 'U'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 1, 1, 0), 'V'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 1, 1, 0), 'W'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 1, 1, 0), 'X'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 1, 1, 0), 'Y'),
|
||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 1, 1, 0), 'Z'),
|
||||
[BRLAPI_DOTS(1,0,0,0,0,0,1,0)] = 'A',
|
||||
[BRLAPI_DOTS(1,1,0,0,0,0,1,0)] = 'B',
|
||||
[BRLAPI_DOTS(1,0,0,1,0,0,1,0)] = 'C',
|
||||
[BRLAPI_DOTS(1,0,0,1,1,0,1,0)] = 'D',
|
||||
[BRLAPI_DOTS(1,0,0,0,1,0,1,0)] = 'E',
|
||||
[BRLAPI_DOTS(1,1,0,1,0,0,1,0)] = 'F',
|
||||
[BRLAPI_DOTS(1,1,0,1,1,0,1,0)] = 'G',
|
||||
[BRLAPI_DOTS(1,1,0,0,1,0,1,0)] = 'H',
|
||||
[BRLAPI_DOTS(0,1,0,1,0,0,1,0)] = 'I',
|
||||
[BRLAPI_DOTS(0,1,0,1,1,0,1,0)] = 'J',
|
||||
[BRLAPI_DOTS(1,0,1,0,0,0,1,0)] = 'K',
|
||||
[BRLAPI_DOTS(1,1,1,0,0,0,1,0)] = 'L',
|
||||
[BRLAPI_DOTS(1,0,1,1,0,0,1,0)] = 'M',
|
||||
[BRLAPI_DOTS(1,0,1,1,1,0,1,0)] = 'N',
|
||||
[BRLAPI_DOTS(1,0,1,0,1,0,1,0)] = 'O',
|
||||
[BRLAPI_DOTS(1,1,1,1,0,0,1,0)] = 'P',
|
||||
[BRLAPI_DOTS(1,1,1,1,1,0,1,0)] = 'Q',
|
||||
[BRLAPI_DOTS(1,1,1,0,1,0,1,0)] = 'R',
|
||||
[BRLAPI_DOTS(0,1,1,1,0,0,1,0)] = 'S',
|
||||
[BRLAPI_DOTS(0,1,1,1,1,0,1,0)] = 'T',
|
||||
[BRLAPI_DOTS(1,0,1,0,0,1,1,0)] = 'U',
|
||||
[BRLAPI_DOTS(1,1,1,0,0,1,1,0)] = 'V',
|
||||
[BRLAPI_DOTS(0,1,0,1,1,1,1,0)] = 'W',
|
||||
[BRLAPI_DOTS(1,0,1,1,0,1,1,0)] = 'X',
|
||||
[BRLAPI_DOTS(1,0,1,1,1,1,1,0)] = 'Y',
|
||||
[BRLAPI_DOTS(1,0,1,0,1,1,1,0)] = 'Z',
|
||||
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 1, 1, 0, 0), '0'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 0, 0, 0, 0), '1'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 0, 0, 0, 0), '2'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 1, 0, 0, 0), '3'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 1, 1, 0, 0), '4'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 0, 1, 0, 0), '5'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 1, 0, 0, 0), '6'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 1, 1, 0, 0), '7'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 0, 1, 0, 0), '8'),
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 1, 0, 0, 0), '9'),
|
||||
[BRLAPI_DOTS(0,0,1,0,1,1,0,0)] = '0',
|
||||
[BRLAPI_DOTS(0,1,0,0,0,0,0,0)] = '1',
|
||||
[BRLAPI_DOTS(0,1,1,0,0,0,0,0)] = '2',
|
||||
[BRLAPI_DOTS(0,1,0,0,1,0,0,0)] = '3',
|
||||
[BRLAPI_DOTS(0,1,0,0,1,1,0,0)] = '4',
|
||||
[BRLAPI_DOTS(0,1,0,0,0,1,0,0)] = '5',
|
||||
[BRLAPI_DOTS(0,1,1,0,1,0,0,0)] = '6',
|
||||
[BRLAPI_DOTS(0,1,1,0,1,1,0,0)] = '7',
|
||||
[BRLAPI_DOTS(0,1,1,0,0,1,0,0)] = '8',
|
||||
[BRLAPI_DOTS(0,0,1,0,1,0,0,0)] = '9',
|
||||
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 1, 0, 0), '.'),
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 0, 1, 0, 0), '+'),
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 0, 1, 0, 0), '-'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 1, 0, 0), '*'),
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 0, 0, 0, 0), '/'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 1, 0, 0), '('),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 1, 0, 0), ')'),
|
||||
[BRLAPI_DOTS(0,0,0,1,0,1,0,0)] = '.',
|
||||
[BRLAPI_DOTS(0,0,1,1,0,1,0,0)] = '+',
|
||||
[BRLAPI_DOTS(0,0,1,0,0,1,0,0)] = '-',
|
||||
[BRLAPI_DOTS(1,0,0,0,0,1,0,0)] = '*',
|
||||
[BRLAPI_DOTS(0,0,1,1,0,0,0,0)] = '/',
|
||||
[BRLAPI_DOTS(1,1,1,0,1,1,0,0)] = '(',
|
||||
[BRLAPI_DOTS(0,1,1,1,1,1,0,0)] = ')',
|
||||
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 1, 0, 0), '&'),
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 1, 1, 0, 0), '#'),
|
||||
[BRLAPI_DOTS(1,1,1,1,0,1,0,0)] = '&',
|
||||
[BRLAPI_DOTS(0,0,1,1,1,1,0,0)] = '#',
|
||||
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 0, 0, 1, 0, 0), ','),
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 0, 1, 1, 0, 0), ';'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 1, 0, 0), ':'),
|
||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 1, 0, 0), '!'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 1, 0, 0), '?'),
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 0, 1, 0, 0, 0), '"'),
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 0, 0, 0, 0), '\''),
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 0, 0, 0), '`'),
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 0, 1, 0), '^'),
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 0, 0, 0), '~'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 1, 1, 0), '['),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 1, 1, 0), ']'),
|
||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 1, 0, 0), '{'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 1, 0, 0), '}'),
|
||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 1, 0, 0), '='),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 1, 0, 0), '<'),
|
||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 1, 0, 0, 0), '>'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 1, 0, 0), '$'),
|
||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 1, 0, 0), '%'),
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 0, 1, 0), '@'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 1, 0, 0), '|'),
|
||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 1, 1, 0), '\\'),
|
||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 1, 0, 0), '_'),
|
||||
[BRLAPI_DOTS(0,0,0,0,0,1,0,0)] = ',',
|
||||
[BRLAPI_DOTS(0,0,0,0,1,1,0,0)] = ';',
|
||||
[BRLAPI_DOTS(1,0,0,0,1,1,0,0)] = ':',
|
||||
[BRLAPI_DOTS(0,1,1,1,0,1,0,0)] = '!',
|
||||
[BRLAPI_DOTS(1,0,0,1,1,1,0,0)] = '?',
|
||||
[BRLAPI_DOTS(0,0,0,0,1,0,0,0)] = '"',
|
||||
[BRLAPI_DOTS(0,0,1,0,0,0,0,0)] ='\'',
|
||||
[BRLAPI_DOTS(0,0,0,1,0,0,0,0)] = '`',
|
||||
[BRLAPI_DOTS(0,0,0,1,1,0,1,0)] = '^',
|
||||
[BRLAPI_DOTS(0,0,0,1,1,0,0,0)] = '~',
|
||||
[BRLAPI_DOTS(0,1,0,1,0,1,1,0)] = '[',
|
||||
[BRLAPI_DOTS(1,1,0,1,1,1,1,0)] = ']',
|
||||
[BRLAPI_DOTS(0,1,0,1,0,1,0,0)] = '{',
|
||||
[BRLAPI_DOTS(1,1,0,1,1,1,0,0)] = '}',
|
||||
[BRLAPI_DOTS(1,1,1,1,1,1,0,0)] = '=',
|
||||
[BRLAPI_DOTS(1,1,0,0,0,1,0,0)] = '<',
|
||||
[BRLAPI_DOTS(0,0,1,1,1,0,0,0)] = '>',
|
||||
[BRLAPI_DOTS(1,1,0,1,0,1,0,0)] = '$',
|
||||
[BRLAPI_DOTS(1,0,0,1,0,1,0,0)] = '%',
|
||||
[BRLAPI_DOTS(0,0,0,1,0,0,1,0)] = '@',
|
||||
[BRLAPI_DOTS(1,1,0,0,1,1,0,0)] = '|',
|
||||
[BRLAPI_DOTS(1,1,0,0,1,1,1,0)] ='\\',
|
||||
[BRLAPI_DOTS(0,0,0,1,1,1,0,0)] = '_',
|
||||
};
|
||||
|
||||
/* The guest OS has started discussing with us, finish initializing BrlAPI */
|
||||
static int baum_deferred_init(BaumChardev *baum)
|
||||
{
|
||||
int tty = BRLAPI_TTY_DEFAULT;
|
||||
QemuConsole *con;
|
||||
|
||||
if (baum->deferred_init) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (brlapi__getDisplaySize(baum->brlapi, &baum->x, &baum->y) == -1) {
|
||||
brlapi_perror("baum: brlapi__getDisplaySize");
|
||||
return 0;
|
||||
}
|
||||
|
||||
con = qemu_console_lookup_by_index(0);
|
||||
if (con && qemu_console_is_graphic(con)) {
|
||||
tty = qemu_console_get_window_id(con);
|
||||
if (tty == -1)
|
||||
tty = BRLAPI_TTY_DEFAULT;
|
||||
}
|
||||
|
||||
if (brlapi__enterTtyMode(baum->brlapi, tty, NULL) == -1) {
|
||||
brlapi_perror("baum: brlapi__enterTtyMode");
|
||||
return 0;
|
||||
}
|
||||
baum->deferred_init = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The serial port can receive more of our data */
|
||||
static void baum_chr_accept_input(struct Chardev *chr)
|
||||
static void baum_accept_input(struct CharDriverState *chr)
|
||||
{
|
||||
BaumChardev *baum = BAUM_CHARDEV(chr);
|
||||
BaumDriverState *baum = chr->opaque;
|
||||
int room, first;
|
||||
|
||||
if (!baum->out_buf_used)
|
||||
@@ -282,25 +243,24 @@ static void baum_chr_accept_input(struct Chardev *chr)
|
||||
}
|
||||
|
||||
/* We want to send a packet */
|
||||
static void baum_write_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
||||
static void baum_write_packet(BaumDriverState *baum, const uint8_t *buf, int len)
|
||||
{
|
||||
Chardev *chr = CHARDEV(baum);
|
||||
uint8_t io_buf[1 + 2 * len], *cur = io_buf;
|
||||
int room;
|
||||
*cur++ = ESC;
|
||||
while (len--)
|
||||
if ((*cur++ = *buf++) == ESC)
|
||||
*cur++ = ESC;
|
||||
room = qemu_chr_be_can_write(chr);
|
||||
room = qemu_chr_be_can_write(baum->chr);
|
||||
len = cur - io_buf;
|
||||
if (len <= room) {
|
||||
/* Fits */
|
||||
qemu_chr_be_write(chr, io_buf, len);
|
||||
qemu_chr_be_write(baum->chr, io_buf, len);
|
||||
} else {
|
||||
int first;
|
||||
uint8_t out;
|
||||
/* Can't fit all, send what can be, and store the rest. */
|
||||
qemu_chr_be_write(chr, io_buf, room);
|
||||
qemu_chr_be_write(baum->chr, io_buf, room);
|
||||
len -= room;
|
||||
cur = io_buf + room;
|
||||
if (len > BUF_SIZE - baum->out_buf_used) {
|
||||
@@ -325,14 +285,14 @@ static void baum_write_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
||||
/* Called when the other end seems to have a wrong idea of our display size */
|
||||
static void baum_cellCount_timer_cb(void *opaque)
|
||||
{
|
||||
BaumChardev *baum = BAUM_CHARDEV(opaque);
|
||||
BaumDriverState *baum = opaque;
|
||||
uint8_t cell_count[] = { BAUM_RSP_CellCount, baum->x * baum->y };
|
||||
DPRINTF("Timeout waiting for DisplayData, sending cell count\n");
|
||||
baum_write_packet(baum, cell_count, sizeof(cell_count));
|
||||
}
|
||||
|
||||
/* Try to interpret a whole incoming packet */
|
||||
static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
||||
static int baum_eat_packet(BaumDriverState *baum, const uint8_t *buf, int len)
|
||||
{
|
||||
const uint8_t *cur = buf;
|
||||
uint8_t req = 0;
|
||||
@@ -386,10 +346,8 @@ static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
||||
cursor = i + 1;
|
||||
c &= ~(BRLAPI_DOT7|BRLAPI_DOT8);
|
||||
}
|
||||
c = nabcc_translation[DOTS2ASCII][c];
|
||||
if (!c) {
|
||||
if (!(c = nabcc_translation[c]))
|
||||
c = '?';
|
||||
}
|
||||
text[i] = c;
|
||||
}
|
||||
timer_del(baum->cellCount_timer);
|
||||
@@ -473,17 +431,15 @@ static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
||||
}
|
||||
|
||||
/* The other end is writing some data. Store it and try to interpret */
|
||||
static int baum_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
||||
static int baum_write(CharDriverState *chr, const uint8_t *buf, int len)
|
||||
{
|
||||
BaumChardev *baum = BAUM_CHARDEV(chr);
|
||||
BaumDriverState *baum = chr->opaque;
|
||||
int tocopy, cur, eaten, orig_len = len;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
if (!baum->brlapi)
|
||||
return len;
|
||||
if (!baum_deferred_init(baum))
|
||||
return len;
|
||||
|
||||
while (len) {
|
||||
/* Complete our buffer as much as possible */
|
||||
@@ -514,31 +470,20 @@ static int baum_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
||||
}
|
||||
|
||||
/* Send the key code to the other end */
|
||||
static void baum_send_key(BaumChardev *baum, uint8_t type, uint8_t value)
|
||||
{
|
||||
static void baum_send_key(BaumDriverState *baum, uint8_t type, uint8_t value) {
|
||||
uint8_t packet[] = { type, value };
|
||||
DPRINTF("writing key %x %x\n", type, value);
|
||||
baum_write_packet(baum, packet, sizeof(packet));
|
||||
}
|
||||
|
||||
static void baum_send_key2(BaumChardev *baum, uint8_t type, uint8_t value,
|
||||
uint8_t value2)
|
||||
{
|
||||
uint8_t packet[] = { type, value, value2 };
|
||||
DPRINTF("writing key %x %x\n", type, value);
|
||||
baum_write_packet(baum, packet, sizeof(packet));
|
||||
}
|
||||
|
||||
/* We got some data on the BrlAPI socket */
|
||||
static void baum_chr_read(void *opaque)
|
||||
{
|
||||
BaumChardev *baum = BAUM_CHARDEV(opaque);
|
||||
BaumDriverState *baum = opaque;
|
||||
brlapi_keyCode_t code;
|
||||
int ret;
|
||||
if (!baum->brlapi)
|
||||
return;
|
||||
if (!baum_deferred_init(baum))
|
||||
return;
|
||||
while ((ret = brlapi__readKey(baum->brlapi, 0, &code)) == 1) {
|
||||
DPRINTF("got key %"BRLAPI_PRIxKEYCODE"\n", code);
|
||||
/* Emulate */
|
||||
@@ -595,17 +540,7 @@ static void baum_chr_read(void *opaque)
|
||||
}
|
||||
break;
|
||||
case BRLAPI_KEY_TYPE_SYM:
|
||||
{
|
||||
brlapi_keyCode_t keysym = code & BRLAPI_KEY_CODE_MASK;
|
||||
if (keysym < 0x100) {
|
||||
uint8_t dots = nabcc_translation[ASCII2DOTS][keysym];
|
||||
if (dots) {
|
||||
baum_send_key2(baum, BAUM_RSP_EntryKeys, 0, dots);
|
||||
baum_send_key2(baum, BAUM_RSP_EntryKeys, 0, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret == -1 && (brlapi_errno != BRLAPI_ERROR_LIBCERR || errno != EINTR)) {
|
||||
@@ -616,24 +551,45 @@ static void baum_chr_read(void *opaque)
|
||||
}
|
||||
}
|
||||
|
||||
static void char_braille_finalize(Object *obj)
|
||||
static void baum_close(struct CharDriverState *chr)
|
||||
{
|
||||
BaumChardev *baum = BAUM_CHARDEV(obj);
|
||||
BaumDriverState *baum = chr->opaque;
|
||||
|
||||
timer_free(baum->cellCount_timer);
|
||||
if (baum->brlapi) {
|
||||
brlapi__closeConnection(baum->brlapi);
|
||||
g_free(baum->brlapi);
|
||||
}
|
||||
g_free(baum);
|
||||
}
|
||||
|
||||
static void baum_chr_open(Chardev *chr,
|
||||
ChardevBackend *backend,
|
||||
bool *be_opened,
|
||||
Error **errp)
|
||||
static CharDriverState *chr_baum_init(const char *id,
|
||||
ChardevBackend *backend,
|
||||
ChardevReturn *ret,
|
||||
Error **errp)
|
||||
{
|
||||
BaumChardev *baum = BAUM_CHARDEV(chr);
|
||||
ChardevCommon *common = backend->u.braille.data;
|
||||
BaumDriverState *baum;
|
||||
CharDriverState *chr;
|
||||
brlapi_handle_t *handle;
|
||||
#if defined(CONFIG_SDL)
|
||||
#if SDL_COMPILEDVERSION < SDL_VERSIONNUM(2, 0, 0)
|
||||
SDL_SysWMinfo info;
|
||||
#endif
|
||||
#endif
|
||||
int tty;
|
||||
|
||||
chr = qemu_chr_alloc(common, errp);
|
||||
if (!chr) {
|
||||
return NULL;
|
||||
}
|
||||
baum = g_malloc0(sizeof(BaumDriverState));
|
||||
baum->chr = chr;
|
||||
|
||||
chr->opaque = baum;
|
||||
chr->chr_write = baum_write;
|
||||
chr->chr_accept_input = baum_accept_input;
|
||||
chr->chr_close = baum_close;
|
||||
|
||||
handle = g_malloc0(brlapi_getHandleSize());
|
||||
baum->brlapi = handle;
|
||||
@@ -642,36 +598,52 @@ static void baum_chr_open(Chardev *chr,
|
||||
if (baum->brlapi_fd == -1) {
|
||||
error_setg(errp, "brlapi__openConnection: %s",
|
||||
brlapi_strerror(brlapi_error_location()));
|
||||
g_free(handle);
|
||||
return;
|
||||
goto fail_handle;
|
||||
}
|
||||
baum->deferred_init = 0;
|
||||
|
||||
baum->cellCount_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, baum_cellCount_timer_cb, baum);
|
||||
|
||||
if (brlapi__getDisplaySize(handle, &baum->x, &baum->y) == -1) {
|
||||
error_setg(errp, "brlapi__getDisplaySize: %s",
|
||||
brlapi_strerror(brlapi_error_location()));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SDL)
|
||||
#if SDL_COMPILEDVERSION < SDL_VERSIONNUM(2, 0, 0)
|
||||
memset(&info, 0, sizeof(info));
|
||||
SDL_VERSION(&info.version);
|
||||
if (SDL_GetWMInfo(&info))
|
||||
tty = info.info.x11.wmwindow;
|
||||
else
|
||||
#endif
|
||||
#endif
|
||||
tty = BRLAPI_TTY_DEFAULT;
|
||||
|
||||
if (brlapi__enterTtyMode(handle, tty, NULL) == -1) {
|
||||
error_setg(errp, "brlapi__enterTtyMode: %s",
|
||||
brlapi_strerror(brlapi_error_location()));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
qemu_set_fd_handler(baum->brlapi_fd, baum_chr_read, NULL, baum);
|
||||
|
||||
return chr;
|
||||
|
||||
fail:
|
||||
timer_free(baum->cellCount_timer);
|
||||
brlapi__closeConnection(handle);
|
||||
fail_handle:
|
||||
g_free(handle);
|
||||
g_free(chr);
|
||||
g_free(baum);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void char_braille_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
||||
|
||||
cc->open = baum_chr_open;
|
||||
cc->chr_write = baum_chr_write;
|
||||
cc->chr_accept_input = baum_chr_accept_input;
|
||||
}
|
||||
|
||||
static const TypeInfo char_braille_type_info = {
|
||||
.name = TYPE_CHARDEV_BRAILLE,
|
||||
.parent = TYPE_CHARDEV,
|
||||
.instance_size = sizeof(BaumChardev),
|
||||
.instance_finalize = char_braille_finalize,
|
||||
.class_init = char_braille_class_init,
|
||||
};
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&char_braille_type_info);
|
||||
register_char_driver("braille", CHARDEV_BACKEND_KIND_BRAILLE, NULL,
|
||||
chr_baum_init);
|
||||
}
|
||||
|
||||
type_init(register_types);
|
||||
@@ -1,400 +0,0 @@
|
||||
/*
|
||||
* QEMU Cryptodev backend for QEMU cipher APIs
|
||||
*
|
||||
* Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
|
||||
*
|
||||
* Authors:
|
||||
* Gonglei <arei.gonglei@huawei.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "sysemu/cryptodev.h"
|
||||
#include "hw/boards.h"
|
||||
#include "qapi/error.h"
|
||||
#include "standard-headers/linux/virtio_crypto.h"
|
||||
#include "crypto/cipher.h"
|
||||
|
||||
|
||||
/**
|
||||
* @TYPE_CRYPTODEV_BACKEND_BUILTIN:
|
||||
* name of backend that uses QEMU cipher API
|
||||
*/
|
||||
#define TYPE_CRYPTODEV_BACKEND_BUILTIN "cryptodev-backend-builtin"
|
||||
|
||||
#define CRYPTODEV_BACKEND_BUILTIN(obj) \
|
||||
OBJECT_CHECK(CryptoDevBackendBuiltin, \
|
||||
(obj), TYPE_CRYPTODEV_BACKEND_BUILTIN)
|
||||
|
||||
typedef struct CryptoDevBackendBuiltin
|
||||
CryptoDevBackendBuiltin;
|
||||
|
||||
typedef struct CryptoDevBackendBuiltinSession {
|
||||
QCryptoCipher *cipher;
|
||||
uint8_t direction; /* encryption or decryption */
|
||||
uint8_t type; /* cipher? hash? aead? */
|
||||
QTAILQ_ENTRY(CryptoDevBackendBuiltinSession) next;
|
||||
} CryptoDevBackendBuiltinSession;
|
||||
|
||||
/* Max number of symmetric sessions */
|
||||
#define MAX_NUM_SESSIONS 256
|
||||
|
||||
#define CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN 512
|
||||
#define CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN 64
|
||||
|
||||
struct CryptoDevBackendBuiltin {
|
||||
CryptoDevBackend parent_obj;
|
||||
|
||||
CryptoDevBackendBuiltinSession *sessions[MAX_NUM_SESSIONS];
|
||||
};
|
||||
|
||||
static void cryptodev_builtin_init(
|
||||
CryptoDevBackend *backend, Error **errp)
|
||||
{
|
||||
/* Only support one queue */
|
||||
int queues = backend->conf.peers.queues;
|
||||
CryptoDevBackendClient *cc;
|
||||
|
||||
if (queues != 1) {
|
||||
error_setg(errp,
|
||||
"Only support one queue in cryptdov-builtin backend");
|
||||
return;
|
||||
}
|
||||
|
||||
cc = cryptodev_backend_new_client(
|
||||
"cryptodev-builtin", NULL);
|
||||
cc->info_str = g_strdup_printf("cryptodev-builtin0");
|
||||
cc->queue_index = 0;
|
||||
backend->conf.peers.ccs[0] = cc;
|
||||
|
||||
backend->conf.crypto_services =
|
||||
1u << VIRTIO_CRYPTO_SERVICE_CIPHER |
|
||||
1u << VIRTIO_CRYPTO_SERVICE_HASH |
|
||||
1u << VIRTIO_CRYPTO_SERVICE_MAC;
|
||||
backend->conf.cipher_algo_l = 1u << VIRTIO_CRYPTO_CIPHER_AES_CBC;
|
||||
backend->conf.hash_algo = 1u << VIRTIO_CRYPTO_HASH_SHA1;
|
||||
/*
|
||||
* Set the Maximum length of crypto request.
|
||||
* Why this value? Just avoid to overflow when
|
||||
* memory allocation for each crypto request.
|
||||
*/
|
||||
backend->conf.max_size = LONG_MAX - sizeof(CryptoDevBackendSymOpInfo);
|
||||
backend->conf.max_cipher_key_len = CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN;
|
||||
backend->conf.max_auth_key_len = CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN;
|
||||
|
||||
cryptodev_backend_set_ready(backend, true);
|
||||
}
|
||||
|
||||
static int
|
||||
cryptodev_builtin_get_unused_session_index(
|
||||
CryptoDevBackendBuiltin *builtin)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < MAX_NUM_SESSIONS; i++) {
|
||||
if (builtin->sessions[i] == NULL) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define AES_KEYSIZE_128 16
|
||||
#define AES_KEYSIZE_192 24
|
||||
#define AES_KEYSIZE_256 32
|
||||
#define AES_KEYSIZE_128_XTS AES_KEYSIZE_256
|
||||
#define AES_KEYSIZE_256_XTS 64
|
||||
|
||||
static int
|
||||
cryptodev_builtin_get_aes_algo(uint32_t key_len, int mode, Error **errp)
|
||||
{
|
||||
int algo;
|
||||
|
||||
if (key_len == AES_KEYSIZE_128) {
|
||||
algo = QCRYPTO_CIPHER_ALG_AES_128;
|
||||
} else if (key_len == AES_KEYSIZE_192) {
|
||||
algo = QCRYPTO_CIPHER_ALG_AES_192;
|
||||
} else if (key_len == AES_KEYSIZE_256) { /* equals AES_KEYSIZE_128_XTS */
|
||||
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
|
||||
algo = QCRYPTO_CIPHER_ALG_AES_128;
|
||||
} else {
|
||||
algo = QCRYPTO_CIPHER_ALG_AES_256;
|
||||
}
|
||||
} else if (key_len == AES_KEYSIZE_256_XTS) {
|
||||
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
|
||||
algo = QCRYPTO_CIPHER_ALG_AES_256;
|
||||
} else {
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
goto err;
|
||||
}
|
||||
|
||||
return algo;
|
||||
|
||||
err:
|
||||
error_setg(errp, "Unsupported key length :%u", key_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int cryptodev_builtin_create_cipher_session(
|
||||
CryptoDevBackendBuiltin *builtin,
|
||||
CryptoDevBackendSymSessionInfo *sess_info,
|
||||
Error **errp)
|
||||
{
|
||||
int algo;
|
||||
int mode;
|
||||
QCryptoCipher *cipher;
|
||||
int index;
|
||||
CryptoDevBackendBuiltinSession *sess;
|
||||
|
||||
if (sess_info->op_type != VIRTIO_CRYPTO_SYM_OP_CIPHER) {
|
||||
error_setg(errp, "Unsupported optype :%u", sess_info->op_type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
index = cryptodev_builtin_get_unused_session_index(builtin);
|
||||
if (index < 0) {
|
||||
error_setg(errp, "Total number of sessions created exceeds %u",
|
||||
MAX_NUM_SESSIONS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (sess_info->cipher_alg) {
|
||||
case VIRTIO_CRYPTO_CIPHER_AES_ECB:
|
||||
mode = QCRYPTO_CIPHER_MODE_ECB;
|
||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
||||
mode, errp);
|
||||
if (algo < 0) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CRYPTO_CIPHER_AES_CBC:
|
||||
mode = QCRYPTO_CIPHER_MODE_CBC;
|
||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
||||
mode, errp);
|
||||
if (algo < 0) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CRYPTO_CIPHER_AES_CTR:
|
||||
mode = QCRYPTO_CIPHER_MODE_CTR;
|
||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
||||
mode, errp);
|
||||
if (algo < 0) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CRYPTO_CIPHER_AES_XTS:
|
||||
mode = QCRYPTO_CIPHER_MODE_XTS;
|
||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
||||
mode, errp);
|
||||
if (algo < 0) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CRYPTO_CIPHER_3DES_ECB:
|
||||
mode = QCRYPTO_CIPHER_MODE_ECB;
|
||||
algo = QCRYPTO_CIPHER_ALG_3DES;
|
||||
break;
|
||||
case VIRTIO_CRYPTO_CIPHER_3DES_CBC:
|
||||
mode = QCRYPTO_CIPHER_MODE_CBC;
|
||||
algo = QCRYPTO_CIPHER_ALG_3DES;
|
||||
break;
|
||||
case VIRTIO_CRYPTO_CIPHER_3DES_CTR:
|
||||
mode = QCRYPTO_CIPHER_MODE_CTR;
|
||||
algo = QCRYPTO_CIPHER_ALG_3DES;
|
||||
break;
|
||||
default:
|
||||
error_setg(errp, "Unsupported cipher alg :%u",
|
||||
sess_info->cipher_alg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cipher = qcrypto_cipher_new(algo, mode,
|
||||
sess_info->cipher_key,
|
||||
sess_info->key_len,
|
||||
errp);
|
||||
if (!cipher) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sess = g_new0(CryptoDevBackendBuiltinSession, 1);
|
||||
sess->cipher = cipher;
|
||||
sess->direction = sess_info->direction;
|
||||
sess->type = sess_info->op_type;
|
||||
|
||||
builtin->sessions[index] = sess;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static int64_t cryptodev_builtin_sym_create_session(
|
||||
CryptoDevBackend *backend,
|
||||
CryptoDevBackendSymSessionInfo *sess_info,
|
||||
uint32_t queue_index, Error **errp)
|
||||
{
|
||||
CryptoDevBackendBuiltin *builtin =
|
||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
||||
int64_t session_id = -1;
|
||||
int ret;
|
||||
|
||||
switch (sess_info->op_code) {
|
||||
case VIRTIO_CRYPTO_CIPHER_CREATE_SESSION:
|
||||
ret = cryptodev_builtin_create_cipher_session(
|
||||
builtin, sess_info, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
} else {
|
||||
session_id = ret;
|
||||
}
|
||||
break;
|
||||
case VIRTIO_CRYPTO_HASH_CREATE_SESSION:
|
||||
case VIRTIO_CRYPTO_MAC_CREATE_SESSION:
|
||||
default:
|
||||
error_setg(errp, "Unsupported opcode :%" PRIu32 "",
|
||||
sess_info->op_code);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return session_id;
|
||||
}
|
||||
|
||||
static int cryptodev_builtin_sym_close_session(
|
||||
CryptoDevBackend *backend,
|
||||
uint64_t session_id,
|
||||
uint32_t queue_index, Error **errp)
|
||||
{
|
||||
CryptoDevBackendBuiltin *builtin =
|
||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
||||
|
||||
if (session_id >= MAX_NUM_SESSIONS ||
|
||||
builtin->sessions[session_id] == NULL) {
|
||||
error_setg(errp, "Cannot find a valid session id: %" PRIu64 "",
|
||||
session_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
qcrypto_cipher_free(builtin->sessions[session_id]->cipher);
|
||||
g_free(builtin->sessions[session_id]);
|
||||
builtin->sessions[session_id] = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cryptodev_builtin_sym_operation(
|
||||
CryptoDevBackend *backend,
|
||||
CryptoDevBackendSymOpInfo *op_info,
|
||||
uint32_t queue_index, Error **errp)
|
||||
{
|
||||
CryptoDevBackendBuiltin *builtin =
|
||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
||||
CryptoDevBackendBuiltinSession *sess;
|
||||
int ret;
|
||||
|
||||
if (op_info->session_id >= MAX_NUM_SESSIONS ||
|
||||
builtin->sessions[op_info->session_id] == NULL) {
|
||||
error_setg(errp, "Cannot find a valid session id: %" PRIu64 "",
|
||||
op_info->session_id);
|
||||
return -VIRTIO_CRYPTO_INVSESS;
|
||||
}
|
||||
|
||||
if (op_info->op_type == VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) {
|
||||
error_setg(errp,
|
||||
"Algorithm chain is unsupported for cryptdoev-builtin");
|
||||
return -VIRTIO_CRYPTO_NOTSUPP;
|
||||
}
|
||||
|
||||
sess = builtin->sessions[op_info->session_id];
|
||||
|
||||
if (op_info->iv_len > 0) {
|
||||
ret = qcrypto_cipher_setiv(sess->cipher, op_info->iv,
|
||||
op_info->iv_len, errp);
|
||||
if (ret < 0) {
|
||||
return -VIRTIO_CRYPTO_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (sess->direction == VIRTIO_CRYPTO_OP_ENCRYPT) {
|
||||
ret = qcrypto_cipher_encrypt(sess->cipher, op_info->src,
|
||||
op_info->dst, op_info->src_len, errp);
|
||||
if (ret < 0) {
|
||||
return -VIRTIO_CRYPTO_ERR;
|
||||
}
|
||||
} else {
|
||||
ret = qcrypto_cipher_decrypt(sess->cipher, op_info->src,
|
||||
op_info->dst, op_info->src_len, errp);
|
||||
if (ret < 0) {
|
||||
return -VIRTIO_CRYPTO_ERR;
|
||||
}
|
||||
}
|
||||
return VIRTIO_CRYPTO_OK;
|
||||
}
|
||||
|
||||
static void cryptodev_builtin_cleanup(
|
||||
CryptoDevBackend *backend,
|
||||
Error **errp)
|
||||
{
|
||||
CryptoDevBackendBuiltin *builtin =
|
||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
||||
size_t i;
|
||||
int queues = backend->conf.peers.queues;
|
||||
CryptoDevBackendClient *cc;
|
||||
|
||||
for (i = 0; i < MAX_NUM_SESSIONS; i++) {
|
||||
if (builtin->sessions[i] != NULL) {
|
||||
cryptodev_builtin_sym_close_session(
|
||||
backend, i, 0, errp);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < queues; i++) {
|
||||
cc = backend->conf.peers.ccs[i];
|
||||
if (cc) {
|
||||
cryptodev_backend_free_client(cc);
|
||||
backend->conf.peers.ccs[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cryptodev_backend_set_ready(backend, false);
|
||||
}
|
||||
|
||||
static void
|
||||
cryptodev_builtin_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
CryptoDevBackendClass *bc = CRYPTODEV_BACKEND_CLASS(oc);
|
||||
|
||||
bc->init = cryptodev_builtin_init;
|
||||
bc->cleanup = cryptodev_builtin_cleanup;
|
||||
bc->create_session = cryptodev_builtin_sym_create_session;
|
||||
bc->close_session = cryptodev_builtin_sym_close_session;
|
||||
bc->do_sym_op = cryptodev_builtin_sym_operation;
|
||||
}
|
||||
|
||||
static const TypeInfo cryptodev_builtin_info = {
|
||||
.name = TYPE_CRYPTODEV_BACKEND_BUILTIN,
|
||||
.parent = TYPE_CRYPTODEV_BACKEND,
|
||||
.class_init = cryptodev_builtin_class_init,
|
||||
.instance_size = sizeof(CryptoDevBackendBuiltin),
|
||||
};
|
||||
|
||||
static void
|
||||
cryptodev_builtin_register_types(void)
|
||||
{
|
||||
type_register_static(&cryptodev_builtin_info);
|
||||
}
|
||||
|
||||
type_init(cryptodev_builtin_register_types);
|
||||
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* QEMU Crypto Device Implementation
|
||||
*
|
||||
* Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
|
||||
*
|
||||
* Authors:
|
||||
* Gonglei <arei.gonglei@huawei.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "sysemu/cryptodev.h"
|
||||
#include "hw/boards.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "qapi-types.h"
|
||||
#include "qapi-visit.h"
|
||||
#include "qemu/config-file.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
#include "hw/virtio/virtio-crypto.h"
|
||||
|
||||
|
||||
static QTAILQ_HEAD(, CryptoDevBackendClient) crypto_clients;
|
||||
|
||||
|
||||
CryptoDevBackendClient *
|
||||
cryptodev_backend_new_client(const char *model,
|
||||
const char *name)
|
||||
{
|
||||
CryptoDevBackendClient *cc;
|
||||
|
||||
cc = g_malloc0(sizeof(CryptoDevBackendClient));
|
||||
cc->model = g_strdup(model);
|
||||
if (name) {
|
||||
cc->name = g_strdup(name);
|
||||
}
|
||||
|
||||
QTAILQ_INSERT_TAIL(&crypto_clients, cc, next);
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
void cryptodev_backend_free_client(
|
||||
CryptoDevBackendClient *cc)
|
||||
{
|
||||
QTAILQ_REMOVE(&crypto_clients, cc, next);
|
||||
g_free(cc->name);
|
||||
g_free(cc->model);
|
||||
g_free(cc->info_str);
|
||||
g_free(cc);
|
||||
}
|
||||
|
||||
void cryptodev_backend_cleanup(
|
||||
CryptoDevBackend *backend,
|
||||
Error **errp)
|
||||
{
|
||||
CryptoDevBackendClass *bc =
|
||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
||||
|
||||
if (bc->cleanup) {
|
||||
bc->cleanup(backend, errp);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t cryptodev_backend_sym_create_session(
|
||||
CryptoDevBackend *backend,
|
||||
CryptoDevBackendSymSessionInfo *sess_info,
|
||||
uint32_t queue_index, Error **errp)
|
||||
{
|
||||
CryptoDevBackendClass *bc =
|
||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
||||
|
||||
if (bc->create_session) {
|
||||
return bc->create_session(backend, sess_info, queue_index, errp);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int cryptodev_backend_sym_close_session(
|
||||
CryptoDevBackend *backend,
|
||||
uint64_t session_id,
|
||||
uint32_t queue_index, Error **errp)
|
||||
{
|
||||
CryptoDevBackendClass *bc =
|
||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
||||
|
||||
if (bc->close_session) {
|
||||
return bc->close_session(backend, session_id, queue_index, errp);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int cryptodev_backend_sym_operation(
|
||||
CryptoDevBackend *backend,
|
||||
CryptoDevBackendSymOpInfo *op_info,
|
||||
uint32_t queue_index, Error **errp)
|
||||
{
|
||||
CryptoDevBackendClass *bc =
|
||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
||||
|
||||
if (bc->do_sym_op) {
|
||||
return bc->do_sym_op(backend, op_info, queue_index, errp);
|
||||
}
|
||||
|
||||
return -VIRTIO_CRYPTO_ERR;
|
||||
}
|
||||
|
||||
int cryptodev_backend_crypto_operation(
|
||||
CryptoDevBackend *backend,
|
||||
void *opaque,
|
||||
uint32_t queue_index, Error **errp)
|
||||
{
|
||||
VirtIOCryptoReq *req = opaque;
|
||||
|
||||
if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) {
|
||||
CryptoDevBackendSymOpInfo *op_info;
|
||||
op_info = req->u.sym_op_info;
|
||||
|
||||
return cryptodev_backend_sym_operation(backend,
|
||||
op_info, queue_index, errp);
|
||||
} else {
|
||||
error_setg(errp, "Unsupported cryptodev alg type: %" PRIu32 "",
|
||||
req->flags);
|
||||
return -VIRTIO_CRYPTO_NOTSUPP;
|
||||
}
|
||||
|
||||
return -VIRTIO_CRYPTO_ERR;
|
||||
}
|
||||
|
||||
static void
|
||||
cryptodev_backend_get_queues(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(obj);
|
||||
uint32_t value = backend->conf.peers.queues;
|
||||
|
||||
visit_type_uint32(v, name, &value, errp);
|
||||
}
|
||||
|
||||
static void
|
||||
cryptodev_backend_set_queues(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(obj);
|
||||
Error *local_err = NULL;
|
||||
uint32_t value;
|
||||
|
||||
visit_type_uint32(v, name, &value, &local_err);
|
||||
if (local_err) {
|
||||
goto out;
|
||||
}
|
||||
if (!value) {
|
||||
error_setg(&local_err, "Property '%s.%s' doesn't take value '%"
|
||||
PRIu32 "'", object_get_typename(obj), name, value);
|
||||
goto out;
|
||||
}
|
||||
backend->conf.peers.queues = value;
|
||||
out:
|
||||
error_propagate(errp, local_err);
|
||||
}
|
||||
|
||||
static void
|
||||
cryptodev_backend_complete(UserCreatable *uc, Error **errp)
|
||||
{
|
||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(uc);
|
||||
CryptoDevBackendClass *bc = CRYPTODEV_BACKEND_GET_CLASS(uc);
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (bc->init) {
|
||||
bc->init(backend, &local_err);
|
||||
if (local_err) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
out:
|
||||
error_propagate(errp, local_err);
|
||||
}
|
||||
|
||||
void cryptodev_backend_set_used(CryptoDevBackend *backend, bool used)
|
||||
{
|
||||
backend->is_used = used;
|
||||
}
|
||||
|
||||
bool cryptodev_backend_is_used(CryptoDevBackend *backend)
|
||||
{
|
||||
return backend->is_used;
|
||||
}
|
||||
|
||||
void cryptodev_backend_set_ready(CryptoDevBackend *backend, bool ready)
|
||||
{
|
||||
backend->ready = ready;
|
||||
}
|
||||
|
||||
bool cryptodev_backend_is_ready(CryptoDevBackend *backend)
|
||||
{
|
||||
return backend->ready;
|
||||
}
|
||||
|
||||
static bool
|
||||
cryptodev_backend_can_be_deleted(UserCreatable *uc, Error **errp)
|
||||
{
|
||||
return !cryptodev_backend_is_used(CRYPTODEV_BACKEND(uc));
|
||||
}
|
||||
|
||||
static void cryptodev_backend_instance_init(Object *obj)
|
||||
{
|
||||
object_property_add(obj, "queues", "uint32",
|
||||
cryptodev_backend_get_queues,
|
||||
cryptodev_backend_set_queues,
|
||||
NULL, NULL, NULL);
|
||||
/* Initialize devices' queues property to 1 */
|
||||
object_property_set_int(obj, 1, "queues", NULL);
|
||||
}
|
||||
|
||||
static void cryptodev_backend_finalize(Object *obj)
|
||||
{
|
||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(obj);
|
||||
|
||||
cryptodev_backend_cleanup(backend, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
cryptodev_backend_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
||||
|
||||
ucc->complete = cryptodev_backend_complete;
|
||||
ucc->can_be_deleted = cryptodev_backend_can_be_deleted;
|
||||
|
||||
QTAILQ_INIT(&crypto_clients);
|
||||
}
|
||||
|
||||
static const TypeInfo cryptodev_backend_info = {
|
||||
.name = TYPE_CRYPTODEV_BACKEND,
|
||||
.parent = TYPE_OBJECT,
|
||||
.instance_size = sizeof(CryptoDevBackend),
|
||||
.instance_init = cryptodev_backend_instance_init,
|
||||
.instance_finalize = cryptodev_backend_finalize,
|
||||
.class_size = sizeof(CryptoDevBackendClass),
|
||||
.class_init = cryptodev_backend_class_init,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
{ TYPE_USER_CREATABLE },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
static void
|
||||
cryptodev_backend_register_types(void)
|
||||
{
|
||||
type_register_static(&cryptodev_backend_info);
|
||||
}
|
||||
|
||||
type_init(cryptodev_backend_register_types);
|
||||
+18
-14
@@ -51,7 +51,7 @@ file_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
|
||||
#ifndef CONFIG_LINUX
|
||||
error_setg(errp, "-mem-path not supported on this host");
|
||||
#else
|
||||
if (!host_memory_backend_mr_inited(backend)) {
|
||||
if (!memory_region_size(&backend->mr)) {
|
||||
gchar *path;
|
||||
backend->force_prealloc = mem_prealloc;
|
||||
path = object_get_canonical_path(OBJECT(backend));
|
||||
@@ -64,6 +64,14 @@ file_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
file_backend_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
HostMemoryBackendClass *bc = MEMORY_BACKEND_CLASS(oc);
|
||||
|
||||
bc->alloc = file_backend_memory_alloc;
|
||||
}
|
||||
|
||||
static char *get_mem_path(Object *o, Error **errp)
|
||||
{
|
||||
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
||||
@@ -76,7 +84,7 @@ static void set_mem_path(Object *o, const char *str, Error **errp)
|
||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
||||
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
||||
|
||||
if (host_memory_backend_mr_inited(backend)) {
|
||||
if (memory_region_size(&backend->mr)) {
|
||||
error_setg(errp, "cannot change property value");
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +104,7 @@ static void file_memory_backend_set_share(Object *o, bool value, Error **errp)
|
||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
||||
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
||||
|
||||
if (host_memory_backend_mr_inited(backend)) {
|
||||
if (memory_region_size(&backend->mr)) {
|
||||
error_setg(errp, "cannot change property value");
|
||||
return;
|
||||
}
|
||||
@@ -104,18 +112,13 @@ static void file_memory_backend_set_share(Object *o, bool value, Error **errp)
|
||||
}
|
||||
|
||||
static void
|
||||
file_backend_class_init(ObjectClass *oc, void *data)
|
||||
file_backend_instance_init(Object *o)
|
||||
{
|
||||
HostMemoryBackendClass *bc = MEMORY_BACKEND_CLASS(oc);
|
||||
|
||||
bc->alloc = file_backend_memory_alloc;
|
||||
|
||||
object_class_property_add_bool(oc, "share",
|
||||
file_memory_backend_get_share, file_memory_backend_set_share,
|
||||
&error_abort);
|
||||
object_class_property_add_str(oc, "mem-path",
|
||||
get_mem_path, set_mem_path,
|
||||
&error_abort);
|
||||
object_property_add_bool(o, "share",
|
||||
file_memory_backend_get_share,
|
||||
file_memory_backend_set_share, NULL);
|
||||
object_property_add_str(o, "mem-path", get_mem_path,
|
||||
set_mem_path, NULL);
|
||||
}
|
||||
|
||||
static void file_backend_instance_finalize(Object *o)
|
||||
@@ -129,6 +132,7 @@ static const TypeInfo file_backend_info = {
|
||||
.name = TYPE_MEMORY_BACKEND_FILE,
|
||||
.parent = TYPE_MEMORY_BACKEND,
|
||||
.class_init = file_backend_class_init,
|
||||
.instance_init = file_backend_instance_init,
|
||||
.instance_finalize = file_backend_instance_finalize,
|
||||
.instance_size = sizeof(HostMemoryBackendFile),
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ ram_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
|
||||
}
|
||||
|
||||
path = object_get_canonical_path_component(OBJECT(backend));
|
||||
memory_region_init_ram_nomigrate(&backend->mr, OBJECT(backend), path,
|
||||
memory_region_init_ram(&backend->mr, OBJECT(backend), path,
|
||||
backend->size, errp);
|
||||
g_free(path);
|
||||
}
|
||||
|
||||
+41
-72
@@ -45,7 +45,7 @@ host_memory_backend_set_size(Object *obj, Visitor *v, const char *name,
|
||||
Error *local_err = NULL;
|
||||
uint64_t value;
|
||||
|
||||
if (host_memory_backend_mr_inited(backend)) {
|
||||
if (memory_region_size(&backend->mr)) {
|
||||
error_setg(&local_err, "cannot change property value");
|
||||
goto out;
|
||||
}
|
||||
@@ -64,6 +64,14 @@ out:
|
||||
error_propagate(errp, local_err);
|
||||
}
|
||||
|
||||
static uint16List **host_memory_append_node(uint16List **node,
|
||||
unsigned long value)
|
||||
{
|
||||
*node = g_malloc0(sizeof(**node));
|
||||
(*node)->value = value;
|
||||
return &(*node)->next;
|
||||
}
|
||||
|
||||
static void
|
||||
host_memory_backend_get_host_nodes(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
@@ -74,13 +82,12 @@ host_memory_backend_get_host_nodes(Object *obj, Visitor *v, const char *name,
|
||||
unsigned long value;
|
||||
|
||||
value = find_first_bit(backend->host_nodes, MAX_NODES);
|
||||
if (value == MAX_NODES) {
|
||||
return;
|
||||
}
|
||||
|
||||
*node = g_malloc0(sizeof(**node));
|
||||
(*node)->value = value;
|
||||
node = &(*node)->next;
|
||||
node = host_memory_append_node(node, value);
|
||||
|
||||
if (value == MAX_NODES) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
do {
|
||||
value = find_next_bit(backend->host_nodes, MAX_NODES, value + 1);
|
||||
@@ -88,11 +95,10 @@ host_memory_backend_get_host_nodes(Object *obj, Visitor *v, const char *name,
|
||||
break;
|
||||
}
|
||||
|
||||
*node = g_malloc0(sizeof(**node));
|
||||
(*node)->value = value;
|
||||
node = &(*node)->next;
|
||||
node = host_memory_append_node(node, value);
|
||||
} while (true);
|
||||
|
||||
out:
|
||||
visit_type_uint16List(v, name, &host_nodes, errp);
|
||||
}
|
||||
|
||||
@@ -146,7 +152,7 @@ static void host_memory_backend_set_merge(Object *obj, bool value, Error **errp)
|
||||
{
|
||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||
|
||||
if (!host_memory_backend_mr_inited(backend)) {
|
||||
if (!memory_region_size(&backend->mr)) {
|
||||
backend->merge = value;
|
||||
return;
|
||||
}
|
||||
@@ -172,7 +178,7 @@ static void host_memory_backend_set_dump(Object *obj, bool value, Error **errp)
|
||||
{
|
||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||
|
||||
if (!host_memory_backend_mr_inited(backend)) {
|
||||
if (!memory_region_size(&backend->mr)) {
|
||||
backend->dump = value;
|
||||
return;
|
||||
}
|
||||
@@ -208,7 +214,7 @@ static void host_memory_backend_set_prealloc(Object *obj, bool value,
|
||||
}
|
||||
}
|
||||
|
||||
if (!host_memory_backend_mr_inited(backend)) {
|
||||
if (!memory_region_size(&backend->mr)) {
|
||||
backend->prealloc = value;
|
||||
return;
|
||||
}
|
||||
@@ -218,7 +224,7 @@ static void host_memory_backend_set_prealloc(Object *obj, bool value,
|
||||
void *ptr = memory_region_get_ram_ptr(&backend->mr);
|
||||
uint64_t sz = memory_region_size(&backend->mr);
|
||||
|
||||
os_mem_prealloc(fd, ptr, sz, smp_cpus, &local_err);
|
||||
os_mem_prealloc(fd, ptr, sz, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
@@ -235,21 +241,32 @@ static void host_memory_backend_init(Object *obj)
|
||||
backend->merge = machine_mem_merge(machine);
|
||||
backend->dump = machine_dump_guest_core(machine);
|
||||
backend->prealloc = mem_prealloc;
|
||||
}
|
||||
|
||||
bool host_memory_backend_mr_inited(HostMemoryBackend *backend)
|
||||
{
|
||||
/*
|
||||
* NOTE: We forbid zero-length memory backend, so here zero means
|
||||
* "we haven't inited the backend memory region yet".
|
||||
*/
|
||||
return memory_region_size(&backend->mr) != 0;
|
||||
object_property_add_bool(obj, "merge",
|
||||
host_memory_backend_get_merge,
|
||||
host_memory_backend_set_merge, NULL);
|
||||
object_property_add_bool(obj, "dump",
|
||||
host_memory_backend_get_dump,
|
||||
host_memory_backend_set_dump, NULL);
|
||||
object_property_add_bool(obj, "prealloc",
|
||||
host_memory_backend_get_prealloc,
|
||||
host_memory_backend_set_prealloc, NULL);
|
||||
object_property_add(obj, "size", "int",
|
||||
host_memory_backend_get_size,
|
||||
host_memory_backend_set_size, NULL, NULL, NULL);
|
||||
object_property_add(obj, "host-nodes", "int",
|
||||
host_memory_backend_get_host_nodes,
|
||||
host_memory_backend_set_host_nodes, NULL, NULL, NULL);
|
||||
object_property_add_enum(obj, "policy", "HostMemPolicy",
|
||||
HostMemPolicy_lookup,
|
||||
host_memory_backend_get_policy,
|
||||
host_memory_backend_set_policy, NULL);
|
||||
}
|
||||
|
||||
MemoryRegion *
|
||||
host_memory_backend_get_memory(HostMemoryBackend *backend, Error **errp)
|
||||
{
|
||||
return host_memory_backend_mr_inited(backend) ? &backend->mr : NULL;
|
||||
return memory_region_size(&backend->mr) ? &backend->mr : NULL;
|
||||
}
|
||||
|
||||
void host_memory_backend_set_mapped(HostMemoryBackend *backend, bool mapped)
|
||||
@@ -331,7 +348,7 @@ host_memory_backend_memory_complete(UserCreatable *uc, Error **errp)
|
||||
*/
|
||||
if (backend->prealloc) {
|
||||
os_mem_prealloc(memory_region_get_fd(&backend->mr), ptr, sz,
|
||||
smp_cpus, &local_err);
|
||||
&local_err);
|
||||
if (local_err) {
|
||||
goto out;
|
||||
}
|
||||
@@ -351,24 +368,6 @@ host_memory_backend_can_be_deleted(UserCreatable *uc, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
static char *get_id(Object *o, Error **errp)
|
||||
{
|
||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
||||
|
||||
return g_strdup(backend->id);
|
||||
}
|
||||
|
||||
static void set_id(Object *o, const char *str, Error **errp)
|
||||
{
|
||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
||||
|
||||
if (backend->id) {
|
||||
error_setg(errp, "cannot change property value");
|
||||
return;
|
||||
}
|
||||
backend->id = g_strdup(str);
|
||||
}
|
||||
|
||||
static void
|
||||
host_memory_backend_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
@@ -376,35 +375,6 @@ host_memory_backend_class_init(ObjectClass *oc, void *data)
|
||||
|
||||
ucc->complete = host_memory_backend_memory_complete;
|
||||
ucc->can_be_deleted = host_memory_backend_can_be_deleted;
|
||||
|
||||
object_class_property_add_bool(oc, "merge",
|
||||
host_memory_backend_get_merge,
|
||||
host_memory_backend_set_merge, &error_abort);
|
||||
object_class_property_add_bool(oc, "dump",
|
||||
host_memory_backend_get_dump,
|
||||
host_memory_backend_set_dump, &error_abort);
|
||||
object_class_property_add_bool(oc, "prealloc",
|
||||
host_memory_backend_get_prealloc,
|
||||
host_memory_backend_set_prealloc, &error_abort);
|
||||
object_class_property_add(oc, "size", "int",
|
||||
host_memory_backend_get_size,
|
||||
host_memory_backend_set_size,
|
||||
NULL, NULL, &error_abort);
|
||||
object_class_property_add(oc, "host-nodes", "int",
|
||||
host_memory_backend_get_host_nodes,
|
||||
host_memory_backend_set_host_nodes,
|
||||
NULL, NULL, &error_abort);
|
||||
object_class_property_add_enum(oc, "policy", "HostMemPolicy",
|
||||
HostMemPolicy_lookup,
|
||||
host_memory_backend_get_policy,
|
||||
host_memory_backend_set_policy, &error_abort);
|
||||
object_class_property_add_str(oc, "id", get_id, set_id, &error_abort);
|
||||
}
|
||||
|
||||
static void host_memory_backend_finalize(Object *o)
|
||||
{
|
||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
||||
g_free(backend->id);
|
||||
}
|
||||
|
||||
static const TypeInfo host_memory_backend_info = {
|
||||
@@ -415,7 +385,6 @@ static const TypeInfo host_memory_backend_info = {
|
||||
.class_init = host_memory_backend_class_init,
|
||||
.instance_size = sizeof(HostMemoryBackend),
|
||||
.instance_init = host_memory_backend_init,
|
||||
.instance_finalize = host_memory_backend_finalize,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
{ TYPE_USER_CREATABLE },
|
||||
{ }
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "chardev/char.h"
|
||||
#include "sysemu/char.h"
|
||||
#include "ui/console.h"
|
||||
#include "ui/input.h"
|
||||
|
||||
@@ -31,23 +31,18 @@
|
||||
#define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6)
|
||||
|
||||
typedef struct {
|
||||
Chardev parent;
|
||||
|
||||
CharDriverState *chr;
|
||||
QemuInputHandlerState *hs;
|
||||
int axis[INPUT_AXIS__MAX];
|
||||
bool btns[INPUT_BUTTON__MAX];
|
||||
bool btnc[INPUT_BUTTON__MAX];
|
||||
uint8_t outbuf[32];
|
||||
int outlen;
|
||||
} MouseChardev;
|
||||
} MouseState;
|
||||
|
||||
#define TYPE_CHARDEV_MSMOUSE "chardev-msmouse"
|
||||
#define MOUSE_CHARDEV(obj) \
|
||||
OBJECT_CHECK(MouseChardev, (obj), TYPE_CHARDEV_MSMOUSE)
|
||||
|
||||
static void msmouse_chr_accept_input(Chardev *chr)
|
||||
static void msmouse_chr_accept_input(CharDriverState *chr)
|
||||
{
|
||||
MouseChardev *mouse = MOUSE_CHARDEV(chr);
|
||||
MouseState *mouse = chr->opaque;
|
||||
int len;
|
||||
|
||||
len = qemu_chr_be_can_write(chr);
|
||||
@@ -65,7 +60,7 @@ static void msmouse_chr_accept_input(Chardev *chr)
|
||||
}
|
||||
}
|
||||
|
||||
static void msmouse_queue_event(MouseChardev *mouse)
|
||||
static void msmouse_queue_event(MouseState *mouse)
|
||||
{
|
||||
unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 };
|
||||
int dx, dy, count = 3;
|
||||
@@ -102,7 +97,7 @@ static void msmouse_queue_event(MouseChardev *mouse)
|
||||
static void msmouse_input_event(DeviceState *dev, QemuConsole *src,
|
||||
InputEvent *evt)
|
||||
{
|
||||
MouseChardev *mouse = MOUSE_CHARDEV(dev);
|
||||
MouseState *mouse = (MouseState *)dev;
|
||||
InputMoveEvent *move;
|
||||
InputBtnEvent *btn;
|
||||
|
||||
@@ -126,24 +121,24 @@ static void msmouse_input_event(DeviceState *dev, QemuConsole *src,
|
||||
|
||||
static void msmouse_input_sync(DeviceState *dev)
|
||||
{
|
||||
MouseChardev *mouse = MOUSE_CHARDEV(dev);
|
||||
Chardev *chr = CHARDEV(dev);
|
||||
MouseState *mouse = (MouseState *)dev;
|
||||
|
||||
msmouse_queue_event(mouse);
|
||||
msmouse_chr_accept_input(chr);
|
||||
msmouse_chr_accept_input(mouse->chr);
|
||||
}
|
||||
|
||||
static int msmouse_chr_write(struct Chardev *s, const uint8_t *buf, int len)
|
||||
static int msmouse_chr_write (struct CharDriverState *s, const uint8_t *buf, int len)
|
||||
{
|
||||
/* Ignore writes to mouse port */
|
||||
return len;
|
||||
}
|
||||
|
||||
static void char_msmouse_finalize(Object *obj)
|
||||
static void msmouse_chr_close (struct CharDriverState *chr)
|
||||
{
|
||||
MouseChardev *mouse = MOUSE_CHARDEV(obj);
|
||||
MouseState *mouse = chr->opaque;
|
||||
|
||||
qemu_input_handler_unregister(mouse->hs);
|
||||
g_free(mouse);
|
||||
}
|
||||
|
||||
static QemuInputHandler msmouse_handler = {
|
||||
@@ -153,38 +148,38 @@ static QemuInputHandler msmouse_handler = {
|
||||
.sync = msmouse_input_sync,
|
||||
};
|
||||
|
||||
static void msmouse_chr_open(Chardev *chr,
|
||||
ChardevBackend *backend,
|
||||
bool *be_opened,
|
||||
Error **errp)
|
||||
static CharDriverState *qemu_chr_open_msmouse(const char *id,
|
||||
ChardevBackend *backend,
|
||||
ChardevReturn *ret,
|
||||
Error **errp)
|
||||
{
|
||||
MouseChardev *mouse = MOUSE_CHARDEV(chr);
|
||||
ChardevCommon *common = backend->u.msmouse.data;
|
||||
MouseState *mouse;
|
||||
CharDriverState *chr;
|
||||
|
||||
*be_opened = false;
|
||||
chr = qemu_chr_alloc(common, errp);
|
||||
if (!chr) {
|
||||
return NULL;
|
||||
}
|
||||
chr->chr_write = msmouse_chr_write;
|
||||
chr->chr_close = msmouse_chr_close;
|
||||
chr->chr_accept_input = msmouse_chr_accept_input;
|
||||
chr->explicit_be_open = true;
|
||||
|
||||
mouse = g_new0(MouseState, 1);
|
||||
mouse->hs = qemu_input_handler_register((DeviceState *)mouse,
|
||||
&msmouse_handler);
|
||||
|
||||
mouse->chr = chr;
|
||||
chr->opaque = mouse;
|
||||
|
||||
return chr;
|
||||
}
|
||||
|
||||
static void char_msmouse_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
||||
|
||||
cc->open = msmouse_chr_open;
|
||||
cc->chr_write = msmouse_chr_write;
|
||||
cc->chr_accept_input = msmouse_chr_accept_input;
|
||||
}
|
||||
|
||||
static const TypeInfo char_msmouse_type_info = {
|
||||
.name = TYPE_CHARDEV_MSMOUSE,
|
||||
.parent = TYPE_CHARDEV,
|
||||
.instance_size = sizeof(MouseChardev),
|
||||
.instance_finalize = char_msmouse_finalize,
|
||||
.class_init = char_msmouse_class_init,
|
||||
};
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&char_msmouse_type_info);
|
||||
register_char_driver("msmouse", CHARDEV_BACKEND_KIND_MSMOUSE, NULL,
|
||||
qemu_chr_open_msmouse);
|
||||
}
|
||||
|
||||
type_init(register_types);
|
||||
+18
-15
@@ -12,9 +12,10 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "sysemu/rng.h"
|
||||
#include "chardev/char-fe.h"
|
||||
#include "sysemu/char.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "hw/qdev.h" /* just for DEFINE_PROP_CHR */
|
||||
|
||||
#define TYPE_RNG_EGD "rng-egd"
|
||||
#define RNG_EGD(obj) OBJECT_CHECK(RngEgd, (obj), TYPE_RNG_EGD)
|
||||
@@ -23,7 +24,7 @@ typedef struct RngEgd
|
||||
{
|
||||
RngBackend parent;
|
||||
|
||||
CharBackend chr;
|
||||
CharDriverState *chr;
|
||||
char *chr_name;
|
||||
} RngEgd;
|
||||
|
||||
@@ -40,9 +41,7 @@ static void rng_egd_request_entropy(RngBackend *b, RngRequest *req)
|
||||
header[0] = 0x02;
|
||||
header[1] = len;
|
||||
|
||||
/* XXX this blocks entire thread. Rewrite to use
|
||||
* qemu_chr_fe_write and background I/O callbacks */
|
||||
qemu_chr_fe_write_all(&s->chr, header, sizeof(header));
|
||||
qemu_chr_fe_write(s->chr, header, sizeof(header));
|
||||
|
||||
size -= len;
|
||||
}
|
||||
@@ -86,7 +85,6 @@ static void rng_egd_chr_read(void *opaque, const uint8_t *buf, int size)
|
||||
static void rng_egd_opened(RngBackend *b, Error **errp)
|
||||
{
|
||||
RngEgd *s = RNG_EGD(b);
|
||||
Chardev *chr;
|
||||
|
||||
if (s->chr_name == NULL) {
|
||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
||||
@@ -94,19 +92,21 @@ static void rng_egd_opened(RngBackend *b, Error **errp)
|
||||
return;
|
||||
}
|
||||
|
||||
chr = qemu_chr_find(s->chr_name);
|
||||
if (chr == NULL) {
|
||||
s->chr = qemu_chr_find(s->chr_name);
|
||||
if (s->chr == NULL) {
|
||||
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
|
||||
"Device '%s' not found", s->chr_name);
|
||||
return;
|
||||
}
|
||||
if (!qemu_chr_fe_init(&s->chr, chr, errp)) {
|
||||
|
||||
if (qemu_chr_fe_claim(s->chr) != 0) {
|
||||
error_setg(errp, QERR_DEVICE_IN_USE, s->chr_name);
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME we should resubmit pending requests when the CDS reconnects. */
|
||||
qemu_chr_fe_set_handlers(&s->chr, rng_egd_chr_can_read,
|
||||
rng_egd_chr_read, NULL, NULL, s, NULL, true);
|
||||
qemu_chr_add_handlers(s->chr, rng_egd_chr_can_read, rng_egd_chr_read,
|
||||
NULL, s);
|
||||
}
|
||||
|
||||
static void rng_egd_set_chardev(Object *obj, const char *value, Error **errp)
|
||||
@@ -125,10 +125,9 @@ static void rng_egd_set_chardev(Object *obj, const char *value, Error **errp)
|
||||
static char *rng_egd_get_chardev(Object *obj, Error **errp)
|
||||
{
|
||||
RngEgd *s = RNG_EGD(obj);
|
||||
Chardev *chr = qemu_chr_fe_get_driver(&s->chr);
|
||||
|
||||
if (chr && chr->label) {
|
||||
return g_strdup(chr->label);
|
||||
if (s->chr && s->chr->label) {
|
||||
return g_strdup(s->chr->label);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -145,7 +144,11 @@ static void rng_egd_finalize(Object *obj)
|
||||
{
|
||||
RngEgd *s = RNG_EGD(obj);
|
||||
|
||||
qemu_chr_fe_deinit(&s->chr, false);
|
||||
if (s->chr) {
|
||||
qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, NULL);
|
||||
qemu_chr_fe_release(s->chr);
|
||||
}
|
||||
|
||||
g_free(s->chr_name);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,23 +25,18 @@
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "chardev/char.h"
|
||||
#include "sysemu/char.h"
|
||||
|
||||
#define BUF_SIZE 32
|
||||
|
||||
typedef struct {
|
||||
Chardev parent;
|
||||
|
||||
CharDriverState *chr;
|
||||
uint8_t in_buf[32];
|
||||
int in_buf_used;
|
||||
} TestdevChardev;
|
||||
|
||||
#define TYPE_CHARDEV_TESTDEV "chardev-testdev"
|
||||
#define TESTDEV_CHARDEV(obj) \
|
||||
OBJECT_CHECK(TestdevChardev, (obj), TYPE_CHARDEV_TESTDEV)
|
||||
} TestdevCharState;
|
||||
|
||||
/* Try to interpret a whole incoming packet */
|
||||
static int testdev_eat_packet(TestdevChardev *testdev)
|
||||
static int testdev_eat_packet(TestdevCharState *testdev)
|
||||
{
|
||||
const uint8_t *cur = testdev->in_buf;
|
||||
int len = testdev->in_buf_used;
|
||||
@@ -82,9 +77,9 @@ static int testdev_eat_packet(TestdevChardev *testdev)
|
||||
}
|
||||
|
||||
/* The other end is writing some data. Store it and try to interpret */
|
||||
static int testdev_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
||||
static int testdev_write(CharDriverState *chr, const uint8_t *buf, int len)
|
||||
{
|
||||
TestdevChardev *testdev = TESTDEV_CHARDEV(chr);
|
||||
TestdevCharState *testdev = chr->opaque;
|
||||
int tocopy, eaten, orig_len = len;
|
||||
|
||||
while (len) {
|
||||
@@ -107,23 +102,35 @@ static int testdev_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
||||
return orig_len;
|
||||
}
|
||||
|
||||
static void char_testdev_class_init(ObjectClass *oc, void *data)
|
||||
static void testdev_close(struct CharDriverState *chr)
|
||||
{
|
||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
||||
TestdevCharState *testdev = chr->opaque;
|
||||
|
||||
cc->chr_write = testdev_chr_write;
|
||||
g_free(testdev);
|
||||
}
|
||||
|
||||
static const TypeInfo char_testdev_type_info = {
|
||||
.name = TYPE_CHARDEV_TESTDEV,
|
||||
.parent = TYPE_CHARDEV,
|
||||
.instance_size = sizeof(TestdevChardev),
|
||||
.class_init = char_testdev_class_init,
|
||||
};
|
||||
static CharDriverState *chr_testdev_init(const char *id,
|
||||
ChardevBackend *backend,
|
||||
ChardevReturn *ret,
|
||||
Error **errp)
|
||||
{
|
||||
TestdevCharState *testdev;
|
||||
CharDriverState *chr;
|
||||
|
||||
testdev = g_new0(TestdevCharState, 1);
|
||||
testdev->chr = chr = g_new0(CharDriverState, 1);
|
||||
|
||||
chr->opaque = testdev;
|
||||
chr->chr_write = testdev_write;
|
||||
chr->chr_close = testdev_close;
|
||||
|
||||
return chr;
|
||||
}
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&char_testdev_type_info);
|
||||
register_char_driver("testdev", CHARDEV_BACKEND_KIND_TESTDEV, NULL,
|
||||
chr_testdev_init);
|
||||
}
|
||||
|
||||
type_init(register_types);
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "exec/cpu-common.h"
|
||||
#include "sysemu/kvm.h"
|
||||
#include "sysemu/balloon.h"
|
||||
#include "trace-root.h"
|
||||
#include "trace.h"
|
||||
#include "qmp-commands.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
|
||||
+11
-14
@@ -1,36 +1,33 @@
|
||||
block-obj-y += raw-format.o qcow.o vdi.o vmdk.o cloop.o bochs.o vpc.o vvfat.o dmg.o
|
||||
block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o qcow2-bitmap.o
|
||||
block-obj-y += qed.o qed-l2-cache.o qed-table.o qed-cluster.o
|
||||
block-obj-y += raw_bsd.o qcow.o vdi.o vmdk.o cloop.o bochs.o vpc.o vvfat.o
|
||||
block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o
|
||||
block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
|
||||
block-obj-y += qed-check.o
|
||||
block-obj-y += vhdx.o vhdx-endian.o vhdx-log.o
|
||||
block-obj-$(CONFIG_VHDX) += vhdx.o vhdx-endian.o vhdx-log.o
|
||||
block-obj-y += quorum.o
|
||||
block-obj-y += parallels.o blkdebug.o blkverify.o blkreplay.o
|
||||
block-obj-y += block-backend.o snapshot.o qapi.o
|
||||
block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
|
||||
block-obj-$(CONFIG_POSIX) += file-posix.o
|
||||
block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
|
||||
block-obj-$(CONFIG_POSIX) += raw-posix.o
|
||||
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
||||
block-obj-y += null.o mirror.o commit.o io.o
|
||||
block-obj-y += throttle-groups.o
|
||||
|
||||
block-obj-y += nbd.o nbd-client.o sheepdog.o
|
||||
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
|
||||
block-obj-$(if $(CONFIG_LIBISCSI),y,n) += iscsi-opts.o
|
||||
block-obj-$(CONFIG_LIBNFS) += nfs.o
|
||||
block-obj-$(CONFIG_CURL) += curl.o
|
||||
block-obj-$(CONFIG_RBD) += rbd.o
|
||||
block-obj-$(CONFIG_GLUSTERFS) += gluster.o
|
||||
block-obj-$(CONFIG_VXHS) += vxhs.o
|
||||
block-obj-$(CONFIG_ARCHIPELAGO) += archipelago.o
|
||||
block-obj-$(CONFIG_LIBSSH2) += ssh.o
|
||||
block-obj-y += accounting.o dirty-bitmap.o
|
||||
block-obj-y += write-threshold.o
|
||||
block-obj-y += backup.o
|
||||
block-obj-$(CONFIG_REPLICATION) += replication.o
|
||||
|
||||
block-obj-y += crypto.o
|
||||
|
||||
common-obj-y += stream.o
|
||||
common-obj-y += backup.o
|
||||
|
||||
nfs.o-libs := $(LIBNFS_LIBS)
|
||||
iscsi.o-cflags := $(LIBISCSI_CFLAGS)
|
||||
iscsi.o-libs := $(LIBISCSI_LIBS)
|
||||
curl.o-cflags := $(CURL_CFLAGS)
|
||||
@@ -39,10 +36,10 @@ rbd.o-cflags := $(RBD_CFLAGS)
|
||||
rbd.o-libs := $(RBD_LIBS)
|
||||
gluster.o-cflags := $(GLUSTERFS_CFLAGS)
|
||||
gluster.o-libs := $(GLUSTERFS_LIBS)
|
||||
vxhs.o-libs := $(VXHS_LIBS)
|
||||
ssh.o-cflags := $(LIBSSH2_CFLAGS)
|
||||
ssh.o-libs := $(LIBSSH2_LIBS)
|
||||
block-obj-$(if $(CONFIG_BZIP2),m,n) += dmg-bz2.o
|
||||
dmg-bz2.o-libs := $(BZIP2_LIBS)
|
||||
archipelago.o-libs := $(ARCHIPELAGO_LIBS)
|
||||
block-obj-m += dmg.o
|
||||
dmg.o-libs := $(BZIP2_LIBS)
|
||||
qcow.o-libs := -lz
|
||||
linux-aio.o-libs := -laio
|
||||
+34
-46
@@ -32,19 +32,15 @@
|
||||
static QEMUClockType clock_type = QEMU_CLOCK_REALTIME;
|
||||
static const int qtest_latency_ns = NANOSECONDS_PER_SECOND / 1000;
|
||||
|
||||
void block_acct_init(BlockAcctStats *stats)
|
||||
{
|
||||
qemu_mutex_init(&stats->lock);
|
||||
if (qtest_enabled()) {
|
||||
clock_type = QEMU_CLOCK_VIRTUAL;
|
||||
}
|
||||
}
|
||||
|
||||
void block_acct_setup(BlockAcctStats *stats, bool account_invalid,
|
||||
bool account_failed)
|
||||
void block_acct_init(BlockAcctStats *stats, bool account_invalid,
|
||||
bool account_failed)
|
||||
{
|
||||
stats->account_invalid = account_invalid;
|
||||
stats->account_failed = account_failed;
|
||||
|
||||
if (qtest_enabled()) {
|
||||
clock_type = QEMU_CLOCK_VIRTUAL;
|
||||
}
|
||||
}
|
||||
|
||||
void block_acct_cleanup(BlockAcctStats *stats)
|
||||
@@ -53,7 +49,6 @@ void block_acct_cleanup(BlockAcctStats *stats)
|
||||
QSLIST_FOREACH_SAFE(s, &stats->intervals, entries, next) {
|
||||
g_free(s);
|
||||
}
|
||||
qemu_mutex_destroy(&stats->lock);
|
||||
}
|
||||
|
||||
void block_acct_add_interval(BlockAcctStats *stats, unsigned interval_length)
|
||||
@@ -63,15 +58,12 @@ void block_acct_add_interval(BlockAcctStats *stats, unsigned interval_length)
|
||||
|
||||
s = g_new0(BlockAcctTimedStats, 1);
|
||||
s->interval_length = interval_length;
|
||||
s->stats = stats;
|
||||
qemu_mutex_lock(&stats->lock);
|
||||
QSLIST_INSERT_HEAD(&stats->intervals, s, entries);
|
||||
|
||||
for (i = 0; i < BLOCK_MAX_IOTYPE; i++) {
|
||||
timed_average_init(&s->latency[i], clock_type,
|
||||
(uint64_t) interval_length * NANOSECONDS_PER_SECOND);
|
||||
}
|
||||
qemu_mutex_unlock(&stats->lock);
|
||||
}
|
||||
|
||||
BlockAcctTimedStats *block_acct_interval_next(BlockAcctStats *stats,
|
||||
@@ -94,8 +86,7 @@ void block_acct_start(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
||||
cookie->type = type;
|
||||
}
|
||||
|
||||
static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
||||
bool failed)
|
||||
void block_acct_done(BlockAcctStats *stats, BlockAcctCookie *cookie)
|
||||
{
|
||||
BlockAcctTimedStats *s;
|
||||
int64_t time_ns = qemu_clock_get_ns(clock_type);
|
||||
@@ -107,16 +98,31 @@ static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
||||
|
||||
assert(cookie->type < BLOCK_MAX_IOTYPE);
|
||||
|
||||
qemu_mutex_lock(&stats->lock);
|
||||
stats->nr_bytes[cookie->type] += cookie->bytes;
|
||||
stats->nr_ops[cookie->type]++;
|
||||
stats->total_time_ns[cookie->type] += latency_ns;
|
||||
stats->last_access_time_ns = time_ns;
|
||||
|
||||
if (failed) {
|
||||
stats->failed_ops[cookie->type]++;
|
||||
} else {
|
||||
stats->nr_bytes[cookie->type] += cookie->bytes;
|
||||
stats->nr_ops[cookie->type]++;
|
||||
QSLIST_FOREACH(s, &stats->intervals, entries) {
|
||||
timed_average_account(&s->latency[cookie->type], latency_ns);
|
||||
}
|
||||
}
|
||||
|
||||
void block_acct_failed(BlockAcctStats *stats, BlockAcctCookie *cookie)
|
||||
{
|
||||
assert(cookie->type < BLOCK_MAX_IOTYPE);
|
||||
|
||||
stats->failed_ops[cookie->type]++;
|
||||
|
||||
if (stats->account_failed) {
|
||||
BlockAcctTimedStats *s;
|
||||
int64_t time_ns = qemu_clock_get_ns(clock_type);
|
||||
int64_t latency_ns = time_ns - cookie->start_time_ns;
|
||||
|
||||
if (qtest_enabled()) {
|
||||
latency_ns = qtest_latency_ns;
|
||||
}
|
||||
|
||||
if (!failed || stats->account_failed) {
|
||||
stats->total_time_ns[cookie->type] += latency_ns;
|
||||
stats->last_access_time_ns = time_ns;
|
||||
|
||||
@@ -124,45 +130,29 @@ static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
||||
timed_average_account(&s->latency[cookie->type], latency_ns);
|
||||
}
|
||||
}
|
||||
|
||||
qemu_mutex_unlock(&stats->lock);
|
||||
}
|
||||
|
||||
void block_acct_done(BlockAcctStats *stats, BlockAcctCookie *cookie)
|
||||
{
|
||||
block_account_one_io(stats, cookie, false);
|
||||
}
|
||||
|
||||
void block_acct_failed(BlockAcctStats *stats, BlockAcctCookie *cookie)
|
||||
{
|
||||
block_account_one_io(stats, cookie, true);
|
||||
}
|
||||
|
||||
void block_acct_invalid(BlockAcctStats *stats, enum BlockAcctType type)
|
||||
{
|
||||
assert(type < BLOCK_MAX_IOTYPE);
|
||||
|
||||
/* block_account_one_io() updates total_time_ns[], but this one does
|
||||
* not. The reason is that invalid requests are accounted during their
|
||||
* submission, therefore there's no actual I/O involved.
|
||||
*/
|
||||
qemu_mutex_lock(&stats->lock);
|
||||
/* block_acct_done() and block_acct_failed() update
|
||||
* total_time_ns[], but this one does not. The reason is that
|
||||
* invalid requests are accounted during their submission,
|
||||
* therefore there's no actual I/O involved. */
|
||||
|
||||
stats->invalid_ops[type]++;
|
||||
|
||||
if (stats->account_invalid) {
|
||||
stats->last_access_time_ns = qemu_clock_get_ns(clock_type);
|
||||
}
|
||||
qemu_mutex_unlock(&stats->lock);
|
||||
}
|
||||
|
||||
void block_acct_merge_done(BlockAcctStats *stats, enum BlockAcctType type,
|
||||
int num_requests)
|
||||
{
|
||||
assert(type < BLOCK_MAX_IOTYPE);
|
||||
|
||||
qemu_mutex_lock(&stats->lock);
|
||||
stats->merged[type] += num_requests;
|
||||
qemu_mutex_unlock(&stats->lock);
|
||||
}
|
||||
|
||||
int64_t block_acct_idle_time_ns(BlockAcctStats *stats)
|
||||
@@ -177,9 +167,7 @@ double block_acct_queue_depth(BlockAcctTimedStats *stats,
|
||||
|
||||
assert(type < BLOCK_MAX_IOTYPE);
|
||||
|
||||
qemu_mutex_lock(&stats->stats->lock);
|
||||
sum = timed_average_sum(&stats->latency[type], &elapsed);
|
||||
qemu_mutex_unlock(&stats->stats->lock);
|
||||
|
||||
return (double) sum / elapsed;
|
||||
}
|
||||
+1082
File diff suppressed because it is too large.
Load diff
+112
-201
@@ -16,19 +16,24 @@
|
||||
#include "trace.h"
|
||||
#include "block/block.h"
|
||||
#include "block/block_int.h"
|
||||
#include "block/blockjob_int.h"
|
||||
#include "block/block_backup.h"
|
||||
#include "block/blockjob.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qemu/ratelimit.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qemu/bitmap.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
#define BACKUP_CLUSTER_SIZE_DEFAULT (1 << 16)
|
||||
#define SLICE_TIME 100000000ULL /* ns */
|
||||
|
||||
typedef struct CowRequest {
|
||||
int64_t start;
|
||||
int64_t end;
|
||||
QLIST_ENTRY(CowRequest) list;
|
||||
CoQueue wait_queue; /* coroutines blocked on this request */
|
||||
} CowRequest;
|
||||
|
||||
typedef struct BackupBlockJob {
|
||||
BlockJob common;
|
||||
BlockBackend *target;
|
||||
@@ -39,14 +44,19 @@ typedef struct BackupBlockJob {
|
||||
BlockdevOnError on_source_error;
|
||||
BlockdevOnError on_target_error;
|
||||
CoRwlock flush_rwlock;
|
||||
uint64_t bytes_read;
|
||||
uint64_t sectors_read;
|
||||
unsigned long *done_bitmap;
|
||||
int64_t cluster_size;
|
||||
bool compress;
|
||||
NotifierWithReturn before_write;
|
||||
QLIST_HEAD(, CowRequest) inflight_reqs;
|
||||
} BackupBlockJob;
|
||||
|
||||
/* Size of a cluster in sectors, instead of bytes. */
|
||||
static inline int64_t cluster_size_sectors(BackupBlockJob *job)
|
||||
{
|
||||
return job->cluster_size / BDRV_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
/* See if in-flight requests overlap and wait for them to complete */
|
||||
static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
||||
int64_t start,
|
||||
@@ -58,8 +68,8 @@ static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
||||
do {
|
||||
retry = false;
|
||||
QLIST_FOREACH(req, &job->inflight_reqs, list) {
|
||||
if (end > req->start_byte && start < req->end_byte) {
|
||||
qemu_co_queue_wait(&req->wait_queue, NULL);
|
||||
if (end > req->start && start < req->end) {
|
||||
qemu_co_queue_wait(&req->wait_queue);
|
||||
retry = true;
|
||||
break;
|
||||
}
|
||||
@@ -69,10 +79,10 @@ static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
||||
|
||||
/* Keep track of an in-flight request */
|
||||
static void cow_request_begin(CowRequest *req, BackupBlockJob *job,
|
||||
int64_t start, int64_t end)
|
||||
int64_t start, int64_t end)
|
||||
{
|
||||
req->start_byte = start;
|
||||
req->end_byte = end;
|
||||
req->start = start;
|
||||
req->end = end;
|
||||
qemu_co_queue_init(&req->wait_queue);
|
||||
QLIST_INSERT_HEAD(&job->inflight_reqs, req, list);
|
||||
}
|
||||
@@ -85,7 +95,7 @@ static void cow_request_end(CowRequest *req)
|
||||
}
|
||||
|
||||
static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
||||
int64_t offset, uint64_t bytes,
|
||||
int64_t sector_num, int nb_sectors,
|
||||
bool *error_is_read,
|
||||
bool is_write_notifier)
|
||||
{
|
||||
@@ -95,37 +105,41 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
||||
QEMUIOVector bounce_qiov;
|
||||
void *bounce_buffer = NULL;
|
||||
int ret = 0;
|
||||
int64_t start, end; /* bytes */
|
||||
int n; /* bytes */
|
||||
int64_t sectors_per_cluster = cluster_size_sectors(job);
|
||||
int64_t start, end;
|
||||
int n;
|
||||
|
||||
qemu_co_rwlock_rdlock(&job->flush_rwlock);
|
||||
|
||||
start = QEMU_ALIGN_DOWN(offset, job->cluster_size);
|
||||
end = QEMU_ALIGN_UP(bytes + offset, job->cluster_size);
|
||||
start = sector_num / sectors_per_cluster;
|
||||
end = DIV_ROUND_UP(sector_num + nb_sectors, sectors_per_cluster);
|
||||
|
||||
trace_backup_do_cow_enter(job, start, offset, bytes);
|
||||
trace_backup_do_cow_enter(job, start, sector_num, nb_sectors);
|
||||
|
||||
wait_for_overlapping_requests(job, start, end);
|
||||
cow_request_begin(&cow_request, job, start, end);
|
||||
|
||||
for (; start < end; start += job->cluster_size) {
|
||||
if (test_bit(start / job->cluster_size, job->done_bitmap)) {
|
||||
for (; start < end; start++) {
|
||||
if (test_bit(start, job->done_bitmap)) {
|
||||
trace_backup_do_cow_skip(job, start);
|
||||
continue; /* already copied */
|
||||
}
|
||||
|
||||
trace_backup_do_cow_process(job, start);
|
||||
|
||||
n = MIN(job->cluster_size, job->common.len - start);
|
||||
n = MIN(sectors_per_cluster,
|
||||
job->common.len / BDRV_SECTOR_SIZE -
|
||||
start * sectors_per_cluster);
|
||||
|
||||
if (!bounce_buffer) {
|
||||
bounce_buffer = blk_blockalign(blk, job->cluster_size);
|
||||
}
|
||||
iov.iov_base = bounce_buffer;
|
||||
iov.iov_len = n;
|
||||
iov.iov_len = n * BDRV_SECTOR_SIZE;
|
||||
qemu_iovec_init_external(&bounce_qiov, &iov, 1);
|
||||
|
||||
ret = blk_co_preadv(blk, start, bounce_qiov.size, &bounce_qiov,
|
||||
ret = blk_co_preadv(blk, start * job->cluster_size,
|
||||
bounce_qiov.size, &bounce_qiov,
|
||||
is_write_notifier ? BDRV_REQ_NO_SERIALISING : 0);
|
||||
if (ret < 0) {
|
||||
trace_backup_do_cow_read_fail(job, start, ret);
|
||||
@@ -136,12 +150,11 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
||||
}
|
||||
|
||||
if (buffer_is_zero(iov.iov_base, iov.iov_len)) {
|
||||
ret = blk_co_pwrite_zeroes(job->target, start,
|
||||
ret = blk_co_pwrite_zeroes(job->target, start * job->cluster_size,
|
||||
bounce_qiov.size, BDRV_REQ_MAY_UNMAP);
|
||||
} else {
|
||||
ret = blk_co_pwritev(job->target, start,
|
||||
bounce_qiov.size, &bounce_qiov,
|
||||
job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0);
|
||||
ret = blk_co_pwritev(job->target, start * job->cluster_size,
|
||||
bounce_qiov.size, &bounce_qiov, 0);
|
||||
}
|
||||
if (ret < 0) {
|
||||
trace_backup_do_cow_write_fail(job, start, ret);
|
||||
@@ -151,13 +164,13 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
||||
goto out;
|
||||
}
|
||||
|
||||
set_bit(start / job->cluster_size, job->done_bitmap);
|
||||
set_bit(start, job->done_bitmap);
|
||||
|
||||
/* Publish progress, guest I/O counts as progress too. Note that the
|
||||
* offset field is an opaque progress value, it is not a disk offset.
|
||||
*/
|
||||
job->bytes_read += n;
|
||||
job->common.offset += n;
|
||||
job->sectors_read += n;
|
||||
job->common.offset += n * BDRV_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
out:
|
||||
@@ -167,7 +180,7 @@ out:
|
||||
|
||||
cow_request_end(&cow_request);
|
||||
|
||||
trace_backup_do_cow_return(job, offset, bytes, ret);
|
||||
trace_backup_do_cow_return(job, sector_num, nb_sectors, ret);
|
||||
|
||||
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
||||
|
||||
@@ -180,12 +193,14 @@ static int coroutine_fn backup_before_write_notify(
|
||||
{
|
||||
BackupBlockJob *job = container_of(notifier, BackupBlockJob, before_write);
|
||||
BdrvTrackedRequest *req = opaque;
|
||||
int64_t sector_num = req->offset >> BDRV_SECTOR_BITS;
|
||||
int nb_sectors = req->bytes >> BDRV_SECTOR_BITS;
|
||||
|
||||
assert(req->bs == blk_bs(job->common.blk));
|
||||
assert(QEMU_IS_ALIGNED(req->offset, BDRV_SECTOR_SIZE));
|
||||
assert(QEMU_IS_ALIGNED(req->bytes, BDRV_SECTOR_SIZE));
|
||||
assert((req->offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||
assert((req->bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||
|
||||
return backup_do_cow(job, req->offset, req->bytes, NULL, true);
|
||||
return backup_do_cow(job, sector_num, nb_sectors, NULL, true);
|
||||
}
|
||||
|
||||
static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||
@@ -196,7 +211,7 @@ static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
||||
return;
|
||||
}
|
||||
ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
|
||||
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
||||
}
|
||||
|
||||
static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
|
||||
@@ -231,14 +246,6 @@ static void backup_abort(BlockJob *job)
|
||||
}
|
||||
}
|
||||
|
||||
static void backup_clean(BlockJob *job)
|
||||
{
|
||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||
assert(s->target);
|
||||
blk_unref(s->target);
|
||||
s->target = NULL;
|
||||
}
|
||||
|
||||
static void backup_attached_aio_context(BlockJob *job, AioContext *aio_context)
|
||||
{
|
||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||
@@ -246,68 +253,14 @@ static void backup_attached_aio_context(BlockJob *job, AioContext *aio_context)
|
||||
blk_set_aio_context(s->target, aio_context);
|
||||
}
|
||||
|
||||
void backup_do_checkpoint(BlockJob *job, Error **errp)
|
||||
{
|
||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
||||
int64_t len;
|
||||
|
||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
||||
|
||||
if (backup_job->sync_mode != MIRROR_SYNC_MODE_NONE) {
|
||||
error_setg(errp, "The backup job only supports block checkpoint in"
|
||||
" sync=none mode");
|
||||
return;
|
||||
}
|
||||
|
||||
len = DIV_ROUND_UP(backup_job->common.len, backup_job->cluster_size);
|
||||
bitmap_zero(backup_job->done_bitmap, len);
|
||||
}
|
||||
|
||||
void backup_wait_for_overlapping_requests(BlockJob *job, int64_t offset,
|
||||
uint64_t bytes)
|
||||
{
|
||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
||||
int64_t start, end;
|
||||
|
||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
||||
|
||||
start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
|
||||
end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
|
||||
wait_for_overlapping_requests(backup_job, start, end);
|
||||
}
|
||||
|
||||
void backup_cow_request_begin(CowRequest *req, BlockJob *job,
|
||||
int64_t offset, uint64_t bytes)
|
||||
{
|
||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
||||
int64_t start, end;
|
||||
|
||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
||||
|
||||
start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
|
||||
end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
|
||||
cow_request_begin(req, backup_job, start, end);
|
||||
}
|
||||
|
||||
void backup_cow_request_end(CowRequest *req)
|
||||
{
|
||||
cow_request_end(req);
|
||||
}
|
||||
|
||||
static void backup_drain(BlockJob *job)
|
||||
{
|
||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||
|
||||
/* Need to keep a reference in case blk_drain triggers execution
|
||||
* of backup_complete...
|
||||
*/
|
||||
if (s->target) {
|
||||
BlockBackend *target = s->target;
|
||||
blk_ref(target);
|
||||
blk_drain(target);
|
||||
blk_unref(target);
|
||||
}
|
||||
}
|
||||
static const BlockJobDriver backup_job_driver = {
|
||||
.instance_size = sizeof(BackupBlockJob),
|
||||
.job_type = BLOCK_JOB_TYPE_BACKUP,
|
||||
.set_speed = backup_set_speed,
|
||||
.commit = backup_commit,
|
||||
.abort = backup_abort,
|
||||
.attached_aio_context = backup_attached_aio_context,
|
||||
};
|
||||
|
||||
static BlockErrorAction backup_error_action(BackupBlockJob *job,
|
||||
bool read, int error)
|
||||
@@ -327,8 +280,11 @@ typedef struct {
|
||||
|
||||
static void backup_complete(BlockJob *job, void *opaque)
|
||||
{
|
||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||
BackupCompleteData *data = opaque;
|
||||
|
||||
blk_unref(s->target);
|
||||
|
||||
block_job_completed(job, data->ret);
|
||||
g_free(data);
|
||||
}
|
||||
@@ -344,8 +300,8 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)
|
||||
*/
|
||||
if (job->common.speed) {
|
||||
uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
|
||||
job->bytes_read);
|
||||
job->bytes_read = 0;
|
||||
job->sectors_read);
|
||||
job->sectors_read = 0;
|
||||
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
|
||||
} else {
|
||||
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
|
||||
@@ -364,19 +320,20 @@ static int coroutine_fn backup_run_incremental(BackupBlockJob *job)
|
||||
int ret = 0;
|
||||
int clusters_per_iter;
|
||||
uint32_t granularity;
|
||||
int64_t offset;
|
||||
int64_t sector;
|
||||
int64_t cluster;
|
||||
int64_t end;
|
||||
int64_t last_cluster = -1;
|
||||
BdrvDirtyBitmapIter *dbi;
|
||||
int64_t sectors_per_cluster = cluster_size_sectors(job);
|
||||
HBitmapIter hbi;
|
||||
|
||||
granularity = bdrv_dirty_bitmap_granularity(job->sync_bitmap);
|
||||
clusters_per_iter = MAX((granularity / job->cluster_size), 1);
|
||||
dbi = bdrv_dirty_iter_new(job->sync_bitmap, 0);
|
||||
bdrv_dirty_iter_init(job->sync_bitmap, &hbi);
|
||||
|
||||
/* Find the next dirty sector(s) */
|
||||
while ((offset = bdrv_dirty_iter_next(dbi) * BDRV_SECTOR_SIZE) >= 0) {
|
||||
cluster = offset / job->cluster_size;
|
||||
while ((sector = hbitmap_iter_next(&hbi)) != -1) {
|
||||
cluster = sector / sectors_per_cluster;
|
||||
|
||||
/* Fake progress updates for any clusters we skipped */
|
||||
if (cluster != last_cluster + 1) {
|
||||
@@ -387,15 +344,15 @@ static int coroutine_fn backup_run_incremental(BackupBlockJob *job)
|
||||
for (end = cluster + clusters_per_iter; cluster < end; cluster++) {
|
||||
do {
|
||||
if (yield_and_check(job)) {
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
ret = backup_do_cow(job, cluster * job->cluster_size,
|
||||
job->cluster_size, &error_is_read,
|
||||
ret = backup_do_cow(job, cluster * sectors_per_cluster,
|
||||
sectors_per_cluster, &error_is_read,
|
||||
false);
|
||||
if ((ret < 0) &&
|
||||
backup_error_action(job, error_is_read, -ret) ==
|
||||
BLOCK_ERROR_ACTION_REPORT) {
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
} while (ret < 0);
|
||||
}
|
||||
@@ -403,8 +360,7 @@ static int coroutine_fn backup_run_incremental(BackupBlockJob *job)
|
||||
/* If the bitmap granularity is smaller than the backup granularity,
|
||||
* we need to advance the iterator pointer to the next cluster. */
|
||||
if (granularity < job->cluster_size) {
|
||||
bdrv_set_dirty_iter(dbi,
|
||||
cluster * job->cluster_size / BDRV_SECTOR_SIZE);
|
||||
bdrv_set_dirty_iter(&hbi, cluster * sectors_per_cluster);
|
||||
}
|
||||
|
||||
last_cluster = cluster - 1;
|
||||
@@ -416,8 +372,6 @@ static int coroutine_fn backup_run_incremental(BackupBlockJob *job)
|
||||
job->common.offset += ((end - last_cluster - 1) * job->cluster_size);
|
||||
}
|
||||
|
||||
out:
|
||||
bdrv_dirty_iter_free(dbi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -426,14 +380,18 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
BackupBlockJob *job = opaque;
|
||||
BackupCompleteData *data;
|
||||
BlockDriverState *bs = blk_bs(job->common.blk);
|
||||
int64_t offset;
|
||||
BlockBackend *target = job->target;
|
||||
int64_t start, end;
|
||||
int64_t sectors_per_cluster = cluster_size_sectors(job);
|
||||
int ret = 0;
|
||||
|
||||
QLIST_INIT(&job->inflight_reqs);
|
||||
qemu_co_rwlock_init(&job->flush_rwlock);
|
||||
|
||||
job->done_bitmap = bitmap_new(DIV_ROUND_UP(job->common.len,
|
||||
job->cluster_size));
|
||||
start = 0;
|
||||
end = DIV_ROUND_UP(job->common.len, job->cluster_size);
|
||||
|
||||
job->done_bitmap = bitmap_new(end);
|
||||
|
||||
job->before_write.notify = backup_before_write_notify;
|
||||
bdrv_add_before_write_notifier(bs, &job->before_write);
|
||||
@@ -448,23 +406,20 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
ret = backup_run_incremental(job);
|
||||
} else {
|
||||
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
||||
for (offset = 0; offset < job->common.len;
|
||||
offset += job->cluster_size) {
|
||||
for (; start < end; start++) {
|
||||
bool error_is_read;
|
||||
int alloced = 0;
|
||||
|
||||
if (yield_and_check(job)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
|
||||
int i;
|
||||
int64_t n;
|
||||
int i, n;
|
||||
int alloced = 0;
|
||||
|
||||
/* Check to see if these blocks are already in the
|
||||
* backing file. */
|
||||
|
||||
for (i = 0; i < job->cluster_size;) {
|
||||
for (i = 0; i < sectors_per_cluster;) {
|
||||
/* bdrv_is_allocated() only returns true/false based
|
||||
* on the first set of sectors it comes across that
|
||||
* are are all in the same state.
|
||||
@@ -472,11 +427,12 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
* backup cluster length. We end up copying more than
|
||||
* needed but at some point that is always the case. */
|
||||
alloced =
|
||||
bdrv_is_allocated(bs, offset + i,
|
||||
job->cluster_size - i, &n);
|
||||
bdrv_is_allocated(bs,
|
||||
start * sectors_per_cluster + i,
|
||||
sectors_per_cluster - i, &n);
|
||||
i += n;
|
||||
|
||||
if (alloced || n == 0) {
|
||||
if (alloced == 1 || n == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -488,12 +444,8 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
}
|
||||
}
|
||||
/* FULL sync mode we copy the whole drive. */
|
||||
if (alloced < 0) {
|
||||
ret = alloced;
|
||||
} else {
|
||||
ret = backup_do_cow(job, offset, job->cluster_size,
|
||||
&error_is_read, false);
|
||||
}
|
||||
ret = backup_do_cow(job, start * sectors_per_cluster,
|
||||
sectors_per_cluster, &error_is_read, false);
|
||||
if (ret < 0) {
|
||||
/* Depending on error action, fail now or retry cluster */
|
||||
BlockErrorAction action =
|
||||
@@ -501,7 +453,7 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
if (action == BLOCK_ERROR_ACTION_REPORT) {
|
||||
break;
|
||||
} else {
|
||||
offset -= job->cluster_size;
|
||||
start--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -515,30 +467,18 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
||||
g_free(job->done_bitmap);
|
||||
|
||||
bdrv_op_unblock_all(blk_bs(target), job->common.blocker);
|
||||
|
||||
data = g_malloc(sizeof(*data));
|
||||
data->ret = ret;
|
||||
block_job_defer_to_main_loop(&job->common, backup_complete, data);
|
||||
}
|
||||
|
||||
static const BlockJobDriver backup_job_driver = {
|
||||
.instance_size = sizeof(BackupBlockJob),
|
||||
.job_type = BLOCK_JOB_TYPE_BACKUP,
|
||||
.start = backup_run,
|
||||
.set_speed = backup_set_speed,
|
||||
.commit = backup_commit,
|
||||
.abort = backup_abort,
|
||||
.clean = backup_clean,
|
||||
.attached_aio_context = backup_attached_aio_context,
|
||||
.drain = backup_drain,
|
||||
};
|
||||
|
||||
BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
void backup_start(const char *job_id, BlockDriverState *bs,
|
||||
BlockDriverState *target, int64_t speed,
|
||||
MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap,
|
||||
bool compress,
|
||||
BlockdevOnError on_source_error,
|
||||
BlockdevOnError on_target_error,
|
||||
int creation_flags,
|
||||
BlockCompletionFunc *cb, void *opaque,
|
||||
BlockJobTxn *txn, Error **errp)
|
||||
{
|
||||
@@ -552,52 +492,46 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
|
||||
if (bs == target) {
|
||||
error_setg(errp, "Source and target cannot be the same");
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bdrv_is_inserted(bs)) {
|
||||
error_setg(errp, "Device is not inserted: %s",
|
||||
bdrv_get_device_name(bs));
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bdrv_is_inserted(target)) {
|
||||
error_setg(errp, "Device is not inserted: %s",
|
||||
bdrv_get_device_name(target));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (compress && target->drv->bdrv_co_pwritev_compressed == NULL) {
|
||||
error_setg(errp, "Compression is not supported for this drive %s",
|
||||
bdrv_get_device_name(target));
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
|
||||
if (!sync_bitmap) {
|
||||
error_setg(errp, "must provide a valid bitmap name for "
|
||||
"\"incremental\" sync mode");
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a new bitmap, and freeze/disable this one. */
|
||||
if (bdrv_dirty_bitmap_create_successor(bs, sync_bitmap, errp) < 0) {
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
} else if (sync_bitmap) {
|
||||
error_setg(errp,
|
||||
"a sync_bitmap was provided to backup_run, "
|
||||
"but received an incompatible sync_mode (%s)",
|
||||
MirrorSyncMode_lookup[sync_mode]);
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
len = bdrv_getlength(bs);
|
||||
@@ -607,46 +541,26 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* job->common.len is fixed, so we can't allow resize */
|
||||
job = block_job_create(job_id, &backup_job_driver, bs,
|
||||
BLK_PERM_CONSISTENT_READ,
|
||||
BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
|
||||
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD,
|
||||
speed, creation_flags, cb, opaque, errp);
|
||||
job = block_job_create(job_id, &backup_job_driver, bs, speed,
|
||||
cb, opaque, errp);
|
||||
if (!job) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* The target must match the source in size, so no resize here either */
|
||||
job->target = blk_new(BLK_PERM_WRITE,
|
||||
BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
|
||||
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD);
|
||||
ret = blk_insert_bs(job->target, target, errp);
|
||||
if (ret < 0) {
|
||||
goto error;
|
||||
}
|
||||
job->target = blk_new();
|
||||
blk_insert_bs(job->target, target);
|
||||
|
||||
job->on_source_error = on_source_error;
|
||||
job->on_target_error = on_target_error;
|
||||
job->sync_mode = sync_mode;
|
||||
job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_INCREMENTAL ?
|
||||
sync_bitmap : NULL;
|
||||
job->compress = compress;
|
||||
|
||||
/* If there is no backing file on the target, we cannot rely on COW if our
|
||||
* backup cluster size is smaller than the target cluster size. Even for
|
||||
* targets with a backing file, try to avoid COW if possible. */
|
||||
ret = bdrv_get_info(target, &bdi);
|
||||
if (ret == -ENOTSUP && !target->backing) {
|
||||
/* Cluster size is not defined */
|
||||
warn_report("The target block device doesn't provide "
|
||||
"information about the block size and it doesn't have a "
|
||||
"backing file. The default block size of %u bytes is "
|
||||
"used. If the actual block size of the target exceeds "
|
||||
"this default, the backup may be unusable",
|
||||
BACKUP_CLUSTER_SIZE_DEFAULT);
|
||||
job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
|
||||
} else if (ret < 0 && !target->backing) {
|
||||
if (ret < 0 && !target->backing) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Couldn't determine the cluster size of the target image, "
|
||||
"which has no backing file");
|
||||
@@ -660,22 +574,19 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
job->cluster_size = MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
|
||||
}
|
||||
|
||||
/* Required permissions are already taken with target's blk_new() */
|
||||
block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
|
||||
&error_abort);
|
||||
bdrv_op_block_all(target, job->common.blocker);
|
||||
job->common.len = len;
|
||||
job->common.co = qemu_coroutine_create(backup_run, job);
|
||||
block_job_txn_add_job(txn, &job->common);
|
||||
|
||||
return &job->common;
|
||||
qemu_coroutine_enter(job->common.co);
|
||||
return;
|
||||
|
||||
error:
|
||||
if (sync_bitmap) {
|
||||
bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
|
||||
}
|
||||
if (job) {
|
||||
backup_clean(&job->common);
|
||||
block_job_early_fail(&job->common);
|
||||
blk_unref(job->target);
|
||||
block_job_unref(&job->common);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
+101
-268
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* Block protocol for I/O error injection
|
||||
*
|
||||
* Copyright (C) 2016-2017 Red Hat, Inc.
|
||||
* Copyright (c) 2010 Kevin Wolf <kwolf@redhat.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@@ -31,18 +30,14 @@
|
||||
#include "qemu/module.h"
|
||||
#include "qapi/qmp/qbool.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qint.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "sysemu/qtest.h"
|
||||
|
||||
typedef struct BDRVBlkdebugState {
|
||||
int state;
|
||||
int new_state;
|
||||
uint64_t align;
|
||||
uint64_t max_transfer;
|
||||
uint64_t opt_write_zero;
|
||||
uint64_t max_write_zero;
|
||||
uint64_t opt_discard;
|
||||
uint64_t max_discard;
|
||||
int align;
|
||||
|
||||
/* For blkdebug_refresh_filename() */
|
||||
char *config_file;
|
||||
@@ -54,6 +49,7 @@ typedef struct BDRVBlkdebugState {
|
||||
|
||||
typedef struct BlkdebugAIOCB {
|
||||
BlockAIOCB common;
|
||||
QEMUBH *bh;
|
||||
int ret;
|
||||
} BlkdebugAIOCB;
|
||||
|
||||
@@ -63,6 +59,10 @@ typedef struct BlkdebugSuspendedReq {
|
||||
QLIST_ENTRY(BlkdebugSuspendedReq) next;
|
||||
} BlkdebugSuspendedReq;
|
||||
|
||||
static const AIOCBInfo blkdebug_aiocb_info = {
|
||||
.aiocb_size = sizeof(BlkdebugAIOCB),
|
||||
};
|
||||
|
||||
enum {
|
||||
ACTION_INJECT_ERROR,
|
||||
ACTION_SET_STATE,
|
||||
@@ -78,7 +78,7 @@ typedef struct BlkdebugRule {
|
||||
int error;
|
||||
int immediately;
|
||||
int once;
|
||||
int64_t offset;
|
||||
int64_t sector;
|
||||
} inject;
|
||||
struct {
|
||||
int new_state;
|
||||
@@ -175,7 +175,6 @@ static int add_rule(void *opaque, QemuOpts *opts, Error **errp)
|
||||
const char* event_name;
|
||||
BlkdebugEvent event;
|
||||
struct BlkdebugRule *rule;
|
||||
int64_t sector;
|
||||
|
||||
/* Find the right event for the rule */
|
||||
event_name = qemu_opt_get(opts, "event");
|
||||
@@ -202,9 +201,7 @@ static int add_rule(void *opaque, QemuOpts *opts, Error **errp)
|
||||
rule->options.inject.once = qemu_opt_get_bool(opts, "once", 0);
|
||||
rule->options.inject.immediately =
|
||||
qemu_opt_get_bool(opts, "immediately", 0);
|
||||
sector = qemu_opt_get_number(opts, "sector", -1);
|
||||
rule->options.inject.offset =
|
||||
sector == -1 ? -1 : sector * BDRV_SECTOR_SIZE;
|
||||
rule->options.inject.sector = qemu_opt_get_number(opts, "sector", -1);
|
||||
break;
|
||||
|
||||
case ACTION_SET_STATE:
|
||||
@@ -306,7 +303,7 @@ static void blkdebug_parse_filename(const char *filename, QDict *options,
|
||||
if (!strstart(filename, "blkdebug:", &filename)) {
|
||||
/* There was no prefix; therefore, all options have to be already
|
||||
present in the QDict (except for the filename) */
|
||||
qdict_put_str(options, "x-image", filename);
|
||||
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,7 +322,7 @@ static void blkdebug_parse_filename(const char *filename, QDict *options,
|
||||
|
||||
/* TODO Allow multi-level nesting and set file.filename here */
|
||||
filename = c + 1;
|
||||
qdict_put_str(options, "x-image", filename);
|
||||
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||
}
|
||||
|
||||
static QemuOptsList runtime_opts = {
|
||||
@@ -347,31 +344,6 @@ static QemuOptsList runtime_opts = {
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Required alignment in bytes",
|
||||
},
|
||||
{
|
||||
.name = "max-transfer",
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Maximum transfer size in bytes",
|
||||
},
|
||||
{
|
||||
.name = "opt-write-zero",
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Optimum write zero alignment in bytes",
|
||||
},
|
||||
{
|
||||
.name = "max-write-zero",
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Maximum write zero size in bytes",
|
||||
},
|
||||
{
|
||||
.name = "opt-discard",
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Optimum discard alignment in bytes",
|
||||
},
|
||||
{
|
||||
.name = "max-discard",
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Maximum discard size in bytes",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
@@ -382,8 +354,8 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
BDRVBlkdebugState *s = bs->opaque;
|
||||
QemuOpts *opts;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
uint64_t align;
|
||||
int ret;
|
||||
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
@@ -412,69 +384,21 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto out;
|
||||
}
|
||||
|
||||
bs->supported_write_flags = BDRV_REQ_FUA &
|
||||
bs->file->bs->supported_write_flags;
|
||||
bs->supported_zero_flags = (BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP) &
|
||||
bs->file->bs->supported_zero_flags;
|
||||
ret = -EINVAL;
|
||||
|
||||
/* Set alignment overrides */
|
||||
s->align = qemu_opt_get_size(opts, "align", 0);
|
||||
if (s->align && (s->align >= INT_MAX || !is_power_of_2(s->align))) {
|
||||
error_setg(errp, "Cannot meet constraints with align %" PRIu64,
|
||||
s->align);
|
||||
goto out;
|
||||
}
|
||||
align = MAX(s->align, bs->file->bs->bl.request_alignment);
|
||||
|
||||
s->max_transfer = qemu_opt_get_size(opts, "max-transfer", 0);
|
||||
if (s->max_transfer &&
|
||||
(s->max_transfer >= INT_MAX ||
|
||||
!QEMU_IS_ALIGNED(s->max_transfer, align))) {
|
||||
error_setg(errp, "Cannot meet constraints with max-transfer %" PRIu64,
|
||||
s->max_transfer);
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->opt_write_zero = qemu_opt_get_size(opts, "opt-write-zero", 0);
|
||||
if (s->opt_write_zero &&
|
||||
(s->opt_write_zero >= INT_MAX ||
|
||||
!QEMU_IS_ALIGNED(s->opt_write_zero, align))) {
|
||||
error_setg(errp, "Cannot meet constraints with opt-write-zero %" PRIu64,
|
||||
s->opt_write_zero);
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->max_write_zero = qemu_opt_get_size(opts, "max-write-zero", 0);
|
||||
if (s->max_write_zero &&
|
||||
(s->max_write_zero >= INT_MAX ||
|
||||
!QEMU_IS_ALIGNED(s->max_write_zero,
|
||||
MAX(s->opt_write_zero, align)))) {
|
||||
error_setg(errp, "Cannot meet constraints with max-write-zero %" PRIu64,
|
||||
s->max_write_zero);
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->opt_discard = qemu_opt_get_size(opts, "opt-discard", 0);
|
||||
if (s->opt_discard &&
|
||||
(s->opt_discard >= INT_MAX ||
|
||||
!QEMU_IS_ALIGNED(s->opt_discard, align))) {
|
||||
error_setg(errp, "Cannot meet constraints with opt-discard %" PRIu64,
|
||||
s->opt_discard);
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->max_discard = qemu_opt_get_size(opts, "max-discard", 0);
|
||||
if (s->max_discard &&
|
||||
(s->max_discard >= INT_MAX ||
|
||||
!QEMU_IS_ALIGNED(s->max_discard,
|
||||
MAX(s->opt_discard, align)))) {
|
||||
error_setg(errp, "Cannot meet constraints with max-discard %" PRIu64,
|
||||
s->max_discard);
|
||||
goto out;
|
||||
/* Set request alignment */
|
||||
align = qemu_opt_get_size(opts, "align", 0);
|
||||
if (align < INT_MAX && is_power_of_2(align)) {
|
||||
s->align = align;
|
||||
} else if (align) {
|
||||
error_setg(errp, "Invalid alignment");
|
||||
ret = -EINVAL;
|
||||
goto fail_unref;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
goto out;
|
||||
|
||||
fail_unref:
|
||||
bdrv_unref_child(bs, bs->file);
|
||||
out:
|
||||
if (ret < 0) {
|
||||
g_free(s->config_file);
|
||||
@@ -483,173 +407,107 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rule_check(BlockDriverState *bs, uint64_t offset, uint64_t bytes)
|
||||
static void error_callback_bh(void *opaque)
|
||||
{
|
||||
struct BlkdebugAIOCB *acb = opaque;
|
||||
qemu_bh_delete(acb->bh);
|
||||
acb->common.cb(acb->common.opaque, acb->ret);
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
|
||||
static BlockAIOCB *inject_error(BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb, void *opaque, BlkdebugRule *rule)
|
||||
{
|
||||
BDRVBlkdebugState *s = bs->opaque;
|
||||
BlkdebugRule *rule = NULL;
|
||||
int error;
|
||||
bool immediately;
|
||||
|
||||
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
||||
uint64_t inject_offset = rule->options.inject.offset;
|
||||
|
||||
if (inject_offset == -1 ||
|
||||
(bytes && inject_offset >= offset &&
|
||||
inject_offset < offset + bytes))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rule || !rule->options.inject.error) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
immediately = rule->options.inject.immediately;
|
||||
error = rule->options.inject.error;
|
||||
int error = rule->options.inject.error;
|
||||
struct BlkdebugAIOCB *acb;
|
||||
QEMUBH *bh;
|
||||
bool immediately = rule->options.inject.immediately;
|
||||
|
||||
if (rule->options.inject.once) {
|
||||
QSIMPLEQ_REMOVE(&s->active_rules, rule, BlkdebugRule, active_next);
|
||||
remove_rule(rule);
|
||||
}
|
||||
|
||||
if (!immediately) {
|
||||
aio_co_schedule(qemu_get_current_aio_context(), qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
if (immediately) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return -error;
|
||||
acb = qemu_aio_get(&blkdebug_aiocb_info, bs, cb, opaque);
|
||||
acb->ret = -error;
|
||||
|
||||
bh = aio_bh_new(bdrv_get_aio_context(bs), error_callback_bh, acb);
|
||||
acb->bh = bh;
|
||||
qemu_bh_schedule(bh);
|
||||
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
blkdebug_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
static BlockAIOCB *blkdebug_aio_readv(BlockDriverState *bs,
|
||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
int err;
|
||||
BDRVBlkdebugState *s = bs->opaque;
|
||||
BlkdebugRule *rule = NULL;
|
||||
|
||||
/* Sanity check block layer guarantees */
|
||||
assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment));
|
||||
assert(QEMU_IS_ALIGNED(bytes, bs->bl.request_alignment));
|
||||
if (bs->bl.max_transfer) {
|
||||
assert(bytes <= bs->bl.max_transfer);
|
||||
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
||||
if (rule->options.inject.sector == -1 ||
|
||||
(rule->options.inject.sector >= sector_num &&
|
||||
rule->options.inject.sector < sector_num + nb_sectors)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err = rule_check(bs, offset, bytes);
|
||||
if (err) {
|
||||
return err;
|
||||
if (rule && rule->options.inject.error) {
|
||||
return inject_error(bs, cb, opaque, rule);
|
||||
}
|
||||
|
||||
return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
|
||||
return bdrv_aio_readv(bs->file, sector_num, qiov, nb_sectors,
|
||||
cb, opaque);
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
blkdebug_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
static BlockAIOCB *blkdebug_aio_writev(BlockDriverState *bs,
|
||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
int err;
|
||||
BDRVBlkdebugState *s = bs->opaque;
|
||||
BlkdebugRule *rule = NULL;
|
||||
|
||||
/* Sanity check block layer guarantees */
|
||||
assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment));
|
||||
assert(QEMU_IS_ALIGNED(bytes, bs->bl.request_alignment));
|
||||
if (bs->bl.max_transfer) {
|
||||
assert(bytes <= bs->bl.max_transfer);
|
||||
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
||||
if (rule->options.inject.sector == -1 ||
|
||||
(rule->options.inject.sector >= sector_num &&
|
||||
rule->options.inject.sector < sector_num + nb_sectors)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err = rule_check(bs, offset, bytes);
|
||||
if (err) {
|
||||
return err;
|
||||
if (rule && rule->options.inject.error) {
|
||||
return inject_error(bs, cb, opaque, rule);
|
||||
}
|
||||
|
||||
return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
|
||||
return bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors,
|
||||
cb, opaque);
|
||||
}
|
||||
|
||||
static int blkdebug_co_flush(BlockDriverState *bs)
|
||||
static BlockAIOCB *blkdebug_aio_flush(BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
int err = rule_check(bs, 0, 0);
|
||||
BDRVBlkdebugState *s = bs->opaque;
|
||||
BlkdebugRule *rule = NULL;
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
||||
if (rule->options.inject.sector == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bdrv_co_flush(bs->file->bs);
|
||||
if (rule && rule->options.inject.error) {
|
||||
return inject_error(bs, cb, opaque, rule);
|
||||
}
|
||||
|
||||
return bdrv_aio_flush(bs->file->bs, cb, opaque);
|
||||
}
|
||||
|
||||
static int coroutine_fn blkdebug_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int bytes,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
uint32_t align = MAX(bs->bl.request_alignment,
|
||||
bs->bl.pwrite_zeroes_alignment);
|
||||
int err;
|
||||
|
||||
/* Only pass through requests that are larger than requested
|
||||
* preferred alignment (so that we test the fallback to writes on
|
||||
* unaligned portions), and check that the block layer never hands
|
||||
* us anything unaligned that crosses an alignment boundary. */
|
||||
if (bytes < align) {
|
||||
assert(QEMU_IS_ALIGNED(offset, align) ||
|
||||
QEMU_IS_ALIGNED(offset + bytes, align) ||
|
||||
DIV_ROUND_UP(offset, align) ==
|
||||
DIV_ROUND_UP(offset + bytes, align));
|
||||
return -ENOTSUP;
|
||||
}
|
||||
assert(QEMU_IS_ALIGNED(offset, align));
|
||||
assert(QEMU_IS_ALIGNED(bytes, align));
|
||||
if (bs->bl.max_pwrite_zeroes) {
|
||||
assert(bytes <= bs->bl.max_pwrite_zeroes);
|
||||
}
|
||||
|
||||
err = rule_check(bs, offset, bytes);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||
}
|
||||
|
||||
static int coroutine_fn blkdebug_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
uint32_t align = bs->bl.pdiscard_alignment;
|
||||
int err;
|
||||
|
||||
/* Only pass through requests that are larger than requested
|
||||
* minimum alignment, and ensure that unaligned requests do not
|
||||
* cross optimum discard boundaries. */
|
||||
if (bytes < bs->bl.request_alignment) {
|
||||
assert(QEMU_IS_ALIGNED(offset, align) ||
|
||||
QEMU_IS_ALIGNED(offset + bytes, align) ||
|
||||
DIV_ROUND_UP(offset, align) ==
|
||||
DIV_ROUND_UP(offset + bytes, align));
|
||||
return -ENOTSUP;
|
||||
}
|
||||
assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment));
|
||||
assert(QEMU_IS_ALIGNED(bytes, bs->bl.request_alignment));
|
||||
if (align && bytes >= align) {
|
||||
assert(QEMU_IS_ALIGNED(offset, align));
|
||||
assert(QEMU_IS_ALIGNED(bytes, align));
|
||||
}
|
||||
if (bs->bl.max_pdiscard) {
|
||||
assert(bytes <= bs->bl.max_pdiscard);
|
||||
}
|
||||
|
||||
err = rule_check(bs, offset, bytes);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return bdrv_co_pdiscard(bs->file->bs, offset, bytes);
|
||||
}
|
||||
|
||||
static int64_t coroutine_fn blkdebug_co_get_block_status(
|
||||
BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
|
||||
BlockDriverState **file)
|
||||
{
|
||||
*pnum = nb_sectors;
|
||||
*file = bs->file->bs;
|
||||
return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
|
||||
(sector_num << BDRV_SECTOR_BITS);
|
||||
}
|
||||
|
||||
static void blkdebug_close(BlockDriverState *bs)
|
||||
{
|
||||
@@ -821,10 +679,9 @@ static int64_t blkdebug_getlength(BlockDriverState *bs)
|
||||
return bdrv_getlength(bs->file->bs);
|
||||
}
|
||||
|
||||
static int blkdebug_truncate(BlockDriverState *bs, int64_t offset,
|
||||
PreallocMode prealloc, Error **errp)
|
||||
static int blkdebug_truncate(BlockDriverState *bs, int64_t offset)
|
||||
{
|
||||
return bdrv_truncate(bs->file, offset, prealloc, errp);
|
||||
return bdrv_truncate(bs->file->bs, offset);
|
||||
}
|
||||
|
||||
static void blkdebug_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
@@ -850,20 +707,16 @@ static void blkdebug_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
}
|
||||
|
||||
if (!force_json && bs->file->bs->exact_filename[0]) {
|
||||
int ret = snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"blkdebug:%s:%s", s->config_file ?: "",
|
||||
bs->file->bs->exact_filename);
|
||||
if (ret >= sizeof(bs->exact_filename)) {
|
||||
/* An overflow makes the filename unusable, so do not report any */
|
||||
bs->exact_filename[0] = 0;
|
||||
}
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"blkdebug:%s:%s", s->config_file ?: "",
|
||||
bs->file->bs->exact_filename);
|
||||
}
|
||||
|
||||
opts = qdict_new();
|
||||
qdict_put_str(opts, "driver", "blkdebug");
|
||||
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("blkdebug")));
|
||||
|
||||
QINCREF(bs->file->bs->full_open_options);
|
||||
qdict_put(opts, "image", bs->file->bs->full_open_options);
|
||||
qdict_put_obj(opts, "image", QOBJECT(bs->file->bs->full_open_options));
|
||||
|
||||
for (e = qdict_first(options); e; e = qdict_next(options, e)) {
|
||||
if (strcmp(qdict_entry_key(e), "x-image")) {
|
||||
@@ -882,21 +735,6 @@ static void blkdebug_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||
if (s->align) {
|
||||
bs->bl.request_alignment = s->align;
|
||||
}
|
||||
if (s->max_transfer) {
|
||||
bs->bl.max_transfer = s->max_transfer;
|
||||
}
|
||||
if (s->opt_write_zero) {
|
||||
bs->bl.pwrite_zeroes_alignment = s->opt_write_zero;
|
||||
}
|
||||
if (s->max_write_zero) {
|
||||
bs->bl.max_pwrite_zeroes = s->max_write_zero;
|
||||
}
|
||||
if (s->opt_discard) {
|
||||
bs->bl.pdiscard_alignment = s->opt_discard;
|
||||
}
|
||||
if (s->max_discard) {
|
||||
bs->bl.max_pdiscard = s->max_discard;
|
||||
}
|
||||
}
|
||||
|
||||
static int blkdebug_reopen_prepare(BDRVReopenState *reopen_state,
|
||||
@@ -914,19 +752,14 @@ static BlockDriver bdrv_blkdebug = {
|
||||
.bdrv_file_open = blkdebug_open,
|
||||
.bdrv_close = blkdebug_close,
|
||||
.bdrv_reopen_prepare = blkdebug_reopen_prepare,
|
||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
||||
|
||||
.bdrv_getlength = blkdebug_getlength,
|
||||
.bdrv_truncate = blkdebug_truncate,
|
||||
.bdrv_refresh_filename = blkdebug_refresh_filename,
|
||||
.bdrv_refresh_limits = blkdebug_refresh_limits,
|
||||
|
||||
.bdrv_co_preadv = blkdebug_co_preadv,
|
||||
.bdrv_co_pwritev = blkdebug_co_pwritev,
|
||||
.bdrv_co_flush_to_disk = blkdebug_co_flush,
|
||||
.bdrv_co_pwrite_zeroes = blkdebug_co_pwrite_zeroes,
|
||||
.bdrv_co_pdiscard = blkdebug_co_pdiscard,
|
||||
.bdrv_co_get_block_status = blkdebug_co_get_block_status,
|
||||
.bdrv_aio_readv = blkdebug_aio_readv,
|
||||
.bdrv_aio_writev = blkdebug_aio_writev,
|
||||
.bdrv_aio_flush = blkdebug_aio_flush,
|
||||
|
||||
.bdrv_debug_event = blkdebug_debug_event,
|
||||
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
|
||||
|
||||
+18
-11
@@ -20,6 +20,11 @@ typedef struct Request {
|
||||
QEMUBH *bh;
|
||||
} Request;
|
||||
|
||||
/* Next request id.
|
||||
This counter is global, because requests from different
|
||||
block devices should not get overlapping ids. */
|
||||
static uint64_t request_id;
|
||||
|
||||
static int blkreplay_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
@@ -37,6 +42,9 @@ static int blkreplay_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (ret < 0) {
|
||||
bdrv_unref_child(bs, bs->file);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -57,7 +65,7 @@ static int64_t blkreplay_getlength(BlockDriverState *bs)
|
||||
static void blkreplay_bh_cb(void *opaque)
|
||||
{
|
||||
Request *req = opaque;
|
||||
aio_co_wake(req->co);
|
||||
qemu_coroutine_enter(req->co);
|
||||
qemu_bh_delete(req->bh);
|
||||
g_free(req);
|
||||
}
|
||||
@@ -76,7 +84,7 @@ static void block_request_create(uint64_t reqid, BlockDriverState *bs,
|
||||
static int coroutine_fn blkreplay_co_preadv(BlockDriverState *bs,
|
||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
uint64_t reqid = blkreplay_next_id();
|
||||
uint64_t reqid = request_id++;
|
||||
int ret = bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
|
||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
@@ -87,7 +95,7 @@ static int coroutine_fn blkreplay_co_preadv(BlockDriverState *bs,
|
||||
static int coroutine_fn blkreplay_co_pwritev(BlockDriverState *bs,
|
||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
uint64_t reqid = blkreplay_next_id();
|
||||
uint64_t reqid = request_id++;
|
||||
int ret = bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
|
||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
@@ -96,10 +104,10 @@ static int coroutine_fn blkreplay_co_pwritev(BlockDriverState *bs,
|
||||
}
|
||||
|
||||
static int coroutine_fn blkreplay_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
int64_t offset, int count, BdrvRequestFlags flags)
|
||||
{
|
||||
uint64_t reqid = blkreplay_next_id();
|
||||
int ret = bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||
uint64_t reqid = request_id++;
|
||||
int ret = bdrv_co_pwrite_zeroes(bs->file, offset, count, flags);
|
||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
|
||||
@@ -107,10 +115,10 @@ static int coroutine_fn blkreplay_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
}
|
||||
|
||||
static int coroutine_fn blkreplay_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int bytes)
|
||||
int64_t offset, int count)
|
||||
{
|
||||
uint64_t reqid = blkreplay_next_id();
|
||||
int ret = bdrv_co_pdiscard(bs->file->bs, offset, bytes);
|
||||
uint64_t reqid = request_id++;
|
||||
int ret = bdrv_co_pdiscard(bs->file->bs, offset, count);
|
||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
|
||||
@@ -119,7 +127,7 @@ static int coroutine_fn blkreplay_co_pdiscard(BlockDriverState *bs,
|
||||
|
||||
static int coroutine_fn blkreplay_co_flush(BlockDriverState *bs)
|
||||
{
|
||||
uint64_t reqid = blkreplay_next_id();
|
||||
uint64_t reqid = request_id++;
|
||||
int ret = bdrv_co_flush(bs->file->bs);
|
||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
@@ -134,7 +142,6 @@ static BlockDriver bdrv_blkreplay = {
|
||||
|
||||
.bdrv_file_open = blkreplay_open,
|
||||
.bdrv_close = blkreplay_close,
|
||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
||||
.bdrv_getlength = blkreplay_getlength,
|
||||
|
||||
.bdrv_co_preadv = blkreplay_co_preadv,
|
||||
|
||||
+123
-111
@@ -19,36 +19,39 @@ typedef struct {
|
||||
BdrvChild *test_file;
|
||||
} BDRVBlkverifyState;
|
||||
|
||||
typedef struct BlkverifyRequest {
|
||||
Coroutine *co;
|
||||
BlockDriverState *bs;
|
||||
typedef struct BlkverifyAIOCB BlkverifyAIOCB;
|
||||
struct BlkverifyAIOCB {
|
||||
BlockAIOCB common;
|
||||
QEMUBH *bh;
|
||||
|
||||
/* Request metadata */
|
||||
bool is_write;
|
||||
uint64_t offset;
|
||||
uint64_t bytes;
|
||||
int flags;
|
||||
|
||||
int (*request_fn)(BdrvChild *, int64_t, unsigned int, QEMUIOVector *,
|
||||
BdrvRequestFlags);
|
||||
|
||||
int ret; /* test image result */
|
||||
int raw_ret; /* raw image result */
|
||||
int64_t sector_num;
|
||||
int nb_sectors;
|
||||
|
||||
int ret; /* first completed request's result */
|
||||
unsigned int done; /* completion counter */
|
||||
|
||||
QEMUIOVector *qiov; /* user I/O vector */
|
||||
QEMUIOVector *raw_qiov; /* cloned I/O vector for raw file */
|
||||
} BlkverifyRequest;
|
||||
QEMUIOVector raw_qiov; /* cloned I/O vector for raw file */
|
||||
void *buf; /* buffer for raw file I/O */
|
||||
|
||||
static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyRequest *r,
|
||||
void (*verify)(BlkverifyAIOCB *acb);
|
||||
};
|
||||
|
||||
static const AIOCBInfo blkverify_aiocb_info = {
|
||||
.aiocb_size = sizeof(BlkverifyAIOCB),
|
||||
};
|
||||
|
||||
static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyAIOCB *acb,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "blkverify: %s offset=%" PRId64 " bytes=%" PRId64 " ",
|
||||
r->is_write ? "write" : "read", r->offset, r->bytes);
|
||||
fprintf(stderr, "blkverify: %s sector_num=%" PRId64 " nb_sectors=%d ",
|
||||
acb->is_write ? "write" : "read", acb->sector_num,
|
||||
acb->nb_sectors);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n");
|
||||
va_end(ap);
|
||||
@@ -67,7 +70,7 @@ static void blkverify_parse_filename(const char *filename, QDict *options,
|
||||
if (!strstart(filename, "blkverify:", &filename)) {
|
||||
/* There was no prefix; therefore, all options have to be already
|
||||
present in the QDict (except for the filename) */
|
||||
qdict_put_str(options, "x-image", filename);
|
||||
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,7 +87,7 @@ static void blkverify_parse_filename(const char *filename, QDict *options,
|
||||
|
||||
/* TODO Allow multi-level nesting and set file.filename here */
|
||||
filename = c + 1;
|
||||
qdict_put_str(options, "x-image", filename);
|
||||
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||
}
|
||||
|
||||
static QemuOptsList runtime_opts = {
|
||||
@@ -142,6 +145,9 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (ret < 0) {
|
||||
bdrv_unref_child(bs, bs->file);
|
||||
}
|
||||
qemu_opts_del(opts);
|
||||
return ret;
|
||||
}
|
||||
@@ -161,106 +167,116 @@ static int64_t blkverify_getlength(BlockDriverState *bs)
|
||||
return bdrv_getlength(s->test_file->bs);
|
||||
}
|
||||
|
||||
static void coroutine_fn blkverify_do_test_req(void *opaque)
|
||||
static BlkverifyAIOCB *blkverify_aio_get(BlockDriverState *bs, bool is_write,
|
||||
int64_t sector_num, QEMUIOVector *qiov,
|
||||
int nb_sectors,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
BlkverifyRequest *r = opaque;
|
||||
BDRVBlkverifyState *s = r->bs->opaque;
|
||||
BlkverifyAIOCB *acb = qemu_aio_get(&blkverify_aiocb_info, bs, cb, opaque);
|
||||
|
||||
r->ret = r->request_fn(s->test_file, r->offset, r->bytes, r->qiov,
|
||||
r->flags);
|
||||
r->done++;
|
||||
qemu_coroutine_enter_if_inactive(r->co);
|
||||
acb->bh = NULL;
|
||||
acb->is_write = is_write;
|
||||
acb->sector_num = sector_num;
|
||||
acb->nb_sectors = nb_sectors;
|
||||
acb->ret = -EINPROGRESS;
|
||||
acb->done = 0;
|
||||
acb->qiov = qiov;
|
||||
acb->buf = NULL;
|
||||
acb->verify = NULL;
|
||||
return acb;
|
||||
}
|
||||
|
||||
static void coroutine_fn blkverify_do_raw_req(void *opaque)
|
||||
static void blkverify_aio_bh(void *opaque)
|
||||
{
|
||||
BlkverifyRequest *r = opaque;
|
||||
BlkverifyAIOCB *acb = opaque;
|
||||
|
||||
r->raw_ret = r->request_fn(r->bs->file, r->offset, r->bytes, r->raw_qiov,
|
||||
r->flags);
|
||||
r->done++;
|
||||
qemu_coroutine_enter_if_inactive(r->co);
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
blkverify_co_prwv(BlockDriverState *bs, BlkverifyRequest *r, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, QEMUIOVector *raw_qiov,
|
||||
int flags, bool is_write)
|
||||
{
|
||||
Coroutine *co_a, *co_b;
|
||||
|
||||
*r = (BlkverifyRequest) {
|
||||
.co = qemu_coroutine_self(),
|
||||
.bs = bs,
|
||||
.offset = offset,
|
||||
.bytes = bytes,
|
||||
.qiov = qiov,
|
||||
.raw_qiov = raw_qiov,
|
||||
.flags = flags,
|
||||
.is_write = is_write,
|
||||
.request_fn = is_write ? bdrv_co_pwritev : bdrv_co_preadv,
|
||||
};
|
||||
|
||||
co_a = qemu_coroutine_create(blkverify_do_test_req, r);
|
||||
co_b = qemu_coroutine_create(blkverify_do_raw_req, r);
|
||||
|
||||
qemu_coroutine_enter(co_a);
|
||||
qemu_coroutine_enter(co_b);
|
||||
|
||||
while (r->done < 2) {
|
||||
qemu_coroutine_yield();
|
||||
qemu_bh_delete(acb->bh);
|
||||
if (acb->buf) {
|
||||
qemu_iovec_destroy(&acb->raw_qiov);
|
||||
qemu_vfree(acb->buf);
|
||||
}
|
||||
|
||||
if (r->ret != r->raw_ret) {
|
||||
blkverify_err(r, "return value mismatch %d != %d", r->ret, r->raw_ret);
|
||||
}
|
||||
|
||||
return r->ret;
|
||||
acb->common.cb(acb->common.opaque, acb->ret);
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
blkverify_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
static void blkverify_aio_cb(void *opaque, int ret)
|
||||
{
|
||||
BlkverifyRequest r;
|
||||
QEMUIOVector raw_qiov;
|
||||
void *buf;
|
||||
ssize_t cmp_offset;
|
||||
int ret;
|
||||
BlkverifyAIOCB *acb = opaque;
|
||||
|
||||
buf = qemu_blockalign(bs->file->bs, qiov->size);
|
||||
qemu_iovec_init(&raw_qiov, qiov->niov);
|
||||
qemu_iovec_clone(&raw_qiov, qiov, buf);
|
||||
switch (++acb->done) {
|
||||
case 1:
|
||||
acb->ret = ret;
|
||||
break;
|
||||
|
||||
ret = blkverify_co_prwv(bs, &r, offset, bytes, qiov, &raw_qiov, flags,
|
||||
false);
|
||||
case 2:
|
||||
if (acb->ret != ret) {
|
||||
blkverify_err(acb, "return value mismatch %d != %d", acb->ret, ret);
|
||||
}
|
||||
|
||||
cmp_offset = qemu_iovec_compare(qiov, &raw_qiov);
|
||||
if (cmp_offset != -1) {
|
||||
blkverify_err(&r, "contents mismatch at offset %" PRId64,
|
||||
offset + cmp_offset);
|
||||
if (acb->verify) {
|
||||
acb->verify(acb);
|
||||
}
|
||||
|
||||
acb->bh = aio_bh_new(bdrv_get_aio_context(acb->common.bs),
|
||||
blkverify_aio_bh, acb);
|
||||
qemu_bh_schedule(acb->bh);
|
||||
break;
|
||||
}
|
||||
|
||||
qemu_iovec_destroy(&raw_qiov);
|
||||
qemu_vfree(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
blkverify_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
static void blkverify_verify_readv(BlkverifyAIOCB *acb)
|
||||
{
|
||||
BlkverifyRequest r;
|
||||
return blkverify_co_prwv(bs, &r, offset, bytes, qiov, qiov, flags, true);
|
||||
ssize_t offset = qemu_iovec_compare(acb->qiov, &acb->raw_qiov);
|
||||
if (offset != -1) {
|
||||
blkverify_err(acb, "contents mismatch in sector %" PRId64,
|
||||
acb->sector_num + (int64_t)(offset / BDRV_SECTOR_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
static int blkverify_co_flush(BlockDriverState *bs)
|
||||
static BlockAIOCB *blkverify_aio_readv(BlockDriverState *bs,
|
||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
BDRVBlkverifyState *s = bs->opaque;
|
||||
BlkverifyAIOCB *acb = blkverify_aio_get(bs, false, sector_num, qiov,
|
||||
nb_sectors, cb, opaque);
|
||||
|
||||
acb->verify = blkverify_verify_readv;
|
||||
acb->buf = qemu_blockalign(bs->file->bs, qiov->size);
|
||||
qemu_iovec_init(&acb->raw_qiov, acb->qiov->niov);
|
||||
qemu_iovec_clone(&acb->raw_qiov, qiov, acb->buf);
|
||||
|
||||
bdrv_aio_readv(s->test_file, sector_num, qiov, nb_sectors,
|
||||
blkverify_aio_cb, acb);
|
||||
bdrv_aio_readv(bs->file, sector_num, &acb->raw_qiov, nb_sectors,
|
||||
blkverify_aio_cb, acb);
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static BlockAIOCB *blkverify_aio_writev(BlockDriverState *bs,
|
||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
BDRVBlkverifyState *s = bs->opaque;
|
||||
BlkverifyAIOCB *acb = blkverify_aio_get(bs, true, sector_num, qiov,
|
||||
nb_sectors, cb, opaque);
|
||||
|
||||
bdrv_aio_writev(s->test_file, sector_num, qiov, nb_sectors,
|
||||
blkverify_aio_cb, acb);
|
||||
bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors,
|
||||
blkverify_aio_cb, acb);
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static BlockAIOCB *blkverify_aio_flush(BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
BDRVBlkverifyState *s = bs->opaque;
|
||||
|
||||
/* Only flush test file, the raw file is not important */
|
||||
return bdrv_co_flush(s->test_file->bs);
|
||||
return bdrv_aio_flush(s->test_file->bs, cb, opaque);
|
||||
}
|
||||
|
||||
static bool blkverify_recurse_is_first_non_filter(BlockDriverState *bs,
|
||||
@@ -288,12 +304,13 @@ static void blkverify_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
&& s->test_file->bs->full_open_options)
|
||||
{
|
||||
QDict *opts = qdict_new();
|
||||
qdict_put_str(opts, "driver", "blkverify");
|
||||
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("blkverify")));
|
||||
|
||||
QINCREF(bs->file->bs->full_open_options);
|
||||
qdict_put(opts, "raw", bs->file->bs->full_open_options);
|
||||
qdict_put_obj(opts, "raw", QOBJECT(bs->file->bs->full_open_options));
|
||||
QINCREF(s->test_file->bs->full_open_options);
|
||||
qdict_put(opts, "test", s->test_file->bs->full_open_options);
|
||||
qdict_put_obj(opts, "test",
|
||||
QOBJECT(s->test_file->bs->full_open_options));
|
||||
|
||||
bs->full_open_options = opts;
|
||||
}
|
||||
@@ -301,14 +318,10 @@ static void blkverify_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
if (bs->file->bs->exact_filename[0]
|
||||
&& s->test_file->bs->exact_filename[0])
|
||||
{
|
||||
int ret = snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"blkverify:%s:%s",
|
||||
bs->file->bs->exact_filename,
|
||||
s->test_file->bs->exact_filename);
|
||||
if (ret >= sizeof(bs->exact_filename)) {
|
||||
/* An overflow makes the filename unusable, so do not report any */
|
||||
bs->exact_filename[0] = 0;
|
||||
}
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"blkverify:%s:%s",
|
||||
bs->file->bs->exact_filename,
|
||||
s->test_file->bs->exact_filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,13 +333,12 @@ static BlockDriver bdrv_blkverify = {
|
||||
.bdrv_parse_filename = blkverify_parse_filename,
|
||||
.bdrv_file_open = blkverify_open,
|
||||
.bdrv_close = blkverify_close,
|
||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
||||
.bdrv_getlength = blkverify_getlength,
|
||||
.bdrv_refresh_filename = blkverify_refresh_filename,
|
||||
|
||||
.bdrv_co_preadv = blkverify_co_preadv,
|
||||
.bdrv_co_pwritev = blkverify_co_pwritev,
|
||||
.bdrv_co_flush = blkverify_co_flush,
|
||||
.bdrv_aio_readv = blkverify_aio_readv,
|
||||
.bdrv_aio_writev = blkverify_aio_writev,
|
||||
.bdrv_aio_flush = blkverify_aio_flush,
|
||||
|
||||
.is_filter = true,
|
||||
.bdrv_recurse_is_first_non_filter = blkverify_recurse_is_first_non_filter,
|
||||
|
||||
+124
-473
@@ -20,7 +20,6 @@
|
||||
#include "qapi-event.h"
|
||||
#include "qemu/id.h"
|
||||
#include "trace.h"
|
||||
#include "migration/misc.h"
|
||||
|
||||
/* Number of coroutines to reserve per attached device model */
|
||||
#define COROUTINE_POOL_RESERVATION 64
|
||||
@@ -39,7 +38,6 @@ struct BlockBackend {
|
||||
BlockBackendPublic public;
|
||||
|
||||
void *dev; /* attached device model, if any */
|
||||
bool legacy_dev; /* true if dev is not a DeviceState */
|
||||
/* TODO change to DeviceState when all users are qdevified */
|
||||
const BlockDevOps *dev_ops;
|
||||
void *dev_opaque;
|
||||
@@ -60,21 +58,14 @@ struct BlockBackend {
|
||||
bool iostatus_enabled;
|
||||
BlockDeviceIoStatus iostatus;
|
||||
|
||||
uint64_t perm;
|
||||
uint64_t shared_perm;
|
||||
bool disable_perm;
|
||||
|
||||
bool allow_write_beyond_eof;
|
||||
|
||||
NotifierList remove_bs_notifiers, insert_bs_notifiers;
|
||||
|
||||
int quiesce_counter;
|
||||
VMChangeStateEntry *vmsh;
|
||||
bool force_allow_inactivate;
|
||||
};
|
||||
|
||||
typedef struct BlockBackendAIOCB {
|
||||
BlockAIOCB common;
|
||||
QEMUBH *bh;
|
||||
BlockBackend *blk;
|
||||
int ret;
|
||||
} BlockBackendAIOCB;
|
||||
@@ -108,175 +99,37 @@ static void blk_root_drained_end(BdrvChild *child);
|
||||
static void blk_root_change_media(BdrvChild *child, bool load);
|
||||
static void blk_root_resize(BdrvChild *child);
|
||||
|
||||
static char *blk_root_get_parent_desc(BdrvChild *child)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
char *dev_id;
|
||||
|
||||
if (blk->name) {
|
||||
return g_strdup(blk->name);
|
||||
}
|
||||
|
||||
dev_id = blk_get_attached_dev_id(blk);
|
||||
if (*dev_id) {
|
||||
return dev_id;
|
||||
} else {
|
||||
/* TODO Callback into the BB owner for something more detailed */
|
||||
g_free(dev_id);
|
||||
return g_strdup("a block device");
|
||||
}
|
||||
}
|
||||
|
||||
static const char *blk_root_get_name(BdrvChild *child)
|
||||
{
|
||||
return blk_name(child->opaque);
|
||||
}
|
||||
|
||||
static void blk_vm_state_changed(void *opaque, int running, RunState state)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
BlockBackend *blk = opaque;
|
||||
|
||||
if (state == RUN_STATE_INMIGRATE) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_del_vm_change_state_handler(blk->vmsh);
|
||||
blk->vmsh = NULL;
|
||||
blk_set_perm(blk, blk->perm, blk->shared_perm, &local_err);
|
||||
if (local_err) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Notifies the user of the BlockBackend that migration has completed. qdev
|
||||
* devices can tighten their permissions in response (specifically revoke
|
||||
* shared write permissions that we needed for storage migration).
|
||||
*
|
||||
* If an error is returned, the VM cannot be allowed to be resumed.
|
||||
*/
|
||||
static void blk_root_activate(BdrvChild *child, Error **errp)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (!blk->disable_perm) {
|
||||
return;
|
||||
}
|
||||
|
||||
blk->disable_perm = false;
|
||||
|
||||
blk_set_perm(blk, blk->perm, BLK_PERM_ALL, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
blk->disable_perm = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
||||
/* Activation can happen when migration process is still active, for
|
||||
* example when nbd_server_add is called during non-shared storage
|
||||
* migration. Defer the shared_perm update to migration completion. */
|
||||
if (!blk->vmsh) {
|
||||
blk->vmsh = qemu_add_vm_change_state_handler(blk_vm_state_changed,
|
||||
blk);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
blk_set_perm(blk, blk->perm, blk->shared_perm, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
blk->disable_perm = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void blk_set_force_allow_inactivate(BlockBackend *blk)
|
||||
{
|
||||
blk->force_allow_inactivate = true;
|
||||
}
|
||||
|
||||
static bool blk_can_inactivate(BlockBackend *blk)
|
||||
{
|
||||
/* If it is a guest device, inactivate is ok. */
|
||||
if (blk->dev || blk_name(blk)[0]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Inactivating means no more writes to the image can be done,
|
||||
* even if those writes would be changes invisible to the
|
||||
* guest. For block job BBs that satisfy this, we can just allow
|
||||
* it. This is the case for mirror job source, which is required
|
||||
* by libvirt non-shared block migration. */
|
||||
if (!(blk->perm & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return blk->force_allow_inactivate;
|
||||
}
|
||||
|
||||
static int blk_root_inactivate(BdrvChild *child)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
|
||||
if (blk->disable_perm) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!blk_can_inactivate(blk)) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
blk->disable_perm = true;
|
||||
if (blk->root) {
|
||||
bdrv_child_try_set_perm(blk->root, 0, BLK_PERM_ALL, &error_abort);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const BdrvChildRole child_root = {
|
||||
.inherit_options = blk_root_inherit_options,
|
||||
|
||||
.change_media = blk_root_change_media,
|
||||
.resize = blk_root_resize,
|
||||
.get_name = blk_root_get_name,
|
||||
.get_parent_desc = blk_root_get_parent_desc,
|
||||
|
||||
.drained_begin = blk_root_drained_begin,
|
||||
.drained_end = blk_root_drained_end,
|
||||
|
||||
.activate = blk_root_activate,
|
||||
.inactivate = blk_root_inactivate,
|
||||
};
|
||||
|
||||
/*
|
||||
* Create a new BlockBackend with a reference count of one.
|
||||
*
|
||||
* @perm is a bitmasks of BLK_PERM_* constants which describes the permissions
|
||||
* to request for a block driver node that is attached to this BlockBackend.
|
||||
* @shared_perm is a bitmask which describes which permissions may be granted
|
||||
* to other users of the attached node.
|
||||
* Both sets of permissions can be changed later using blk_set_perm().
|
||||
*
|
||||
* Store an error through @errp on failure, unless it's null.
|
||||
* Return the new BlockBackend on success, null on failure.
|
||||
*/
|
||||
BlockBackend *blk_new(uint64_t perm, uint64_t shared_perm)
|
||||
BlockBackend *blk_new(void)
|
||||
{
|
||||
BlockBackend *blk;
|
||||
|
||||
blk = g_new0(BlockBackend, 1);
|
||||
blk->refcnt = 1;
|
||||
blk->perm = perm;
|
||||
blk->shared_perm = shared_perm;
|
||||
blk_set_enable_write_cache(blk, true);
|
||||
|
||||
qemu_co_mutex_init(&blk->public.throttled_reqs_lock);
|
||||
qemu_co_queue_init(&blk->public.throttled_reqs[0]);
|
||||
qemu_co_queue_init(&blk->public.throttled_reqs[1]);
|
||||
block_acct_init(&blk->stats);
|
||||
|
||||
notifier_list_init(&blk->remove_bs_notifiers);
|
||||
notifier_list_init(&blk->insert_bs_notifiers);
|
||||
@@ -302,38 +155,15 @@ BlockBackend *blk_new_open(const char *filename, const char *reference,
|
||||
{
|
||||
BlockBackend *blk;
|
||||
BlockDriverState *bs;
|
||||
uint64_t perm;
|
||||
|
||||
/* blk_new_open() is mainly used in .bdrv_create implementations and the
|
||||
* tools where sharing isn't a concern because the BDS stays private, so we
|
||||
* just request permission according to the flags.
|
||||
*
|
||||
* The exceptions are xen_disk and blockdev_init(); in these cases, the
|
||||
* caller of blk_new_open() doesn't make use of the permissions, but they
|
||||
* shouldn't hurt either. We can still share everything here because the
|
||||
* guest devices will add their own blockers if they can't share. */
|
||||
perm = BLK_PERM_CONSISTENT_READ;
|
||||
if (flags & BDRV_O_RDWR) {
|
||||
perm |= BLK_PERM_WRITE;
|
||||
}
|
||||
if (flags & BDRV_O_RESIZE) {
|
||||
perm |= BLK_PERM_RESIZE;
|
||||
}
|
||||
|
||||
blk = blk_new(perm, BLK_PERM_ALL);
|
||||
blk = blk_new();
|
||||
bs = bdrv_open(filename, reference, options, flags, errp);
|
||||
if (!bs) {
|
||||
blk_unref(blk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
blk->root = bdrv_root_attach_child(bs, "root", &child_root,
|
||||
perm, BLK_PERM_ALL, blk, errp);
|
||||
if (!blk->root) {
|
||||
bdrv_unref(bs);
|
||||
blk_unref(blk);
|
||||
return NULL;
|
||||
}
|
||||
blk->root = bdrv_root_attach_child(bs, "root", &child_root, blk);
|
||||
|
||||
return blk;
|
||||
}
|
||||
@@ -343,16 +173,9 @@ static void blk_delete(BlockBackend *blk)
|
||||
assert(!blk->refcnt);
|
||||
assert(!blk->name);
|
||||
assert(!blk->dev);
|
||||
if (blk->public.throttle_state) {
|
||||
blk_io_limits_disable(blk);
|
||||
}
|
||||
if (blk->root) {
|
||||
blk_remove_bs(blk);
|
||||
}
|
||||
if (blk->vmsh) {
|
||||
qemu_del_vm_change_state_handler(blk->vmsh);
|
||||
blk->vmsh = NULL;
|
||||
}
|
||||
assert(QLIST_EMPTY(&blk->remove_bs_notifiers.notifiers));
|
||||
assert(QLIST_EMPTY(&blk->insert_bs_notifiers.notifiers));
|
||||
QTAILQ_REMOVE(&block_backends, blk, link);
|
||||
@@ -404,7 +227,7 @@ void blk_unref(BlockBackend *blk)
|
||||
* Behaves similarly to blk_next() but iterates over all BlockBackends, even the
|
||||
* ones which are hidden (i.e. are not referenced by the monitor).
|
||||
*/
|
||||
BlockBackend *blk_all_next(BlockBackend *blk)
|
||||
static BlockBackend *blk_all_next(BlockBackend *blk)
|
||||
{
|
||||
return blk ? QTAILQ_NEXT(blk, link)
|
||||
: QTAILQ_FIRST(&block_backends);
|
||||
@@ -536,7 +359,7 @@ void monitor_remove_blk(BlockBackend *blk)
|
||||
* Return @blk's name, a non-null string.
|
||||
* Returns an empty string iff @blk is not referenced by the monitor.
|
||||
*/
|
||||
const char *blk_name(const BlockBackend *blk)
|
||||
const char *blk_name(BlockBackend *blk)
|
||||
{
|
||||
return blk->name ?: "";
|
||||
}
|
||||
@@ -586,22 +409,6 @@ bool bdrv_has_blk(BlockDriverState *bs)
|
||||
return bdrv_first_blk(bs) != NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if @bs has only BlockBackends as parents.
|
||||
*/
|
||||
bool bdrv_is_root_node(BlockDriverState *bs)
|
||||
{
|
||||
BdrvChild *c;
|
||||
|
||||
QLIST_FOREACH(c, &bs->parents, next_parent) {
|
||||
if (c->role != &child_root) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return @blk's DriveInfo if any, else null.
|
||||
*/
|
||||
@@ -672,79 +479,32 @@ void blk_remove_bs(BlockBackend *blk)
|
||||
/*
|
||||
* Associates a new BlockDriverState with @blk.
|
||||
*/
|
||||
int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp)
|
||||
void blk_insert_bs(BlockBackend *blk, BlockDriverState *bs)
|
||||
{
|
||||
blk->root = bdrv_root_attach_child(bs, "root", &child_root,
|
||||
blk->perm, blk->shared_perm, blk, errp);
|
||||
if (blk->root == NULL) {
|
||||
return -EPERM;
|
||||
}
|
||||
bdrv_ref(bs);
|
||||
blk->root = bdrv_root_attach_child(bs, "root", &child_root, blk);
|
||||
|
||||
notifier_list_notify(&blk->insert_bs_notifiers, blk);
|
||||
if (blk->public.throttle_state) {
|
||||
throttle_timers_attach_aio_context(
|
||||
&blk->public.throttle_timers, bdrv_get_aio_context(bs));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the permission bitmasks that the user of the BlockBackend needs.
|
||||
*/
|
||||
int blk_set_perm(BlockBackend *blk, uint64_t perm, uint64_t shared_perm,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (blk->root && !blk->disable_perm) {
|
||||
ret = bdrv_child_try_set_perm(blk->root, perm, shared_perm, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
blk->perm = perm;
|
||||
blk->shared_perm = shared_perm;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void blk_get_perm(BlockBackend *blk, uint64_t *perm, uint64_t *shared_perm)
|
||||
{
|
||||
*perm = blk->perm;
|
||||
*shared_perm = blk->shared_perm;
|
||||
}
|
||||
|
||||
static int blk_do_attach_dev(BlockBackend *blk, void *dev)
|
||||
{
|
||||
if (blk->dev) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* While migration is still incoming, we don't need to apply the
|
||||
* permissions of guest device BlockBackends. We might still have a block
|
||||
* job or NBD server writing to the image for storage migration. */
|
||||
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
||||
blk->disable_perm = true;
|
||||
}
|
||||
|
||||
blk_ref(blk);
|
||||
blk->dev = dev;
|
||||
blk->legacy_dev = false;
|
||||
blk_iostatus_reset(blk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach device model @dev to @blk.
|
||||
* Return 0 on success, -EBUSY when a device model is attached already.
|
||||
*/
|
||||
int blk_attach_dev(BlockBackend *blk, DeviceState *dev)
|
||||
int blk_attach_dev(BlockBackend *blk, void *dev)
|
||||
/* TODO change to DeviceState *dev when all users are qdevified */
|
||||
{
|
||||
return blk_do_attach_dev(blk, dev);
|
||||
if (blk->dev) {
|
||||
return -EBUSY;
|
||||
}
|
||||
blk_ref(blk);
|
||||
blk->dev = dev;
|
||||
blk_iostatus_reset(blk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -752,12 +512,11 @@ int blk_attach_dev(BlockBackend *blk, DeviceState *dev)
|
||||
* @blk must not have a device model attached already.
|
||||
* TODO qdevified devices don't use this, remove when devices are qdevified
|
||||
*/
|
||||
void blk_attach_dev_legacy(BlockBackend *blk, void *dev)
|
||||
void blk_attach_dev_nofail(BlockBackend *blk, void *dev)
|
||||
{
|
||||
if (blk_do_attach_dev(blk, dev) < 0) {
|
||||
if (blk_attach_dev(blk, dev) < 0) {
|
||||
abort();
|
||||
}
|
||||
blk->legacy_dev = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -772,7 +531,6 @@ void blk_detach_dev(BlockBackend *blk, void *dev)
|
||||
blk->dev_ops = NULL;
|
||||
blk->dev_opaque = NULL;
|
||||
blk->guest_block_size = 512;
|
||||
blk_set_perm(blk, 0, BLK_PERM_ALL, &error_abort);
|
||||
blk_unref(blk);
|
||||
}
|
||||
|
||||
@@ -785,42 +543,6 @@ void *blk_get_attached_dev(BlockBackend *blk)
|
||||
return blk->dev;
|
||||
}
|
||||
|
||||
/* Return the qdev ID, or if no ID is assigned the QOM path, of the block
|
||||
* device attached to the BlockBackend. */
|
||||
char *blk_get_attached_dev_id(BlockBackend *blk)
|
||||
{
|
||||
DeviceState *dev;
|
||||
|
||||
assert(!blk->legacy_dev);
|
||||
dev = blk->dev;
|
||||
|
||||
if (!dev) {
|
||||
return g_strdup("");
|
||||
} else if (dev->id) {
|
||||
return g_strdup(dev->id);
|
||||
}
|
||||
return object_get_canonical_path(OBJECT(dev));
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the BlockBackend which has the device model @dev attached if it
|
||||
* exists, else null.
|
||||
*
|
||||
* @dev must not be null.
|
||||
*/
|
||||
BlockBackend *blk_by_dev(void *dev)
|
||||
{
|
||||
BlockBackend *blk = NULL;
|
||||
|
||||
assert(dev != NULL);
|
||||
while ((blk = blk_all_next(blk)) != NULL) {
|
||||
if (blk->dev == dev) {
|
||||
return blk;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set @blk's device model callbacks to @ops.
|
||||
* @opaque is the opaque argument to pass to the callbacks.
|
||||
@@ -829,59 +551,35 @@ BlockBackend *blk_by_dev(void *dev)
|
||||
void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops,
|
||||
void *opaque)
|
||||
{
|
||||
/* All drivers that use blk_set_dev_ops() are qdevified and we want to keep
|
||||
* it that way, so we can assume blk->dev, if present, is a DeviceState if
|
||||
* blk->dev_ops is set. Non-device users may use dev_ops without device. */
|
||||
assert(!blk->legacy_dev);
|
||||
|
||||
blk->dev_ops = ops;
|
||||
blk->dev_opaque = opaque;
|
||||
|
||||
/* Are we currently quiesced? Should we enforce this right now? */
|
||||
if (blk->quiesce_counter && ops->drained_begin) {
|
||||
ops->drained_begin(opaque);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Notify @blk's attached device model of media change.
|
||||
*
|
||||
* If @load is true, notify of media load. This action can fail, meaning that
|
||||
* the medium cannot be loaded. @errp is set then.
|
||||
*
|
||||
* If @load is false, notify of media eject. This can never fail.
|
||||
*
|
||||
* If @load is true, notify of media load.
|
||||
* Else, notify of media eject.
|
||||
* Also send DEVICE_TRAY_MOVED events as appropriate.
|
||||
*/
|
||||
void blk_dev_change_media_cb(BlockBackend *blk, bool load, Error **errp)
|
||||
void blk_dev_change_media_cb(BlockBackend *blk, bool load)
|
||||
{
|
||||
if (blk->dev_ops && blk->dev_ops->change_media_cb) {
|
||||
bool tray_was_open, tray_is_open;
|
||||
Error *local_err = NULL;
|
||||
|
||||
assert(!blk->legacy_dev);
|
||||
|
||||
tray_was_open = blk_dev_is_tray_open(blk);
|
||||
blk->dev_ops->change_media_cb(blk->dev_opaque, load, &local_err);
|
||||
if (local_err) {
|
||||
assert(load == true);
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
blk->dev_ops->change_media_cb(blk->dev_opaque, load);
|
||||
tray_is_open = blk_dev_is_tray_open(blk);
|
||||
|
||||
if (tray_was_open != tray_is_open) {
|
||||
char *id = blk_get_attached_dev_id(blk);
|
||||
qapi_event_send_device_tray_moved(blk_name(blk), id, tray_is_open,
|
||||
qapi_event_send_device_tray_moved(blk_name(blk), tray_is_open,
|
||||
&error_abort);
|
||||
g_free(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blk_root_change_media(BdrvChild *child, bool load)
|
||||
{
|
||||
blk_dev_change_media_cb(child->opaque, load, NULL);
|
||||
blk_dev_change_media_cb(child->opaque, load);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1029,30 +727,40 @@ static int blk_check_byte_request(BlockBackend *blk, int64_t offset,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int blk_check_request(BlockBackend *blk, int64_t sector_num,
|
||||
int nb_sectors)
|
||||
{
|
||||
if (sector_num < 0 || sector_num > INT64_MAX / BDRV_SECTOR_SIZE) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (nb_sectors < 0 || nb_sectors > INT_MAX / BDRV_SECTOR_SIZE) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return blk_check_byte_request(blk, sector_num * BDRV_SECTOR_SIZE,
|
||||
nb_sectors * BDRV_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset,
|
||||
unsigned int bytes, QEMUIOVector *qiov,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
int ret;
|
||||
BlockDriverState *bs = blk_bs(blk);
|
||||
|
||||
trace_blk_co_preadv(blk, bs, offset, bytes, flags);
|
||||
trace_blk_co_preadv(blk, blk_bs(blk), offset, bytes, flags);
|
||||
|
||||
ret = blk_check_byte_request(blk, offset, bytes);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bdrv_inc_in_flight(bs);
|
||||
|
||||
/* throttling disk I/O */
|
||||
if (blk->public.throttle_state) {
|
||||
throttle_group_co_io_limits_intercept(blk, bytes, false);
|
||||
}
|
||||
|
||||
ret = bdrv_co_preadv(blk->root, offset, bytes, qiov, flags);
|
||||
bdrv_dec_in_flight(bs);
|
||||
return ret;
|
||||
return bdrv_co_preadv(blk->root, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
|
||||
@@ -1060,17 +768,14 @@ int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
int ret;
|
||||
BlockDriverState *bs = blk_bs(blk);
|
||||
|
||||
trace_blk_co_pwritev(blk, bs, offset, bytes, flags);
|
||||
trace_blk_co_pwritev(blk, blk_bs(blk), offset, bytes, flags);
|
||||
|
||||
ret = blk_check_byte_request(blk, offset, bytes);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bdrv_inc_in_flight(bs);
|
||||
|
||||
/* throttling disk I/O */
|
||||
if (blk->public.throttle_state) {
|
||||
throttle_group_co_io_limits_intercept(blk, bytes, true);
|
||||
@@ -1080,9 +785,7 @@ int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
|
||||
flags |= BDRV_REQ_FUA;
|
||||
}
|
||||
|
||||
ret = bdrv_co_pwritev(blk->root, offset, bytes, qiov, flags);
|
||||
bdrv_dec_in_flight(bs);
|
||||
return ret;
|
||||
return bdrv_co_pwritev(blk->root, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
typedef struct BlkRwCo {
|
||||
@@ -1113,8 +816,10 @@ static int blk_prw(BlockBackend *blk, int64_t offset, uint8_t *buf,
|
||||
int64_t bytes, CoroutineEntry co_entry,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
AioContext *aio_context;
|
||||
QEMUIOVector qiov;
|
||||
struct iovec iov;
|
||||
Coroutine *co;
|
||||
BlkRwCo rwco;
|
||||
|
||||
iov = (struct iovec) {
|
||||
@@ -1131,13 +836,12 @@ static int blk_prw(BlockBackend *blk, int64_t offset, uint8_t *buf,
|
||||
.ret = NOT_DONE,
|
||||
};
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
/* Fast-path if already in coroutine context */
|
||||
co_entry(&rwco);
|
||||
} else {
|
||||
Coroutine *co = qemu_coroutine_create(co_entry, &rwco);
|
||||
bdrv_coroutine_enter(blk_bs(blk), co);
|
||||
BDRV_POLL_WHILE(blk_bs(blk), rwco.ret == NOT_DONE);
|
||||
co = qemu_coroutine_create(co_entry, &rwco);
|
||||
qemu_coroutine_enter(co);
|
||||
|
||||
aio_context = blk_get_aio_context(blk);
|
||||
while (rwco.ret == NOT_DONE) {
|
||||
aio_poll(aio_context, true);
|
||||
}
|
||||
|
||||
return rwco.ret;
|
||||
@@ -1160,9 +864,9 @@ int blk_pread_unthrottled(BlockBackend *blk, int64_t offset, uint8_t *buf,
|
||||
}
|
||||
|
||||
int blk_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
int count, BdrvRequestFlags flags)
|
||||
{
|
||||
return blk_prw(blk, offset, NULL, bytes, blk_write_entry,
|
||||
return blk_prw(blk, offset, NULL, count, blk_write_entry,
|
||||
flags | BDRV_REQ_ZERO_WRITE);
|
||||
}
|
||||
|
||||
@@ -1174,8 +878,7 @@ int blk_make_zero(BlockBackend *blk, BdrvRequestFlags flags)
|
||||
static void error_callback_bh(void *opaque)
|
||||
{
|
||||
struct BlockBackendAIOCB *acb = opaque;
|
||||
|
||||
bdrv_dec_in_flight(acb->common.bs);
|
||||
qemu_bh_delete(acb->bh);
|
||||
acb->common.cb(acb->common.opaque, acb->ret);
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
@@ -1185,13 +888,16 @@ BlockAIOCB *blk_abort_aio_request(BlockBackend *blk,
|
||||
void *opaque, int ret)
|
||||
{
|
||||
struct BlockBackendAIOCB *acb;
|
||||
QEMUBH *bh;
|
||||
|
||||
bdrv_inc_in_flight(blk_bs(blk));
|
||||
acb = blk_aio_get(&block_backend_aiocb_info, blk, cb, opaque);
|
||||
acb->blk = blk;
|
||||
acb->ret = ret;
|
||||
|
||||
aio_bh_schedule_oneshot(blk_get_aio_context(blk), error_callback_bh, acb);
|
||||
bh = aio_bh_new(blk_get_aio_context(blk), error_callback_bh, acb);
|
||||
acb->bh = bh;
|
||||
qemu_bh_schedule(bh);
|
||||
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
@@ -1200,6 +906,7 @@ typedef struct BlkAioEmAIOCB {
|
||||
BlkRwCo rwco;
|
||||
int bytes;
|
||||
bool has_returned;
|
||||
QEMUBH* bh;
|
||||
} BlkAioEmAIOCB;
|
||||
|
||||
static const AIOCBInfo blk_aio_em_aiocb_info = {
|
||||
@@ -1208,8 +915,11 @@ static const AIOCBInfo blk_aio_em_aiocb_info = {
|
||||
|
||||
static void blk_aio_complete(BlkAioEmAIOCB *acb)
|
||||
{
|
||||
if (acb->bh) {
|
||||
assert(acb->has_returned);
|
||||
qemu_bh_delete(acb->bh);
|
||||
}
|
||||
if (acb->has_returned) {
|
||||
bdrv_dec_in_flight(acb->common.bs);
|
||||
acb->common.cb(acb->common.opaque, acb->rwco.ret);
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
@@ -1217,9 +927,7 @@ static void blk_aio_complete(BlkAioEmAIOCB *acb)
|
||||
|
||||
static void blk_aio_complete_bh(void *opaque)
|
||||
{
|
||||
BlkAioEmAIOCB *acb = opaque;
|
||||
assert(acb->has_returned);
|
||||
blk_aio_complete(acb);
|
||||
blk_aio_complete(opaque);
|
||||
}
|
||||
|
||||
static BlockAIOCB *blk_aio_prwv(BlockBackend *blk, int64_t offset, int bytes,
|
||||
@@ -1230,7 +938,6 @@ static BlockAIOCB *blk_aio_prwv(BlockBackend *blk, int64_t offset, int bytes,
|
||||
BlkAioEmAIOCB *acb;
|
||||
Coroutine *co;
|
||||
|
||||
bdrv_inc_in_flight(blk_bs(blk));
|
||||
acb = blk_aio_get(&blk_aio_em_aiocb_info, blk, cb, opaque);
|
||||
acb->rwco = (BlkRwCo) {
|
||||
.blk = blk,
|
||||
@@ -1240,15 +947,16 @@ static BlockAIOCB *blk_aio_prwv(BlockBackend *blk, int64_t offset, int bytes,
|
||||
.ret = NOT_DONE,
|
||||
};
|
||||
acb->bytes = bytes;
|
||||
acb->bh = NULL;
|
||||
acb->has_returned = false;
|
||||
|
||||
co = qemu_coroutine_create(co_entry, acb);
|
||||
bdrv_coroutine_enter(blk_bs(blk), co);
|
||||
qemu_coroutine_enter(co);
|
||||
|
||||
acb->has_returned = true;
|
||||
if (acb->rwco.ret != NOT_DONE) {
|
||||
aio_bh_schedule_oneshot(blk_get_aio_context(blk),
|
||||
blk_aio_complete_bh, acb);
|
||||
acb->bh = aio_bh_new(blk_get_aio_context(blk), blk_aio_complete_bh, acb);
|
||||
qemu_bh_schedule(acb->bh);
|
||||
}
|
||||
|
||||
return &acb->common;
|
||||
@@ -1347,36 +1055,26 @@ BlockAIOCB *blk_aio_pwritev(BlockBackend *blk, int64_t offset,
|
||||
blk_aio_write_entry, flags, cb, opaque);
|
||||
}
|
||||
|
||||
static void blk_aio_flush_entry(void *opaque)
|
||||
{
|
||||
BlkAioEmAIOCB *acb = opaque;
|
||||
BlkRwCo *rwco = &acb->rwco;
|
||||
|
||||
rwco->ret = blk_co_flush(rwco->blk);
|
||||
blk_aio_complete(acb);
|
||||
}
|
||||
|
||||
BlockAIOCB *blk_aio_flush(BlockBackend *blk,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
return blk_aio_prwv(blk, 0, 0, NULL, blk_aio_flush_entry, 0, cb, opaque);
|
||||
}
|
||||
if (!blk_is_available(blk)) {
|
||||
return blk_abort_aio_request(blk, cb, opaque, -ENOMEDIUM);
|
||||
}
|
||||
|
||||
static void blk_aio_pdiscard_entry(void *opaque)
|
||||
{
|
||||
BlkAioEmAIOCB *acb = opaque;
|
||||
BlkRwCo *rwco = &acb->rwco;
|
||||
|
||||
rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, acb->bytes);
|
||||
blk_aio_complete(acb);
|
||||
return bdrv_aio_flush(blk_bs(blk), cb, opaque);
|
||||
}
|
||||
|
||||
BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk,
|
||||
int64_t offset, int bytes,
|
||||
int64_t offset, int count,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
return blk_aio_prwv(blk, offset, bytes, NULL, blk_aio_pdiscard_entry, 0,
|
||||
cb, opaque);
|
||||
int ret = blk_check_byte_request(blk, offset, count);
|
||||
if (ret < 0) {
|
||||
return blk_abort_aio_request(blk, cb, opaque, ret);
|
||||
}
|
||||
|
||||
return bdrv_aio_pdiscard(blk_bs(blk), offset, count, cb, opaque);
|
||||
}
|
||||
|
||||
void blk_aio_cancel(BlockAIOCB *acb)
|
||||
@@ -1389,60 +1087,33 @@ void blk_aio_cancel_async(BlockAIOCB *acb)
|
||||
bdrv_aio_cancel_async(acb);
|
||||
}
|
||||
|
||||
int blk_co_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
|
||||
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
|
||||
{
|
||||
if (!blk_is_available(blk)) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
return bdrv_co_ioctl(blk_bs(blk), req, buf);
|
||||
}
|
||||
|
||||
static void blk_ioctl_entry(void *opaque)
|
||||
{
|
||||
BlkRwCo *rwco = opaque;
|
||||
rwco->ret = blk_co_ioctl(rwco->blk, rwco->offset,
|
||||
rwco->qiov->iov[0].iov_base);
|
||||
}
|
||||
|
||||
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
|
||||
{
|
||||
return blk_prw(blk, req, buf, 0, blk_ioctl_entry, 0);
|
||||
}
|
||||
|
||||
static void blk_aio_ioctl_entry(void *opaque)
|
||||
{
|
||||
BlkAioEmAIOCB *acb = opaque;
|
||||
BlkRwCo *rwco = &acb->rwco;
|
||||
|
||||
rwco->ret = blk_co_ioctl(rwco->blk, rwco->offset,
|
||||
rwco->qiov->iov[0].iov_base);
|
||||
blk_aio_complete(acb);
|
||||
return bdrv_ioctl(blk_bs(blk), req, buf);
|
||||
}
|
||||
|
||||
BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
QEMUIOVector qiov;
|
||||
struct iovec iov;
|
||||
if (!blk_is_available(blk)) {
|
||||
return blk_abort_aio_request(blk, cb, opaque, -ENOMEDIUM);
|
||||
}
|
||||
|
||||
iov = (struct iovec) {
|
||||
.iov_base = buf,
|
||||
.iov_len = 0,
|
||||
};
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
return blk_aio_prwv(blk, req, 0, &qiov, blk_aio_ioctl_entry, 0, cb, opaque);
|
||||
return bdrv_aio_ioctl(blk_bs(blk), req, buf, cb, opaque);
|
||||
}
|
||||
|
||||
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int bytes)
|
||||
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int count)
|
||||
{
|
||||
int ret = blk_check_byte_request(blk, offset, bytes);
|
||||
int ret = blk_check_byte_request(blk, offset, count);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pdiscard(blk_bs(blk), offset, bytes);
|
||||
return bdrv_co_pdiscard(blk_bs(blk), offset, count);
|
||||
}
|
||||
|
||||
int blk_co_flush(BlockBackend *blk)
|
||||
@@ -1454,15 +1125,13 @@ int blk_co_flush(BlockBackend *blk)
|
||||
return bdrv_co_flush(blk_bs(blk));
|
||||
}
|
||||
|
||||
static void blk_flush_entry(void *opaque)
|
||||
{
|
||||
BlkRwCo *rwco = opaque;
|
||||
rwco->ret = blk_co_flush(rwco->blk);
|
||||
}
|
||||
|
||||
int blk_flush(BlockBackend *blk)
|
||||
{
|
||||
return blk_prw(blk, 0, NULL, 0, blk_flush_entry, 0);
|
||||
if (!blk_is_available(blk)) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
return bdrv_flush(blk_bs(blk));
|
||||
}
|
||||
|
||||
void blk_drain(BlockBackend *blk)
|
||||
@@ -1517,9 +1186,8 @@ static void send_qmp_error_event(BlockBackend *blk,
|
||||
IoOperationType optype;
|
||||
|
||||
optype = is_read ? IO_OPERATION_TYPE_READ : IO_OPERATION_TYPE_WRITE;
|
||||
qapi_event_send_block_io_error(blk_name(blk),
|
||||
bdrv_get_node_name(blk_bs(blk)), optype,
|
||||
action, blk_iostatus_is_enabled(blk),
|
||||
qapi_event_send_block_io_error(blk_name(blk), optype, action,
|
||||
blk_iostatus_is_enabled(blk),
|
||||
error == ENOSPC, strerror(error),
|
||||
&error_abort);
|
||||
}
|
||||
@@ -1624,10 +1292,6 @@ void blk_lock_medium(BlockBackend *blk, bool locked)
|
||||
void blk_eject(BlockBackend *blk, bool eject_flag)
|
||||
{
|
||||
BlockDriverState *bs = blk_bs(blk);
|
||||
char *id;
|
||||
|
||||
/* blk_eject is only called by qdevified devices */
|
||||
assert(!blk->legacy_dev);
|
||||
|
||||
if (bs) {
|
||||
bdrv_eject(bs, eject_flag);
|
||||
@@ -1635,10 +1299,8 @@ void blk_eject(BlockBackend *blk, bool eject_flag)
|
||||
|
||||
/* Whether or not we ejected on the backend,
|
||||
* the frontend experienced a tray event. */
|
||||
id = blk_get_attached_dev_id(blk);
|
||||
qapi_event_send_device_tray_moved(blk_name(blk), id,
|
||||
qapi_event_send_device_tray_moved(blk_name(blk),
|
||||
eject_flag, &error_abort);
|
||||
g_free(id);
|
||||
}
|
||||
|
||||
int blk_get_flags(BlockBackend *blk)
|
||||
@@ -1821,39 +1483,40 @@ void *blk_aio_get(const AIOCBInfo *aiocb_info, BlockBackend *blk,
|
||||
}
|
||||
|
||||
int coroutine_fn blk_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
int count, BdrvRequestFlags flags)
|
||||
{
|
||||
return blk_co_pwritev(blk, offset, bytes, NULL,
|
||||
return blk_co_pwritev(blk, offset, count, NULL,
|
||||
flags | BDRV_REQ_ZERO_WRITE);
|
||||
}
|
||||
|
||||
int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf,
|
||||
int count)
|
||||
int blk_write_compressed(BlockBackend *blk, int64_t sector_num,
|
||||
const uint8_t *buf, int nb_sectors)
|
||||
{
|
||||
return blk_prw(blk, offset, (void *) buf, count, blk_write_entry,
|
||||
BDRV_REQ_WRITE_COMPRESSED);
|
||||
int ret = blk_check_request(blk, sector_num, nb_sectors);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_write_compressed(blk_bs(blk), sector_num, buf, nb_sectors);
|
||||
}
|
||||
|
||||
int blk_truncate(BlockBackend *blk, int64_t offset, PreallocMode prealloc,
|
||||
Error **errp)
|
||||
int blk_truncate(BlockBackend *blk, int64_t offset)
|
||||
{
|
||||
if (!blk_is_available(blk)) {
|
||||
error_setg(errp, "No medium inserted");
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
return bdrv_truncate(blk->root, offset, prealloc, errp);
|
||||
return bdrv_truncate(blk_bs(blk), offset);
|
||||
}
|
||||
|
||||
static void blk_pdiscard_entry(void *opaque)
|
||||
int blk_pdiscard(BlockBackend *blk, int64_t offset, int count)
|
||||
{
|
||||
BlkRwCo *rwco = opaque;
|
||||
rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, rwco->qiov->size);
|
||||
}
|
||||
int ret = blk_check_byte_request(blk, offset, count);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int blk_pdiscard(BlockBackend *blk, int64_t offset, int bytes)
|
||||
{
|
||||
return blk_prw(blk, offset, NULL, bytes, blk_pdiscard_entry, 0);
|
||||
return bdrv_pdiscard(blk_bs(blk), offset, count);
|
||||
}
|
||||
|
||||
int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,
|
||||
@@ -1918,12 +1581,13 @@ void blk_update_root_state(BlockBackend *blk)
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the detect-zeroes setting to be used for bdrv_open() of a
|
||||
* BlockDriverState which is supposed to inherit the root state.
|
||||
* Applies the information in the root state to the given BlockDriverState. This
|
||||
* does not include the flags which have to be specified for bdrv_open(), use
|
||||
* blk_get_open_flags_from_root_state() to inquire them.
|
||||
*/
|
||||
bool blk_get_detect_zeroes_from_root_state(BlockBackend *blk)
|
||||
void blk_apply_root_state(BlockBackend *blk, BlockDriverState *bs)
|
||||
{
|
||||
return blk->root_state.detect_zeroes;
|
||||
bs->detect_zeroes = blk->root_state.detect_zeroes;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2008,16 +1672,10 @@ static void blk_root_drained_begin(BdrvChild *child)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
|
||||
if (++blk->quiesce_counter == 1) {
|
||||
if (blk->dev_ops && blk->dev_ops->drained_begin) {
|
||||
blk->dev_ops->drained_begin(blk->dev_opaque);
|
||||
}
|
||||
}
|
||||
|
||||
/* Note that blk->root may not be accessible here yet if we are just
|
||||
* attaching to a BlockDriverState that is drained. Use child instead. */
|
||||
|
||||
if (atomic_fetch_inc(&blk->public.io_limits_disabled) == 0) {
|
||||
if (blk->public.io_limits_disabled++ == 0) {
|
||||
throttle_group_restart_blk(blk);
|
||||
}
|
||||
}
|
||||
@@ -2025,14 +1683,7 @@ static void blk_root_drained_begin(BdrvChild *child)
|
||||
static void blk_root_drained_end(BdrvChild *child)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
assert(blk->quiesce_counter);
|
||||
|
||||
assert(blk->public.io_limits_disabled);
|
||||
atomic_dec(&blk->public.io_limits_disabled);
|
||||
|
||||
if (--blk->quiesce_counter == 0) {
|
||||
if (blk->dev_ops && blk->dev_ops->drained_end) {
|
||||
blk->dev_ops->drained_end(blk->dev_opaque);
|
||||
}
|
||||
}
|
||||
--blk->public.io_limits_disabled;
|
||||
}
|
||||
+1
-11
@@ -104,16 +104,7 @@ static int bochs_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
struct bochs_header bochs;
|
||||
int ret;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = bdrv_set_read_only(bs, true, errp); /* no write support yet */
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
bs->read_only = true; /* no write support yet */
|
||||
|
||||
ret = bdrv_pread(bs->file, 0, &bochs, sizeof(bochs));
|
||||
if (ret < 0) {
|
||||
@@ -296,7 +287,6 @@ static BlockDriver bdrv_bochs = {
|
||||
.instance_size = sizeof(BDRVBochsState),
|
||||
.bdrv_probe = bochs_probe,
|
||||
.bdrv_open = bochs_open,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_refresh_limits = bochs_refresh_limits,
|
||||
.bdrv_co_preadv = bochs_co_preadv,
|
||||
.bdrv_close = bochs_close,
|
||||
|
||||
+1
-11
@@ -66,16 +66,7 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
uint32_t offsets_size, max_compressed_block_size = 1, i;
|
||||
int ret;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = bdrv_set_read_only(bs, true, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
bs->read_only = true;
|
||||
|
||||
/* read header */
|
||||
ret = bdrv_pread(bs->file, 128, &s->block_size, 4);
|
||||
@@ -293,7 +284,6 @@ static BlockDriver bdrv_cloop = {
|
||||
.instance_size = sizeof(BDRVCloopState),
|
||||
.bdrv_probe = cloop_probe,
|
||||
.bdrv_open = cloop_open,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_refresh_limits = cloop_refresh_limits,
|
||||
.bdrv_co_preadv = cloop_co_preadv,
|
||||
.bdrv_close = cloop_close,
|
||||
|
||||
+61
-251
@@ -13,10 +13,9 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "trace.h"
|
||||
#include "block/block_int.h"
|
||||
#include "block/blockjob_int.h"
|
||||
#include "block/blockjob.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qemu/ratelimit.h"
|
||||
@@ -37,7 +36,6 @@ typedef struct CommitBlockJob {
|
||||
BlockJob common;
|
||||
RateLimit limit;
|
||||
BlockDriverState *active;
|
||||
BlockDriverState *commit_top_bs;
|
||||
BlockBackend *top;
|
||||
BlockBackend *base;
|
||||
BlockdevOnError on_error;
|
||||
@@ -47,25 +45,26 @@ typedef struct CommitBlockJob {
|
||||
} CommitBlockJob;
|
||||
|
||||
static int coroutine_fn commit_populate(BlockBackend *bs, BlockBackend *base,
|
||||
int64_t offset, uint64_t bytes,
|
||||
int64_t sector_num, int nb_sectors,
|
||||
void *buf)
|
||||
{
|
||||
int ret = 0;
|
||||
QEMUIOVector qiov;
|
||||
struct iovec iov = {
|
||||
.iov_base = buf,
|
||||
.iov_len = bytes,
|
||||
.iov_len = nb_sectors * BDRV_SECTOR_SIZE,
|
||||
};
|
||||
|
||||
assert(bytes < SIZE_MAX);
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
ret = blk_co_preadv(bs, offset, qiov.size, &qiov, 0);
|
||||
ret = blk_co_preadv(bs, sector_num * BDRV_SECTOR_SIZE,
|
||||
qiov.size, &qiov, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = blk_co_pwritev(base, offset, qiov.size, &qiov, 0);
|
||||
ret = blk_co_pwritev(base, sector_num * BDRV_SECTOR_SIZE,
|
||||
qiov.size, &qiov, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@@ -84,29 +83,12 @@ static void commit_complete(BlockJob *job, void *opaque)
|
||||
BlockDriverState *active = s->active;
|
||||
BlockDriverState *top = blk_bs(s->top);
|
||||
BlockDriverState *base = blk_bs(s->base);
|
||||
BlockDriverState *overlay_bs = bdrv_find_overlay(active, s->commit_top_bs);
|
||||
BlockDriverState *overlay_bs;
|
||||
int ret = data->ret;
|
||||
bool remove_commit_top_bs = false;
|
||||
|
||||
/* Make sure overlay_bs and top stay around until bdrv_set_backing_hd() */
|
||||
bdrv_ref(top);
|
||||
if (overlay_bs) {
|
||||
bdrv_ref(overlay_bs);
|
||||
}
|
||||
|
||||
/* Remove base node parent that still uses BLK_PERM_WRITE/RESIZE before
|
||||
* the normal backing chain can be restored. */
|
||||
blk_unref(s->base);
|
||||
|
||||
if (!block_job_is_cancelled(&s->common) && ret == 0) {
|
||||
/* success */
|
||||
ret = bdrv_drop_intermediate(active, s->commit_top_bs, base,
|
||||
s->backing_file_str);
|
||||
} else if (overlay_bs) {
|
||||
/* XXX Can (or should) we somehow keep 'consistent read' blocked even
|
||||
* after the failed/cancelled commit job is gone? If we already wrote
|
||||
* something to base, the intermediate images aren't valid any more. */
|
||||
remove_commit_top_bs = true;
|
||||
ret = bdrv_drop_intermediate(active, top, base, s->backing_file_str);
|
||||
}
|
||||
|
||||
/* restore base open flags here if appropriate (e.g., change the base back
|
||||
@@ -115,46 +97,32 @@ static void commit_complete(BlockJob *job, void *opaque)
|
||||
if (s->base_flags != bdrv_get_flags(base)) {
|
||||
bdrv_reopen(base, s->base_flags, NULL);
|
||||
}
|
||||
overlay_bs = bdrv_find_overlay(active, top);
|
||||
if (overlay_bs && s->orig_overlay_flags != bdrv_get_flags(overlay_bs)) {
|
||||
bdrv_reopen(overlay_bs, s->orig_overlay_flags, NULL);
|
||||
}
|
||||
g_free(s->backing_file_str);
|
||||
blk_unref(s->top);
|
||||
|
||||
/* If there is more than one reference to the job (e.g. if called from
|
||||
* block_job_finish_sync()), block_job_completed() won't free it and
|
||||
* therefore the blockers on the intermediate nodes remain. This would
|
||||
* cause bdrv_set_backing_hd() to fail. */
|
||||
block_job_remove_all_bdrv(job);
|
||||
|
||||
blk_unref(s->base);
|
||||
block_job_completed(&s->common, ret);
|
||||
g_free(data);
|
||||
|
||||
/* If bdrv_drop_intermediate() didn't already do that, remove the commit
|
||||
* filter driver from the backing chain. Do this as the final step so that
|
||||
* the 'consistent read' permission can be granted. */
|
||||
if (remove_commit_top_bs) {
|
||||
bdrv_set_backing_hd(overlay_bs, top, &error_abort);
|
||||
}
|
||||
|
||||
bdrv_unref(overlay_bs);
|
||||
bdrv_unref(top);
|
||||
}
|
||||
|
||||
static void coroutine_fn commit_run(void *opaque)
|
||||
{
|
||||
CommitBlockJob *s = opaque;
|
||||
CommitCompleteData *data;
|
||||
int64_t offset;
|
||||
int64_t sector_num, end;
|
||||
uint64_t delay_ns = 0;
|
||||
int ret = 0;
|
||||
int64_t n = 0; /* bytes */
|
||||
int n = 0;
|
||||
void *buf = NULL;
|
||||
int bytes_written = 0;
|
||||
int64_t base_len;
|
||||
|
||||
ret = s->common.len = blk_getlength(s->top);
|
||||
|
||||
|
||||
if (s->common.len < 0) {
|
||||
goto out;
|
||||
}
|
||||
@@ -165,15 +133,16 @@ static void coroutine_fn commit_run(void *opaque)
|
||||
}
|
||||
|
||||
if (base_len < s->common.len) {
|
||||
ret = blk_truncate(s->base, s->common.len, PREALLOC_MODE_OFF, NULL);
|
||||
ret = blk_truncate(s->base, s->common.len);
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
end = s->common.len >> BDRV_SECTOR_BITS;
|
||||
buf = blk_blockalign(s->top, COMMIT_BUFFER_SIZE);
|
||||
|
||||
for (offset = 0; offset < s->common.len; offset += n) {
|
||||
for (sector_num = 0; sector_num < end; sector_num += n) {
|
||||
bool copy;
|
||||
|
||||
/* Note that even when no rate limit is applied we need to yield
|
||||
@@ -185,12 +154,14 @@ static void coroutine_fn commit_run(void *opaque)
|
||||
}
|
||||
/* Copy if allocated above the base */
|
||||
ret = bdrv_is_allocated_above(blk_bs(s->top), blk_bs(s->base),
|
||||
offset, COMMIT_BUFFER_SIZE, &n);
|
||||
sector_num,
|
||||
COMMIT_BUFFER_SIZE / BDRV_SECTOR_SIZE,
|
||||
&n);
|
||||
copy = (ret == 1);
|
||||
trace_commit_one_iteration(s, offset, n, ret);
|
||||
trace_commit_one_iteration(s, sector_num, n, ret);
|
||||
if (copy) {
|
||||
ret = commit_populate(s->top, s->base, offset, n, buf);
|
||||
bytes_written += n;
|
||||
ret = commit_populate(s->top, s->base, sector_num, n, buf);
|
||||
bytes_written += n * BDRV_SECTOR_SIZE;
|
||||
}
|
||||
if (ret < 0) {
|
||||
BlockErrorAction action =
|
||||
@@ -203,7 +174,7 @@ static void coroutine_fn commit_run(void *opaque)
|
||||
}
|
||||
}
|
||||
/* Publish progress */
|
||||
s->common.offset += n;
|
||||
s->common.offset += n * BDRV_SECTOR_SIZE;
|
||||
|
||||
if (copy && s->common.speed) {
|
||||
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
||||
@@ -228,77 +199,26 @@ static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
||||
return;
|
||||
}
|
||||
ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
|
||||
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
||||
}
|
||||
|
||||
static const BlockJobDriver commit_job_driver = {
|
||||
.instance_size = sizeof(CommitBlockJob),
|
||||
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
||||
.set_speed = commit_set_speed,
|
||||
.start = commit_run,
|
||||
};
|
||||
|
||||
static int coroutine_fn bdrv_commit_top_preadv(BlockDriverState *bs,
|
||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
return bdrv_co_preadv(bs->backing, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
static int64_t coroutine_fn bdrv_commit_top_get_block_status(
|
||||
BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
|
||||
BlockDriverState **file)
|
||||
{
|
||||
*pnum = nb_sectors;
|
||||
*file = bs->backing->bs;
|
||||
return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
|
||||
(sector_num << BDRV_SECTOR_BITS);
|
||||
}
|
||||
|
||||
static void bdrv_commit_top_refresh_filename(BlockDriverState *bs, QDict *opts)
|
||||
{
|
||||
bdrv_refresh_filename(bs->backing->bs);
|
||||
pstrcpy(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
bs->backing->bs->filename);
|
||||
}
|
||||
|
||||
static void bdrv_commit_top_close(BlockDriverState *bs)
|
||||
{
|
||||
}
|
||||
|
||||
static void bdrv_commit_top_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||
const BdrvChildRole *role,
|
||||
uint64_t perm, uint64_t shared,
|
||||
uint64_t *nperm, uint64_t *nshared)
|
||||
{
|
||||
*nperm = 0;
|
||||
*nshared = BLK_PERM_ALL;
|
||||
}
|
||||
|
||||
/* Dummy node that provides consistent read to its users without requiring it
|
||||
* from its backing file and that allows writes on the backing file chain. */
|
||||
static BlockDriver bdrv_commit_top = {
|
||||
.format_name = "commit_top",
|
||||
.bdrv_co_preadv = bdrv_commit_top_preadv,
|
||||
.bdrv_co_get_block_status = bdrv_commit_top_get_block_status,
|
||||
.bdrv_refresh_filename = bdrv_commit_top_refresh_filename,
|
||||
.bdrv_close = bdrv_commit_top_close,
|
||||
.bdrv_child_perm = bdrv_commit_top_child_perm,
|
||||
};
|
||||
|
||||
void commit_start(const char *job_id, BlockDriverState *bs,
|
||||
BlockDriverState *base, BlockDriverState *top, int64_t speed,
|
||||
BlockdevOnError on_error, const char *backing_file_str,
|
||||
const char *filter_node_name, Error **errp)
|
||||
BlockdevOnError on_error, BlockCompletionFunc *cb,
|
||||
void *opaque, const char *backing_file_str, Error **errp)
|
||||
{
|
||||
CommitBlockJob *s;
|
||||
BlockReopenQueue *reopen_queue = NULL;
|
||||
int orig_overlay_flags;
|
||||
int orig_base_flags;
|
||||
BlockDriverState *iter;
|
||||
BlockDriverState *overlay_bs;
|
||||
BlockDriverState *commit_top_bs = NULL;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
assert(top != bs);
|
||||
if (top == base) {
|
||||
@@ -313,8 +233,8 @@ void commit_start(const char *job_id, BlockDriverState *bs,
|
||||
return;
|
||||
}
|
||||
|
||||
s = block_job_create(job_id, &commit_job_driver, bs, 0, BLK_PERM_ALL,
|
||||
speed, BLOCK_JOB_DEFAULT, NULL, NULL, errp);
|
||||
s = block_job_create(job_id, &commit_job_driver, bs, speed,
|
||||
cb, opaque, errp);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
@@ -323,99 +243,29 @@ void commit_start(const char *job_id, BlockDriverState *bs,
|
||||
orig_overlay_flags = bdrv_get_flags(overlay_bs);
|
||||
|
||||
/* convert base & overlay_bs to r/w, if necessary */
|
||||
if (!(orig_base_flags & BDRV_O_RDWR)) {
|
||||
reopen_queue = bdrv_reopen_queue(reopen_queue, base, NULL,
|
||||
orig_base_flags | BDRV_O_RDWR);
|
||||
}
|
||||
if (!(orig_overlay_flags & BDRV_O_RDWR)) {
|
||||
reopen_queue = bdrv_reopen_queue(reopen_queue, overlay_bs, NULL,
|
||||
orig_overlay_flags | BDRV_O_RDWR);
|
||||
}
|
||||
if (!(orig_base_flags & BDRV_O_RDWR)) {
|
||||
reopen_queue = bdrv_reopen_queue(reopen_queue, base, NULL,
|
||||
orig_base_flags | BDRV_O_RDWR);
|
||||
}
|
||||
if (reopen_queue) {
|
||||
bdrv_reopen_multiple(bdrv_get_aio_context(bs), reopen_queue, &local_err);
|
||||
bdrv_reopen_multiple(reopen_queue, &local_err);
|
||||
if (local_err != NULL) {
|
||||
error_propagate(errp, local_err);
|
||||
goto fail;
|
||||
block_job_unref(&s->common);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert commit_top block node above top, so we can block consistent read
|
||||
* on the backing chain below it */
|
||||
commit_top_bs = bdrv_new_open_driver(&bdrv_commit_top, filter_node_name, 0,
|
||||
errp);
|
||||
if (commit_top_bs == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
if (!filter_node_name) {
|
||||
commit_top_bs->implicit = true;
|
||||
}
|
||||
commit_top_bs->total_sectors = top->total_sectors;
|
||||
bdrv_set_aio_context(commit_top_bs, bdrv_get_aio_context(top));
|
||||
|
||||
bdrv_set_backing_hd(commit_top_bs, top, &local_err);
|
||||
if (local_err) {
|
||||
bdrv_unref(commit_top_bs);
|
||||
commit_top_bs = NULL;
|
||||
error_propagate(errp, local_err);
|
||||
goto fail;
|
||||
}
|
||||
bdrv_set_backing_hd(overlay_bs, commit_top_bs, &local_err);
|
||||
if (local_err) {
|
||||
bdrv_unref(commit_top_bs);
|
||||
commit_top_bs = NULL;
|
||||
error_propagate(errp, local_err);
|
||||
goto fail;
|
||||
}
|
||||
s->base = blk_new();
|
||||
blk_insert_bs(s->base, base);
|
||||
|
||||
s->commit_top_bs = commit_top_bs;
|
||||
bdrv_unref(commit_top_bs);
|
||||
|
||||
/* Block all nodes between top and base, because they will
|
||||
* disappear from the chain after this operation. */
|
||||
assert(bdrv_chain_contains(top, base));
|
||||
for (iter = top; iter != base; iter = backing_bs(iter)) {
|
||||
/* XXX BLK_PERM_WRITE needs to be allowed so we don't block ourselves
|
||||
* at s->base (if writes are blocked for a node, they are also blocked
|
||||
* for its backing file). The other options would be a second filter
|
||||
* driver above s->base. */
|
||||
ret = block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
|
||||
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = block_job_add_bdrv(&s->common, "base", base, 0, BLK_PERM_ALL, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* overlay_bs must be blocked because it needs to be modified to
|
||||
* update the backing image string. */
|
||||
ret = block_job_add_bdrv(&s->common, "overlay of top", overlay_bs,
|
||||
BLK_PERM_GRAPH_MOD, BLK_PERM_ALL, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
s->base = blk_new(BLK_PERM_CONSISTENT_READ
|
||||
| BLK_PERM_WRITE
|
||||
| BLK_PERM_RESIZE,
|
||||
BLK_PERM_CONSISTENT_READ
|
||||
| BLK_PERM_GRAPH_MOD
|
||||
| BLK_PERM_WRITE_UNCHANGED);
|
||||
ret = blk_insert_bs(s->base, base, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Required permissions are already taken with block_job_add_bdrv() */
|
||||
s->top = blk_new(0, BLK_PERM_ALL);
|
||||
ret = blk_insert_bs(s->top, top, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
s->top = blk_new();
|
||||
blk_insert_bs(s->top, top);
|
||||
|
||||
s->active = bs;
|
||||
|
||||
@@ -425,40 +275,24 @@ void commit_start(const char *job_id, BlockDriverState *bs,
|
||||
s->backing_file_str = g_strdup(backing_file_str);
|
||||
|
||||
s->on_error = on_error;
|
||||
s->common.co = qemu_coroutine_create(commit_run, s);
|
||||
|
||||
trace_commit_start(bs, base, top, s);
|
||||
block_job_start(&s->common);
|
||||
return;
|
||||
|
||||
fail:
|
||||
if (s->base) {
|
||||
blk_unref(s->base);
|
||||
}
|
||||
if (s->top) {
|
||||
blk_unref(s->top);
|
||||
}
|
||||
if (commit_top_bs) {
|
||||
bdrv_set_backing_hd(overlay_bs, top, &error_abort);
|
||||
}
|
||||
block_job_early_fail(&s->common);
|
||||
trace_commit_start(bs, base, top, s, s->common.co, opaque);
|
||||
qemu_coroutine_enter(s->common.co);
|
||||
}
|
||||
|
||||
|
||||
#define COMMIT_BUF_SIZE (2048 * BDRV_SECTOR_SIZE)
|
||||
#define COMMIT_BUF_SECTORS 2048
|
||||
|
||||
/* commit COW file into the raw image */
|
||||
int bdrv_commit(BlockDriverState *bs)
|
||||
{
|
||||
BlockBackend *src, *backing;
|
||||
BlockDriverState *backing_file_bs = NULL;
|
||||
BlockDriverState *commit_top_bs = NULL;
|
||||
BlockDriver *drv = bs->drv;
|
||||
int64_t offset, length, backing_length;
|
||||
int ro, open_flags;
|
||||
int64_t n;
|
||||
int64_t sector, total_sectors, length, backing_length;
|
||||
int n, ro, open_flags;
|
||||
int ret = 0;
|
||||
uint8_t *buf = NULL;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (!drv)
|
||||
return -ENOMEDIUM;
|
||||
@@ -481,34 +315,11 @@ int bdrv_commit(BlockDriverState *bs)
|
||||
}
|
||||
}
|
||||
|
||||
src = blk_new(BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL);
|
||||
backing = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
|
||||
src = blk_new();
|
||||
blk_insert_bs(src, bs);
|
||||
|
||||
ret = blk_insert_bs(src, bs, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
goto ro_cleanup;
|
||||
}
|
||||
|
||||
/* Insert commit_top block node above backing, so we can write to it */
|
||||
backing_file_bs = backing_bs(bs);
|
||||
|
||||
commit_top_bs = bdrv_new_open_driver(&bdrv_commit_top, NULL, BDRV_O_RDWR,
|
||||
&local_err);
|
||||
if (commit_top_bs == NULL) {
|
||||
error_report_err(local_err);
|
||||
goto ro_cleanup;
|
||||
}
|
||||
bdrv_set_aio_context(commit_top_bs, bdrv_get_aio_context(backing_file_bs));
|
||||
|
||||
bdrv_set_backing_hd(commit_top_bs, backing_file_bs, &error_abort);
|
||||
bdrv_set_backing_hd(bs, commit_top_bs, &error_abort);
|
||||
|
||||
ret = blk_insert_bs(backing, backing_file_bs, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
goto ro_cleanup;
|
||||
}
|
||||
backing = blk_new();
|
||||
blk_insert_bs(backing, bs->backing->bs);
|
||||
|
||||
length = blk_getlength(src);
|
||||
if (length < 0) {
|
||||
@@ -526,33 +337,36 @@ int bdrv_commit(BlockDriverState *bs)
|
||||
* grow the backing file image if possible. If not possible,
|
||||
* we must return an error */
|
||||
if (length > backing_length) {
|
||||
ret = blk_truncate(backing, length, PREALLOC_MODE_OFF, &local_err);
|
||||
ret = blk_truncate(backing, length);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
goto ro_cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
total_sectors = length >> BDRV_SECTOR_BITS;
|
||||
|
||||
/* blk_try_blockalign() for src will choose an alignment that works for
|
||||
* backing as well, so no need to compare the alignment manually. */
|
||||
buf = blk_try_blockalign(src, COMMIT_BUF_SIZE);
|
||||
buf = blk_try_blockalign(src, COMMIT_BUF_SECTORS * BDRV_SECTOR_SIZE);
|
||||
if (buf == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto ro_cleanup;
|
||||
}
|
||||
|
||||
for (offset = 0; offset < length; offset += n) {
|
||||
ret = bdrv_is_allocated(bs, offset, COMMIT_BUF_SIZE, &n);
|
||||
for (sector = 0; sector < total_sectors; sector += n) {
|
||||
ret = bdrv_is_allocated(bs, sector, COMMIT_BUF_SECTORS, &n);
|
||||
if (ret < 0) {
|
||||
goto ro_cleanup;
|
||||
}
|
||||
if (ret) {
|
||||
ret = blk_pread(src, offset, buf, n);
|
||||
ret = blk_pread(src, sector * BDRV_SECTOR_SIZE, buf,
|
||||
n * BDRV_SECTOR_SIZE);
|
||||
if (ret < 0) {
|
||||
goto ro_cleanup;
|
||||
}
|
||||
|
||||
ret = blk_pwrite(backing, offset, buf, n, 0);
|
||||
ret = blk_pwrite(backing, sector * BDRV_SECTOR_SIZE, buf,
|
||||
n * BDRV_SECTOR_SIZE, 0);
|
||||
if (ret < 0) {
|
||||
goto ro_cleanup;
|
||||
}
|
||||
@@ -577,12 +391,8 @@ int bdrv_commit(BlockDriverState *bs)
|
||||
ro_cleanup:
|
||||
qemu_vfree(buf);
|
||||
|
||||
blk_unref(backing);
|
||||
if (backing_file_bs) {
|
||||
bdrv_set_backing_hd(bs, backing_file_bs, &error_abort);
|
||||
}
|
||||
bdrv_unref(commit_top_bs);
|
||||
blk_unref(src);
|
||||
blk_unref(backing);
|
||||
|
||||
if (ro) {
|
||||
/* ignoring error return here */
|
||||
|
||||
+61
-54
@@ -24,10 +24,15 @@
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "crypto/block.h"
|
||||
#include "qapi/opts-visitor.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi-visit.h"
|
||||
#include "qapi/error.h"
|
||||
#include "block/crypto.h"
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET "key-secret"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG "cipher-alg"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE "cipher-mode"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG "ivgen-alg"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"
|
||||
|
||||
typedef struct BlockCrypto BlockCrypto;
|
||||
|
||||
@@ -53,8 +58,8 @@ static ssize_t block_crypto_read_func(QCryptoBlock *block,
|
||||
size_t offset,
|
||||
uint8_t *buf,
|
||||
size_t buflen,
|
||||
void *opaque,
|
||||
Error **errp)
|
||||
Error **errp,
|
||||
void *opaque)
|
||||
{
|
||||
BlockDriverState *bs = opaque;
|
||||
ssize_t ret;
|
||||
@@ -80,8 +85,8 @@ static ssize_t block_crypto_write_func(QCryptoBlock *block,
|
||||
size_t offset,
|
||||
const uint8_t *buf,
|
||||
size_t buflen,
|
||||
void *opaque,
|
||||
Error **errp)
|
||||
Error **errp,
|
||||
void *opaque)
|
||||
{
|
||||
struct BlockCryptoCreateData *data = opaque;
|
||||
ssize_t ret;
|
||||
@@ -97,8 +102,8 @@ static ssize_t block_crypto_write_func(QCryptoBlock *block,
|
||||
|
||||
static ssize_t block_crypto_init_func(QCryptoBlock *block,
|
||||
size_t headerlen,
|
||||
void *opaque,
|
||||
Error **errp)
|
||||
Error **errp,
|
||||
void *opaque)
|
||||
{
|
||||
struct BlockCryptoCreateData *data = opaque;
|
||||
int ret;
|
||||
@@ -129,7 +134,11 @@ static QemuOptsList block_crypto_runtime_opts_luks = {
|
||||
.name = "crypto",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(block_crypto_runtime_opts_luks.head),
|
||||
.desc = {
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(""),
|
||||
{
|
||||
.name = BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "ID of the secret that provides the encryption key",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
@@ -144,21 +153,44 @@ static QemuOptsList block_crypto_create_opts_luks = {
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Virtual disk size"
|
||||
},
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(""),
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG(""),
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE(""),
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG(""),
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG(""),
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG(""),
|
||||
BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(""),
|
||||
{
|
||||
.name = BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "ID of the secret that provides the encryption key",
|
||||
},
|
||||
{
|
||||
.name = BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Name of encryption cipher algorithm",
|
||||
},
|
||||
{
|
||||
.name = BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Name of encryption cipher mode",
|
||||
},
|
||||
{
|
||||
.name = BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Name of IV generator algorithm",
|
||||
},
|
||||
{
|
||||
.name = BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Name of IV generator hash algorithm",
|
||||
},
|
||||
{
|
||||
.name = BLOCK_CRYPTO_OPT_LUKS_HASH_ALG,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Name of encryption hash algorithm",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
QCryptoBlockOpenOptions *
|
||||
static QCryptoBlockOpenOptions *
|
||||
block_crypto_open_opts_init(QCryptoBlockFormat format,
|
||||
QDict *opts,
|
||||
QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
Visitor *v;
|
||||
@@ -168,7 +200,7 @@ block_crypto_open_opts_init(QCryptoBlockFormat format,
|
||||
ret = g_new0(QCryptoBlockOpenOptions, 1);
|
||||
ret->format = format;
|
||||
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(opts));
|
||||
v = opts_visitor_new(opts);
|
||||
|
||||
visit_start_struct(v, NULL, NULL, 0, &local_err);
|
||||
if (local_err) {
|
||||
@@ -181,11 +213,6 @@ block_crypto_open_opts_init(QCryptoBlockFormat format,
|
||||
v, &ret->u.luks, &local_err);
|
||||
break;
|
||||
|
||||
case Q_CRYPTO_BLOCK_FORMAT_QCOW:
|
||||
visit_type_QCryptoBlockOptionsQCow_members(
|
||||
v, &ret->u.qcow, &local_err);
|
||||
break;
|
||||
|
||||
default:
|
||||
error_setg(&local_err, "Unsupported block format %d", format);
|
||||
break;
|
||||
@@ -207,9 +234,9 @@ block_crypto_open_opts_init(QCryptoBlockFormat format,
|
||||
}
|
||||
|
||||
|
||||
QCryptoBlockCreateOptions *
|
||||
static QCryptoBlockCreateOptions *
|
||||
block_crypto_create_opts_init(QCryptoBlockFormat format,
|
||||
QDict *opts,
|
||||
QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
Visitor *v;
|
||||
@@ -219,7 +246,7 @@ block_crypto_create_opts_init(QCryptoBlockFormat format,
|
||||
ret = g_new0(QCryptoBlockCreateOptions, 1);
|
||||
ret->format = format;
|
||||
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(opts));
|
||||
v = opts_visitor_new(opts);
|
||||
|
||||
visit_start_struct(v, NULL, NULL, 0, &local_err);
|
||||
if (local_err) {
|
||||
@@ -232,11 +259,6 @@ block_crypto_create_opts_init(QCryptoBlockFormat format,
|
||||
v, &ret->u.luks, &local_err);
|
||||
break;
|
||||
|
||||
case Q_CRYPTO_BLOCK_FORMAT_QCOW:
|
||||
visit_type_QCryptoBlockOptionsQCow_members(
|
||||
v, &ret->u.qcow, &local_err);
|
||||
break;
|
||||
|
||||
default:
|
||||
error_setg(&local_err, "Unsupported block format %d", format);
|
||||
break;
|
||||
@@ -271,13 +293,6 @@ static int block_crypto_open_generic(QCryptoBlockFormat format,
|
||||
int ret = -EINVAL;
|
||||
QCryptoBlockOpenOptions *open_opts = NULL;
|
||||
unsigned int cflags = 0;
|
||||
QDict *cryptoopts = NULL;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
opts = qemu_opts_create(opts_spec, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
@@ -286,9 +301,7 @@ static int block_crypto_open_generic(QCryptoBlockFormat format,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cryptoopts = qemu_opts_to_qdict(opts, NULL);
|
||||
|
||||
open_opts = block_crypto_open_opts_init(format, cryptoopts, errp);
|
||||
open_opts = block_crypto_open_opts_init(format, opts, errp);
|
||||
if (!open_opts) {
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -296,7 +309,7 @@ static int block_crypto_open_generic(QCryptoBlockFormat format,
|
||||
if (flags & BDRV_O_NO_IO) {
|
||||
cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
|
||||
}
|
||||
crypto->block = qcrypto_block_open(open_opts, NULL,
|
||||
crypto->block = qcrypto_block_open(open_opts,
|
||||
block_crypto_read_func,
|
||||
bs,
|
||||
cflags,
|
||||
@@ -308,10 +321,10 @@ static int block_crypto_open_generic(QCryptoBlockFormat format,
|
||||
}
|
||||
|
||||
bs->encrypted = true;
|
||||
bs->valid_key = true;
|
||||
|
||||
ret = 0;
|
||||
cleanup:
|
||||
QDECREF(cryptoopts);
|
||||
qapi_free_QCryptoBlockOpenOptions(open_opts);
|
||||
return ret;
|
||||
}
|
||||
@@ -331,16 +344,13 @@ static int block_crypto_create_generic(QCryptoBlockFormat format,
|
||||
.opts = opts,
|
||||
.filename = filename,
|
||||
};
|
||||
QDict *cryptoopts;
|
||||
|
||||
cryptoopts = qemu_opts_to_qdict(opts, NULL);
|
||||
|
||||
create_opts = block_crypto_create_opts_init(format, cryptoopts, errp);
|
||||
create_opts = block_crypto_create_opts_init(format, opts, errp);
|
||||
if (!create_opts) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
crypto = qcrypto_block_create(create_opts, NULL,
|
||||
crypto = qcrypto_block_create(create_opts,
|
||||
block_crypto_init_func,
|
||||
block_crypto_write_func,
|
||||
&data,
|
||||
@@ -353,15 +363,13 @@ static int block_crypto_create_generic(QCryptoBlockFormat format,
|
||||
|
||||
ret = 0;
|
||||
cleanup:
|
||||
QDECREF(cryptoopts);
|
||||
qcrypto_block_free(crypto);
|
||||
blk_unref(data.blk);
|
||||
qapi_free_QCryptoBlockCreateOptions(create_opts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int block_crypto_truncate(BlockDriverState *bs, int64_t offset,
|
||||
PreallocMode prealloc, Error **errp)
|
||||
static int block_crypto_truncate(BlockDriverState *bs, int64_t offset)
|
||||
{
|
||||
BlockCrypto *crypto = bs->opaque;
|
||||
size_t payload_offset =
|
||||
@@ -369,7 +377,7 @@ static int block_crypto_truncate(BlockDriverState *bs, int64_t offset,
|
||||
|
||||
offset += payload_offset;
|
||||
|
||||
return bdrv_truncate(bs->file, offset, prealloc, errp);
|
||||
return bdrv_truncate(bs->file->bs, offset);
|
||||
}
|
||||
|
||||
static void block_crypto_close(BlockDriverState *bs)
|
||||
@@ -608,7 +616,6 @@ BlockDriver bdrv_crypto_luks = {
|
||||
.bdrv_probe = block_crypto_probe_luks,
|
||||
.bdrv_open = block_crypto_open_luks,
|
||||
.bdrv_close = block_crypto_close,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_create = block_crypto_create_luks,
|
||||
.bdrv_truncate = block_crypto_truncate,
|
||||
.create_opts = &block_crypto_create_opts_luks,
|
||||
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* QEMU block full disk encryption
|
||||
*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BLOCK_CRYPTO_H__
|
||||
#define BLOCK_CRYPTO_H__
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_KEY_SECRET(prefix, helpstr) \
|
||||
{ \
|
||||
.name = prefix BLOCK_CRYPTO_OPT_QCOW_KEY_SECRET, \
|
||||
.type = QEMU_OPT_STRING, \
|
||||
.help = helpstr, \
|
||||
}
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_QCOW_KEY_SECRET "key-secret"
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_QCOW_KEY_SECRET(prefix) \
|
||||
BLOCK_CRYPTO_OPT_DEF_KEY_SECRET(prefix, \
|
||||
"ID of the secret that provides the AES encryption key")
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET "key-secret"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG "cipher-alg"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE "cipher-mode"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG "ivgen-alg"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"
|
||||
#define BLOCK_CRYPTO_OPT_LUKS_ITER_TIME "iter-time"
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(prefix) \
|
||||
BLOCK_CRYPTO_OPT_DEF_KEY_SECRET(prefix, \
|
||||
"ID of the secret that provides the keyslot passphrase")
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG(prefix) \
|
||||
{ \
|
||||
.name = prefix BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG, \
|
||||
.type = QEMU_OPT_STRING, \
|
||||
.help = "Name of encryption cipher algorithm", \
|
||||
}
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE(prefix) \
|
||||
{ \
|
||||
.name = prefix BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE, \
|
||||
.type = QEMU_OPT_STRING, \
|
||||
.help = "Name of encryption cipher mode", \
|
||||
}
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG(prefix) \
|
||||
{ \
|
||||
.name = prefix BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG, \
|
||||
.type = QEMU_OPT_STRING, \
|
||||
.help = "Name of IV generator algorithm", \
|
||||
}
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG(prefix) \
|
||||
{ \
|
||||
.name = prefix BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG, \
|
||||
.type = QEMU_OPT_STRING, \
|
||||
.help = "Name of IV generator hash algorithm", \
|
||||
}
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG(prefix) \
|
||||
{ \
|
||||
.name = prefix BLOCK_CRYPTO_OPT_LUKS_HASH_ALG, \
|
||||
.type = QEMU_OPT_STRING, \
|
||||
.help = "Name of encryption hash algorithm", \
|
||||
}
|
||||
|
||||
#define BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix) \
|
||||
{ \
|
||||
.name = prefix BLOCK_CRYPTO_OPT_LUKS_ITER_TIME, \
|
||||
.type = QEMU_OPT_NUMBER, \
|
||||
.help = "Time to spend in PBKDF in milliseconds", \
|
||||
}
|
||||
|
||||
QCryptoBlockCreateOptions *
|
||||
block_crypto_create_opts_init(QCryptoBlockFormat format,
|
||||
QDict *opts,
|
||||
Error **errp);
|
||||
|
||||
QCryptoBlockOpenOptions *
|
||||
block_crypto_open_opts_init(QCryptoBlockFormat format,
|
||||
QDict *opts,
|
||||
Error **errp);
|
||||
|
||||
#endif /* BLOCK_CRYPTO_H__ */
|
||||
+127
-206
@@ -68,7 +68,8 @@ static CURLMcode __curl_multi_socket_action(CURLM *multi_handle,
|
||||
#endif
|
||||
|
||||
#define PROTOCOLS (CURLPROTO_HTTP | CURLPROTO_HTTPS | \
|
||||
CURLPROTO_FTP | CURLPROTO_FTPS)
|
||||
CURLPROTO_FTP | CURLPROTO_FTPS | \
|
||||
CURLPROTO_TFTP)
|
||||
|
||||
#define CURL_NUM_STATES 8
|
||||
#define CURL_NUM_ACB 8
|
||||
@@ -76,12 +77,15 @@ static CURLMcode __curl_multi_socket_action(CURLM *multi_handle,
|
||||
#define CURL_TIMEOUT_DEFAULT 5
|
||||
#define CURL_TIMEOUT_MAX 10000
|
||||
|
||||
#define FIND_RET_NONE 0
|
||||
#define FIND_RET_OK 1
|
||||
#define FIND_RET_WAIT 2
|
||||
|
||||
#define CURL_BLOCK_OPT_URL "url"
|
||||
#define CURL_BLOCK_OPT_READAHEAD "readahead"
|
||||
#define CURL_BLOCK_OPT_SSLVERIFY "sslverify"
|
||||
#define CURL_BLOCK_OPT_TIMEOUT "timeout"
|
||||
#define CURL_BLOCK_OPT_COOKIE "cookie"
|
||||
#define CURL_BLOCK_OPT_COOKIE_SECRET "cookie-secret"
|
||||
#define CURL_BLOCK_OPT_USERNAME "username"
|
||||
#define CURL_BLOCK_OPT_PASSWORD_SECRET "password-secret"
|
||||
#define CURL_BLOCK_OPT_PROXY_USERNAME "proxy-username"
|
||||
@@ -90,17 +94,15 @@ static CURLMcode __curl_multi_socket_action(CURLM *multi_handle,
|
||||
struct BDRVCURLState;
|
||||
|
||||
typedef struct CURLAIOCB {
|
||||
Coroutine *co;
|
||||
BlockAIOCB common;
|
||||
QEMUBH *bh;
|
||||
QEMUIOVector *qiov;
|
||||
|
||||
uint64_t offset;
|
||||
uint64_t bytes;
|
||||
int ret;
|
||||
int64_t sector_num;
|
||||
int nb_sectors;
|
||||
|
||||
size_t start;
|
||||
size_t end;
|
||||
|
||||
QSIMPLEQ_ENTRY(CURLAIOCB) next;
|
||||
} CURLAIOCB;
|
||||
|
||||
typedef struct CURLSocket {
|
||||
@@ -115,7 +117,7 @@ typedef struct CURLState
|
||||
CURL *curl;
|
||||
QLIST_HEAD(, CURLSocket) sockets;
|
||||
char *orig_buf;
|
||||
uint64_t buf_start;
|
||||
size_t buf_start;
|
||||
size_t buf_off;
|
||||
size_t buf_len;
|
||||
char range[128];
|
||||
@@ -126,7 +128,7 @@ typedef struct CURLState
|
||||
typedef struct BDRVCURLState {
|
||||
CURLM *multi;
|
||||
QEMUTimer timer;
|
||||
uint64_t len;
|
||||
size_t len;
|
||||
CURLState states[CURL_NUM_STATES];
|
||||
char *url;
|
||||
size_t readahead_size;
|
||||
@@ -135,8 +137,6 @@ typedef struct BDRVCURLState {
|
||||
char *cookie;
|
||||
bool accept_range;
|
||||
AioContext *aio_context;
|
||||
QemuMutex mutex;
|
||||
QSIMPLEQ_HEAD(, CURLAIOCB) free_state_waitq;
|
||||
char *username;
|
||||
char *password;
|
||||
char *proxyusername;
|
||||
@@ -148,7 +148,6 @@ static void curl_multi_do(void *arg);
|
||||
static void curl_multi_read(void *arg);
|
||||
|
||||
#ifdef NEED_CURL_TIMER_CALLBACK
|
||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
||||
static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
|
||||
{
|
||||
BDRVCURLState *s = opaque;
|
||||
@@ -165,7 +164,6 @@ static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
||||
static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
|
||||
void *userp, void *sp)
|
||||
{
|
||||
@@ -196,26 +194,25 @@ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
|
||||
switch (action) {
|
||||
case CURL_POLL_IN:
|
||||
aio_set_fd_handler(s->aio_context, fd, false,
|
||||
curl_multi_read, NULL, NULL, state);
|
||||
curl_multi_read, NULL, state);
|
||||
break;
|
||||
case CURL_POLL_OUT:
|
||||
aio_set_fd_handler(s->aio_context, fd, false,
|
||||
NULL, curl_multi_do, NULL, state);
|
||||
NULL, curl_multi_do, state);
|
||||
break;
|
||||
case CURL_POLL_INOUT:
|
||||
aio_set_fd_handler(s->aio_context, fd, false,
|
||||
curl_multi_read, curl_multi_do, NULL, state);
|
||||
curl_multi_read, curl_multi_do, state);
|
||||
break;
|
||||
case CURL_POLL_REMOVE:
|
||||
aio_set_fd_handler(s->aio_context, fd, false,
|
||||
NULL, NULL, NULL, NULL);
|
||||
NULL, NULL, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
||||
static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||
{
|
||||
BDRVCURLState *s = opaque;
|
||||
@@ -230,7 +227,6 @@ static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||
return realsize;
|
||||
}
|
||||
|
||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
||||
static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||
{
|
||||
CURLState *s = ((CURLState*)opaque);
|
||||
@@ -258,7 +254,7 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||
continue;
|
||||
|
||||
if ((s->buf_off >= acb->end)) {
|
||||
size_t request_length = acb->bytes;
|
||||
size_t request_length = acb->nb_sectors * BDRV_SECTOR_SIZE;
|
||||
|
||||
qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
|
||||
acb->end - acb->start);
|
||||
@@ -269,11 +265,9 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||
request_length - offset);
|
||||
}
|
||||
|
||||
acb->ret = 0;
|
||||
acb->common.cb(acb->common.opaque, 0);
|
||||
qemu_aio_unref(acb);
|
||||
s->acb[i] = NULL;
|
||||
qemu_mutex_unlock(&s->s->mutex);
|
||||
aio_co_wake(acb->co);
|
||||
qemu_mutex_lock(&s->s->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,19 +276,18 @@ read_end:
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
/* Called with s->mutex held. */
|
||||
static bool curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len,
|
||||
CURLAIOCB *acb)
|
||||
static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len,
|
||||
CURLAIOCB *acb)
|
||||
{
|
||||
int i;
|
||||
uint64_t end = start + len;
|
||||
uint64_t clamped_end = MIN(end, s->len);
|
||||
uint64_t clamped_len = clamped_end - start;
|
||||
size_t end = start + len;
|
||||
size_t clamped_end = MIN(end, s->len);
|
||||
size_t clamped_len = clamped_end - start;
|
||||
|
||||
for (i=0; i<CURL_NUM_STATES; i++) {
|
||||
CURLState *state = &s->states[i];
|
||||
uint64_t buf_end = (state->buf_start + state->buf_off);
|
||||
uint64_t buf_fend = (state->buf_start + state->buf_len);
|
||||
size_t buf_end = (state->buf_start + state->buf_off);
|
||||
size_t buf_fend = (state->buf_start + state->buf_len);
|
||||
|
||||
if (!state->orig_buf)
|
||||
continue;
|
||||
@@ -313,8 +306,9 @@ static bool curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len,
|
||||
if (clamped_len < len) {
|
||||
qemu_iovec_memset(acb->qiov, clamped_len, 0, len - clamped_len);
|
||||
}
|
||||
acb->ret = 0;
|
||||
return true;
|
||||
acb->common.cb(acb->common.opaque, 0);
|
||||
|
||||
return FIND_RET_OK;
|
||||
}
|
||||
|
||||
// Wait for unfinished chunks
|
||||
@@ -332,16 +326,15 @@ static bool curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len,
|
||||
for (j=0; j<CURL_NUM_ACB; j++) {
|
||||
if (!state->acb[j]) {
|
||||
state->acb[j] = acb;
|
||||
return true;
|
||||
return FIND_RET_WAIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return FIND_RET_NONE;
|
||||
}
|
||||
|
||||
/* Called with s->mutex held. */
|
||||
static void curl_multi_check_completion(BDRVCURLState *s)
|
||||
{
|
||||
int msgs_in_queue;
|
||||
@@ -383,11 +376,9 @@ static void curl_multi_check_completion(BDRVCURLState *s)
|
||||
continue;
|
||||
}
|
||||
|
||||
acb->ret = -EIO;
|
||||
acb->common.cb(acb->common.opaque, -EPROTO);
|
||||
qemu_aio_unref(acb);
|
||||
state->acb[i] = NULL;
|
||||
qemu_mutex_unlock(&s->mutex);
|
||||
aio_co_wake(acb->co);
|
||||
qemu_mutex_lock(&s->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,9 +388,9 @@ static void curl_multi_check_completion(BDRVCURLState *s)
|
||||
}
|
||||
}
|
||||
|
||||
/* Called with s->mutex held. */
|
||||
static void curl_multi_do_locked(CURLState *s)
|
||||
static void curl_multi_do(void *arg)
|
||||
{
|
||||
CURLState *s = (CURLState *)arg;
|
||||
CURLSocket *socket, *next_socket;
|
||||
int running;
|
||||
int r;
|
||||
@@ -417,23 +408,12 @@ static void curl_multi_do_locked(CURLState *s)
|
||||
}
|
||||
}
|
||||
|
||||
static void curl_multi_do(void *arg)
|
||||
{
|
||||
CURLState *s = (CURLState *)arg;
|
||||
|
||||
qemu_mutex_lock(&s->s->mutex);
|
||||
curl_multi_do_locked(s);
|
||||
qemu_mutex_unlock(&s->s->mutex);
|
||||
}
|
||||
|
||||
static void curl_multi_read(void *arg)
|
||||
{
|
||||
CURLState *s = (CURLState *)arg;
|
||||
|
||||
qemu_mutex_lock(&s->s->mutex);
|
||||
curl_multi_do_locked(s);
|
||||
curl_multi_do(arg);
|
||||
curl_multi_check_completion(s->s);
|
||||
qemu_mutex_unlock(&s->s->mutex);
|
||||
}
|
||||
|
||||
static void curl_multi_timeout_do(void *arg)
|
||||
@@ -446,38 +426,40 @@ static void curl_multi_timeout_do(void *arg)
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_mutex_lock(&s->mutex);
|
||||
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
|
||||
|
||||
curl_multi_check_completion(s);
|
||||
qemu_mutex_unlock(&s->mutex);
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Called with s->mutex held. */
|
||||
static CURLState *curl_find_state(BDRVCURLState *s)
|
||||
static CURLState *curl_init_state(BlockDriverState *bs, BDRVCURLState *s)
|
||||
{
|
||||
CURLState *state = NULL;
|
||||
int i;
|
||||
int i, j;
|
||||
|
||||
do {
|
||||
for (i=0; i<CURL_NUM_STATES; i++) {
|
||||
for (j=0; j<CURL_NUM_ACB; j++)
|
||||
if (s->states[i].acb[j])
|
||||
continue;
|
||||
if (s->states[i].in_use)
|
||||
continue;
|
||||
|
||||
for (i = 0; i < CURL_NUM_STATES; i++) {
|
||||
if (!s->states[i].in_use) {
|
||||
state = &s->states[i];
|
||||
state->in_use = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
if (!state) {
|
||||
aio_poll(bdrv_get_aio_context(bs), true);
|
||||
}
|
||||
} while(!state);
|
||||
|
||||
static int curl_init_state(BDRVCURLState *s, CURLState *state)
|
||||
{
|
||||
if (!state->curl) {
|
||||
state->curl = curl_easy_init();
|
||||
if (!state->curl) {
|
||||
return -EIO;
|
||||
return NULL;
|
||||
}
|
||||
curl_easy_setopt(state->curl, CURLOPT_URL, s->url);
|
||||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER,
|
||||
@@ -530,18 +512,11 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
|
||||
QLIST_INIT(&state->sockets);
|
||||
state->s = s;
|
||||
|
||||
return 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
/* Called with s->mutex held. */
|
||||
static void curl_clean_state(CURLState *s)
|
||||
{
|
||||
CURLAIOCB *next;
|
||||
int j;
|
||||
for (j = 0; j < CURL_NUM_ACB; j++) {
|
||||
assert(!s->acb[j]);
|
||||
}
|
||||
|
||||
if (s->s->multi)
|
||||
curl_multi_remove_handle(s->s->multi, s->curl);
|
||||
|
||||
@@ -553,20 +528,12 @@ static void curl_clean_state(CURLState *s)
|
||||
}
|
||||
|
||||
s->in_use = 0;
|
||||
|
||||
next = QSIMPLEQ_FIRST(&s->s->free_state_waitq);
|
||||
if (next) {
|
||||
QSIMPLEQ_REMOVE_HEAD(&s->s->free_state_waitq, next);
|
||||
qemu_mutex_unlock(&s->s->mutex);
|
||||
aio_co_wake(next->co);
|
||||
qemu_mutex_lock(&s->s->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void curl_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
qdict_put_str(options, CURL_BLOCK_OPT_URL, filename);
|
||||
qdict_put(options, CURL_BLOCK_OPT_URL, qstring_from_str(filename));
|
||||
}
|
||||
|
||||
static void curl_detach_aio_context(BlockDriverState *bs)
|
||||
@@ -574,7 +541,6 @@ static void curl_detach_aio_context(BlockDriverState *bs)
|
||||
BDRVCURLState *s = bs->opaque;
|
||||
int i;
|
||||
|
||||
qemu_mutex_lock(&s->mutex);
|
||||
for (i = 0; i < CURL_NUM_STATES; i++) {
|
||||
if (s->states[i].in_use) {
|
||||
curl_clean_state(&s->states[i]);
|
||||
@@ -590,7 +556,6 @@ static void curl_detach_aio_context(BlockDriverState *bs)
|
||||
curl_multi_cleanup(s->multi);
|
||||
s->multi = NULL;
|
||||
}
|
||||
qemu_mutex_unlock(&s->mutex);
|
||||
|
||||
timer_del(&s->timer);
|
||||
}
|
||||
@@ -643,11 +608,6 @@ static QemuOptsList runtime_opts = {
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Pass the cookie or list of cookies with each request"
|
||||
},
|
||||
{
|
||||
.name = CURL_BLOCK_OPT_COOKIE_SECRET,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "ID of secret used as cookie passed with each request"
|
||||
},
|
||||
{
|
||||
.name = CURL_BLOCK_OPT_USERNAME,
|
||||
.type = QEMU_OPT_STRING,
|
||||
@@ -682,10 +642,8 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error *local_err = NULL;
|
||||
const char *file;
|
||||
const char *cookie;
|
||||
const char *cookie_secret;
|
||||
double d;
|
||||
const char *secretid;
|
||||
const char *protocol_delimiter;
|
||||
|
||||
static int inited = 0;
|
||||
|
||||
@@ -694,7 +652,6 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
return -EROFS;
|
||||
}
|
||||
|
||||
qemu_mutex_init(&s->mutex);
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
@@ -720,22 +677,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
s->sslverify = qemu_opt_get_bool(opts, CURL_BLOCK_OPT_SSLVERIFY, true);
|
||||
|
||||
cookie = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE);
|
||||
cookie_secret = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE_SECRET);
|
||||
|
||||
if (cookie && cookie_secret) {
|
||||
error_setg(errp,
|
||||
"curl driver cannot handle both cookie and cookie secret");
|
||||
goto out_noclean;
|
||||
}
|
||||
|
||||
if (cookie_secret) {
|
||||
s->cookie = qcrypto_secret_lookup_as_utf8(cookie_secret, errp);
|
||||
if (!s->cookie) {
|
||||
goto out_noclean;
|
||||
}
|
||||
} else {
|
||||
s->cookie = g_strdup(cookie);
|
||||
}
|
||||
s->cookie = g_strdup(cookie);
|
||||
|
||||
file = qemu_opt_get(opts, CURL_BLOCK_OPT_URL);
|
||||
if (file == NULL) {
|
||||
@@ -743,15 +685,6 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto out_noclean;
|
||||
}
|
||||
|
||||
if (!strstart(file, bs->drv->protocol_name, &protocol_delimiter) ||
|
||||
!strstart(protocol_delimiter, "://", NULL))
|
||||
{
|
||||
error_setg(errp, "%s curl driver cannot handle the URL '%s' (does not "
|
||||
"start with '%s://')", bs->drv->protocol_name, file,
|
||||
bs->drv->protocol_name);
|
||||
goto out_noclean;
|
||||
}
|
||||
|
||||
s->username = g_strdup(qemu_opt_get(opts, CURL_BLOCK_OPT_USERNAME));
|
||||
secretid = qemu_opt_get(opts, CURL_BLOCK_OPT_PASSWORD_SECRET);
|
||||
|
||||
@@ -778,22 +711,14 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
}
|
||||
|
||||
DPRINTF("CURL: Opening %s\n", file);
|
||||
QSIMPLEQ_INIT(&s->free_state_waitq);
|
||||
s->aio_context = bdrv_get_aio_context(bs);
|
||||
s->url = g_strdup(file);
|
||||
qemu_mutex_lock(&s->mutex);
|
||||
state = curl_find_state(s);
|
||||
qemu_mutex_unlock(&s->mutex);
|
||||
if (!state) {
|
||||
state = curl_init_state(bs, s);
|
||||
if (!state)
|
||||
goto out_noclean;
|
||||
}
|
||||
|
||||
// Get file size
|
||||
|
||||
if (curl_init_state(s, state) < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->accept_range = false;
|
||||
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1);
|
||||
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION,
|
||||
@@ -801,28 +726,11 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s);
|
||||
if (curl_easy_perform(state->curl))
|
||||
goto out;
|
||||
if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d)) {
|
||||
curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d);
|
||||
if (d)
|
||||
s->len = (size_t)d;
|
||||
else if(!s->len)
|
||||
goto out;
|
||||
}
|
||||
/* Prior CURL 7.19.4 return value of 0 could mean that the file size is not
|
||||
* know or the size is zero. From 7.19.4 CURL returns -1 if size is not
|
||||
* known and zero if it is realy zero-length file. */
|
||||
#if LIBCURL_VERSION_NUM >= 0x071304
|
||||
if (d < 0) {
|
||||
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
|
||||
"Server didn't report file size.");
|
||||
goto out;
|
||||
}
|
||||
#else
|
||||
if (d <= 0) {
|
||||
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
|
||||
"Unknown file size or zero-length file.");
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
s->len = d;
|
||||
|
||||
if ((!strncasecmp(s->url, "http://", strlen("http://"))
|
||||
|| !strncasecmp(s->url, "https://", strlen("https://")))
|
||||
&& !s->accept_range) {
|
||||
@@ -830,11 +738,9 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
"Server does not support 'range' (byte ranges).");
|
||||
goto out;
|
||||
}
|
||||
DPRINTF("CURL: Size = %" PRIu64 "\n", s->len);
|
||||
DPRINTF("CURL: Size = %zd\n", s->len);
|
||||
|
||||
qemu_mutex_lock(&s->mutex);
|
||||
curl_clean_state(state);
|
||||
qemu_mutex_unlock(&s->mutex);
|
||||
curl_easy_cleanup(state->curl);
|
||||
state->curl = NULL;
|
||||
|
||||
@@ -848,51 +754,53 @@ out:
|
||||
curl_easy_cleanup(state->curl);
|
||||
state->curl = NULL;
|
||||
out_noclean:
|
||||
qemu_mutex_destroy(&s->mutex);
|
||||
g_free(s->cookie);
|
||||
g_free(s->url);
|
||||
qemu_opts_del(opts);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void curl_setup_preadv(BlockDriverState *bs, CURLAIOCB *acb)
|
||||
static const AIOCBInfo curl_aiocb_info = {
|
||||
.aiocb_size = sizeof(CURLAIOCB),
|
||||
};
|
||||
|
||||
|
||||
static void curl_readv_bh_cb(void *p)
|
||||
{
|
||||
CURLState *state;
|
||||
int running;
|
||||
|
||||
BDRVCURLState *s = bs->opaque;
|
||||
CURLAIOCB *acb = p;
|
||||
BDRVCURLState *s = acb->common.bs->opaque;
|
||||
|
||||
uint64_t start = acb->offset;
|
||||
uint64_t end;
|
||||
qemu_bh_delete(acb->bh);
|
||||
acb->bh = NULL;
|
||||
|
||||
qemu_mutex_lock(&s->mutex);
|
||||
size_t start = acb->sector_num * BDRV_SECTOR_SIZE;
|
||||
size_t end;
|
||||
|
||||
// In case we have the requested data already (e.g. read-ahead),
|
||||
// we can just call the callback and be done.
|
||||
if (curl_find_buf(s, start, acb->bytes, acb)) {
|
||||
goto out;
|
||||
switch (curl_find_buf(s, start, acb->nb_sectors * BDRV_SECTOR_SIZE, acb)) {
|
||||
case FIND_RET_OK:
|
||||
qemu_aio_unref(acb);
|
||||
// fall through
|
||||
case FIND_RET_WAIT:
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// No cache found, so let's start a new request
|
||||
for (;;) {
|
||||
state = curl_find_state(s);
|
||||
if (state) {
|
||||
break;
|
||||
}
|
||||
QSIMPLEQ_INSERT_TAIL(&s->free_state_waitq, acb, next);
|
||||
qemu_mutex_unlock(&s->mutex);
|
||||
qemu_coroutine_yield();
|
||||
qemu_mutex_lock(&s->mutex);
|
||||
}
|
||||
|
||||
if (curl_init_state(s, state) < 0) {
|
||||
curl_clean_state(state);
|
||||
acb->ret = -EIO;
|
||||
goto out;
|
||||
state = curl_init_state(acb->common.bs, s);
|
||||
if (!state) {
|
||||
acb->common.cb(acb->common.opaque, -EIO);
|
||||
qemu_aio_unref(acb);
|
||||
return;
|
||||
}
|
||||
|
||||
acb->start = 0;
|
||||
acb->end = MIN(acb->bytes, s->len - start);
|
||||
acb->end = MIN(acb->nb_sectors * BDRV_SECTOR_SIZE, s->len - start);
|
||||
|
||||
state->buf_off = 0;
|
||||
g_free(state->orig_buf);
|
||||
@@ -902,41 +810,38 @@ static void curl_setup_preadv(BlockDriverState *bs, CURLAIOCB *acb)
|
||||
state->orig_buf = g_try_malloc(state->buf_len);
|
||||
if (state->buf_len && state->orig_buf == NULL) {
|
||||
curl_clean_state(state);
|
||||
acb->ret = -ENOMEM;
|
||||
goto out;
|
||||
acb->common.cb(acb->common.opaque, -ENOMEM);
|
||||
qemu_aio_unref(acb);
|
||||
return;
|
||||
}
|
||||
state->acb[0] = acb;
|
||||
|
||||
snprintf(state->range, 127, "%" PRIu64 "-%" PRIu64, start, end);
|
||||
DPRINTF("CURL (AIO): Reading %" PRIu64 " at %" PRIu64 " (%s)\n",
|
||||
acb->bytes, start, state->range);
|
||||
snprintf(state->range, 127, "%zd-%zd", start, end);
|
||||
DPRINTF("CURL (AIO): Reading %llu at %zd (%s)\n",
|
||||
(acb->nb_sectors * BDRV_SECTOR_SIZE), start, state->range);
|
||||
curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range);
|
||||
|
||||
curl_multi_add_handle(s->multi, state->curl);
|
||||
|
||||
/* Tell curl it needs to kick things off */
|
||||
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
|
||||
|
||||
out:
|
||||
qemu_mutex_unlock(&s->mutex);
|
||||
}
|
||||
|
||||
static int coroutine_fn curl_co_preadv(BlockDriverState *bs,
|
||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
static BlockAIOCB *curl_aio_readv(BlockDriverState *bs,
|
||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
CURLAIOCB acb = {
|
||||
.co = qemu_coroutine_self(),
|
||||
.ret = -EINPROGRESS,
|
||||
.qiov = qiov,
|
||||
.offset = offset,
|
||||
.bytes = bytes
|
||||
};
|
||||
CURLAIOCB *acb;
|
||||
|
||||
curl_setup_preadv(bs, &acb);
|
||||
while (acb.ret == -EINPROGRESS) {
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
return acb.ret;
|
||||
acb = qemu_aio_get(&curl_aiocb_info, bs, cb, opaque);
|
||||
|
||||
acb->qiov = qiov;
|
||||
acb->sector_num = sector_num;
|
||||
acb->nb_sectors = nb_sectors;
|
||||
|
||||
acb->bh = aio_bh_new(bdrv_get_aio_context(bs), curl_readv_bh_cb, acb);
|
||||
qemu_bh_schedule(acb->bh);
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static void curl_close(BlockDriverState *bs)
|
||||
@@ -945,7 +850,6 @@ static void curl_close(BlockDriverState *bs)
|
||||
|
||||
DPRINTF("CURL: Close\n");
|
||||
curl_detach_aio_context(bs);
|
||||
qemu_mutex_destroy(&s->mutex);
|
||||
|
||||
g_free(s->cookie);
|
||||
g_free(s->url);
|
||||
@@ -967,7 +871,7 @@ static BlockDriver bdrv_http = {
|
||||
.bdrv_close = curl_close,
|
||||
.bdrv_getlength = curl_getlength,
|
||||
|
||||
.bdrv_co_preadv = curl_co_preadv,
|
||||
.bdrv_aio_readv = curl_aio_readv,
|
||||
|
||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||
@@ -983,7 +887,7 @@ static BlockDriver bdrv_https = {
|
||||
.bdrv_close = curl_close,
|
||||
.bdrv_getlength = curl_getlength,
|
||||
|
||||
.bdrv_co_preadv = curl_co_preadv,
|
||||
.bdrv_aio_readv = curl_aio_readv,
|
||||
|
||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||
@@ -999,7 +903,7 @@ static BlockDriver bdrv_ftp = {
|
||||
.bdrv_close = curl_close,
|
||||
.bdrv_getlength = curl_getlength,
|
||||
|
||||
.bdrv_co_preadv = curl_co_preadv,
|
||||
.bdrv_aio_readv = curl_aio_readv,
|
||||
|
||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||
@@ -1015,7 +919,23 @@ static BlockDriver bdrv_ftps = {
|
||||
.bdrv_close = curl_close,
|
||||
.bdrv_getlength = curl_getlength,
|
||||
|
||||
.bdrv_co_preadv = curl_co_preadv,
|
||||
.bdrv_aio_readv = curl_aio_readv,
|
||||
|
||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||
};
|
||||
|
||||
static BlockDriver bdrv_tftp = {
|
||||
.format_name = "tftp",
|
||||
.protocol_name = "tftp",
|
||||
|
||||
.instance_size = sizeof(BDRVCURLState),
|
||||
.bdrv_parse_filename = curl_parse_filename,
|
||||
.bdrv_file_open = curl_open,
|
||||
.bdrv_close = curl_close,
|
||||
.bdrv_getlength = curl_getlength,
|
||||
|
||||
.bdrv_aio_readv = curl_aio_readv,
|
||||
|
||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||
@@ -1027,6 +947,7 @@ static void curl_block_init(void)
|
||||
bdrv_register(&bdrv_https);
|
||||
bdrv_register(&bdrv_ftp);
|
||||
bdrv_register(&bdrv_ftps);
|
||||
bdrv_register(&bdrv_tftp);
|
||||
}
|
||||
|
||||
block_init(curl_block_init);
|
||||
+21
-401
@@ -37,53 +37,14 @@
|
||||
* or enabled. A frozen bitmap can only abdicate() or reclaim().
|
||||
*/
|
||||
struct BdrvDirtyBitmap {
|
||||
QemuMutex *mutex;
|
||||
HBitmap *bitmap; /* Dirty sector bitmap implementation */
|
||||
HBitmap *meta; /* Meta dirty bitmap */
|
||||
BdrvDirtyBitmap *successor; /* Anonymous child; implies frozen status */
|
||||
char *name; /* Optional non-empty unique ID */
|
||||
int64_t size; /* Size of the bitmap (Number of sectors) */
|
||||
bool disabled; /* Bitmap is disabled. It ignores all writes to
|
||||
the device */
|
||||
int active_iterators; /* How many iterators are active */
|
||||
bool readonly; /* Bitmap is read-only. This field also
|
||||
prevents the respective image from being
|
||||
modified (i.e. blocks writes and discards).
|
||||
Such operations must fail and both the image
|
||||
and this bitmap must remain unchanged while
|
||||
this flag is set. */
|
||||
bool autoload; /* For persistent bitmaps: bitmap must be
|
||||
autoloaded on image opening */
|
||||
bool persistent; /* bitmap must be saved to owner disk image */
|
||||
bool disabled; /* Bitmap is read-only */
|
||||
QLIST_ENTRY(BdrvDirtyBitmap) list;
|
||||
};
|
||||
|
||||
struct BdrvDirtyBitmapIter {
|
||||
HBitmapIter hbi;
|
||||
BdrvDirtyBitmap *bitmap;
|
||||
};
|
||||
|
||||
static inline void bdrv_dirty_bitmaps_lock(BlockDriverState *bs)
|
||||
{
|
||||
qemu_mutex_lock(&bs->dirty_bitmap_mutex);
|
||||
}
|
||||
|
||||
static inline void bdrv_dirty_bitmaps_unlock(BlockDriverState *bs)
|
||||
{
|
||||
qemu_mutex_unlock(&bs->dirty_bitmap_mutex);
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_lock(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_unlock(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
}
|
||||
|
||||
/* Called with BQL or dirty_bitmap lock taken. */
|
||||
BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs, const char *name)
|
||||
{
|
||||
BdrvDirtyBitmap *bm;
|
||||
@@ -97,17 +58,13 @@ BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs, const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
||||
g_free(bitmap->name);
|
||||
bitmap->name = NULL;
|
||||
bitmap->persistent = false;
|
||||
bitmap->autoload = false;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
|
||||
uint32_t granularity,
|
||||
const char *name,
|
||||
@@ -132,109 +89,24 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
|
||||
return NULL;
|
||||
}
|
||||
bitmap = g_new0(BdrvDirtyBitmap, 1);
|
||||
bitmap->mutex = &bs->dirty_bitmap_mutex;
|
||||
bitmap->bitmap = hbitmap_alloc(bitmap_size, ctz32(sector_granularity));
|
||||
bitmap->size = bitmap_size;
|
||||
bitmap->name = g_strdup(name);
|
||||
bitmap->disabled = false;
|
||||
bdrv_dirty_bitmaps_lock(bs);
|
||||
QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
|
||||
bdrv_dirty_bitmaps_unlock(bs);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/* bdrv_create_meta_dirty_bitmap
|
||||
*
|
||||
* Create a meta dirty bitmap that tracks the changes of bits in @bitmap. I.e.
|
||||
* when a dirty status bit in @bitmap is changed (either from reset to set or
|
||||
* the other way around), its respective meta dirty bitmap bit will be marked
|
||||
* dirty as well.
|
||||
*
|
||||
* @bitmap: the block dirty bitmap for which to create a meta dirty bitmap.
|
||||
* @chunk_size: how many bytes of bitmap data does each bit in the meta bitmap
|
||||
* track.
|
||||
*/
|
||||
void bdrv_create_meta_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
||||
int chunk_size)
|
||||
{
|
||||
assert(!bitmap->meta);
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
bitmap->meta = hbitmap_create_meta(bitmap->bitmap,
|
||||
chunk_size * BITS_PER_BYTE);
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
}
|
||||
|
||||
void bdrv_release_meta_dirty_bitmap(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
assert(bitmap->meta);
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
hbitmap_free_meta(bitmap->bitmap);
|
||||
bitmap->meta = NULL;
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
}
|
||||
|
||||
int bdrv_dirty_bitmap_get_meta_locked(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *bitmap, int64_t sector,
|
||||
int nb_sectors)
|
||||
{
|
||||
uint64_t i;
|
||||
int sectors_per_bit = 1 << hbitmap_granularity(bitmap->meta);
|
||||
|
||||
/* To optimize: we can make hbitmap to internally check the range in a
|
||||
* coarse level, or at least do it word by word. */
|
||||
for (i = sector; i < sector + nb_sectors; i += sectors_per_bit) {
|
||||
if (hbitmap_get(bitmap->meta, i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int bdrv_dirty_bitmap_get_meta(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *bitmap, int64_t sector,
|
||||
int nb_sectors)
|
||||
{
|
||||
bool dirty;
|
||||
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
dirty = bdrv_dirty_bitmap_get_meta_locked(bs, bitmap, sector, nb_sectors);
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_reset_meta(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *bitmap, int64_t sector,
|
||||
int nb_sectors)
|
||||
{
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
hbitmap_reset(bitmap->meta, sector, nb_sectors);
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
}
|
||||
|
||||
int64_t bdrv_dirty_bitmap_size(const BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return bitmap->size;
|
||||
}
|
||||
|
||||
const char *bdrv_dirty_bitmap_name(const BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return bitmap->name;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return bitmap->successor;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return !(bitmap->disabled || bitmap->successor);
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
DirtyBitmapStatus bdrv_dirty_bitmap_status(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
if (bdrv_dirty_bitmap_frozen(bitmap)) {
|
||||
@@ -249,7 +121,6 @@ DirtyBitmapStatus bdrv_dirty_bitmap_status(BdrvDirtyBitmap *bitmap)
|
||||
/**
|
||||
* Create a successor bitmap destined to replace this bitmap after an operation.
|
||||
* Requires that the bitmap is not frozen and has no successor.
|
||||
* Called with BQL taken.
|
||||
*/
|
||||
int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *bitmap, Error **errp)
|
||||
@@ -282,7 +153,6 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
|
||||
/**
|
||||
* For a bitmap with a successor, yield our name to the successor,
|
||||
* delete the old bitmap, and return a handle to the new bitmap.
|
||||
* Called with BQL taken.
|
||||
*/
|
||||
BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *bitmap,
|
||||
@@ -301,10 +171,6 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
|
||||
bitmap->name = NULL;
|
||||
successor->name = name;
|
||||
bitmap->successor = NULL;
|
||||
successor->persistent = bitmap->persistent;
|
||||
bitmap->persistent = false;
|
||||
successor->autoload = bitmap->autoload;
|
||||
bitmap->autoload = false;
|
||||
bdrv_release_dirty_bitmap(bs, bitmap);
|
||||
|
||||
return successor;
|
||||
@@ -314,7 +180,6 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
|
||||
* In cases of failure where we can no longer safely delete the parent,
|
||||
* we may wish to re-join the parent and child/successor.
|
||||
* The merged parent will be un-frozen, but not explicitly re-enabled.
|
||||
* Called with BQL taken.
|
||||
*/
|
||||
BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *parent,
|
||||
@@ -339,112 +204,59 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
|
||||
|
||||
/**
|
||||
* Truncates _all_ bitmaps attached to a BDS.
|
||||
* Called with BQL taken.
|
||||
*/
|
||||
void bdrv_dirty_bitmap_truncate(BlockDriverState *bs)
|
||||
{
|
||||
BdrvDirtyBitmap *bitmap;
|
||||
uint64_t size = bdrv_nb_sectors(bs);
|
||||
|
||||
bdrv_dirty_bitmaps_lock(bs);
|
||||
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
||||
assert(!bitmap->active_iterators);
|
||||
hbitmap_truncate(bitmap->bitmap, size);
|
||||
bitmap->size = size;
|
||||
}
|
||||
bdrv_dirty_bitmaps_unlock(bs);
|
||||
}
|
||||
|
||||
static bool bdrv_dirty_bitmap_has_name(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return !!bdrv_dirty_bitmap_name(bitmap);
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
static void bdrv_do_release_matching_dirty_bitmap(
|
||||
BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
||||
bool (*cond)(BdrvDirtyBitmap *bitmap))
|
||||
static void bdrv_do_release_matching_dirty_bitmap(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *bitmap,
|
||||
bool only_named)
|
||||
{
|
||||
BdrvDirtyBitmap *bm, *next;
|
||||
bdrv_dirty_bitmaps_lock(bs);
|
||||
QLIST_FOREACH_SAFE(bm, &bs->dirty_bitmaps, list, next) {
|
||||
if ((!bitmap || bm == bitmap) && (!cond || cond(bm))) {
|
||||
assert(!bm->active_iterators);
|
||||
if ((!bitmap || bm == bitmap) && (!only_named || bm->name)) {
|
||||
assert(!bdrv_dirty_bitmap_frozen(bm));
|
||||
assert(!bm->meta);
|
||||
QLIST_REMOVE(bm, list);
|
||||
hbitmap_free(bm->bitmap);
|
||||
g_free(bm->name);
|
||||
g_free(bm);
|
||||
|
||||
if (bitmap) {
|
||||
goto out;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitmap) {
|
||||
abort();
|
||||
}
|
||||
|
||||
out:
|
||||
bdrv_dirty_bitmaps_unlock(bs);
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
bdrv_do_release_matching_dirty_bitmap(bs, bitmap, NULL);
|
||||
bdrv_do_release_matching_dirty_bitmap(bs, bitmap, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all named dirty bitmaps attached to a BDS (for use in bdrv_close()).
|
||||
* There must not be any frozen bitmaps attached.
|
||||
* This function does not remove persistent bitmaps from the storage.
|
||||
* Called with BQL taken.
|
||||
*/
|
||||
void bdrv_release_named_dirty_bitmaps(BlockDriverState *bs)
|
||||
{
|
||||
bdrv_do_release_matching_dirty_bitmap(bs, NULL, bdrv_dirty_bitmap_has_name);
|
||||
bdrv_do_release_matching_dirty_bitmap(bs, NULL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all persistent dirty bitmaps attached to a BDS (for use in
|
||||
* bdrv_inactivate_recurse()).
|
||||
* There must not be any frozen bitmaps attached.
|
||||
* This function does not remove persistent bitmaps from the storage.
|
||||
*/
|
||||
void bdrv_release_persistent_dirty_bitmaps(BlockDriverState *bs)
|
||||
{
|
||||
bdrv_do_release_matching_dirty_bitmap(bs, NULL,
|
||||
bdrv_dirty_bitmap_get_persistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove persistent dirty bitmap from the storage if it exists.
|
||||
* Absence of bitmap is not an error, because we have the following scenario:
|
||||
* BdrvDirtyBitmap can have .persistent = true but not yet saved and have no
|
||||
* stored version. For such bitmap bdrv_remove_persistent_dirty_bitmap() should
|
||||
* not fail.
|
||||
* This function doesn't release corresponding BdrvDirtyBitmap.
|
||||
*/
|
||||
void bdrv_remove_persistent_dirty_bitmap(BlockDriverState *bs,
|
||||
const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
if (bs->drv && bs->drv->bdrv_remove_persistent_dirty_bitmap) {
|
||||
bs->drv->bdrv_remove_persistent_dirty_bitmap(bs, name, errp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
void bdrv_disable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
||||
bitmap->disabled = true;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
void bdrv_enable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
||||
@@ -457,11 +269,10 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
|
||||
BlockDirtyInfoList *list = NULL;
|
||||
BlockDirtyInfoList **plist = &list;
|
||||
|
||||
bdrv_dirty_bitmaps_lock(bs);
|
||||
QLIST_FOREACH(bm, &bs->dirty_bitmaps, list) {
|
||||
BlockDirtyInfo *info = g_new0(BlockDirtyInfo, 1);
|
||||
BlockDirtyInfoList *entry = g_new0(BlockDirtyInfoList, 1);
|
||||
info->count = bdrv_get_dirty_count(bm) << BDRV_SECTOR_BITS;
|
||||
info->count = bdrv_get_dirty_count(bm);
|
||||
info->granularity = bdrv_dirty_bitmap_granularity(bm);
|
||||
info->has_name = !!bm->name;
|
||||
info->name = g_strdup(bm->name);
|
||||
@@ -470,14 +281,12 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
|
||||
*plist = entry;
|
||||
plist = &entry->next;
|
||||
}
|
||||
bdrv_dirty_bitmaps_unlock(bs);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/* Called within bdrv_dirty_bitmap_lock..unlock */
|
||||
int bdrv_get_dirty_locked(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
||||
int64_t sector)
|
||||
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
||||
int64_t sector)
|
||||
{
|
||||
if (bitmap) {
|
||||
return hbitmap_get(bitmap->bitmap, sector);
|
||||
@@ -506,89 +315,33 @@ uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs)
|
||||
return granularity;
|
||||
}
|
||||
|
||||
uint32_t bdrv_dirty_bitmap_granularity(const BdrvDirtyBitmap *bitmap)
|
||||
uint32_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return BDRV_SECTOR_SIZE << hbitmap_granularity(bitmap->bitmap);
|
||||
}
|
||||
|
||||
uint32_t bdrv_dirty_bitmap_meta_granularity(BdrvDirtyBitmap *bitmap)
|
||||
void bdrv_dirty_iter_init(BdrvDirtyBitmap *bitmap, HBitmapIter *hbi)
|
||||
{
|
||||
return BDRV_SECTOR_SIZE << hbitmap_granularity(bitmap->meta);
|
||||
}
|
||||
|
||||
BdrvDirtyBitmapIter *bdrv_dirty_iter_new(BdrvDirtyBitmap *bitmap,
|
||||
uint64_t first_sector)
|
||||
{
|
||||
BdrvDirtyBitmapIter *iter = g_new(BdrvDirtyBitmapIter, 1);
|
||||
hbitmap_iter_init(&iter->hbi, bitmap->bitmap, first_sector);
|
||||
iter->bitmap = bitmap;
|
||||
bitmap->active_iterators++;
|
||||
return iter;
|
||||
}
|
||||
|
||||
BdrvDirtyBitmapIter *bdrv_dirty_meta_iter_new(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
BdrvDirtyBitmapIter *iter = g_new(BdrvDirtyBitmapIter, 1);
|
||||
hbitmap_iter_init(&iter->hbi, bitmap->meta, 0);
|
||||
iter->bitmap = bitmap;
|
||||
bitmap->active_iterators++;
|
||||
return iter;
|
||||
}
|
||||
|
||||
void bdrv_dirty_iter_free(BdrvDirtyBitmapIter *iter)
|
||||
{
|
||||
if (!iter) {
|
||||
return;
|
||||
}
|
||||
assert(iter->bitmap->active_iterators > 0);
|
||||
iter->bitmap->active_iterators--;
|
||||
g_free(iter);
|
||||
}
|
||||
|
||||
int64_t bdrv_dirty_iter_next(BdrvDirtyBitmapIter *iter)
|
||||
{
|
||||
return hbitmap_iter_next(&iter->hbi);
|
||||
}
|
||||
|
||||
/* Called within bdrv_dirty_bitmap_lock..unlock */
|
||||
void bdrv_set_dirty_bitmap_locked(BdrvDirtyBitmap *bitmap,
|
||||
int64_t cur_sector, int64_t nr_sectors)
|
||||
{
|
||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
||||
assert(!bdrv_dirty_bitmap_readonly(bitmap));
|
||||
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
|
||||
hbitmap_iter_init(hbi, bitmap->bitmap, 0);
|
||||
}
|
||||
|
||||
void bdrv_set_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
||||
int64_t cur_sector, int64_t nr_sectors)
|
||||
{
|
||||
bdrv_dirty_bitmap_lock(bitmap);
|
||||
bdrv_set_dirty_bitmap_locked(bitmap, cur_sector, nr_sectors);
|
||||
bdrv_dirty_bitmap_unlock(bitmap);
|
||||
}
|
||||
|
||||
/* Called within bdrv_dirty_bitmap_lock..unlock */
|
||||
void bdrv_reset_dirty_bitmap_locked(BdrvDirtyBitmap *bitmap,
|
||||
int64_t cur_sector, int64_t nr_sectors)
|
||||
{
|
||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
||||
assert(!bdrv_dirty_bitmap_readonly(bitmap));
|
||||
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
|
||||
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
|
||||
}
|
||||
|
||||
void bdrv_reset_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
||||
int64_t cur_sector, int64_t nr_sectors)
|
||||
{
|
||||
bdrv_dirty_bitmap_lock(bitmap);
|
||||
bdrv_reset_dirty_bitmap_locked(bitmap, cur_sector, nr_sectors);
|
||||
bdrv_dirty_bitmap_unlock(bitmap);
|
||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
||||
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
|
||||
}
|
||||
|
||||
void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap, HBitmap **out)
|
||||
{
|
||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
||||
assert(!bdrv_dirty_bitmap_readonly(bitmap));
|
||||
bdrv_dirty_bitmap_lock(bitmap);
|
||||
if (!out) {
|
||||
hbitmap_reset_all(bitmap->bitmap);
|
||||
} else {
|
||||
@@ -597,171 +350,38 @@ void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap, HBitmap **out)
|
||||
hbitmap_granularity(backup));
|
||||
*out = backup;
|
||||
}
|
||||
bdrv_dirty_bitmap_unlock(bitmap);
|
||||
}
|
||||
|
||||
void bdrv_undo_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap, HBitmap *in)
|
||||
{
|
||||
HBitmap *tmp = bitmap->bitmap;
|
||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
||||
assert(!bdrv_dirty_bitmap_readonly(bitmap));
|
||||
bitmap->bitmap = in;
|
||||
hbitmap_free(tmp);
|
||||
}
|
||||
|
||||
uint64_t bdrv_dirty_bitmap_serialization_size(const BdrvDirtyBitmap *bitmap,
|
||||
uint64_t start, uint64_t count)
|
||||
{
|
||||
return hbitmap_serialization_size(bitmap->bitmap, start, count);
|
||||
}
|
||||
|
||||
uint64_t bdrv_dirty_bitmap_serialization_align(const BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return hbitmap_serialization_granularity(bitmap->bitmap);
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_serialize_part(const BdrvDirtyBitmap *bitmap,
|
||||
uint8_t *buf, uint64_t start,
|
||||
uint64_t count)
|
||||
{
|
||||
hbitmap_serialize_part(bitmap->bitmap, buf, start, count);
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_deserialize_part(BdrvDirtyBitmap *bitmap,
|
||||
uint8_t *buf, uint64_t start,
|
||||
uint64_t count, bool finish)
|
||||
{
|
||||
hbitmap_deserialize_part(bitmap->bitmap, buf, start, count, finish);
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_deserialize_zeroes(BdrvDirtyBitmap *bitmap,
|
||||
uint64_t start, uint64_t count,
|
||||
bool finish)
|
||||
{
|
||||
hbitmap_deserialize_zeroes(bitmap->bitmap, start, count, finish);
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_deserialize_ones(BdrvDirtyBitmap *bitmap,
|
||||
uint64_t start, uint64_t count,
|
||||
bool finish)
|
||||
{
|
||||
hbitmap_deserialize_ones(bitmap->bitmap, start, count, finish);
|
||||
}
|
||||
|
||||
void bdrv_dirty_bitmap_deserialize_finish(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
hbitmap_deserialize_finish(bitmap->bitmap);
|
||||
}
|
||||
|
||||
void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||
int64_t nr_sectors)
|
||||
{
|
||||
BdrvDirtyBitmap *bitmap;
|
||||
|
||||
if (QLIST_EMPTY(&bs->dirty_bitmaps)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bdrv_dirty_bitmaps_lock(bs);
|
||||
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
||||
if (!bdrv_dirty_bitmap_enabled(bitmap)) {
|
||||
continue;
|
||||
}
|
||||
assert(!bdrv_dirty_bitmap_readonly(bitmap));
|
||||
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
|
||||
}
|
||||
bdrv_dirty_bitmaps_unlock(bs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance a BdrvDirtyBitmapIter to an arbitrary offset.
|
||||
* Advance an HBitmapIter to an arbitrary offset.
|
||||
*/
|
||||
void bdrv_set_dirty_iter(BdrvDirtyBitmapIter *iter, int64_t sector_num)
|
||||
void bdrv_set_dirty_iter(HBitmapIter *hbi, int64_t offset)
|
||||
{
|
||||
hbitmap_iter_init(&iter->hbi, iter->hbi.hb, sector_num);
|
||||
assert(hbi->hb);
|
||||
hbitmap_iter_init(hbi, hbi->hb, offset);
|
||||
}
|
||||
|
||||
int64_t bdrv_get_dirty_count(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return hbitmap_count(bitmap->bitmap);
|
||||
}
|
||||
|
||||
int64_t bdrv_get_meta_dirty_count(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return hbitmap_count(bitmap->meta);
|
||||
}
|
||||
|
||||
bool bdrv_dirty_bitmap_readonly(const BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return bitmap->readonly;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
void bdrv_dirty_bitmap_set_readonly(BdrvDirtyBitmap *bitmap, bool value)
|
||||
{
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
bitmap->readonly = value;
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
}
|
||||
|
||||
bool bdrv_has_readonly_bitmaps(BlockDriverState *bs)
|
||||
{
|
||||
BdrvDirtyBitmap *bm;
|
||||
QLIST_FOREACH(bm, &bs->dirty_bitmaps, list) {
|
||||
if (bm->readonly) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
void bdrv_dirty_bitmap_set_autoload(BdrvDirtyBitmap *bitmap, bool autoload)
|
||||
{
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
bitmap->autoload = autoload;
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
}
|
||||
|
||||
bool bdrv_dirty_bitmap_get_autoload(const BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return bitmap->autoload;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
void bdrv_dirty_bitmap_set_persistance(BdrvDirtyBitmap *bitmap, bool persistent)
|
||||
{
|
||||
qemu_mutex_lock(bitmap->mutex);
|
||||
bitmap->persistent = persistent;
|
||||
qemu_mutex_unlock(bitmap->mutex);
|
||||
}
|
||||
|
||||
bool bdrv_dirty_bitmap_get_persistance(BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return bitmap->persistent;
|
||||
}
|
||||
|
||||
bool bdrv_has_changed_persistent_bitmaps(BlockDriverState *bs)
|
||||
{
|
||||
BdrvDirtyBitmap *bm;
|
||||
QLIST_FOREACH(bm, &bs->dirty_bitmaps, list) {
|
||||
if (bm->persistent && !bm->readonly) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
BdrvDirtyBitmap *bdrv_dirty_bitmap_next(BlockDriverState *bs,
|
||||
BdrvDirtyBitmap *bitmap)
|
||||
{
|
||||
return bitmap == NULL ? QLIST_FIRST(&bs->dirty_bitmaps) :
|
||||
QLIST_NEXT(bitmap, list);
|
||||
}
|
||||
|
||||
char *bdrv_dirty_bitmap_sha256(const BdrvDirtyBitmap *bitmap, Error **errp)
|
||||
{
|
||||
return hbitmap_sha256(bitmap->bitmap, errp);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* DMG bzip2 uncompression
|
||||
*
|
||||
* Copyright (c) 2004 Johannes E. Schindelin
|
||||
* Copyright (c) 2016 Red Hat, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "dmg.h"
|
||||
#include <bzlib.h>
|
||||
|
||||
static int dmg_uncompress_bz2_do(char *next_in, unsigned int avail_in,
|
||||
char *next_out, unsigned int avail_out)
|
||||
{
|
||||
int ret;
|
||||
uint64_t total_out;
|
||||
bz_stream bzstream = {};
|
||||
|
||||
ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
|
||||
if (ret != BZ_OK) {
|
||||
return -1;
|
||||
}
|
||||
bzstream.next_in = next_in;
|
||||
bzstream.avail_in = avail_in;
|
||||
bzstream.next_out = next_out;
|
||||
bzstream.avail_out = avail_out;
|
||||
ret = BZ2_bzDecompress(&bzstream);
|
||||
total_out = ((uint64_t)bzstream.total_out_hi32 << 32) +
|
||||
bzstream.total_out_lo32;
|
||||
BZ2_bzDecompressEnd(&bzstream);
|
||||
if (ret != BZ_STREAM_END ||
|
||||
total_out != avail_out) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
static void dmg_bz2_init(void)
|
||||
{
|
||||
assert(!dmg_uncompress_bz2);
|
||||
dmg_uncompress_bz2 = dmg_uncompress_bz2_do;
|
||||
}
|
||||
+53
-29
@@ -28,10 +28,10 @@
|
||||
#include "qemu/bswap.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/module.h"
|
||||
#include "dmg.h"
|
||||
|
||||
int (*dmg_uncompress_bz2)(char *next_in, unsigned int avail_in,
|
||||
char *next_out, unsigned int avail_out);
|
||||
#include <zlib.h>
|
||||
#ifdef CONFIG_BZIP2
|
||||
#include <bzlib.h>
|
||||
#endif
|
||||
|
||||
enum {
|
||||
/* Limit chunk sizes to prevent unreasonable amounts of memory being used
|
||||
@@ -41,6 +41,31 @@ enum {
|
||||
DMG_SECTORCOUNTS_MAX = DMG_LENGTHS_MAX / 512,
|
||||
};
|
||||
|
||||
typedef struct BDRVDMGState {
|
||||
CoMutex lock;
|
||||
/* each chunk contains a certain number of sectors,
|
||||
* offsets[i] is the offset in the .dmg file,
|
||||
* lengths[i] is the length of the compressed chunk,
|
||||
* sectors[i] is the sector beginning at offsets[i],
|
||||
* sectorcounts[i] is the number of sectors in that chunk,
|
||||
* the sectors array is ordered
|
||||
* 0<=i<n_chunks */
|
||||
|
||||
uint32_t n_chunks;
|
||||
uint32_t* types;
|
||||
uint64_t* offsets;
|
||||
uint64_t* lengths;
|
||||
uint64_t* sectors;
|
||||
uint64_t* sectorcounts;
|
||||
uint32_t current_chunk;
|
||||
uint8_t *compressed_chunk;
|
||||
uint8_t *uncompressed_chunk;
|
||||
z_stream zstream;
|
||||
#ifdef CONFIG_BZIP2
|
||||
bz_stream bzstream;
|
||||
#endif
|
||||
} BDRVDMGState;
|
||||
|
||||
static int dmg_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||
{
|
||||
int len;
|
||||
@@ -185,9 +210,10 @@ static bool dmg_is_known_block_type(uint32_t entry_type)
|
||||
case 0x00000001: /* uncompressed */
|
||||
case 0x00000002: /* zeroes */
|
||||
case 0x80000005: /* zlib */
|
||||
return true;
|
||||
#ifdef CONFIG_BZIP2
|
||||
case 0x80000006: /* bzip2 */
|
||||
return !!dmg_uncompress_bz2;
|
||||
#endif
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -413,18 +439,7 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
int64_t offset;
|
||||
int ret;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = bdrv_set_read_only(bs, true, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
block_module_load_one("dmg-bz2");
|
||||
bs->read_only = true;
|
||||
|
||||
s->n_chunks = 0;
|
||||
s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
|
||||
@@ -572,6 +587,9 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
||||
if (!is_sector_in_chunk(s, s->current_chunk, sector_num)) {
|
||||
int ret;
|
||||
uint32_t chunk = search_chunk(s, sector_num);
|
||||
#ifdef CONFIG_BZIP2
|
||||
uint64_t total_out;
|
||||
#endif
|
||||
|
||||
if (chunk >= s->n_chunks) {
|
||||
return -1;
|
||||
@@ -602,10 +620,8 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
||||
return -1;
|
||||
}
|
||||
break; }
|
||||
#ifdef CONFIG_BZIP2
|
||||
case 0x80000006: /* bzip2 compressed */
|
||||
if (!dmg_uncompress_bz2) {
|
||||
break;
|
||||
}
|
||||
/* we need to buffer, because only the chunk as whole can be
|
||||
* inflated. */
|
||||
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
||||
@@ -614,15 +630,24 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = dmg_uncompress_bz2((char *)s->compressed_chunk,
|
||||
(unsigned int) s->lengths[chunk],
|
||||
(char *)s->uncompressed_chunk,
|
||||
(unsigned int)
|
||||
(512 * s->sectorcounts[chunk]));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
ret = BZ2_bzDecompressInit(&s->bzstream, 0, 0);
|
||||
if (ret != BZ_OK) {
|
||||
return -1;
|
||||
}
|
||||
s->bzstream.next_in = (char *)s->compressed_chunk;
|
||||
s->bzstream.avail_in = (unsigned int) s->lengths[chunk];
|
||||
s->bzstream.next_out = (char *)s->uncompressed_chunk;
|
||||
s->bzstream.avail_out = (unsigned int) 512 * s->sectorcounts[chunk];
|
||||
ret = BZ2_bzDecompress(&s->bzstream);
|
||||
total_out = ((uint64_t)s->bzstream.total_out_hi32 << 32) +
|
||||
s->bzstream.total_out_lo32;
|
||||
BZ2_bzDecompressEnd(&s->bzstream);
|
||||
if (ret != BZ_STREAM_END ||
|
||||
total_out != 512 * s->sectorcounts[chunk]) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
#endif /* CONFIG_BZIP2 */
|
||||
case 1: /* copy */
|
||||
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
||||
s->uncompressed_chunk, s->lengths[chunk]);
|
||||
@@ -701,7 +726,6 @@ static BlockDriver bdrv_dmg = {
|
||||
.bdrv_probe = dmg_probe,
|
||||
.bdrv_open = dmg_open,
|
||||
.bdrv_refresh_limits = dmg_refresh_limits,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_co_preadv = dmg_co_preadv,
|
||||
.bdrv_close = dmg_close,
|
||||
};
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Header for DMG driver
|
||||
*
|
||||
* Copyright (c) 2004-2006 Fabrice Bellard
|
||||
* Copyright (c) 2016 Red hat, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef BLOCK_DMG_H
|
||||
#define BLOCK_DMG_H
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "block/block_int.h"
|
||||
#include <zlib.h>
|
||||
|
||||
typedef struct BDRVDMGState {
|
||||
CoMutex lock;
|
||||
/* each chunk contains a certain number of sectors,
|
||||
* offsets[i] is the offset in the .dmg file,
|
||||
* lengths[i] is the length of the compressed chunk,
|
||||
* sectors[i] is the sector beginning at offsets[i],
|
||||
* sectorcounts[i] is the number of sectors in that chunk,
|
||||
* the sectors array is ordered
|
||||
* 0<=i<n_chunks */
|
||||
|
||||
uint32_t n_chunks;
|
||||
uint32_t *types;
|
||||
uint64_t *offsets;
|
||||
uint64_t *lengths;
|
||||
uint64_t *sectors;
|
||||
uint64_t *sectorcounts;
|
||||
uint32_t current_chunk;
|
||||
uint8_t *compressed_chunk;
|
||||
uint8_t *uncompressed_chunk;
|
||||
z_stream zstream;
|
||||
} BDRVDMGState;
|
||||
|
||||
extern int (*dmg_uncompress_bz2)(char *next_in, unsigned int avail_in,
|
||||
char *next_out, unsigned int avail_out);
|
||||
|
||||
#endif
|
||||
+167
-299
@@ -12,10 +12,8 @@
|
||||
#include "block/block_int.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qapi/util.h"
|
||||
#include "qemu/uri.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/cutils.h"
|
||||
|
||||
#define GLUSTER_OPT_FILENAME "filename"
|
||||
#define GLUSTER_OPT_VOLUME "volume"
|
||||
@@ -32,14 +30,13 @@
|
||||
#define GLUSTER_DEFAULT_PORT 24007
|
||||
#define GLUSTER_DEBUG_DEFAULT 4
|
||||
#define GLUSTER_DEBUG_MAX 9
|
||||
#define GLUSTER_OPT_LOGFILE "logfile"
|
||||
#define GLUSTER_LOGFILE_DEFAULT "-" /* handled in libgfapi as /dev/stderr */
|
||||
|
||||
#define GERR_INDEX_HINT "hint: check in 'server' array index '%d'\n"
|
||||
|
||||
typedef struct GlusterAIOCB {
|
||||
int64_t size;
|
||||
int ret;
|
||||
QEMUBH *bh;
|
||||
Coroutine *coroutine;
|
||||
AioContext *aio_context;
|
||||
} GlusterAIOCB;
|
||||
@@ -47,9 +44,8 @@ typedef struct GlusterAIOCB {
|
||||
typedef struct BDRVGlusterState {
|
||||
struct glfs *glfs;
|
||||
struct glfs_fd *fd;
|
||||
char *logfile;
|
||||
bool supports_seek_data;
|
||||
int debug;
|
||||
int debug_level;
|
||||
} BDRVGlusterState;
|
||||
|
||||
typedef struct BDRVGlusterReopenState {
|
||||
@@ -58,19 +54,6 @@ typedef struct BDRVGlusterReopenState {
|
||||
} BDRVGlusterReopenState;
|
||||
|
||||
|
||||
typedef struct GlfsPreopened {
|
||||
char *volume;
|
||||
glfs_t *fs;
|
||||
int ref;
|
||||
} GlfsPreopened;
|
||||
|
||||
typedef struct ListElement {
|
||||
QLIST_ENTRY(ListElement) list;
|
||||
GlfsPreopened saved;
|
||||
} ListElement;
|
||||
|
||||
static QLIST_HEAD(glfs_list, ListElement) glfs_list;
|
||||
|
||||
static QemuOptsList qemu_gluster_create_opts = {
|
||||
.name = "qemu-gluster-create-opts",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(qemu_gluster_create_opts.head),
|
||||
@@ -90,11 +73,6 @@ static QemuOptsList qemu_gluster_create_opts = {
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Gluster log level, valid range is 0-9",
|
||||
},
|
||||
{
|
||||
.name = GLUSTER_OPT_LOGFILE,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Logfile path of libgfapi",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
}
|
||||
};
|
||||
@@ -113,11 +91,6 @@ static QemuOptsList runtime_opts = {
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Gluster log level, valid range is 0-9",
|
||||
},
|
||||
{
|
||||
.name = GLUSTER_OPT_LOGFILE,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Logfile path of libgfapi",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
@@ -152,7 +125,7 @@ static QemuOptsList runtime_type_opts = {
|
||||
{
|
||||
.name = GLUSTER_OPT_TYPE,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "inet|unix",
|
||||
.help = "tcp|unix",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
@@ -171,14 +144,14 @@ static QemuOptsList runtime_unix_opts = {
|
||||
},
|
||||
};
|
||||
|
||||
static QemuOptsList runtime_inet_opts = {
|
||||
.name = "gluster_inet",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_inet_opts.head),
|
||||
static QemuOptsList runtime_tcp_opts = {
|
||||
.name = "gluster_tcp",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_tcp_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = GLUSTER_OPT_TYPE,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "inet|unix",
|
||||
.help = "tcp|unix",
|
||||
},
|
||||
{
|
||||
.name = GLUSTER_OPT_HOST,
|
||||
@@ -187,7 +160,7 @@ static QemuOptsList runtime_inet_opts = {
|
||||
},
|
||||
{
|
||||
.name = GLUSTER_OPT_PORT,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "port number on which glusterd is listening (default 24007)",
|
||||
},
|
||||
{
|
||||
@@ -209,58 +182,6 @@ static QemuOptsList runtime_inet_opts = {
|
||||
},
|
||||
};
|
||||
|
||||
static void glfs_set_preopened(const char *volume, glfs_t *fs)
|
||||
{
|
||||
ListElement *entry = NULL;
|
||||
|
||||
entry = g_new(ListElement, 1);
|
||||
|
||||
entry->saved.volume = g_strdup(volume);
|
||||
|
||||
entry->saved.fs = fs;
|
||||
entry->saved.ref = 1;
|
||||
|
||||
QLIST_INSERT_HEAD(&glfs_list, entry, list);
|
||||
}
|
||||
|
||||
static glfs_t *glfs_find_preopened(const char *volume)
|
||||
{
|
||||
ListElement *entry = NULL;
|
||||
|
||||
QLIST_FOREACH(entry, &glfs_list, list) {
|
||||
if (strcmp(entry->saved.volume, volume) == 0) {
|
||||
entry->saved.ref++;
|
||||
return entry->saved.fs;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void glfs_clear_preopened(glfs_t *fs)
|
||||
{
|
||||
ListElement *entry = NULL;
|
||||
ListElement *next;
|
||||
|
||||
if (fs == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
QLIST_FOREACH_SAFE(entry, &glfs_list, list, next) {
|
||||
if (entry->saved.fs == fs) {
|
||||
if (--entry->saved.ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
QLIST_REMOVE(entry, list);
|
||||
|
||||
glfs_fini(entry->saved.fs);
|
||||
g_free(entry->saved.volume);
|
||||
g_free(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_volume_options(BlockdevOptionsGluster *gconf, char *path)
|
||||
{
|
||||
char *p, *q;
|
||||
@@ -321,7 +242,7 @@ static int parse_volume_options(BlockdevOptionsGluster *gconf, char *path)
|
||||
static int qemu_gluster_parse_uri(BlockdevOptionsGluster *gconf,
|
||||
const char *filename)
|
||||
{
|
||||
SocketAddress *gsconf;
|
||||
GlusterServer *gsconf;
|
||||
URI *uri;
|
||||
QueryParams *qp = NULL;
|
||||
bool is_unix = false;
|
||||
@@ -332,20 +253,21 @@ static int qemu_gluster_parse_uri(BlockdevOptionsGluster *gconf,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gconf->server = g_new0(SocketAddressList, 1);
|
||||
gconf->server->value = gsconf = g_new0(SocketAddress, 1);
|
||||
gconf->server = g_new0(GlusterServerList, 1);
|
||||
gconf->server->value = gsconf = g_new0(GlusterServer, 1);
|
||||
|
||||
/* transport */
|
||||
if (!uri->scheme || !strcmp(uri->scheme, "gluster")) {
|
||||
gsconf->type = SOCKET_ADDRESS_TYPE_INET;
|
||||
gsconf->type = GLUSTER_TRANSPORT_TCP;
|
||||
} else if (!strcmp(uri->scheme, "gluster+tcp")) {
|
||||
gsconf->type = SOCKET_ADDRESS_TYPE_INET;
|
||||
gsconf->type = GLUSTER_TRANSPORT_TCP;
|
||||
} else if (!strcmp(uri->scheme, "gluster+unix")) {
|
||||
gsconf->type = SOCKET_ADDRESS_TYPE_UNIX;
|
||||
gsconf->type = GLUSTER_TRANSPORT_UNIX;
|
||||
is_unix = true;
|
||||
} else if (!strcmp(uri->scheme, "gluster+rdma")) {
|
||||
gsconf->type = SOCKET_ADDRESS_TYPE_INET;
|
||||
warn_report("rdma feature is not supported, falling back to tcp");
|
||||
gsconf->type = GLUSTER_TRANSPORT_TCP;
|
||||
error_report("Warning: rdma feature is not supported, falling "
|
||||
"back to tcp");
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
@@ -373,11 +295,11 @@ static int qemu_gluster_parse_uri(BlockdevOptionsGluster *gconf,
|
||||
}
|
||||
gsconf->u.q_unix.path = g_strdup(qp->p[0].value);
|
||||
} else {
|
||||
gsconf->u.inet.host = g_strdup(uri->server ? uri->server : "localhost");
|
||||
gsconf->u.tcp.host = g_strdup(uri->server ? uri->server : "localhost");
|
||||
if (uri->port) {
|
||||
gsconf->u.inet.port = g_strdup_printf("%d", uri->port);
|
||||
gsconf->u.tcp.port = g_strdup_printf("%d", uri->port);
|
||||
} else {
|
||||
gsconf->u.inet.port = g_strdup_printf("%d", GLUSTER_DEFAULT_PORT);
|
||||
gsconf->u.tcp.port = g_strdup_printf("%d", GLUSTER_DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,43 +317,23 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
|
||||
struct glfs *glfs;
|
||||
int ret;
|
||||
int old_errno;
|
||||
SocketAddressList *server;
|
||||
unsigned long long port;
|
||||
|
||||
glfs = glfs_find_preopened(gconf->volume);
|
||||
if (glfs) {
|
||||
return glfs;
|
||||
}
|
||||
GlusterServerList *server;
|
||||
|
||||
glfs = glfs_new(gconf->volume);
|
||||
if (!glfs) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
glfs_set_preopened(gconf->volume, glfs);
|
||||
|
||||
for (server = gconf->server; server; server = server->next) {
|
||||
switch (server->value->type) {
|
||||
case SOCKET_ADDRESS_TYPE_UNIX:
|
||||
ret = glfs_set_volfile_server(glfs, "unix",
|
||||
if (server->value->type == GLUSTER_TRANSPORT_UNIX) {
|
||||
ret = glfs_set_volfile_server(glfs,
|
||||
GlusterTransport_lookup[server->value->type],
|
||||
server->value->u.q_unix.path, 0);
|
||||
break;
|
||||
case SOCKET_ADDRESS_TYPE_INET:
|
||||
if (parse_uint_full(server->value->u.inet.port, &port, 10) < 0 ||
|
||||
port > 65535) {
|
||||
error_setg(errp, "'%s' is not a valid port number",
|
||||
server->value->u.inet.port);
|
||||
errno = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
ret = glfs_set_volfile_server(glfs, "tcp",
|
||||
server->value->u.inet.host,
|
||||
(int)port);
|
||||
break;
|
||||
case SOCKET_ADDRESS_TYPE_VSOCK:
|
||||
case SOCKET_ADDRESS_TYPE_FD:
|
||||
default:
|
||||
abort();
|
||||
} else {
|
||||
ret = glfs_set_volfile_server(glfs,
|
||||
GlusterTransport_lookup[server->value->type],
|
||||
server->value->u.tcp.host,
|
||||
atoi(server->value->u.tcp.port));
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
@@ -439,7 +341,7 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
|
||||
}
|
||||
}
|
||||
|
||||
ret = glfs_set_logging(glfs, gconf->logfile, gconf->debug);
|
||||
ret = glfs_set_logging(glfs, "-", gconf->debug_level);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
@@ -449,13 +351,13 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
|
||||
error_setg(errp, "Gluster connection for volume %s, path %s failed"
|
||||
" to connect", gconf->volume, gconf->path);
|
||||
for (server = gconf->server; server; server = server->next) {
|
||||
if (server->value->type == SOCKET_ADDRESS_TYPE_UNIX) {
|
||||
if (server->value->type == GLUSTER_TRANSPORT_UNIX) {
|
||||
error_append_hint(errp, "hint: failed on socket %s ",
|
||||
server->value->u.q_unix.path);
|
||||
} else {
|
||||
error_append_hint(errp, "hint: failed on host %s and port %s ",
|
||||
server->value->u.inet.host,
|
||||
server->value->u.inet.port);
|
||||
server->value->u.tcp.host,
|
||||
server->value->u.tcp.port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,12 +375,29 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
|
||||
out:
|
||||
if (glfs) {
|
||||
old_errno = errno;
|
||||
glfs_clear_preopened(glfs);
|
||||
glfs_fini(glfs);
|
||||
errno = old_errno;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int qapi_enum_parse(const char *opt)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!opt) {
|
||||
return GLUSTER_TRANSPORT__MAX;
|
||||
}
|
||||
|
||||
for (i = 0; i < GLUSTER_TRANSPORT__MAX; i++) {
|
||||
if (!strcmp(opt, GlusterTransport_lookup[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the json formatted command line into qapi.
|
||||
*/
|
||||
@@ -486,13 +405,14 @@ static int qemu_gluster_parse_json(BlockdevOptionsGluster *gconf,
|
||||
QDict *options, Error **errp)
|
||||
{
|
||||
QemuOpts *opts;
|
||||
SocketAddress *gsconf = NULL;
|
||||
SocketAddressList *curr = NULL;
|
||||
GlusterServer *gsconf;
|
||||
GlusterServerList *curr = NULL;
|
||||
QDict *backing_options = NULL;
|
||||
Error *local_err = NULL;
|
||||
char *str = NULL;
|
||||
const char *ptr;
|
||||
int i, type, num_servers;
|
||||
size_t num_servers;
|
||||
int i;
|
||||
|
||||
/* create opts info from runtime_json_opts list */
|
||||
opts = qemu_opts_create(&runtime_json_opts, NULL, 0, &error_abort);
|
||||
@@ -534,32 +454,25 @@ static int qemu_gluster_parse_json(BlockdevOptionsGluster *gconf,
|
||||
}
|
||||
|
||||
ptr = qemu_opt_get(opts, GLUSTER_OPT_TYPE);
|
||||
gsconf = g_new0(GlusterServer, 1);
|
||||
gsconf->type = qapi_enum_parse(ptr);
|
||||
if (!ptr) {
|
||||
error_setg(&local_err, QERR_MISSING_PARAMETER, GLUSTER_OPT_TYPE);
|
||||
error_append_hint(&local_err, GERR_INDEX_HINT, i);
|
||||
goto out;
|
||||
|
||||
}
|
||||
gsconf = g_new0(SocketAddress, 1);
|
||||
if (!strcmp(ptr, "tcp")) {
|
||||
ptr = "inet"; /* accept legacy "tcp" */
|
||||
}
|
||||
type = qapi_enum_parse(SocketAddressType_lookup, ptr,
|
||||
SOCKET_ADDRESS_TYPE__MAX, -1, NULL);
|
||||
if (type != SOCKET_ADDRESS_TYPE_INET
|
||||
&& type != SOCKET_ADDRESS_TYPE_UNIX) {
|
||||
error_setg(&local_err,
|
||||
"Parameter '%s' may be 'inet' or 'unix'",
|
||||
GLUSTER_OPT_TYPE);
|
||||
if (gsconf->type == GLUSTER_TRANSPORT__MAX) {
|
||||
error_setg(&local_err, QERR_INVALID_PARAMETER_VALUE,
|
||||
GLUSTER_OPT_TYPE, "tcp or unix");
|
||||
error_append_hint(&local_err, GERR_INDEX_HINT, i);
|
||||
goto out;
|
||||
}
|
||||
gsconf->type = type;
|
||||
qemu_opts_del(opts);
|
||||
|
||||
if (gsconf->type == SOCKET_ADDRESS_TYPE_INET) {
|
||||
/* create opts info from runtime_inet_opts list */
|
||||
opts = qemu_opts_create(&runtime_inet_opts, NULL, 0, &error_abort);
|
||||
if (gsconf->type == GLUSTER_TRANSPORT_TCP) {
|
||||
/* create opts info from runtime_tcp_opts list */
|
||||
opts = qemu_opts_create(&runtime_tcp_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, backing_options, &local_err);
|
||||
if (local_err) {
|
||||
goto out;
|
||||
@@ -572,7 +485,7 @@ static int qemu_gluster_parse_json(BlockdevOptionsGluster *gconf,
|
||||
error_append_hint(&local_err, GERR_INDEX_HINT, i);
|
||||
goto out;
|
||||
}
|
||||
gsconf->u.inet.host = g_strdup(ptr);
|
||||
gsconf->u.tcp.host = g_strdup(ptr);
|
||||
ptr = qemu_opt_get(opts, GLUSTER_OPT_PORT);
|
||||
if (!ptr) {
|
||||
error_setg(&local_err, QERR_MISSING_PARAMETER,
|
||||
@@ -580,28 +493,28 @@ static int qemu_gluster_parse_json(BlockdevOptionsGluster *gconf,
|
||||
error_append_hint(&local_err, GERR_INDEX_HINT, i);
|
||||
goto out;
|
||||
}
|
||||
gsconf->u.inet.port = g_strdup(ptr);
|
||||
gsconf->u.tcp.port = g_strdup(ptr);
|
||||
|
||||
/* defend for unsupported fields in InetSocketAddress,
|
||||
* i.e. @ipv4, @ipv6 and @to
|
||||
*/
|
||||
ptr = qemu_opt_get(opts, GLUSTER_OPT_TO);
|
||||
if (ptr) {
|
||||
gsconf->u.inet.has_to = true;
|
||||
gsconf->u.tcp.has_to = true;
|
||||
}
|
||||
ptr = qemu_opt_get(opts, GLUSTER_OPT_IPV4);
|
||||
if (ptr) {
|
||||
gsconf->u.inet.has_ipv4 = true;
|
||||
gsconf->u.tcp.has_ipv4 = true;
|
||||
}
|
||||
ptr = qemu_opt_get(opts, GLUSTER_OPT_IPV6);
|
||||
if (ptr) {
|
||||
gsconf->u.inet.has_ipv6 = true;
|
||||
gsconf->u.tcp.has_ipv6 = true;
|
||||
}
|
||||
if (gsconf->u.inet.has_to) {
|
||||
if (gsconf->u.tcp.has_to) {
|
||||
error_setg(&local_err, "Parameter 'to' not supported");
|
||||
goto out;
|
||||
}
|
||||
if (gsconf->u.inet.has_ipv4 || gsconf->u.inet.has_ipv6) {
|
||||
if (gsconf->u.tcp.has_ipv4 || gsconf->u.tcp.has_ipv6) {
|
||||
error_setg(&local_err, "Parameters 'ipv4/ipv6' not supported");
|
||||
goto out;
|
||||
}
|
||||
@@ -626,18 +539,16 @@ static int qemu_gluster_parse_json(BlockdevOptionsGluster *gconf,
|
||||
}
|
||||
|
||||
if (gconf->server == NULL) {
|
||||
gconf->server = g_new0(SocketAddressList, 1);
|
||||
gconf->server = g_new0(GlusterServerList, 1);
|
||||
gconf->server->value = gsconf;
|
||||
curr = gconf->server;
|
||||
} else {
|
||||
curr->next = g_new0(SocketAddressList, 1);
|
||||
curr->next = g_new0(GlusterServerList, 1);
|
||||
curr->next->value = gsconf;
|
||||
curr = curr->next;
|
||||
}
|
||||
gsconf = NULL;
|
||||
|
||||
QDECREF(backing_options);
|
||||
backing_options = NULL;
|
||||
qdict_del(backing_options, str);
|
||||
g_free(str);
|
||||
str = NULL;
|
||||
}
|
||||
@@ -646,10 +557,11 @@ static int qemu_gluster_parse_json(BlockdevOptionsGluster *gconf,
|
||||
|
||||
out:
|
||||
error_propagate(errp, local_err);
|
||||
qapi_free_SocketAddress(gsconf);
|
||||
qemu_opts_del(opts);
|
||||
g_free(str);
|
||||
QDECREF(backing_options);
|
||||
if (str) {
|
||||
qdict_del(backing_options, str);
|
||||
g_free(str);
|
||||
}
|
||||
errno = EINVAL;
|
||||
return -errno;
|
||||
}
|
||||
@@ -664,9 +576,7 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "invalid URI");
|
||||
error_append_hint(errp, "Usage: file=gluster[+transport]://"
|
||||
"[host[:port]]volume/path[?socket=...]"
|
||||
"[,file.debug=N]"
|
||||
"[,file.logfile=/path/filename.log]\n");
|
||||
"[host[:port]]/volume/path[?socket=...]\n");
|
||||
errno = -ret;
|
||||
return NULL;
|
||||
}
|
||||
@@ -676,9 +586,7 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
|
||||
error_append_hint(errp, "Usage: "
|
||||
"-drive driver=qcow2,file.driver=gluster,"
|
||||
"file.volume=testvol,file.path=/path/a.qcow2"
|
||||
"[,file.debug=9]"
|
||||
"[,file.logfile=/path/filename.log],"
|
||||
"file.server.0.type=inet,"
|
||||
"[,file.debug=9],file.server.0.type=tcp,"
|
||||
"file.server.0.host=1.2.3.4,"
|
||||
"file.server.0.port=24007,"
|
||||
"file.server.1.transport=unix,"
|
||||
@@ -693,6 +601,15 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
|
||||
return qemu_gluster_glfs_init(gconf, errp);
|
||||
}
|
||||
|
||||
static void qemu_gluster_complete_aio(void *opaque)
|
||||
{
|
||||
GlusterAIOCB *acb = (GlusterAIOCB *)opaque;
|
||||
|
||||
qemu_bh_delete(acb->bh);
|
||||
acb->bh = NULL;
|
||||
qemu_coroutine_enter(acb->coroutine);
|
||||
}
|
||||
|
||||
/*
|
||||
* AIO callback routine called from GlusterFS thread.
|
||||
*/
|
||||
@@ -708,7 +625,8 @@ static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg)
|
||||
acb->ret = -EIO; /* Partial read/write - fail it */
|
||||
}
|
||||
|
||||
aio_co_schedule(acb->aio_context, acb->coroutine);
|
||||
acb->bh = aio_bh_new(acb->aio_context, qemu_gluster_complete_aio, acb);
|
||||
qemu_bh_schedule(acb->bh);
|
||||
}
|
||||
|
||||
static void qemu_gluster_parse_flags(int bdrv_flags, int *open_flags)
|
||||
@@ -737,10 +655,7 @@ static void qemu_gluster_parse_flags(int bdrv_flags, int *open_flags)
|
||||
*/
|
||||
static bool qemu_gluster_test_seek(struct glfs_fd *fd)
|
||||
{
|
||||
off_t ret = 0;
|
||||
|
||||
#if defined SEEK_HOLE && defined SEEK_DATA
|
||||
off_t eof;
|
||||
off_t ret, eof;
|
||||
|
||||
eof = glfs_lseek(fd, 0, SEEK_END);
|
||||
if (eof < 0) {
|
||||
@@ -750,8 +665,6 @@ static bool qemu_gluster_test_seek(struct glfs_fd *fd)
|
||||
|
||||
/* this should always fail with ENXIO if SEEK_DATA is supported */
|
||||
ret = glfs_lseek(fd, eof, SEEK_DATA);
|
||||
#endif
|
||||
|
||||
return (ret < 0) && (errno == ENXIO);
|
||||
}
|
||||
|
||||
@@ -764,7 +677,7 @@ static int qemu_gluster_open(BlockDriverState *bs, QDict *options,
|
||||
BlockdevOptionsGluster *gconf = NULL;
|
||||
QemuOpts *opts;
|
||||
Error *local_err = NULL;
|
||||
const char *filename, *logfile;
|
||||
const char *filename;
|
||||
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
@@ -776,24 +689,17 @@ static int qemu_gluster_open(BlockDriverState *bs, QDict *options,
|
||||
|
||||
filename = qemu_opt_get(opts, GLUSTER_OPT_FILENAME);
|
||||
|
||||
s->debug = qemu_opt_get_number(opts, GLUSTER_OPT_DEBUG,
|
||||
GLUSTER_DEBUG_DEFAULT);
|
||||
if (s->debug < 0) {
|
||||
s->debug = 0;
|
||||
} else if (s->debug > GLUSTER_DEBUG_MAX) {
|
||||
s->debug = GLUSTER_DEBUG_MAX;
|
||||
s->debug_level = qemu_opt_get_number(opts, GLUSTER_OPT_DEBUG,
|
||||
GLUSTER_DEBUG_DEFAULT);
|
||||
if (s->debug_level < 0) {
|
||||
s->debug_level = 0;
|
||||
} else if (s->debug_level > GLUSTER_DEBUG_MAX) {
|
||||
s->debug_level = GLUSTER_DEBUG_MAX;
|
||||
}
|
||||
|
||||
gconf = g_new0(BlockdevOptionsGluster, 1);
|
||||
gconf->debug = s->debug;
|
||||
gconf->has_debug = true;
|
||||
|
||||
logfile = qemu_opt_get(opts, GLUSTER_OPT_LOGFILE);
|
||||
s->logfile = g_strdup(logfile ? logfile : GLUSTER_LOGFILE_DEFAULT);
|
||||
|
||||
gconf->logfile = g_strdup(s->logfile);
|
||||
gconf->has_logfile = true;
|
||||
|
||||
gconf->debug_level = s->debug_level;
|
||||
gconf->has_debug_level = true;
|
||||
s->glfs = qemu_gluster_init(gconf, filename, options, errp);
|
||||
if (!s->glfs) {
|
||||
ret = -errno;
|
||||
@@ -832,13 +738,12 @@ out:
|
||||
if (!ret) {
|
||||
return ret;
|
||||
}
|
||||
g_free(s->logfile);
|
||||
if (s->fd) {
|
||||
glfs_close(s->fd);
|
||||
}
|
||||
|
||||
glfs_clear_preopened(s->glfs);
|
||||
|
||||
if (s->glfs) {
|
||||
glfs_fini(s->glfs);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -862,10 +767,8 @@ static int qemu_gluster_reopen_prepare(BDRVReopenState *state,
|
||||
qemu_gluster_parse_flags(state->flags, &open_flags);
|
||||
|
||||
gconf = g_new0(BlockdevOptionsGluster, 1);
|
||||
gconf->debug = s->debug;
|
||||
gconf->has_debug = true;
|
||||
gconf->logfile = g_strdup(s->logfile);
|
||||
gconf->has_logfile = true;
|
||||
gconf->debug_level = s->debug_level;
|
||||
gconf->has_debug_level = true;
|
||||
reop_s->glfs = qemu_gluster_init(gconf, state->bs->filename, NULL, errp);
|
||||
if (reop_s->glfs == NULL) {
|
||||
ret = -errno;
|
||||
@@ -905,8 +808,9 @@ static void qemu_gluster_reopen_commit(BDRVReopenState *state)
|
||||
if (s->fd) {
|
||||
glfs_close(s->fd);
|
||||
}
|
||||
|
||||
glfs_clear_preopened(s->glfs);
|
||||
if (s->glfs) {
|
||||
glfs_fini(s->glfs);
|
||||
}
|
||||
|
||||
/* use the newly opened image / connection */
|
||||
s->fd = reop_s->fd;
|
||||
@@ -931,7 +835,9 @@ static void qemu_gluster_reopen_abort(BDRVReopenState *state)
|
||||
glfs_close(reop_s->fd);
|
||||
}
|
||||
|
||||
glfs_clear_preopened(reop_s->glfs);
|
||||
if (reop_s->glfs) {
|
||||
glfs_fini(reop_s->glfs);
|
||||
}
|
||||
|
||||
g_free(state->opaque);
|
||||
state->opaque = NULL;
|
||||
@@ -962,6 +868,29 @@ static coroutine_fn int qemu_gluster_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
qemu_coroutine_yield();
|
||||
return acb.ret;
|
||||
}
|
||||
|
||||
static inline bool gluster_supports_zerofill(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int qemu_gluster_zerofill(struct glfs_fd *fd, int64_t offset,
|
||||
int64_t size)
|
||||
{
|
||||
return glfs_zerofill(fd, offset, size);
|
||||
}
|
||||
|
||||
#else
|
||||
static inline bool gluster_supports_zerofill(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int qemu_gluster_zerofill(struct glfs_fd *fd, int64_t offset,
|
||||
int64_t size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int qemu_gluster_create(const char *filename,
|
||||
@@ -971,26 +900,19 @@ static int qemu_gluster_create(const char *filename,
|
||||
struct glfs *glfs;
|
||||
struct glfs_fd *fd;
|
||||
int ret = 0;
|
||||
PreallocMode prealloc;
|
||||
int prealloc = 0;
|
||||
int64_t total_size = 0;
|
||||
char *tmp = NULL;
|
||||
Error *local_err = NULL;
|
||||
|
||||
gconf = g_new0(BlockdevOptionsGluster, 1);
|
||||
gconf->debug = qemu_opt_get_number_del(opts, GLUSTER_OPT_DEBUG,
|
||||
GLUSTER_DEBUG_DEFAULT);
|
||||
if (gconf->debug < 0) {
|
||||
gconf->debug = 0;
|
||||
} else if (gconf->debug > GLUSTER_DEBUG_MAX) {
|
||||
gconf->debug = GLUSTER_DEBUG_MAX;
|
||||
gconf->debug_level = qemu_opt_get_number_del(opts, GLUSTER_OPT_DEBUG,
|
||||
GLUSTER_DEBUG_DEFAULT);
|
||||
if (gconf->debug_level < 0) {
|
||||
gconf->debug_level = 0;
|
||||
} else if (gconf->debug_level > GLUSTER_DEBUG_MAX) {
|
||||
gconf->debug_level = GLUSTER_DEBUG_MAX;
|
||||
}
|
||||
gconf->has_debug = true;
|
||||
|
||||
gconf->logfile = qemu_opt_get_del(opts, GLUSTER_OPT_LOGFILE);
|
||||
if (!gconf->logfile) {
|
||||
gconf->logfile = g_strdup(GLUSTER_LOGFILE_DEFAULT);
|
||||
}
|
||||
gconf->has_logfile = true;
|
||||
gconf->has_debug_level = true;
|
||||
|
||||
glfs = qemu_gluster_init(gconf, filename, NULL, errp);
|
||||
if (!glfs) {
|
||||
@@ -1002,12 +924,13 @@ static int qemu_gluster_create(const char *filename,
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
tmp = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
||||
prealloc = qapi_enum_parse(PreallocMode_lookup, tmp,
|
||||
PREALLOC_MODE__MAX, PREALLOC_MODE_OFF,
|
||||
&local_err);
|
||||
g_free(tmp);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
if (!tmp || !strcmp(tmp, "off")) {
|
||||
prealloc = 0;
|
||||
} else if (!strcmp(tmp, "full") && gluster_supports_zerofill()) {
|
||||
prealloc = 1;
|
||||
} else {
|
||||
error_setg(errp, "Invalid preallocation mode: '%s'"
|
||||
" or GlusterFS doesn't support zerofill API", tmp);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -1016,50 +939,25 @@ static int qemu_gluster_create(const char *filename,
|
||||
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
|
||||
if (!fd) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (prealloc) {
|
||||
#ifdef CONFIG_GLUSTERFS_FALLOCATE
|
||||
case PREALLOC_MODE_FALLOC:
|
||||
if (glfs_fallocate(fd, 0, 0, total_size)) {
|
||||
error_setg(errp, "Could not preallocate data for the new file");
|
||||
ret = -errno;
|
||||
}
|
||||
break;
|
||||
#endif /* CONFIG_GLUSTERFS_FALLOCATE */
|
||||
#ifdef CONFIG_GLUSTERFS_ZEROFILL
|
||||
case PREALLOC_MODE_FULL:
|
||||
} else {
|
||||
if (!glfs_ftruncate(fd, total_size)) {
|
||||
if (glfs_zerofill(fd, 0, total_size)) {
|
||||
error_setg(errp, "Could not zerofill the new file");
|
||||
if (prealloc && qemu_gluster_zerofill(fd, 0, total_size)) {
|
||||
ret = -errno;
|
||||
}
|
||||
} else {
|
||||
error_setg(errp, "Could not resize file");
|
||||
ret = -errno;
|
||||
}
|
||||
break;
|
||||
#endif /* CONFIG_GLUSTERFS_ZEROFILL */
|
||||
case PREALLOC_MODE_OFF:
|
||||
if (glfs_ftruncate(fd, total_size) != 0) {
|
||||
ret = -errno;
|
||||
error_setg(errp, "Could not resize file");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
error_setg(errp, "Unsupported preallocation mode: %s",
|
||||
PreallocMode_lookup[prealloc]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (glfs_close(fd) != 0) {
|
||||
ret = -errno;
|
||||
if (glfs_close(fd) != 0) {
|
||||
ret = -errno;
|
||||
}
|
||||
}
|
||||
out:
|
||||
g_free(tmp);
|
||||
qapi_free_BlockdevOptionsGluster(gconf);
|
||||
glfs_clear_preopened(glfs);
|
||||
if (glfs) {
|
||||
glfs_fini(glfs);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1094,23 +992,14 @@ static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
|
||||
return acb.ret;
|
||||
}
|
||||
|
||||
static int qemu_gluster_truncate(BlockDriverState *bs, int64_t offset,
|
||||
PreallocMode prealloc, Error **errp)
|
||||
static int qemu_gluster_truncate(BlockDriverState *bs, int64_t offset)
|
||||
{
|
||||
int ret;
|
||||
BDRVGlusterState *s = bs->opaque;
|
||||
|
||||
if (prealloc != PREALLOC_MODE_OFF) {
|
||||
error_setg(errp, "Unsupported preallocation mode '%s'",
|
||||
PreallocMode_lookup[prealloc]);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
ret = glfs_ftruncate(s->fd, offset);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
error_setg_errno(errp, -ret, "Failed to truncate file");
|
||||
return ret;
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1136,12 +1025,11 @@ static void qemu_gluster_close(BlockDriverState *bs)
|
||||
{
|
||||
BDRVGlusterState *s = bs->opaque;
|
||||
|
||||
g_free(s->logfile);
|
||||
if (s->fd) {
|
||||
glfs_close(s->fd);
|
||||
s->fd = NULL;
|
||||
}
|
||||
glfs_clear_preopened(s->glfs);
|
||||
glfs_fini(s->glfs);
|
||||
}
|
||||
|
||||
static coroutine_fn int qemu_gluster_co_flush_to_disk(BlockDriverState *bs)
|
||||
@@ -1254,20 +1142,18 @@ static int qemu_gluster_has_zero_init(BlockDriverState *bs)
|
||||
* If @start is in a trailing hole or beyond EOF, return -ENXIO.
|
||||
* If we can't find out, return a negative errno other than -ENXIO.
|
||||
*
|
||||
* (Shamefully copied from file-posix.c, only miniscule adaptions.)
|
||||
* (Shamefully copied from raw-posix.c, only miniscule adaptions.)
|
||||
*/
|
||||
static int find_allocation(BlockDriverState *bs, off_t start,
|
||||
off_t *data, off_t *hole)
|
||||
{
|
||||
BDRVGlusterState *s = bs->opaque;
|
||||
off_t offs;
|
||||
|
||||
if (!s->supports_seek_data) {
|
||||
goto exit;
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
#if defined SEEK_HOLE && defined SEEK_DATA
|
||||
off_t offs;
|
||||
|
||||
/*
|
||||
* SEEK_DATA cases:
|
||||
* D1. offs == start: start is in data
|
||||
@@ -1283,14 +1169,7 @@ static int find_allocation(BlockDriverState *bs, off_t start,
|
||||
if (offs < 0) {
|
||||
return -errno; /* D3 or D4 */
|
||||
}
|
||||
|
||||
if (offs < start) {
|
||||
/* This is not a valid return by lseek(). We are safe to just return
|
||||
* -EIO in this case, and we'll treat it like D4. Unfortunately some
|
||||
* versions of gluster server will return offs < start, so an assert
|
||||
* here will unnecessarily abort QEMU. */
|
||||
return -EIO;
|
||||
}
|
||||
assert(offs >= start);
|
||||
|
||||
if (offs > start) {
|
||||
/* D2: in hole, next data at offs */
|
||||
@@ -1322,14 +1201,7 @@ static int find_allocation(BlockDriverState *bs, off_t start,
|
||||
if (offs < 0) {
|
||||
return -errno; /* D1 and (H3 or H4) */
|
||||
}
|
||||
|
||||
if (offs < start) {
|
||||
/* This is not a valid return by lseek(). We are safe to just return
|
||||
* -EIO in this case, and we'll treat it like H4. Unfortunately some
|
||||
* versions of gluster server will return offs < start, so an assert
|
||||
* here will unnecessarily abort QEMU. */
|
||||
return -EIO;
|
||||
}
|
||||
assert(offs >= start);
|
||||
|
||||
if (offs > start) {
|
||||
/*
|
||||
@@ -1345,10 +1217,6 @@ static int find_allocation(BlockDriverState *bs, off_t start,
|
||||
|
||||
/* D1 and H1 */
|
||||
return -EBUSY;
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1364,7 +1232,7 @@ exit:
|
||||
* 'nb_sectors' is the max value 'pnum' should be set to. If nb_sectors goes
|
||||
* beyond the end of the disk image it will be clamped.
|
||||
*
|
||||
* (Based on raw_co_get_block_status() from file-posix.c.)
|
||||
* (Based on raw_co_get_block_status() from raw-posix.c.)
|
||||
*/
|
||||
static int64_t coroutine_fn qemu_gluster_co_get_block_status(
|
||||
BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
|
||||
|
||||
+641
-464
File diff suppressed because it is too large.
Load diff
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* QEMU Block driver for iSCSI images (static options)
|
||||
*
|
||||
* Copyright (c) 2017 Peter Lieven <pl@kamp.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/config-file.h"
|
||||
|
||||
static QemuOptsList qemu_iscsi_opts = {
|
||||
.name = "iscsi",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(qemu_iscsi_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "user",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "username for CHAP authentication to target",
|
||||
},{
|
||||
.name = "password",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "password for CHAP authentication to target",
|
||||
},{
|
||||
.name = "password-secret",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "ID of the secret providing password for CHAP "
|
||||
"authentication to target",
|
||||
},{
|
||||
.name = "header-digest",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "HeaderDigest setting. "
|
||||
"{CRC32C|CRC32C-NONE|NONE-CRC32C|NONE}",
|
||||
},{
|
||||
.name = "initiator-name",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Initiator iqn name to use when connecting",
|
||||
},{
|
||||
.name = "timeout",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Request timeout in seconds (default 0 = no timeout)",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static void iscsi_block_opts_init(void)
|
||||
{
|
||||
qemu_add_opts(&qemu_iscsi_opts);
|
||||
}
|
||||
|
||||
block_init(iscsi_block_opts_init);
|
||||
+206
-422
File diff suppressed because it is too large.
Load diff
+51
-177
@@ -54,11 +54,12 @@ struct LinuxAioState {
|
||||
io_context_t ctx;
|
||||
EventNotifier e;
|
||||
|
||||
/* io queue for submit at batch. Protected by AioContext lock. */
|
||||
/* io queue for submit at batch */
|
||||
LaioQueue io_q;
|
||||
|
||||
/* I/O completion processing. Only runs in I/O thread. */
|
||||
/* I/O completion processing */
|
||||
QEMUBH *completion_bh;
|
||||
struct io_event events[MAX_EVENTS];
|
||||
int event_idx;
|
||||
int event_max;
|
||||
};
|
||||
@@ -94,159 +95,64 @@ static void qemu_laio_process_completion(struct qemu_laiocb *laiocb)
|
||||
|
||||
laiocb->ret = ret;
|
||||
if (laiocb->co) {
|
||||
/* If the coroutine is already entered it must be in ioq_submit() and
|
||||
* will notice laio->ret has been filled in when it eventually runs
|
||||
* later. Coroutines cannot be entered recursively so avoid doing
|
||||
* that!
|
||||
*/
|
||||
if (!qemu_coroutine_entered(laiocb->co)) {
|
||||
aio_co_wake(laiocb->co);
|
||||
}
|
||||
qemu_coroutine_enter(laiocb->co);
|
||||
} else {
|
||||
laiocb->common.cb(laiocb->common.opaque, ret);
|
||||
qemu_aio_unref(laiocb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aio_ring buffer which is shared between userspace and kernel.
|
||||
*
|
||||
* This copied from linux/fs/aio.c, common header does not exist
|
||||
* but AIO exists for ages so we assume ABI is stable.
|
||||
*/
|
||||
struct aio_ring {
|
||||
unsigned id; /* kernel internal index number */
|
||||
unsigned nr; /* number of io_events */
|
||||
unsigned head; /* Written to by userland or by kernel. */
|
||||
unsigned tail;
|
||||
|
||||
unsigned magic;
|
||||
unsigned compat_features;
|
||||
unsigned incompat_features;
|
||||
unsigned header_length; /* size of aio_ring */
|
||||
|
||||
struct io_event io_events[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* io_getevents_peek:
|
||||
* @ctx: AIO context
|
||||
* @events: pointer on events array, output value
|
||||
|
||||
* Returns the number of completed events and sets a pointer
|
||||
* on events array. This function does not update the internal
|
||||
* ring buffer, only reads head and tail. When @events has been
|
||||
* processed io_getevents_commit() must be called.
|
||||
*/
|
||||
static inline unsigned int io_getevents_peek(io_context_t ctx,
|
||||
struct io_event **events)
|
||||
{
|
||||
struct aio_ring *ring = (struct aio_ring *)ctx;
|
||||
unsigned int head = ring->head, tail = ring->tail;
|
||||
unsigned int nr;
|
||||
|
||||
nr = tail >= head ? tail - head : ring->nr - head;
|
||||
*events = ring->io_events + head;
|
||||
/* To avoid speculative loads of s->events[i] before observing tail.
|
||||
Paired with smp_wmb() inside linux/fs/aio.c: aio_complete(). */
|
||||
smp_rmb();
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* io_getevents_commit:
|
||||
* @ctx: AIO context
|
||||
* @nr: the number of events on which head should be advanced
|
||||
*
|
||||
* Advances head of a ring buffer.
|
||||
*/
|
||||
static inline void io_getevents_commit(io_context_t ctx, unsigned int nr)
|
||||
{
|
||||
struct aio_ring *ring = (struct aio_ring *)ctx;
|
||||
|
||||
if (nr) {
|
||||
ring->head = (ring->head + nr) % ring->nr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* io_getevents_advance_and_peek:
|
||||
* @ctx: AIO context
|
||||
* @events: pointer on events array, output value
|
||||
* @nr: the number of events on which head should be advanced
|
||||
*
|
||||
* Advances head of a ring buffer and returns number of elements left.
|
||||
*/
|
||||
static inline unsigned int
|
||||
io_getevents_advance_and_peek(io_context_t ctx,
|
||||
struct io_event **events,
|
||||
unsigned int nr)
|
||||
{
|
||||
io_getevents_commit(ctx, nr);
|
||||
return io_getevents_peek(ctx, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* qemu_laio_process_completions:
|
||||
* @s: AIO state
|
||||
*
|
||||
* Fetches completed I/O requests and invokes their callbacks.
|
||||
/* The completion BH fetches completed I/O requests and invokes their
|
||||
* callbacks.
|
||||
*
|
||||
* The function is somewhat tricky because it supports nested event loops, for
|
||||
* example when a request callback invokes aio_poll(). In order to do this,
|
||||
* indices are kept in LinuxAioState. Function schedules BH completion so it
|
||||
* can be called again in a nested event loop. When there are no events left
|
||||
* to complete the BH is being canceled.
|
||||
* the completion events array and index are kept in LinuxAioState. The BH
|
||||
* reschedules itself as long as there are completions pending so it will
|
||||
* either be called again in a nested event loop or will be called after all
|
||||
* events have been completed. When there are no events left to complete, the
|
||||
* BH returns without rescheduling.
|
||||
*/
|
||||
static void qemu_laio_process_completions(LinuxAioState *s)
|
||||
{
|
||||
struct io_event *events;
|
||||
|
||||
/* Reschedule so nested event loops see currently pending completions */
|
||||
qemu_bh_schedule(s->completion_bh);
|
||||
|
||||
while ((s->event_max = io_getevents_advance_and_peek(s->ctx, &events,
|
||||
s->event_idx))) {
|
||||
for (s->event_idx = 0; s->event_idx < s->event_max; ) {
|
||||
struct iocb *iocb = events[s->event_idx].obj;
|
||||
struct qemu_laiocb *laiocb =
|
||||
container_of(iocb, struct qemu_laiocb, iocb);
|
||||
|
||||
laiocb->ret = io_event_ret(&events[s->event_idx]);
|
||||
|
||||
/* Change counters one-by-one because we can be nested. */
|
||||
s->io_q.in_flight--;
|
||||
s->event_idx++;
|
||||
qemu_laio_process_completion(laiocb);
|
||||
}
|
||||
}
|
||||
|
||||
qemu_bh_cancel(s->completion_bh);
|
||||
|
||||
/* If we are nested we have to notify the level above that we are done
|
||||
* by setting event_max to zero, upper level will then jump out of it's
|
||||
* own `for` loop. If we are the last all counters droped to zero. */
|
||||
s->event_max = 0;
|
||||
s->event_idx = 0;
|
||||
}
|
||||
|
||||
static void qemu_laio_process_completions_and_submit(LinuxAioState *s)
|
||||
{
|
||||
qemu_laio_process_completions(s);
|
||||
|
||||
aio_context_acquire(s->aio_context);
|
||||
if (!s->io_q.plugged && !QSIMPLEQ_EMPTY(&s->io_q.pending)) {
|
||||
ioq_submit(s);
|
||||
}
|
||||
aio_context_release(s->aio_context);
|
||||
}
|
||||
|
||||
static void qemu_laio_completion_bh(void *opaque)
|
||||
{
|
||||
LinuxAioState *s = opaque;
|
||||
|
||||
qemu_laio_process_completions_and_submit(s);
|
||||
/* Fetch more completion events when empty */
|
||||
if (s->event_idx == s->event_max) {
|
||||
do {
|
||||
struct timespec ts = { 0 };
|
||||
s->event_max = io_getevents(s->ctx, MAX_EVENTS, MAX_EVENTS,
|
||||
s->events, &ts);
|
||||
} while (s->event_max == -EINTR);
|
||||
|
||||
s->event_idx = 0;
|
||||
if (s->event_max <= 0) {
|
||||
s->event_max = 0;
|
||||
return; /* no more events */
|
||||
}
|
||||
s->io_q.in_flight -= s->event_max;
|
||||
}
|
||||
|
||||
/* Reschedule so nested event loops see currently pending completions */
|
||||
qemu_bh_schedule(s->completion_bh);
|
||||
|
||||
/* Process completion events */
|
||||
while (s->event_idx < s->event_max) {
|
||||
struct iocb *iocb = s->events[s->event_idx].obj;
|
||||
struct qemu_laiocb *laiocb =
|
||||
container_of(iocb, struct qemu_laiocb, iocb);
|
||||
|
||||
laiocb->ret = io_event_ret(&s->events[s->event_idx]);
|
||||
s->event_idx++;
|
||||
|
||||
qemu_laio_process_completion(laiocb);
|
||||
}
|
||||
|
||||
if (!s->io_q.plugged && !QSIMPLEQ_EMPTY(&s->io_q.pending)) {
|
||||
ioq_submit(s);
|
||||
}
|
||||
|
||||
qemu_bh_cancel(s->completion_bh);
|
||||
}
|
||||
|
||||
static void qemu_laio_completion_cb(EventNotifier *e)
|
||||
@@ -254,24 +160,10 @@ static void qemu_laio_completion_cb(EventNotifier *e)
|
||||
LinuxAioState *s = container_of(e, LinuxAioState, e);
|
||||
|
||||
if (event_notifier_test_and_clear(&s->e)) {
|
||||
qemu_laio_process_completions_and_submit(s);
|
||||
qemu_laio_completion_bh(s);
|
||||
}
|
||||
}
|
||||
|
||||
static bool qemu_laio_poll_cb(void *opaque)
|
||||
{
|
||||
EventNotifier *e = opaque;
|
||||
LinuxAioState *s = container_of(e, LinuxAioState, e);
|
||||
struct io_event *events;
|
||||
|
||||
if (!io_getevents_peek(s->ctx, &events)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qemu_laio_process_completions_and_submit(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void laio_cancel(BlockAIOCB *blockacb)
|
||||
{
|
||||
struct qemu_laiocb *laiocb = (struct qemu_laiocb *)blockacb;
|
||||
@@ -344,19 +236,6 @@ static void ioq_submit(LinuxAioState *s)
|
||||
QSIMPLEQ_SPLIT_AFTER(&s->io_q.pending, aiocb, next, &completed);
|
||||
} while (ret == len && !QSIMPLEQ_EMPTY(&s->io_q.pending));
|
||||
s->io_q.blocked = (s->io_q.in_queue > 0);
|
||||
|
||||
if (s->io_q.in_flight) {
|
||||
/* We can try to complete something just right away if there are
|
||||
* still requests in-flight. */
|
||||
qemu_laio_process_completions(s);
|
||||
/*
|
||||
* Even we have completed everything (in_flight == 0), the queue can
|
||||
* have still pended requests (in_queue > 0). We do not attempt to
|
||||
* repeat submission to avoid IO hang. The reason is simple: s->e is
|
||||
* still set and completion callback will be called shortly and all
|
||||
* pended requests will be submitted from there.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
void laio_io_plug(BlockDriverState *bs, LinuxAioState *s)
|
||||
@@ -414,7 +293,6 @@ int coroutine_fn laio_co_submit(BlockDriverState *bs, LinuxAioState *s, int fd,
|
||||
.co = qemu_coroutine_self(),
|
||||
.nbytes = qiov->size,
|
||||
.ctx = s,
|
||||
.ret = -EINPROGRESS,
|
||||
.is_read = (type == QEMU_AIO_READ),
|
||||
.qiov = qiov,
|
||||
};
|
||||
@@ -424,9 +302,7 @@ int coroutine_fn laio_co_submit(BlockDriverState *bs, LinuxAioState *s, int fd,
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (laiocb.ret == -EINPROGRESS) {
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
qemu_coroutine_yield();
|
||||
return laiocb.ret;
|
||||
}
|
||||
|
||||
@@ -456,9 +332,8 @@ BlockAIOCB *laio_submit(BlockDriverState *bs, LinuxAioState *s, int fd,
|
||||
|
||||
void laio_detach_aio_context(LinuxAioState *s, AioContext *old_context)
|
||||
{
|
||||
aio_set_event_notifier(old_context, &s->e, false, NULL, NULL);
|
||||
aio_set_event_notifier(old_context, &s->e, false, NULL);
|
||||
qemu_bh_delete(s->completion_bh);
|
||||
s->aio_context = NULL;
|
||||
}
|
||||
|
||||
void laio_attach_aio_context(LinuxAioState *s, AioContext *new_context)
|
||||
@@ -466,8 +341,7 @@ void laio_attach_aio_context(LinuxAioState *s, AioContext *new_context)
|
||||
s->aio_context = new_context;
|
||||
s->completion_bh = aio_bh_new(new_context, qemu_laio_completion_bh, s);
|
||||
aio_set_event_notifier(new_context, &s->e, false,
|
||||
qemu_laio_completion_cb,
|
||||
qemu_laio_poll_cb);
|
||||
qemu_laio_completion_cb);
|
||||
}
|
||||
|
||||
LinuxAioState *laio_init(void)
|
||||
|
||||
+271
-576
File diff suppressed because it is too large.
Load diff
+140
-178
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* QEMU Block driver for NBD
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
* Copyright (C) 2008 Bull S.A.S.
|
||||
* Author: Laurent Vivier <Laurent.Vivier@bull.net>
|
||||
*
|
||||
@@ -28,28 +27,25 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "nbd-client.h"
|
||||
|
||||
#define HANDLE_TO_INDEX(bs, handle) ((handle) ^ ((uint64_t)(intptr_t)bs))
|
||||
#define INDEX_TO_HANDLE(bs, index) ((index) ^ ((uint64_t)(intptr_t)bs))
|
||||
|
||||
static void nbd_recv_coroutines_enter_all(NBDClientSession *s)
|
||||
static void nbd_recv_coroutines_enter_all(NbdClientSession *s)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
||||
NBDClientRequest *req = &s->requests[i];
|
||||
|
||||
if (req->coroutine && req->receiving) {
|
||||
aio_co_wake(req->coroutine);
|
||||
if (s->recv_coroutine[i]) {
|
||||
qemu_coroutine_enter(s->recv_coroutine[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void nbd_teardown_connection(BlockDriverState *bs)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NbdClientSession *client = nbd_get_client_session(bs);
|
||||
|
||||
if (!client->ioc) { /* Already closed */
|
||||
return;
|
||||
@@ -59,7 +55,7 @@ static void nbd_teardown_connection(BlockDriverState *bs)
|
||||
qio_channel_shutdown(client->ioc,
|
||||
QIO_CHANNEL_SHUTDOWN_BOTH,
|
||||
NULL);
|
||||
BDRV_POLL_WHILE(bs, client->read_reply_co);
|
||||
nbd_recv_coroutines_enter_all(client);
|
||||
|
||||
nbd_client_detach_aio_context(bs);
|
||||
object_unref(OBJECT(client->sioc));
|
||||
@@ -68,96 +64,93 @@ static void nbd_teardown_connection(BlockDriverState *bs)
|
||||
client->ioc = NULL;
|
||||
}
|
||||
|
||||
static coroutine_fn void nbd_read_reply_entry(void *opaque)
|
||||
static void nbd_reply_ready(void *opaque)
|
||||
{
|
||||
NBDClientSession *s = opaque;
|
||||
BlockDriverState *bs = opaque;
|
||||
NbdClientSession *s = nbd_get_client_session(bs);
|
||||
uint64_t i;
|
||||
int ret = 0;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
while (!s->quit) {
|
||||
assert(s->reply.handle == 0);
|
||||
ret = nbd_receive_reply(s->ioc, &s->reply, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
if (ret <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* There's no need for a mutex on the receive side, because the
|
||||
* handler acts as a synchronization point and ensures that only
|
||||
* one coroutine is called until the reply finishes.
|
||||
*/
|
||||
i = HANDLE_TO_INDEX(s, s->reply.handle);
|
||||
if (i >= MAX_NBD_REQUESTS ||
|
||||
!s->requests[i].coroutine ||
|
||||
!s->requests[i].receiving) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* We're woken up again by the request itself. Note that there
|
||||
* is no race between yielding and reentering read_reply_co. This
|
||||
* is because:
|
||||
*
|
||||
* - if the request runs on the same AioContext, it is only
|
||||
* entered after we yield
|
||||
*
|
||||
* - if the request runs on a different AioContext, reentering
|
||||
* read_reply_co happens through a bottom half, which can only
|
||||
* run after we yield.
|
||||
*/
|
||||
aio_co_wake(s->requests[i].coroutine);
|
||||
qemu_coroutine_yield();
|
||||
if (!s->ioc) { /* Already closed */
|
||||
return;
|
||||
}
|
||||
|
||||
s->quit = true;
|
||||
nbd_recv_coroutines_enter_all(s);
|
||||
s->read_reply_co = NULL;
|
||||
if (s->reply.handle == 0) {
|
||||
/* No reply already in flight. Fetch a header. It is possible
|
||||
* that another thread has done the same thing in parallel, so
|
||||
* the socket is not readable anymore.
|
||||
*/
|
||||
ret = nbd_receive_reply(s->ioc, &s->reply);
|
||||
if (ret == -EAGAIN) {
|
||||
return;
|
||||
}
|
||||
if (ret < 0) {
|
||||
s->reply.handle = 0;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* There's no need for a mutex on the receive side, because the
|
||||
* handler acts as a synchronization point and ensures that only
|
||||
* one coroutine is called until the reply finishes. */
|
||||
i = HANDLE_TO_INDEX(s, s->reply.handle);
|
||||
if (i >= MAX_NBD_REQUESTS) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (s->recv_coroutine[i]) {
|
||||
qemu_coroutine_enter(s->recv_coroutine[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
fail:
|
||||
nbd_teardown_connection(bs);
|
||||
}
|
||||
|
||||
static void nbd_restart_write(void *opaque)
|
||||
{
|
||||
BlockDriverState *bs = opaque;
|
||||
|
||||
qemu_coroutine_enter(nbd_get_client_session(bs)->send_coroutine);
|
||||
}
|
||||
|
||||
static int nbd_co_send_request(BlockDriverState *bs,
|
||||
NBDRequest *request,
|
||||
struct nbd_request *request,
|
||||
QEMUIOVector *qiov)
|
||||
{
|
||||
NBDClientSession *s = nbd_get_client_session(bs);
|
||||
NbdClientSession *s = nbd_get_client_session(bs);
|
||||
AioContext *aio_context;
|
||||
int rc, ret, i;
|
||||
|
||||
qemu_co_mutex_lock(&s->send_mutex);
|
||||
while (s->in_flight == MAX_NBD_REQUESTS) {
|
||||
qemu_co_queue_wait(&s->free_sema, &s->send_mutex);
|
||||
}
|
||||
s->in_flight++;
|
||||
|
||||
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
||||
if (s->requests[i].coroutine == NULL) {
|
||||
if (s->recv_coroutine[i] == NULL) {
|
||||
s->recv_coroutine[i] = qemu_coroutine_self();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_assert(qemu_in_coroutine());
|
||||
assert(i < MAX_NBD_REQUESTS);
|
||||
|
||||
s->requests[i].coroutine = qemu_coroutine_self();
|
||||
s->requests[i].receiving = false;
|
||||
|
||||
request->handle = INDEX_TO_HANDLE(s, i);
|
||||
|
||||
if (s->quit) {
|
||||
rc = -EIO;
|
||||
goto err;
|
||||
}
|
||||
if (!s->ioc) {
|
||||
rc = -EPIPE;
|
||||
goto err;
|
||||
qemu_co_mutex_unlock(&s->send_mutex);
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
s->send_coroutine = qemu_coroutine_self();
|
||||
aio_context = bdrv_get_aio_context(bs);
|
||||
|
||||
aio_set_fd_handler(aio_context, s->sioc->fd, false,
|
||||
nbd_reply_ready, nbd_restart_write, bs);
|
||||
if (qiov) {
|
||||
qio_channel_set_cork(s->ioc, true);
|
||||
rc = nbd_send_request(s->ioc, request);
|
||||
if (rc >= 0 && !s->quit) {
|
||||
ret = nbd_rwv(s->ioc, qiov->iov, qiov->niov, request->len, false,
|
||||
NULL);
|
||||
if (rc >= 0) {
|
||||
ret = nbd_wr_syncv(s->ioc, qiov->iov, qiov->niov, request->len,
|
||||
false);
|
||||
if (ret != request->len) {
|
||||
rc = -EIO;
|
||||
}
|
||||
@@ -166,214 +159,195 @@ static int nbd_co_send_request(BlockDriverState *bs,
|
||||
} else {
|
||||
rc = nbd_send_request(s->ioc, request);
|
||||
}
|
||||
|
||||
err:
|
||||
if (rc < 0) {
|
||||
s->quit = true;
|
||||
s->requests[i].coroutine = NULL;
|
||||
s->in_flight--;
|
||||
qemu_co_queue_next(&s->free_sema);
|
||||
}
|
||||
aio_set_fd_handler(aio_context, s->sioc->fd, false,
|
||||
nbd_reply_ready, NULL, bs);
|
||||
s->send_coroutine = NULL;
|
||||
qemu_co_mutex_unlock(&s->send_mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void nbd_co_receive_reply(NBDClientSession *s,
|
||||
NBDRequest *request,
|
||||
NBDReply *reply,
|
||||
static void nbd_co_receive_reply(NbdClientSession *s,
|
||||
struct nbd_request *request,
|
||||
struct nbd_reply *reply,
|
||||
QEMUIOVector *qiov)
|
||||
{
|
||||
int i = HANDLE_TO_INDEX(s, request->handle);
|
||||
int ret;
|
||||
|
||||
/* Wait until we're woken up by nbd_read_reply_entry. */
|
||||
s->requests[i].receiving = true;
|
||||
/* Wait until we're woken up by the read handler. TODO: perhaps
|
||||
* peek at the next reply and avoid yielding if it's ours? */
|
||||
qemu_coroutine_yield();
|
||||
s->requests[i].receiving = false;
|
||||
*reply = s->reply;
|
||||
if (reply->handle != request->handle || !s->ioc || s->quit) {
|
||||
if (reply->handle != request->handle ||
|
||||
!s->ioc) {
|
||||
reply->error = EIO;
|
||||
} else {
|
||||
if (qiov && reply->error == 0) {
|
||||
ret = nbd_rwv(s->ioc, qiov->iov, qiov->niov, request->len, true,
|
||||
NULL);
|
||||
ret = nbd_wr_syncv(s->ioc, qiov->iov, qiov->niov, request->len,
|
||||
true);
|
||||
if (ret != request->len) {
|
||||
reply->error = EIO;
|
||||
s->quit = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tell the read handler to read another header. */
|
||||
s->reply.handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
s->requests[i].coroutine = NULL;
|
||||
|
||||
/* Kick the read_reply_co to get the next reply. */
|
||||
if (s->read_reply_co) {
|
||||
aio_co_wake(s->read_reply_co);
|
||||
static void nbd_coroutine_start(NbdClientSession *s,
|
||||
struct nbd_request *request)
|
||||
{
|
||||
/* Poor man semaphore. The free_sema is locked when no other request
|
||||
* can be accepted, and unlocked after receiving one reply. */
|
||||
if (s->in_flight >= MAX_NBD_REQUESTS - 1) {
|
||||
qemu_co_mutex_lock(&s->free_sema);
|
||||
assert(s->in_flight < MAX_NBD_REQUESTS);
|
||||
}
|
||||
s->in_flight++;
|
||||
|
||||
qemu_co_mutex_lock(&s->send_mutex);
|
||||
s->in_flight--;
|
||||
qemu_co_queue_next(&s->free_sema);
|
||||
qemu_co_mutex_unlock(&s->send_mutex);
|
||||
/* s->recv_coroutine[i] is set as soon as we get the send_lock. */
|
||||
}
|
||||
|
||||
static void nbd_coroutine_end(NbdClientSession *s,
|
||||
struct nbd_request *request)
|
||||
{
|
||||
int i = HANDLE_TO_INDEX(s, request->handle);
|
||||
s->recv_coroutine[i] = NULL;
|
||||
if (s->in_flight-- == MAX_NBD_REQUESTS) {
|
||||
qemu_co_mutex_unlock(&s->free_sema);
|
||||
}
|
||||
}
|
||||
|
||||
int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = {
|
||||
NbdClientSession *client = nbd_get_client_session(bs);
|
||||
struct nbd_request request = {
|
||||
.type = NBD_CMD_READ,
|
||||
.from = offset,
|
||||
.len = bytes,
|
||||
};
|
||||
NBDReply reply;
|
||||
struct nbd_reply reply;
|
||||
ssize_t ret;
|
||||
|
||||
assert(bytes <= NBD_MAX_BUFFER_SIZE);
|
||||
assert(!flags);
|
||||
|
||||
nbd_coroutine_start(client, &request);
|
||||
ret = nbd_co_send_request(bs, &request, NULL);
|
||||
if (ret < 0) {
|
||||
reply.error = -ret;
|
||||
} else {
|
||||
nbd_co_receive_reply(client, &request, &reply, qiov);
|
||||
}
|
||||
nbd_coroutine_end(client, &request);
|
||||
return -reply.error;
|
||||
}
|
||||
|
||||
int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = {
|
||||
NbdClientSession *client = nbd_get_client_session(bs);
|
||||
struct nbd_request request = {
|
||||
.type = NBD_CMD_WRITE,
|
||||
.from = offset,
|
||||
.len = bytes,
|
||||
};
|
||||
NBDReply reply;
|
||||
struct nbd_reply reply;
|
||||
ssize_t ret;
|
||||
|
||||
assert(!(client->info.flags & NBD_FLAG_READ_ONLY));
|
||||
if (flags & BDRV_REQ_FUA) {
|
||||
assert(client->info.flags & NBD_FLAG_SEND_FUA);
|
||||
request.flags |= NBD_CMD_FLAG_FUA;
|
||||
assert(client->nbdflags & NBD_FLAG_SEND_FUA);
|
||||
request.type |= NBD_CMD_FLAG_FUA;
|
||||
}
|
||||
|
||||
assert(bytes <= NBD_MAX_BUFFER_SIZE);
|
||||
|
||||
nbd_coroutine_start(client, &request);
|
||||
ret = nbd_co_send_request(bs, &request, qiov);
|
||||
if (ret < 0) {
|
||||
reply.error = -ret;
|
||||
} else {
|
||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
||||
}
|
||||
return -reply.error;
|
||||
}
|
||||
|
||||
int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
ssize_t ret;
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = {
|
||||
.type = NBD_CMD_WRITE_ZEROES,
|
||||
.from = offset,
|
||||
.len = bytes,
|
||||
};
|
||||
NBDReply reply;
|
||||
|
||||
assert(!(client->info.flags & NBD_FLAG_READ_ONLY));
|
||||
if (!(client->info.flags & NBD_FLAG_SEND_WRITE_ZEROES)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (flags & BDRV_REQ_FUA) {
|
||||
assert(client->info.flags & NBD_FLAG_SEND_FUA);
|
||||
request.flags |= NBD_CMD_FLAG_FUA;
|
||||
}
|
||||
if (!(flags & BDRV_REQ_MAY_UNMAP)) {
|
||||
request.flags |= NBD_CMD_FLAG_NO_HOLE;
|
||||
}
|
||||
|
||||
ret = nbd_co_send_request(bs, &request, NULL);
|
||||
if (ret < 0) {
|
||||
reply.error = -ret;
|
||||
} else {
|
||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
||||
}
|
||||
nbd_coroutine_end(client, &request);
|
||||
return -reply.error;
|
||||
}
|
||||
|
||||
int nbd_client_co_flush(BlockDriverState *bs)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = { .type = NBD_CMD_FLUSH };
|
||||
NBDReply reply;
|
||||
NbdClientSession *client = nbd_get_client_session(bs);
|
||||
struct nbd_request request = { .type = NBD_CMD_FLUSH };
|
||||
struct nbd_reply reply;
|
||||
ssize_t ret;
|
||||
|
||||
if (!(client->info.flags & NBD_FLAG_SEND_FLUSH)) {
|
||||
if (!(client->nbdflags & NBD_FLAG_SEND_FLUSH)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
request.from = 0;
|
||||
request.len = 0;
|
||||
|
||||
nbd_coroutine_start(client, &request);
|
||||
ret = nbd_co_send_request(bs, &request, NULL);
|
||||
if (ret < 0) {
|
||||
reply.error = -ret;
|
||||
} else {
|
||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
||||
}
|
||||
nbd_coroutine_end(client, &request);
|
||||
return -reply.error;
|
||||
}
|
||||
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes)
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = {
|
||||
NbdClientSession *client = nbd_get_client_session(bs);
|
||||
struct nbd_request request = {
|
||||
.type = NBD_CMD_TRIM,
|
||||
.from = offset,
|
||||
.len = bytes,
|
||||
.len = count,
|
||||
};
|
||||
NBDReply reply;
|
||||
struct nbd_reply reply;
|
||||
ssize_t ret;
|
||||
|
||||
assert(!(client->info.flags & NBD_FLAG_READ_ONLY));
|
||||
if (!(client->info.flags & NBD_FLAG_SEND_TRIM)) {
|
||||
if (!(client->nbdflags & NBD_FLAG_SEND_TRIM)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
nbd_coroutine_start(client, &request);
|
||||
ret = nbd_co_send_request(bs, &request, NULL);
|
||||
if (ret < 0) {
|
||||
reply.error = -ret;
|
||||
} else {
|
||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
||||
}
|
||||
nbd_coroutine_end(client, &request);
|
||||
return -reply.error;
|
||||
|
||||
}
|
||||
|
||||
void nbd_client_detach_aio_context(BlockDriverState *bs)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
qio_channel_detach_aio_context(QIO_CHANNEL(client->ioc));
|
||||
aio_set_fd_handler(bdrv_get_aio_context(bs),
|
||||
nbd_get_client_session(bs)->sioc->fd,
|
||||
false, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
||||
AioContext *new_context)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
qio_channel_attach_aio_context(QIO_CHANNEL(client->ioc), new_context);
|
||||
aio_co_schedule(new_context, client->read_reply_co);
|
||||
aio_set_fd_handler(new_context, nbd_get_client_session(bs)->sioc->fd,
|
||||
false, nbd_reply_ready, NULL, bs);
|
||||
}
|
||||
|
||||
void nbd_client_close(BlockDriverState *bs)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = { .type = NBD_CMD_DISC };
|
||||
NbdClientSession *client = nbd_get_client_session(bs);
|
||||
struct nbd_request request = {
|
||||
.type = NBD_CMD_DISC,
|
||||
.from = 0,
|
||||
.len = 0
|
||||
};
|
||||
|
||||
if (client->ioc == NULL) {
|
||||
return;
|
||||
@@ -391,40 +365,28 @@ int nbd_client_init(BlockDriverState *bs,
|
||||
const char *hostname,
|
||||
Error **errp)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NbdClientSession *client = nbd_get_client_session(bs);
|
||||
int ret;
|
||||
|
||||
/* NBD handshake */
|
||||
logout("session init %s\n", export);
|
||||
qio_channel_set_blocking(QIO_CHANNEL(sioc), true, NULL);
|
||||
|
||||
client->info.request_sizes = true;
|
||||
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
|
||||
&client->nbdflags,
|
||||
tlscreds, hostname,
|
||||
&client->ioc, &client->info, errp);
|
||||
&client->ioc,
|
||||
&client->size, errp);
|
||||
if (ret < 0) {
|
||||
logout("Failed to negotiate with the NBD server\n");
|
||||
return ret;
|
||||
}
|
||||
if (client->info.flags & NBD_FLAG_READ_ONLY &&
|
||||
!bdrv_is_read_only(bs)) {
|
||||
error_setg(errp,
|
||||
"request for write access conflicts with read-only export");
|
||||
return -EACCES;
|
||||
}
|
||||
if (client->info.flags & NBD_FLAG_SEND_FUA) {
|
||||
if (client->nbdflags & NBD_FLAG_SEND_FUA) {
|
||||
bs->supported_write_flags = BDRV_REQ_FUA;
|
||||
bs->supported_zero_flags |= BDRV_REQ_FUA;
|
||||
}
|
||||
if (client->info.flags & NBD_FLAG_SEND_WRITE_ZEROES) {
|
||||
bs->supported_zero_flags |= BDRV_REQ_MAY_UNMAP;
|
||||
}
|
||||
if (client->info.min_block > bs->bl.request_alignment) {
|
||||
bs->bl.request_alignment = client->info.min_block;
|
||||
}
|
||||
|
||||
qemu_co_mutex_init(&client->send_mutex);
|
||||
qemu_co_queue_init(&client->free_sema);
|
||||
qemu_co_mutex_init(&client->free_sema);
|
||||
client->sioc = sioc;
|
||||
object_ref(OBJECT(client->sioc));
|
||||
|
||||
@@ -436,7 +398,7 @@ int nbd_client_init(BlockDriverState *bs,
|
||||
/* Now that we're connected, set the socket to be non-blocking and
|
||||
* kick the reply mechanism. */
|
||||
qio_channel_set_blocking(QIO_CHANNEL(sioc), false, NULL);
|
||||
client->read_reply_co = qemu_coroutine_create(nbd_read_reply_entry, client);
|
||||
|
||||
nbd_client_attach_aio_context(bs, bdrv_get_aio_context(bs));
|
||||
|
||||
logout("Established connection with NBD server\n");
|
||||
|
||||
+12
-17
@@ -17,27 +17,24 @@
|
||||
|
||||
#define MAX_NBD_REQUESTS 16
|
||||
|
||||
typedef struct {
|
||||
Coroutine *coroutine;
|
||||
bool receiving; /* waiting for read_reply_co? */
|
||||
} NBDClientRequest;
|
||||
|
||||
typedef struct NBDClientSession {
|
||||
typedef struct NbdClientSession {
|
||||
QIOChannelSocket *sioc; /* The master data channel */
|
||||
QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */
|
||||
NBDExportInfo info;
|
||||
uint16_t nbdflags;
|
||||
off_t size;
|
||||
|
||||
CoMutex send_mutex;
|
||||
CoQueue free_sema;
|
||||
Coroutine *read_reply_co;
|
||||
CoMutex free_sema;
|
||||
Coroutine *send_coroutine;
|
||||
int in_flight;
|
||||
|
||||
NBDClientRequest requests[MAX_NBD_REQUESTS];
|
||||
NBDReply reply;
|
||||
bool quit;
|
||||
} NBDClientSession;
|
||||
Coroutine *recv_coroutine[MAX_NBD_REQUESTS];
|
||||
struct nbd_reply reply;
|
||||
|
||||
NBDClientSession *nbd_get_client_session(BlockDriverState *bs);
|
||||
bool is_unix;
|
||||
} NbdClientSession;
|
||||
|
||||
NbdClientSession *nbd_get_client_session(BlockDriverState *bs);
|
||||
|
||||
int nbd_client_init(BlockDriverState *bs,
|
||||
QIOChannelSocket *sock,
|
||||
@@ -47,12 +44,10 @@ int nbd_client_init(BlockDriverState *bs,
|
||||
Error **errp);
|
||||
void nbd_client_close(BlockDriverState *bs);
|
||||
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes);
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count);
|
||||
int nbd_client_co_flush(BlockDriverState *bs);
|
||||
int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
||||
int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
||||
int bytes, BdrvRequestFlags flags);
|
||||
int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
||||
|
||||
|
||||
+103
-188
@@ -32,22 +32,19 @@
|
||||
#include "qemu/uri.h"
|
||||
#include "block/block_int.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qapi-visit.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/qobject-output-visitor.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qint.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qemu/cutils.h"
|
||||
|
||||
#define EN_OPTSTR ":exportname="
|
||||
|
||||
typedef struct BDRVNBDState {
|
||||
NBDClientSession client;
|
||||
NbdClientSession client;
|
||||
|
||||
/* For nbd_refresh_filename() */
|
||||
SocketAddress *saddr;
|
||||
char *export, *tlscredsid;
|
||||
char *path, *host, *port, *export, *tlscredsid;
|
||||
} BDRVNBDState;
|
||||
|
||||
static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
@@ -64,11 +61,11 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
}
|
||||
|
||||
/* transport */
|
||||
if (!g_strcmp0(uri->scheme, "nbd")) {
|
||||
if (!strcmp(uri->scheme, "nbd")) {
|
||||
is_unix = false;
|
||||
} else if (!g_strcmp0(uri->scheme, "nbd+tcp")) {
|
||||
} else if (!strcmp(uri->scheme, "nbd+tcp")) {
|
||||
is_unix = false;
|
||||
} else if (!g_strcmp0(uri->scheme, "nbd+unix")) {
|
||||
} else if (!strcmp(uri->scheme, "nbd+unix")) {
|
||||
is_unix = true;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
@@ -78,7 +75,7 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
p = uri->path ? uri->path : "/";
|
||||
p += strspn(p, "/");
|
||||
if (p[0]) {
|
||||
qdict_put_str(options, "export", p);
|
||||
qdict_put(options, "export", qstring_from_str(p));
|
||||
}
|
||||
|
||||
qp = query_params_parse(uri->query);
|
||||
@@ -93,12 +90,9 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
qdict_put_str(options, "server.type", "unix");
|
||||
qdict_put_str(options, "server.path", qp->p[0].value);
|
||||
qdict_put(options, "path", qstring_from_str(qp->p[0].value));
|
||||
} else {
|
||||
QString *host;
|
||||
char *port_str;
|
||||
|
||||
/* nbd[+tcp]://host[:port]/export */
|
||||
if (!uri->server) {
|
||||
ret = -EINVAL;
|
||||
@@ -113,12 +107,12 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
host = qstring_from_str(uri->server);
|
||||
}
|
||||
|
||||
qdict_put_str(options, "server.type", "inet");
|
||||
qdict_put(options, "server.host", host);
|
||||
|
||||
port_str = g_strdup_printf("%d", uri->port ?: NBD_DEFAULT_PORT);
|
||||
qdict_put_str(options, "server.port", port_str);
|
||||
g_free(port_str);
|
||||
qdict_put(options, "host", host);
|
||||
if (uri->port) {
|
||||
char* port_str = g_strdup_printf("%d", uri->port);
|
||||
qdict_put(options, "port", qstring_from_str(port_str));
|
||||
g_free(port_str);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
@@ -129,26 +123,6 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool nbd_has_filename_options_conflict(QDict *options, Error **errp)
|
||||
{
|
||||
const QDictEntry *e;
|
||||
|
||||
for (e = qdict_first(options); e; e = qdict_next(options, e)) {
|
||||
if (!strcmp(e->key, "host") ||
|
||||
!strcmp(e->key, "port") ||
|
||||
!strcmp(e->key, "path") ||
|
||||
!strcmp(e->key, "export") ||
|
||||
strstart(e->key, "server.", NULL))
|
||||
{
|
||||
error_setg(errp, "Option '%s' cannot be used with a file name",
|
||||
e->key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
@@ -157,7 +131,12 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
const char *host_spec;
|
||||
const char *unixpath;
|
||||
|
||||
if (nbd_has_filename_options_conflict(options, errp)) {
|
||||
if (qdict_haskey(options, "host")
|
||||
|| qdict_haskey(options, "port")
|
||||
|| qdict_haskey(options, "path"))
|
||||
{
|
||||
error_setg(errp, "host/port/path and a file name may not be specified "
|
||||
"at the same time");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -179,7 +158,7 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
export_name[0] = 0; /* truncate 'file' */
|
||||
export_name += strlen(EN_OPTSTR);
|
||||
|
||||
qdict_put_str(options, "export", export_name);
|
||||
qdict_put(options, "export", qstring_from_str(export_name));
|
||||
}
|
||||
|
||||
/* extract the host_spec - fail if it's not nbd:... */
|
||||
@@ -194,19 +173,17 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
|
||||
/* are we a UNIX or TCP socket? */
|
||||
if (strstart(host_spec, "unix:", &unixpath)) {
|
||||
qdict_put_str(options, "server.type", "unix");
|
||||
qdict_put_str(options, "server.path", unixpath);
|
||||
qdict_put(options, "path", qstring_from_str(unixpath));
|
||||
} else {
|
||||
InetSocketAddress *addr = g_new(InetSocketAddress, 1);
|
||||
InetSocketAddress *addr = NULL;
|
||||
|
||||
if (inet_parse(addr, host_spec, errp)) {
|
||||
goto out_inet;
|
||||
addr = inet_parse(host_spec, errp);
|
||||
if (!addr) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
qdict_put_str(options, "server.type", "inet");
|
||||
qdict_put_str(options, "server.host", addr->host);
|
||||
qdict_put_str(options, "server.port", addr->port);
|
||||
out_inet:
|
||||
qdict_put(options, "host", qstring_from_str(addr->host));
|
||||
qdict_put(options, "port", qstring_from_str(addr->port));
|
||||
qapi_free_InetSocketAddress(addr);
|
||||
}
|
||||
|
||||
@@ -214,92 +191,51 @@ out:
|
||||
g_free(file);
|
||||
}
|
||||
|
||||
static bool nbd_process_legacy_socket_options(QDict *output_options,
|
||||
QemuOpts *legacy_opts,
|
||||
Error **errp)
|
||||
static SocketAddress *nbd_config(BDRVNBDState *s, QemuOpts *opts, Error **errp)
|
||||
{
|
||||
const char *path = qemu_opt_get(legacy_opts, "path");
|
||||
const char *host = qemu_opt_get(legacy_opts, "host");
|
||||
const char *port = qemu_opt_get(legacy_opts, "port");
|
||||
const QDictEntry *e;
|
||||
SocketAddress *saddr;
|
||||
|
||||
if (!path && !host && !port) {
|
||||
return true;
|
||||
s->path = g_strdup(qemu_opt_get(opts, "path"));
|
||||
s->host = g_strdup(qemu_opt_get(opts, "host"));
|
||||
|
||||
if (!s->path == !s->host) {
|
||||
if (s->path) {
|
||||
error_setg(errp, "path and host may not be used at the same time.");
|
||||
} else {
|
||||
error_setg(errp, "one of path and host must be specified.");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (e = qdict_first(output_options); e; e = qdict_next(output_options, e))
|
||||
{
|
||||
if (strstart(e->key, "server.", NULL)) {
|
||||
error_setg(errp, "Cannot use 'server' and path/host/port at the "
|
||||
"same time");
|
||||
return false;
|
||||
saddr = g_new0(SocketAddress, 1);
|
||||
|
||||
if (s->path) {
|
||||
UnixSocketAddress *q_unix;
|
||||
saddr->type = SOCKET_ADDRESS_KIND_UNIX;
|
||||
q_unix = saddr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
|
||||
q_unix->path = g_strdup(s->path);
|
||||
} else {
|
||||
InetSocketAddress *inet;
|
||||
|
||||
s->port = g_strdup(qemu_opt_get(opts, "port"));
|
||||
|
||||
saddr->type = SOCKET_ADDRESS_KIND_INET;
|
||||
inet = saddr->u.inet.data = g_new0(InetSocketAddress, 1);
|
||||
inet->host = g_strdup(s->host);
|
||||
inet->port = g_strdup(s->port);
|
||||
if (!inet->port) {
|
||||
inet->port = g_strdup_printf("%d", NBD_DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
if (path && host) {
|
||||
error_setg(errp, "path and host may not be used at the same time");
|
||||
return false;
|
||||
} else if (path) {
|
||||
if (port) {
|
||||
error_setg(errp, "port may not be used without host");
|
||||
return false;
|
||||
}
|
||||
s->client.is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
|
||||
|
||||
qdict_put_str(output_options, "server.type", "unix");
|
||||
qdict_put_str(output_options, "server.path", path);
|
||||
} else if (host) {
|
||||
qdict_put_str(output_options, "server.type", "inet");
|
||||
qdict_put_str(output_options, "server.host", host);
|
||||
qdict_put_str(output_options, "server.port",
|
||||
port ?: stringify(NBD_DEFAULT_PORT));
|
||||
}
|
||||
s->export = g_strdup(qemu_opt_get(opts, "export"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static SocketAddress *nbd_config(BDRVNBDState *s, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
SocketAddress *saddr = NULL;
|
||||
QDict *addr = NULL;
|
||||
QObject *crumpled_addr = NULL;
|
||||
Visitor *iv = NULL;
|
||||
Error *local_err = NULL;
|
||||
|
||||
qdict_extract_subqdict(options, &addr, "server.");
|
||||
if (!qdict_size(addr)) {
|
||||
error_setg(errp, "NBD server address missing");
|
||||
goto done;
|
||||
}
|
||||
|
||||
crumpled_addr = qdict_crumple(addr, errp);
|
||||
if (!crumpled_addr) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME .numeric, .to, .ipv4 or .ipv6 don't work with -drive
|
||||
* server.type=inet. .to doesn't matter, it's ignored anyway.
|
||||
* That's because when @options come from -blockdev or
|
||||
* blockdev_add, members are typed according to the QAPI schema,
|
||||
* but when they come from -drive, they're all QString. The
|
||||
* visitor expects the former.
|
||||
*/
|
||||
iv = qobject_input_visitor_new(crumpled_addr);
|
||||
visit_type_SocketAddress(iv, NULL, &saddr, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
QDECREF(addr);
|
||||
qobject_decref(crumpled_addr);
|
||||
visit_free(iv);
|
||||
return saddr;
|
||||
}
|
||||
|
||||
NBDClientSession *nbd_get_client_session(BlockDriverState *bs)
|
||||
NbdClientSession *nbd_get_client_session(BlockDriverState *bs)
|
||||
{
|
||||
BDRVNBDState *s = bs->opaque;
|
||||
return &s->client;
|
||||
@@ -312,13 +248,11 @@ static QIOChannelSocket *nbd_establish_connection(SocketAddress *saddr,
|
||||
Error *local_err = NULL;
|
||||
|
||||
sioc = qio_channel_socket_new();
|
||||
qio_channel_set_name(QIO_CHANNEL(sioc), "nbd-client");
|
||||
|
||||
qio_channel_socket_connect_sync(sioc,
|
||||
saddr,
|
||||
&local_err);
|
||||
if (local_err) {
|
||||
object_unref(OBJECT(sioc));
|
||||
error_propagate(errp, local_err);
|
||||
return NULL;
|
||||
}
|
||||
@@ -398,6 +332,7 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
QemuOpts *opts = NULL;
|
||||
Error *local_err = NULL;
|
||||
QIOChannelSocket *sioc = NULL;
|
||||
SocketAddress *saddr = NULL;
|
||||
QCryptoTLSCreds *tlscreds = NULL;
|
||||
const char *hostname = NULL;
|
||||
int ret = -EINVAL;
|
||||
@@ -409,19 +344,12 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Translate @host, @port, and @path to a SocketAddress */
|
||||
if (!nbd_process_legacy_socket_options(options, opts, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Pop the config into our state object. Exit if invalid. */
|
||||
s->saddr = nbd_config(s, options, errp);
|
||||
if (!s->saddr) {
|
||||
saddr = nbd_config(s, opts, errp);
|
||||
if (!saddr) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->export = g_strdup(qemu_opt_get(opts, "export"));
|
||||
|
||||
s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds"));
|
||||
if (s->tlscredsid) {
|
||||
tlscreds = nbd_get_tls_creds(s->tlscredsid, errp);
|
||||
@@ -429,18 +357,17 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* TODO SOCKET_ADDRESS_KIND_FD where fd has AF_INET or AF_INET6 */
|
||||
if (s->saddr->type != SOCKET_ADDRESS_TYPE_INET) {
|
||||
if (saddr->type != SOCKET_ADDRESS_KIND_INET) {
|
||||
error_setg(errp, "TLS only supported over IP sockets");
|
||||
goto error;
|
||||
}
|
||||
hostname = s->saddr->u.inet.host;
|
||||
hostname = saddr->u.inet.data->host;
|
||||
}
|
||||
|
||||
/* establish TCP connection, return error if it fails
|
||||
* TODO: Configurable retry-until-timeout behaviour.
|
||||
*/
|
||||
sioc = nbd_establish_connection(s->saddr, errp);
|
||||
sioc = nbd_establish_connection(saddr, errp);
|
||||
if (!sioc) {
|
||||
ret = -ECONNREFUSED;
|
||||
goto error;
|
||||
@@ -457,10 +384,13 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
object_unref(OBJECT(tlscreds));
|
||||
}
|
||||
if (ret < 0) {
|
||||
qapi_free_SocketAddress(s->saddr);
|
||||
g_free(s->path);
|
||||
g_free(s->host);
|
||||
g_free(s->port);
|
||||
g_free(s->export);
|
||||
g_free(s->tlscredsid);
|
||||
}
|
||||
qapi_free_SocketAddress(saddr);
|
||||
qemu_opts_del(opts);
|
||||
return ret;
|
||||
}
|
||||
@@ -472,17 +402,8 @@ static int nbd_co_flush(BlockDriverState *bs)
|
||||
|
||||
static void nbd_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||
{
|
||||
NBDClientSession *s = nbd_get_client_session(bs);
|
||||
uint32_t max = MIN_NON_ZERO(NBD_MAX_BUFFER_SIZE, s->info.max_block);
|
||||
|
||||
bs->bl.max_pdiscard = max;
|
||||
bs->bl.max_pwrite_zeroes = max;
|
||||
bs->bl.max_transfer = max;
|
||||
|
||||
if (s->info.opt_block &&
|
||||
s->info.opt_block > bs->bl.opt_transfer) {
|
||||
bs->bl.opt_transfer = s->info.opt_block;
|
||||
}
|
||||
bs->bl.max_pdiscard = NBD_MAX_BUFFER_SIZE;
|
||||
bs->bl.max_transfer = NBD_MAX_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
static void nbd_close(BlockDriverState *bs)
|
||||
@@ -491,7 +412,9 @@ static void nbd_close(BlockDriverState *bs)
|
||||
|
||||
nbd_client_close(bs);
|
||||
|
||||
qapi_free_SocketAddress(s->saddr);
|
||||
g_free(s->path);
|
||||
g_free(s->host);
|
||||
g_free(s->port);
|
||||
g_free(s->export);
|
||||
g_free(s->tlscredsid);
|
||||
}
|
||||
@@ -500,7 +423,7 @@ static int64_t nbd_getlength(BlockDriverState *bs)
|
||||
{
|
||||
BDRVNBDState *s = bs->opaque;
|
||||
|
||||
return s->client.info.size;
|
||||
return s->client.size;
|
||||
}
|
||||
|
||||
static void nbd_detach_aio_context(BlockDriverState *bs)
|
||||
@@ -518,50 +441,45 @@ static void nbd_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
{
|
||||
BDRVNBDState *s = bs->opaque;
|
||||
QDict *opts = qdict_new();
|
||||
QObject *saddr_qdict;
|
||||
Visitor *ov;
|
||||
const char *host = NULL, *port = NULL, *path = NULL;
|
||||
|
||||
if (s->saddr->type == SOCKET_ADDRESS_TYPE_INET) {
|
||||
const InetSocketAddress *inet = &s->saddr->u.inet;
|
||||
if (!inet->has_ipv4 && !inet->has_ipv6 && !inet->has_to) {
|
||||
host = inet->host;
|
||||
port = inet->port;
|
||||
}
|
||||
} else if (s->saddr->type == SOCKET_ADDRESS_TYPE_UNIX) {
|
||||
path = s->saddr->u.q_unix.path;
|
||||
} /* else can't represent as pseudo-filename */
|
||||
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("nbd")));
|
||||
|
||||
qdict_put_str(opts, "driver", "nbd");
|
||||
|
||||
if (path && s->export) {
|
||||
if (s->path && s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd+unix:///%s?socket=%s", s->export, path);
|
||||
} else if (path && !s->export) {
|
||||
"nbd+unix:///%s?socket=%s", s->export, s->path);
|
||||
} else if (s->path && !s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd+unix://?socket=%s", path);
|
||||
} else if (host && s->export) {
|
||||
"nbd+unix://?socket=%s", s->path);
|
||||
} else if (!s->path && s->export && s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s:%s/%s", host, port, s->export);
|
||||
} else if (host && !s->export) {
|
||||
"nbd://%s:%s/%s", s->host, s->port, s->export);
|
||||
} else if (!s->path && s->export && !s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s:%s", host, port);
|
||||
"nbd://%s/%s", s->host, s->export);
|
||||
} else if (!s->path && !s->export && s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s:%s", s->host, s->port);
|
||||
} else if (!s->path && !s->export && !s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s", s->host);
|
||||
}
|
||||
|
||||
ov = qobject_output_visitor_new(&saddr_qdict);
|
||||
visit_type_SocketAddress(ov, NULL, &s->saddr, &error_abort);
|
||||
visit_complete(ov, &saddr_qdict);
|
||||
visit_free(ov);
|
||||
qdict_put_obj(opts, "server", saddr_qdict);
|
||||
|
||||
if (s->path) {
|
||||
qdict_put_obj(opts, "path", QOBJECT(qstring_from_str(s->path)));
|
||||
} else if (s->port) {
|
||||
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(s->host)));
|
||||
qdict_put_obj(opts, "port", QOBJECT(qstring_from_str(s->port)));
|
||||
} else {
|
||||
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(s->host)));
|
||||
}
|
||||
if (s->export) {
|
||||
qdict_put_str(opts, "export", s->export);
|
||||
qdict_put_obj(opts, "export", QOBJECT(qstring_from_str(s->export)));
|
||||
}
|
||||
if (s->tlscredsid) {
|
||||
qdict_put_str(opts, "tls-creds", s->tlscredsid);
|
||||
qdict_put_obj(opts, "tls-creds",
|
||||
QOBJECT(qstring_from_str(s->tlscredsid)));
|
||||
}
|
||||
|
||||
qdict_flatten(opts);
|
||||
bs->full_open_options = opts;
|
||||
}
|
||||
|
||||
@@ -573,7 +491,6 @@ static BlockDriver bdrv_nbd = {
|
||||
.bdrv_file_open = nbd_open,
|
||||
.bdrv_co_preadv = nbd_client_co_preadv,
|
||||
.bdrv_co_pwritev = nbd_client_co_pwritev,
|
||||
.bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes,
|
||||
.bdrv_close = nbd_close,
|
||||
.bdrv_co_flush_to_os = nbd_co_flush,
|
||||
.bdrv_co_pdiscard = nbd_client_co_pdiscard,
|
||||
@@ -592,7 +509,6 @@ static BlockDriver bdrv_nbd_tcp = {
|
||||
.bdrv_file_open = nbd_open,
|
||||
.bdrv_co_preadv = nbd_client_co_preadv,
|
||||
.bdrv_co_pwritev = nbd_client_co_pwritev,
|
||||
.bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes,
|
||||
.bdrv_close = nbd_close,
|
||||
.bdrv_co_flush_to_os = nbd_co_flush,
|
||||
.bdrv_co_pdiscard = nbd_client_co_pdiscard,
|
||||
@@ -611,7 +527,6 @@ static BlockDriver bdrv_nbd_unix = {
|
||||
.bdrv_file_open = nbd_open,
|
||||
.bdrv_co_preadv = nbd_client_co_preadv,
|
||||
.bdrv_co_pwritev = nbd_client_co_pwritev,
|
||||
.bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes,
|
||||
.bdrv_close = nbd_close,
|
||||
.bdrv_co_flush_to_os = nbd_co_flush,
|
||||
.bdrv_co_pdiscard = nbd_client_co_pdiscard,
|
||||
|
||||
+155
-466
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* QEMU Block driver for native access to files on NFS shares
|
||||
*
|
||||
* Copyright (c) 2014-2017 Peter Lieven <pl@kamp.de>
|
||||
* Copyright (c) 2014-2016 Peter Lieven <pl@kamp.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -35,14 +35,8 @@
|
||||
#include "qemu/uri.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi-visit.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/qobject-output-visitor.h"
|
||||
#include <nfsc/libnfs.h>
|
||||
|
||||
|
||||
#define QEMU_NFS_MAX_READAHEAD_SIZE 1048576
|
||||
#define QEMU_NFS_MAX_PAGECACHE_SIZE (8388608 / NFS_BLKSIZE)
|
||||
#define QEMU_NFS_MAX_DEBUG_LEVEL 2
|
||||
@@ -53,139 +47,23 @@ typedef struct NFSClient {
|
||||
int events;
|
||||
bool has_zero_init;
|
||||
AioContext *aio_context;
|
||||
QemuMutex mutex;
|
||||
blkcnt_t st_blocks;
|
||||
bool cache_used;
|
||||
NFSServer *server;
|
||||
char *path;
|
||||
int64_t uid, gid, tcp_syncnt, readahead, pagecache, debug;
|
||||
} NFSClient;
|
||||
|
||||
typedef struct NFSRPC {
|
||||
BlockDriverState *bs;
|
||||
int ret;
|
||||
int complete;
|
||||
QEMUIOVector *iov;
|
||||
struct stat *st;
|
||||
Coroutine *co;
|
||||
QEMUBH *bh;
|
||||
NFSClient *client;
|
||||
} NFSRPC;
|
||||
|
||||
static int nfs_parse_uri(const char *filename, QDict *options, Error **errp)
|
||||
{
|
||||
URI *uri = NULL;
|
||||
QueryParams *qp = NULL;
|
||||
int ret = -EINVAL, i;
|
||||
|
||||
uri = uri_parse(filename);
|
||||
if (!uri) {
|
||||
error_setg(errp, "Invalid URI specified");
|
||||
goto out;
|
||||
}
|
||||
if (g_strcmp0(uri->scheme, "nfs") != 0) {
|
||||
error_setg(errp, "URI scheme must be 'nfs'");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!uri->server) {
|
||||
error_setg(errp, "missing hostname in URI");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!uri->path) {
|
||||
error_setg(errp, "missing file path in URI");
|
||||
goto out;
|
||||
}
|
||||
|
||||
qp = query_params_parse(uri->query);
|
||||
if (!qp) {
|
||||
error_setg(errp, "could not parse query parameters");
|
||||
goto out;
|
||||
}
|
||||
|
||||
qdict_put_str(options, "server.host", uri->server);
|
||||
qdict_put_str(options, "server.type", "inet");
|
||||
qdict_put_str(options, "path", uri->path);
|
||||
|
||||
for (i = 0; i < qp->n; i++) {
|
||||
unsigned long long val;
|
||||
if (!qp->p[i].value) {
|
||||
error_setg(errp, "Value for NFS parameter expected: %s",
|
||||
qp->p[i].name);
|
||||
goto out;
|
||||
}
|
||||
if (parse_uint_full(qp->p[i].value, &val, 0)) {
|
||||
error_setg(errp, "Illegal value for NFS parameter: %s",
|
||||
qp->p[i].name);
|
||||
goto out;
|
||||
}
|
||||
if (!strcmp(qp->p[i].name, "uid")) {
|
||||
qdict_put_str(options, "user", qp->p[i].value);
|
||||
} else if (!strcmp(qp->p[i].name, "gid")) {
|
||||
qdict_put_str(options, "group", qp->p[i].value);
|
||||
} else if (!strcmp(qp->p[i].name, "tcp-syncnt")) {
|
||||
qdict_put_str(options, "tcp-syn-count", qp->p[i].value);
|
||||
} else if (!strcmp(qp->p[i].name, "readahead")) {
|
||||
qdict_put_str(options, "readahead-size", qp->p[i].value);
|
||||
} else if (!strcmp(qp->p[i].name, "pagecache")) {
|
||||
qdict_put_str(options, "page-cache-size", qp->p[i].value);
|
||||
} else if (!strcmp(qp->p[i].name, "debug")) {
|
||||
qdict_put_str(options, "debug", qp->p[i].value);
|
||||
} else {
|
||||
error_setg(errp, "Unknown NFS parameter name: %s",
|
||||
qp->p[i].name);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
ret = 0;
|
||||
out:
|
||||
if (qp) {
|
||||
query_params_free(qp);
|
||||
}
|
||||
if (uri) {
|
||||
uri_free(uri);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool nfs_has_filename_options_conflict(QDict *options, Error **errp)
|
||||
{
|
||||
const QDictEntry *qe;
|
||||
|
||||
for (qe = qdict_first(options); qe; qe = qdict_next(options, qe)) {
|
||||
if (!strcmp(qe->key, "host") ||
|
||||
!strcmp(qe->key, "path") ||
|
||||
!strcmp(qe->key, "user") ||
|
||||
!strcmp(qe->key, "group") ||
|
||||
!strcmp(qe->key, "tcp-syn-count") ||
|
||||
!strcmp(qe->key, "readahead-size") ||
|
||||
!strcmp(qe->key, "page-cache-size") ||
|
||||
!strcmp(qe->key, "debug") ||
|
||||
strstart(qe->key, "server.", NULL))
|
||||
{
|
||||
error_setg(errp, "Option %s cannot be used with a filename",
|
||||
qe->key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void nfs_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
if (nfs_has_filename_options_conflict(options, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nfs_parse_uri(filename, options, errp);
|
||||
}
|
||||
|
||||
static void nfs_process_read(void *arg);
|
||||
static void nfs_process_write(void *arg);
|
||||
|
||||
/* Called with QemuMutex held. */
|
||||
static void nfs_set_events(NFSClient *client)
|
||||
{
|
||||
int ev = nfs_which_events(client->context);
|
||||
@@ -193,8 +71,7 @@ static void nfs_set_events(NFSClient *client)
|
||||
aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
|
||||
false,
|
||||
(ev & POLLIN) ? nfs_process_read : NULL,
|
||||
(ev & POLLOUT) ? nfs_process_write : NULL,
|
||||
NULL, client);
|
||||
(ev & POLLOUT) ? nfs_process_write : NULL, client);
|
||||
|
||||
}
|
||||
client->events = ev;
|
||||
@@ -203,48 +80,39 @@ static void nfs_set_events(NFSClient *client)
|
||||
static void nfs_process_read(void *arg)
|
||||
{
|
||||
NFSClient *client = arg;
|
||||
|
||||
qemu_mutex_lock(&client->mutex);
|
||||
nfs_service(client->context, POLLIN);
|
||||
nfs_set_events(client);
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
}
|
||||
|
||||
static void nfs_process_write(void *arg)
|
||||
{
|
||||
NFSClient *client = arg;
|
||||
|
||||
qemu_mutex_lock(&client->mutex);
|
||||
nfs_service(client->context, POLLOUT);
|
||||
nfs_set_events(client);
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
}
|
||||
|
||||
static void nfs_co_init_task(BlockDriverState *bs, NFSRPC *task)
|
||||
static void nfs_co_init_task(NFSClient *client, NFSRPC *task)
|
||||
{
|
||||
*task = (NFSRPC) {
|
||||
.co = qemu_coroutine_self(),
|
||||
.bs = bs,
|
||||
.client = bs->opaque,
|
||||
.client = client,
|
||||
};
|
||||
}
|
||||
|
||||
static void nfs_co_generic_bh_cb(void *opaque)
|
||||
{
|
||||
NFSRPC *task = opaque;
|
||||
|
||||
task->complete = 1;
|
||||
aio_co_wake(task->co);
|
||||
qemu_bh_delete(task->bh);
|
||||
qemu_coroutine_enter(task->co);
|
||||
}
|
||||
|
||||
/* Called (via nfs_service) with QemuMutex held. */
|
||||
static void
|
||||
nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
|
||||
void *private_data)
|
||||
{
|
||||
NFSRPC *task = private_data;
|
||||
task->ret = ret;
|
||||
assert(!task->st);
|
||||
if (task->ret > 0 && task->iov) {
|
||||
if (task->ret <= task->iov->size) {
|
||||
qemu_iovec_from_buf(task->iov, 0, data, task->ret);
|
||||
@@ -252,33 +120,40 @@ nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
|
||||
task->ret = -EIO;
|
||||
}
|
||||
}
|
||||
if (task->ret == 0 && task->st) {
|
||||
memcpy(task->st, data, sizeof(struct stat));
|
||||
}
|
||||
if (task->ret < 0) {
|
||||
error_report("NFS Error: %s", nfs_get_error(nfs));
|
||||
}
|
||||
aio_bh_schedule_oneshot(task->client->aio_context,
|
||||
nfs_co_generic_bh_cb, task);
|
||||
if (task->co) {
|
||||
task->bh = aio_bh_new(task->client->aio_context,
|
||||
nfs_co_generic_bh_cb, task);
|
||||
qemu_bh_schedule(task->bh);
|
||||
} else {
|
||||
task->complete = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int coroutine_fn nfs_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *iov,
|
||||
int flags)
|
||||
static int coroutine_fn nfs_co_readv(BlockDriverState *bs,
|
||||
int64_t sector_num, int nb_sectors,
|
||||
QEMUIOVector *iov)
|
||||
{
|
||||
NFSClient *client = bs->opaque;
|
||||
NFSRPC task;
|
||||
|
||||
nfs_co_init_task(bs, &task);
|
||||
nfs_co_init_task(client, &task);
|
||||
task.iov = iov;
|
||||
|
||||
qemu_mutex_lock(&client->mutex);
|
||||
if (nfs_pread_async(client->context, client->fh,
|
||||
offset, bytes, nfs_co_generic_cb, &task) != 0) {
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
sector_num * BDRV_SECTOR_SIZE,
|
||||
nb_sectors * BDRV_SECTOR_SIZE,
|
||||
nfs_co_generic_cb, &task) != 0) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nfs_set_events(client);
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
while (!task.complete) {
|
||||
nfs_set_events(client);
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
|
||||
@@ -294,50 +169,39 @@ static int coroutine_fn nfs_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn nfs_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *iov,
|
||||
int flags)
|
||||
static int coroutine_fn nfs_co_writev(BlockDriverState *bs,
|
||||
int64_t sector_num, int nb_sectors,
|
||||
QEMUIOVector *iov)
|
||||
{
|
||||
NFSClient *client = bs->opaque;
|
||||
NFSRPC task;
|
||||
char *buf = NULL;
|
||||
bool my_buffer = false;
|
||||
|
||||
nfs_co_init_task(bs, &task);
|
||||
nfs_co_init_task(client, &task);
|
||||
|
||||
if (iov->niov != 1) {
|
||||
buf = g_try_malloc(bytes);
|
||||
if (bytes && buf == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
qemu_iovec_to_buf(iov, 0, buf, bytes);
|
||||
my_buffer = true;
|
||||
} else {
|
||||
buf = iov->iov[0].iov_base;
|
||||
buf = g_try_malloc(nb_sectors * BDRV_SECTOR_SIZE);
|
||||
if (nb_sectors && buf == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
qemu_mutex_lock(&client->mutex);
|
||||
qemu_iovec_to_buf(iov, 0, buf, nb_sectors * BDRV_SECTOR_SIZE);
|
||||
|
||||
if (nfs_pwrite_async(client->context, client->fh,
|
||||
offset, bytes, buf,
|
||||
nfs_co_generic_cb, &task) != 0) {
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
if (my_buffer) {
|
||||
g_free(buf);
|
||||
}
|
||||
sector_num * BDRV_SECTOR_SIZE,
|
||||
nb_sectors * BDRV_SECTOR_SIZE,
|
||||
buf, nfs_co_generic_cb, &task) != 0) {
|
||||
g_free(buf);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nfs_set_events(client);
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
while (!task.complete) {
|
||||
nfs_set_events(client);
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
|
||||
if (my_buffer) {
|
||||
g_free(buf);
|
||||
}
|
||||
g_free(buf);
|
||||
|
||||
if (task.ret != bytes) {
|
||||
if (task.ret != nb_sectors * BDRV_SECTOR_SIZE) {
|
||||
return task.ret < 0 ? task.ret : -EIO;
|
||||
}
|
||||
|
||||
@@ -349,62 +213,30 @@ static int coroutine_fn nfs_co_flush(BlockDriverState *bs)
|
||||
NFSClient *client = bs->opaque;
|
||||
NFSRPC task;
|
||||
|
||||
nfs_co_init_task(bs, &task);
|
||||
nfs_co_init_task(client, &task);
|
||||
|
||||
qemu_mutex_lock(&client->mutex);
|
||||
if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb,
|
||||
&task) != 0) {
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nfs_set_events(client);
|
||||
qemu_mutex_unlock(&client->mutex);
|
||||
while (!task.complete) {
|
||||
nfs_set_events(client);
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
|
||||
return task.ret;
|
||||
}
|
||||
|
||||
/* TODO Convert to fine grained options */
|
||||
static QemuOptsList runtime_opts = {
|
||||
.name = "nfs",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "path",
|
||||
.name = "filename",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Path of the image on the host",
|
||||
},
|
||||
{
|
||||
.name = "user",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "UID value to use when talking to the server",
|
||||
},
|
||||
{
|
||||
.name = "group",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "GID value to use when talking to the server",
|
||||
},
|
||||
{
|
||||
.name = "tcp-syn-count",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Number of SYNs to send during the session establish",
|
||||
},
|
||||
{
|
||||
.name = "readahead-size",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Set the readahead size in bytes",
|
||||
},
|
||||
{
|
||||
.name = "page-cache-size",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Set the pagecache size in bytes",
|
||||
},
|
||||
{
|
||||
.name = "debug",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Set the NFS debug level (max 2)",
|
||||
.help = "URL to the NFS file",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
@@ -415,7 +247,7 @@ static void nfs_detach_aio_context(BlockDriverState *bs)
|
||||
NFSClient *client = bs->opaque;
|
||||
|
||||
aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
|
||||
false, NULL, NULL, NULL, NULL);
|
||||
false, NULL, NULL, NULL);
|
||||
client->events = 0;
|
||||
}
|
||||
|
||||
@@ -433,17 +265,12 @@ static void nfs_client_close(NFSClient *client)
|
||||
if (client->context) {
|
||||
if (client->fh) {
|
||||
nfs_close(client->context, client->fh);
|
||||
client->fh = NULL;
|
||||
}
|
||||
aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
|
||||
false, NULL, NULL, NULL, NULL);
|
||||
false, NULL, NULL, NULL);
|
||||
nfs_destroy_context(client->context);
|
||||
client->context = NULL;
|
||||
}
|
||||
g_free(client->path);
|
||||
qemu_mutex_destroy(&client->mutex);
|
||||
qapi_free_NFSServer(client->server);
|
||||
client->server = NULL;
|
||||
memset(client, 0, sizeof(NFSClient));
|
||||
}
|
||||
|
||||
static void nfs_file_close(BlockDriverState *bs)
|
||||
@@ -452,73 +279,25 @@ static void nfs_file_close(BlockDriverState *bs)
|
||||
nfs_client_close(client);
|
||||
}
|
||||
|
||||
static NFSServer *nfs_config(QDict *options, Error **errp)
|
||||
static int64_t nfs_client_open(NFSClient *client, const char *filename,
|
||||
int flags, Error **errp, int open_flags)
|
||||
{
|
||||
NFSServer *server = NULL;
|
||||
QDict *addr = NULL;
|
||||
QObject *crumpled_addr = NULL;
|
||||
Visitor *iv = NULL;
|
||||
Error *local_error = NULL;
|
||||
|
||||
qdict_extract_subqdict(options, &addr, "server.");
|
||||
if (!qdict_size(addr)) {
|
||||
error_setg(errp, "NFS server address missing");
|
||||
goto out;
|
||||
}
|
||||
|
||||
crumpled_addr = qdict_crumple(addr, errp);
|
||||
if (!crumpled_addr) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Caution: this works only because all scalar members of
|
||||
* NFSServer are QString in @crumpled_addr. The visitor expects
|
||||
* @crumpled_addr to be typed according to the QAPI schema. It
|
||||
* is when @options come from -blockdev or blockdev_add. But when
|
||||
* they come from -drive, they're all QString.
|
||||
*/
|
||||
iv = qobject_input_visitor_new(crumpled_addr);
|
||||
visit_type_NFSServer(iv, NULL, &server, &local_error);
|
||||
if (local_error) {
|
||||
error_propagate(errp, local_error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
QDECREF(addr);
|
||||
qobject_decref(crumpled_addr);
|
||||
visit_free(iv);
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
||||
int flags, int open_flags, Error **errp)
|
||||
{
|
||||
int64_t ret = -EINVAL;
|
||||
QemuOpts *opts = NULL;
|
||||
Error *local_err = NULL;
|
||||
int ret = -EINVAL, i;
|
||||
struct stat st;
|
||||
URI *uri;
|
||||
QueryParams *qp = NULL;
|
||||
char *file = NULL, *strp = NULL;
|
||||
|
||||
qemu_mutex_init(&client->mutex);
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
uri = uri_parse(filename);
|
||||
if (!uri) {
|
||||
error_setg(errp, "Invalid URL specified");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
client->path = g_strdup(qemu_opt_get(opts, "path"));
|
||||
if (!client->path) {
|
||||
ret = -EINVAL;
|
||||
error_setg(errp, "No path was specified");
|
||||
if (!uri->server) {
|
||||
error_setg(errp, "Invalid URL specified");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
strp = strrchr(client->path, '/');
|
||||
strp = strrchr(uri->path, '/');
|
||||
if (strp == NULL) {
|
||||
error_setg(errp, "Invalid URL specified");
|
||||
goto fail;
|
||||
@@ -526,89 +305,85 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
||||
file = g_strdup(strp);
|
||||
*strp = 0;
|
||||
|
||||
/* Pop the config into our state object, Exit if invalid */
|
||||
client->server = nfs_config(options, errp);
|
||||
if (!client->server) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
client->context = nfs_init_context();
|
||||
if (client->context == NULL) {
|
||||
error_setg(errp, "Failed to init NFS context");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (qemu_opt_get(opts, "user")) {
|
||||
client->uid = qemu_opt_get_number(opts, "user", 0);
|
||||
nfs_set_uid(client->context, client->uid);
|
||||
}
|
||||
|
||||
if (qemu_opt_get(opts, "group")) {
|
||||
client->gid = qemu_opt_get_number(opts, "group", 0);
|
||||
nfs_set_gid(client->context, client->gid);
|
||||
}
|
||||
|
||||
if (qemu_opt_get(opts, "tcp-syn-count")) {
|
||||
client->tcp_syncnt = qemu_opt_get_number(opts, "tcp-syn-count", 0);
|
||||
nfs_set_tcp_syncnt(client->context, client->tcp_syncnt);
|
||||
}
|
||||
|
||||
qp = query_params_parse(uri->query);
|
||||
for (i = 0; i < qp->n; i++) {
|
||||
unsigned long long val;
|
||||
if (!qp->p[i].value) {
|
||||
error_setg(errp, "Value for NFS parameter expected: %s",
|
||||
qp->p[i].name);
|
||||
goto fail;
|
||||
}
|
||||
if (parse_uint_full(qp->p[i].value, &val, 0)) {
|
||||
error_setg(errp, "Illegal value for NFS parameter: %s",
|
||||
qp->p[i].name);
|
||||
goto fail;
|
||||
}
|
||||
if (!strcmp(qp->p[i].name, "uid")) {
|
||||
nfs_set_uid(client->context, val);
|
||||
} else if (!strcmp(qp->p[i].name, "gid")) {
|
||||
nfs_set_gid(client->context, val);
|
||||
} else if (!strcmp(qp->p[i].name, "tcp-syncnt")) {
|
||||
nfs_set_tcp_syncnt(client->context, val);
|
||||
#ifdef LIBNFS_FEATURE_READAHEAD
|
||||
if (qemu_opt_get(opts, "readahead-size")) {
|
||||
if (open_flags & BDRV_O_NOCACHE) {
|
||||
error_setg(errp, "Cannot enable NFS readahead "
|
||||
"if cache.direct = on");
|
||||
goto fail;
|
||||
}
|
||||
client->readahead = qemu_opt_get_number(opts, "readahead-size", 0);
|
||||
if (client->readahead > QEMU_NFS_MAX_READAHEAD_SIZE) {
|
||||
warn_report("Truncating NFS readahead size to %d",
|
||||
QEMU_NFS_MAX_READAHEAD_SIZE);
|
||||
client->readahead = QEMU_NFS_MAX_READAHEAD_SIZE;
|
||||
}
|
||||
nfs_set_readahead(client->context, client->readahead);
|
||||
} else if (!strcmp(qp->p[i].name, "readahead")) {
|
||||
if (open_flags & BDRV_O_NOCACHE) {
|
||||
error_setg(errp, "Cannot enable NFS readahead "
|
||||
"if cache.direct = on");
|
||||
goto fail;
|
||||
}
|
||||
if (val > QEMU_NFS_MAX_READAHEAD_SIZE) {
|
||||
error_report("NFS Warning: Truncating NFS readahead"
|
||||
" size to %d", QEMU_NFS_MAX_READAHEAD_SIZE);
|
||||
val = QEMU_NFS_MAX_READAHEAD_SIZE;
|
||||
}
|
||||
nfs_set_readahead(client->context, val);
|
||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
||||
nfs_set_pagecache_ttl(client->context, 0);
|
||||
nfs_set_pagecache_ttl(client->context, 0);
|
||||
#endif
|
||||
client->cache_used = true;
|
||||
}
|
||||
client->cache_used = true;
|
||||
#endif
|
||||
|
||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
||||
if (qemu_opt_get(opts, "page-cache-size")) {
|
||||
if (open_flags & BDRV_O_NOCACHE) {
|
||||
error_setg(errp, "Cannot enable NFS pagecache "
|
||||
"if cache.direct = on");
|
||||
goto fail;
|
||||
}
|
||||
client->pagecache = qemu_opt_get_number(opts, "page-cache-size", 0);
|
||||
if (client->pagecache > QEMU_NFS_MAX_PAGECACHE_SIZE) {
|
||||
warn_report("Truncating NFS pagecache size to %d pages",
|
||||
QEMU_NFS_MAX_PAGECACHE_SIZE);
|
||||
client->pagecache = QEMU_NFS_MAX_PAGECACHE_SIZE;
|
||||
}
|
||||
nfs_set_pagecache(client->context, client->pagecache);
|
||||
nfs_set_pagecache_ttl(client->context, 0);
|
||||
client->cache_used = true;
|
||||
}
|
||||
nfs_set_pagecache_ttl(client->context, 0);
|
||||
} else if (!strcmp(qp->p[i].name, "pagecache")) {
|
||||
if (open_flags & BDRV_O_NOCACHE) {
|
||||
error_setg(errp, "Cannot enable NFS pagecache "
|
||||
"if cache.direct = on");
|
||||
goto fail;
|
||||
}
|
||||
if (val > QEMU_NFS_MAX_PAGECACHE_SIZE) {
|
||||
error_report("NFS Warning: Truncating NFS pagecache"
|
||||
" size to %d pages", QEMU_NFS_MAX_PAGECACHE_SIZE);
|
||||
val = QEMU_NFS_MAX_PAGECACHE_SIZE;
|
||||
}
|
||||
nfs_set_pagecache(client->context, val);
|
||||
nfs_set_pagecache_ttl(client->context, 0);
|
||||
client->cache_used = true;
|
||||
#endif
|
||||
|
||||
#ifdef LIBNFS_FEATURE_DEBUG
|
||||
if (qemu_opt_get(opts, "debug")) {
|
||||
client->debug = qemu_opt_get_number(opts, "debug", 0);
|
||||
/* limit the maximum debug level to avoid potential flooding
|
||||
* of our log files. */
|
||||
if (client->debug > QEMU_NFS_MAX_DEBUG_LEVEL) {
|
||||
warn_report("Limiting NFS debug level to %d",
|
||||
QEMU_NFS_MAX_DEBUG_LEVEL);
|
||||
client->debug = QEMU_NFS_MAX_DEBUG_LEVEL;
|
||||
}
|
||||
nfs_set_debug(client->context, client->debug);
|
||||
}
|
||||
} else if (!strcmp(qp->p[i].name, "debug")) {
|
||||
/* limit the maximum debug level to avoid potential flooding
|
||||
* of our log files. */
|
||||
if (val > QEMU_NFS_MAX_DEBUG_LEVEL) {
|
||||
error_report("NFS Warning: Limiting NFS debug level"
|
||||
" to %d", QEMU_NFS_MAX_DEBUG_LEVEL);
|
||||
val = QEMU_NFS_MAX_DEBUG_LEVEL;
|
||||
}
|
||||
nfs_set_debug(client->context, val);
|
||||
#endif
|
||||
} else {
|
||||
error_setg(errp, "Unknown NFS parameter name: %s",
|
||||
qp->p[i].name);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = nfs_mount(client->context, client->server->host, client->path);
|
||||
ret = nfs_mount(client->context, uri->server, uri->path);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Failed to mount nfs share: %s",
|
||||
nfs_get_error(client->context));
|
||||
@@ -641,13 +416,14 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
||||
ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
|
||||
client->st_blocks = st.st_blocks;
|
||||
client->has_zero_init = S_ISREG(st.st_mode);
|
||||
*strp = '/';
|
||||
goto out;
|
||||
|
||||
fail:
|
||||
nfs_client_close(client);
|
||||
out:
|
||||
qemu_opts_del(opts);
|
||||
if (qp) {
|
||||
query_params_free(qp);
|
||||
}
|
||||
uri_free(uri);
|
||||
g_free(file);
|
||||
return ret;
|
||||
}
|
||||
@@ -656,18 +432,28 @@ static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp) {
|
||||
NFSClient *client = bs->opaque;
|
||||
int64_t ret;
|
||||
QemuOpts *opts;
|
||||
Error *local_err = NULL;
|
||||
|
||||
client->aio_context = bdrv_get_aio_context(bs);
|
||||
|
||||
ret = nfs_client_open(client, options,
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
ret = nfs_client_open(client, qemu_opt_get(opts, "filename"),
|
||||
(flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
|
||||
bs->open_flags, errp);
|
||||
errp, bs->open_flags);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
bs->total_sectors = ret;
|
||||
ret = 0;
|
||||
out:
|
||||
qemu_opts_del(opts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -686,9 +472,9 @@ static QemuOptsList nfs_create_opts = {
|
||||
|
||||
static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
|
||||
{
|
||||
int64_t ret, total_size;
|
||||
int ret = 0;
|
||||
int64_t total_size = 0;
|
||||
NFSClient *client = g_new0(NFSClient, 1);
|
||||
QDict *options = NULL;
|
||||
|
||||
client->aio_context = qemu_get_aio_context();
|
||||
|
||||
@@ -696,20 +482,13 @@ static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
|
||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
options = qdict_new();
|
||||
ret = nfs_parse_uri(url, options, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = nfs_client_open(client, options, O_CREAT, 0, errp);
|
||||
ret = nfs_client_open(client, url, O_CREAT, errp, 0);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
ret = nfs_ftruncate(client->context, client->fh, total_size);
|
||||
nfs_client_close(client);
|
||||
out:
|
||||
QDECREF(options);
|
||||
g_free(client);
|
||||
return ret;
|
||||
}
|
||||
@@ -720,25 +499,6 @@ static int nfs_has_zero_init(BlockDriverState *bs)
|
||||
return client->has_zero_init;
|
||||
}
|
||||
|
||||
/* Called (via nfs_service) with QemuMutex held. */
|
||||
static void
|
||||
nfs_get_allocated_file_size_cb(int ret, struct nfs_context *nfs, void *data,
|
||||
void *private_data)
|
||||
{
|
||||
NFSRPC *task = private_data;
|
||||
task->ret = ret;
|
||||
if (task->ret == 0) {
|
||||
memcpy(task->st, data, sizeof(struct stat));
|
||||
}
|
||||
if (task->ret < 0) {
|
||||
error_report("NFS Error: %s", nfs_get_error(nfs));
|
||||
}
|
||||
|
||||
/* Set task->complete before reading bs->wakeup. */
|
||||
atomic_mb_set(&task->complete, 1);
|
||||
bdrv_wakeup(task->bs);
|
||||
}
|
||||
|
||||
static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
|
||||
{
|
||||
NFSClient *client = bs->opaque;
|
||||
@@ -750,38 +510,24 @@ static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
|
||||
return client->st_blocks * 512;
|
||||
}
|
||||
|
||||
task.bs = bs;
|
||||
task.st = &st;
|
||||
if (nfs_fstat_async(client->context, client->fh, nfs_get_allocated_file_size_cb,
|
||||
if (nfs_fstat_async(client->context, client->fh, nfs_co_generic_cb,
|
||||
&task) != 0) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nfs_set_events(client);
|
||||
BDRV_POLL_WHILE(bs, !task.complete);
|
||||
while (!task.complete) {
|
||||
nfs_set_events(client);
|
||||
aio_poll(client->aio_context, true);
|
||||
}
|
||||
|
||||
return (task.ret < 0 ? task.ret : st.st_blocks * 512);
|
||||
}
|
||||
|
||||
static int nfs_file_truncate(BlockDriverState *bs, int64_t offset,
|
||||
PreallocMode prealloc, Error **errp)
|
||||
static int nfs_file_truncate(BlockDriverState *bs, int64_t offset)
|
||||
{
|
||||
NFSClient *client = bs->opaque;
|
||||
int ret;
|
||||
|
||||
if (prealloc != PREALLOC_MODE_OFF) {
|
||||
error_setg(errp, "Unsupported preallocation mode '%s'",
|
||||
PreallocMode_lookup[prealloc]);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
ret = nfs_ftruncate(client->context, client->fh, offset);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "Failed to truncate file");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return nfs_ftruncate(client->context, client->fh, offset);
|
||||
}
|
||||
|
||||
/* Note that this will not re-establish a connection with the NFS server
|
||||
@@ -818,62 +564,6 @@ static int nfs_reopen_prepare(BDRVReopenState *state,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nfs_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
{
|
||||
NFSClient *client = bs->opaque;
|
||||
QDict *opts = qdict_new();
|
||||
QObject *server_qdict;
|
||||
Visitor *ov;
|
||||
|
||||
qdict_put_str(opts, "driver", "nfs");
|
||||
|
||||
if (client->uid && !client->gid) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nfs://%s%s?uid=%" PRId64, client->server->host, client->path,
|
||||
client->uid);
|
||||
} else if (!client->uid && client->gid) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nfs://%s%s?gid=%" PRId64, client->server->host, client->path,
|
||||
client->gid);
|
||||
} else if (client->uid && client->gid) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nfs://%s%s?uid=%" PRId64 "&gid=%" PRId64,
|
||||
client->server->host, client->path, client->uid, client->gid);
|
||||
} else {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nfs://%s%s", client->server->host, client->path);
|
||||
}
|
||||
|
||||
ov = qobject_output_visitor_new(&server_qdict);
|
||||
visit_type_NFSServer(ov, NULL, &client->server, &error_abort);
|
||||
visit_complete(ov, &server_qdict);
|
||||
qdict_put_obj(opts, "server", server_qdict);
|
||||
qdict_put_str(opts, "path", client->path);
|
||||
|
||||
if (client->uid) {
|
||||
qdict_put_int(opts, "user", client->uid);
|
||||
}
|
||||
if (client->gid) {
|
||||
qdict_put_int(opts, "group", client->gid);
|
||||
}
|
||||
if (client->tcp_syncnt) {
|
||||
qdict_put_int(opts, "tcp-syn-cnt", client->tcp_syncnt);
|
||||
}
|
||||
if (client->readahead) {
|
||||
qdict_put_int(opts, "readahead-size", client->readahead);
|
||||
}
|
||||
if (client->pagecache) {
|
||||
qdict_put_int(opts, "page-cache-size", client->pagecache);
|
||||
}
|
||||
if (client->debug) {
|
||||
qdict_put_int(opts, "debug", client->debug);
|
||||
}
|
||||
|
||||
visit_free(ov);
|
||||
qdict_flatten(opts);
|
||||
bs->full_open_options = opts;
|
||||
}
|
||||
|
||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
||||
static void nfs_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
@@ -888,7 +578,7 @@ static BlockDriver bdrv_nfs = {
|
||||
.protocol_name = "nfs",
|
||||
|
||||
.instance_size = sizeof(NFSClient),
|
||||
.bdrv_parse_filename = nfs_parse_filename,
|
||||
.bdrv_needs_filename = true,
|
||||
.create_opts = &nfs_create_opts,
|
||||
|
||||
.bdrv_has_zero_init = nfs_has_zero_init,
|
||||
@@ -900,13 +590,12 @@ static BlockDriver bdrv_nfs = {
|
||||
.bdrv_create = nfs_file_create,
|
||||
.bdrv_reopen_prepare = nfs_reopen_prepare,
|
||||
|
||||
.bdrv_co_preadv = nfs_co_preadv,
|
||||
.bdrv_co_pwritev = nfs_co_pwritev,
|
||||
.bdrv_co_readv = nfs_co_readv,
|
||||
.bdrv_co_writev = nfs_co_writev,
|
||||
.bdrv_co_flush_to_disk = nfs_co_flush,
|
||||
|
||||
.bdrv_detach_aio_context = nfs_detach_aio_context,
|
||||
.bdrv_attach_aio_context = nfs_attach_aio_context,
|
||||
.bdrv_refresh_filename = nfs_refresh_filename,
|
||||
|
||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
||||
.bdrv_invalidate_cache = nfs_invalidate_cache,
|
||||
|
||||
+10
-28
@@ -29,6 +29,11 @@ static QemuOptsList runtime_opts = {
|
||||
.name = "null",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "filename",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "",
|
||||
},
|
||||
{
|
||||
.name = BLOCK_OPT_SIZE,
|
||||
.type = QEMU_OPT_SIZE,
|
||||
@@ -49,30 +54,6 @@ static QemuOptsList runtime_opts = {
|
||||
},
|
||||
};
|
||||
|
||||
static void null_co_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
/* This functions only exists so that a null-co:// filename is accepted
|
||||
* with the null-co driver. */
|
||||
if (strcmp(filename, "null-co://")) {
|
||||
error_setg(errp, "The only allowed filename for this driver is "
|
||||
"'null-co://'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void null_aio_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
/* This functions only exists so that a null-aio:// filename is accepted
|
||||
* with the null-aio driver. */
|
||||
if (strcmp(filename, "null-aio://")) {
|
||||
error_setg(errp, "The only allowed filename for this driver is "
|
||||
"'null-aio://'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int null_file_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
@@ -143,6 +124,7 @@ static coroutine_fn int null_co_flush(BlockDriverState *bs)
|
||||
|
||||
typedef struct {
|
||||
BlockAIOCB common;
|
||||
QEMUBH *bh;
|
||||
QEMUTimer timer;
|
||||
} NullAIOCB;
|
||||
|
||||
@@ -154,6 +136,7 @@ static void null_bh_cb(void *opaque)
|
||||
{
|
||||
NullAIOCB *acb = opaque;
|
||||
acb->common.cb(acb->common.opaque, 0);
|
||||
qemu_bh_delete(acb->bh);
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
|
||||
@@ -181,7 +164,8 @@ static inline BlockAIOCB *null_aio_common(BlockDriverState *bs,
|
||||
timer_mod_ns(&acb->timer,
|
||||
qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + s->latency_ns);
|
||||
} else {
|
||||
aio_bh_schedule_oneshot(bdrv_get_aio_context(bs), null_bh_cb, acb);
|
||||
acb->bh = aio_bh_new(bdrv_get_aio_context(bs), null_bh_cb, acb);
|
||||
qemu_bh_schedule(acb->bh);
|
||||
}
|
||||
return &acb->common;
|
||||
}
|
||||
@@ -251,7 +235,7 @@ static void null_refresh_filename(BlockDriverState *bs, QDict *opts)
|
||||
bs->drv->format_name);
|
||||
}
|
||||
|
||||
qdict_put_str(opts, "driver", bs->drv->format_name);
|
||||
qdict_put(opts, "driver", qstring_from_str(bs->drv->format_name));
|
||||
bs->full_open_options = opts;
|
||||
}
|
||||
|
||||
@@ -261,7 +245,6 @@ static BlockDriver bdrv_null_co = {
|
||||
.instance_size = sizeof(BDRVNullState),
|
||||
|
||||
.bdrv_file_open = null_file_open,
|
||||
.bdrv_parse_filename = null_co_parse_filename,
|
||||
.bdrv_close = null_close,
|
||||
.bdrv_getlength = null_getlength,
|
||||
|
||||
@@ -281,7 +264,6 @@ static BlockDriver bdrv_null_aio = {
|
||||
.instance_size = sizeof(BDRVNullState),
|
||||
|
||||
.bdrv_file_open = null_file_open,
|
||||
.bdrv_parse_filename = null_aio_parse_filename,
|
||||
.bdrv_close = null_close,
|
||||
.bdrv_getlength = null_getlength,
|
||||
|
||||
|
||||
+17
-41
@@ -114,7 +114,7 @@ static QemuOptsList parallels_runtime_opts = {
|
||||
.name = PARALLELS_OPT_PREALLOC_SIZE,
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "Preallocation size on image expansion",
|
||||
.def_value_str = "128M",
|
||||
.def_value_str = "128MiB",
|
||||
},
|
||||
{
|
||||
.name = PARALLELS_OPT_PREALLOC_MODE,
|
||||
@@ -192,7 +192,8 @@ static int64_t allocate_clusters(BlockDriverState *bs, int64_t sector_num,
|
||||
int nb_sectors, int *pnum)
|
||||
{
|
||||
BDRVParallelsState *s = bs->opaque;
|
||||
int64_t pos, space, idx, to_allocate, i, len;
|
||||
uint32_t idx, to_allocate, i;
|
||||
int64_t pos, space;
|
||||
|
||||
pos = block_status(s, sector_num, nb_sectors, pnum);
|
||||
if (pos > 0) {
|
||||
@@ -200,25 +201,13 @@ static int64_t allocate_clusters(BlockDriverState *bs, int64_t sector_num,
|
||||
}
|
||||
|
||||
idx = sector_num / s->tracks;
|
||||
to_allocate = DIV_ROUND_UP(sector_num + *pnum, s->tracks) - idx;
|
||||
|
||||
/* This function is called only by parallels_co_writev(), which will never
|
||||
* pass a sector_num at or beyond the end of the image (because the block
|
||||
* layer never passes such a sector_num to that function). Therefore, idx
|
||||
* is always below s->bat_size.
|
||||
* block_status() will limit *pnum so that sector_num + *pnum will not
|
||||
* exceed the image end. Therefore, idx + to_allocate cannot exceed
|
||||
* s->bat_size.
|
||||
* Note that s->bat_size is an unsigned int, therefore idx + to_allocate
|
||||
* will always fit into a uint32_t. */
|
||||
assert(idx < s->bat_size && idx + to_allocate <= s->bat_size);
|
||||
|
||||
space = to_allocate * s->tracks;
|
||||
len = bdrv_getlength(bs->file->bs);
|
||||
if (len < 0) {
|
||||
return len;
|
||||
if (idx >= s->bat_size) {
|
||||
return -EINVAL;
|
||||
}
|
||||
if (s->data_end + space > (len >> BDRV_SECTOR_BITS)) {
|
||||
|
||||
to_allocate = DIV_ROUND_UP(sector_num + *pnum, s->tracks) - idx;
|
||||
space = to_allocate * s->tracks;
|
||||
if (s->data_end + space > bdrv_getlength(bs->file->bs) >> BDRV_SECTOR_BITS) {
|
||||
int ret;
|
||||
space += s->prealloc_size;
|
||||
if (s->prealloc_mode == PRL_PREALLOC_MODE_FALLOCATE) {
|
||||
@@ -226,9 +215,8 @@ static int64_t allocate_clusters(BlockDriverState *bs, int64_t sector_num,
|
||||
s->data_end << BDRV_SECTOR_BITS,
|
||||
space << BDRV_SECTOR_BITS, 0);
|
||||
} else {
|
||||
ret = bdrv_truncate(bs->file,
|
||||
(s->data_end + space) << BDRV_SECTOR_BITS,
|
||||
PREALLOC_MODE_OFF, NULL);
|
||||
ret = bdrv_truncate(bs->file->bs,
|
||||
(s->data_end + space) << BDRV_SECTOR_BITS);
|
||||
}
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
@@ -461,11 +449,8 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
size - res->image_end_offset);
|
||||
res->leaks += count;
|
||||
if (fix & BDRV_FIX_LEAKS) {
|
||||
Error *local_err = NULL;
|
||||
ret = bdrv_truncate(bs->file, res->image_end_offset,
|
||||
PREALLOC_MODE_OFF, &local_err);
|
||||
ret = bdrv_truncate(bs->file->bs, res->image_end_offset);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
res->check_errors++;
|
||||
return ret;
|
||||
}
|
||||
@@ -503,8 +488,7 @@ static int parallels_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
}
|
||||
|
||||
file = blk_new_open(filename, NULL, NULL,
|
||||
BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
|
||||
&local_err);
|
||||
BDRV_O_RDWR | BDRV_O_PROTOCOL, &local_err);
|
||||
if (file == NULL) {
|
||||
error_propagate(errp, local_err);
|
||||
return -EIO;
|
||||
@@ -512,7 +496,7 @@ static int parallels_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
|
||||
blk_set_allow_write_beyond_eof(file, true);
|
||||
|
||||
ret = blk_truncate(file, 0, PREALLOC_MODE_OFF, errp);
|
||||
ret = blk_truncate(file, 0);
|
||||
if (ret < 0) {
|
||||
goto exit;
|
||||
}
|
||||
@@ -597,12 +581,6 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error *local_err = NULL;
|
||||
char *buf;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = bdrv_pread(bs->file, 0, &ph, sizeof(ph));
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
@@ -702,8 +680,8 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
if (local_err != NULL) {
|
||||
goto fail_options;
|
||||
}
|
||||
|
||||
if (!bdrv_has_zero_init(bs->file->bs)) {
|
||||
if (!bdrv_has_zero_init(bs->file->bs) ||
|
||||
bdrv_truncate(bs->file->bs, bdrv_getlength(bs->file->bs)) != 0) {
|
||||
s->prealloc_mode = PRL_PREALLOC_MODE_FALLOCATE;
|
||||
}
|
||||
|
||||
@@ -746,8 +724,7 @@ static void parallels_close(BlockDriverState *bs)
|
||||
}
|
||||
|
||||
if (bs->open_flags & BDRV_O_RDWR) {
|
||||
bdrv_truncate(bs->file, s->data_end << BDRV_SECTOR_BITS,
|
||||
PREALLOC_MODE_OFF, NULL);
|
||||
bdrv_truncate(bs->file->bs, s->data_end << BDRV_SECTOR_BITS);
|
||||
}
|
||||
|
||||
g_free(s->bat_dirty_bmap);
|
||||
@@ -779,7 +756,6 @@ static BlockDriver bdrv_parallels = {
|
||||
.bdrv_probe = parallels_probe,
|
||||
.bdrv_open = parallels_open,
|
||||
.bdrv_close = parallels_close,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_co_get_block_status = parallels_co_get_block_status,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_co_flush_to_os = parallels_co_flush_to_os,
|
||||
|
||||
+73
-95
@@ -29,7 +29,7 @@
|
||||
#include "block/write-threshold.h"
|
||||
#include "qmp-commands.h"
|
||||
#include "qapi-visit.h"
|
||||
#include "qapi/qobject-output-visitor.h"
|
||||
#include "qapi/qmp-output-visitor.h"
|
||||
#include "qapi/qmp/types.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qemu/cutils.h"
|
||||
@@ -45,7 +45,7 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
|
||||
info->ro = bs->read_only;
|
||||
info->drv = g_strdup(bs->drv->format_name);
|
||||
info->encrypted = bs->encrypted;
|
||||
info->encryption_key_missing = false;
|
||||
info->encryption_key_missing = bdrv_key_required(bs);
|
||||
|
||||
info->cache = g_new(BlockdevCacheInfo, 1);
|
||||
*info->cache = (BlockdevCacheInfo) {
|
||||
@@ -64,6 +64,7 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
|
||||
info->backing_file = g_strdup(bs->backing_file);
|
||||
}
|
||||
|
||||
info->backing_file_depth = bdrv_get_backing_file_depth(bs);
|
||||
info->detect_zeroes = bs->detect_zeroes;
|
||||
|
||||
if (blk && blk_get_public(blk)->throttle_state) {
|
||||
@@ -124,7 +125,6 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
|
||||
|
||||
bs0 = bs;
|
||||
p_image_info = &info->image;
|
||||
info->backing_file_depth = 0;
|
||||
while (1) {
|
||||
Error *local_err = NULL;
|
||||
bdrv_query_image_info(bs0, p_image_info, &local_err);
|
||||
@@ -133,22 +133,13 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
|
||||
qapi_free_BlockDeviceInfo(info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (bs0->drv && bs0->backing) {
|
||||
info->backing_file_depth++;
|
||||
bs0 = bs0->backing->bs;
|
||||
(*p_image_info)->has_backing_image = true;
|
||||
p_image_info = &((*p_image_info)->backing_image);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Skip automatically inserted nodes that the user isn't aware of for
|
||||
* query-block (blk != NULL), but not for query-named-block-nodes */
|
||||
while (blk && bs0->drv && bs0->implicit) {
|
||||
bs0 = backing_bs(bs0);
|
||||
assert(bs0);
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
@@ -246,8 +237,8 @@ void bdrv_query_image_info(BlockDriverState *bs,
|
||||
|
||||
size = bdrv_getlength(bs);
|
||||
if (size < 0) {
|
||||
error_setg_errno(errp, -size, "Can't get image size '%s'",
|
||||
bs->exact_filename);
|
||||
error_setg_errno(errp, -size, "Can't get size of device '%s'",
|
||||
bdrv_get_device_name(bs));
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -331,26 +322,11 @@ static void bdrv_query_info(BlockBackend *blk, BlockInfo **p_info,
|
||||
{
|
||||
BlockInfo *info = g_malloc0(sizeof(*info));
|
||||
BlockDriverState *bs = blk_bs(blk);
|
||||
char *qdev;
|
||||
|
||||
/* Skip automatically inserted nodes that the user isn't aware of */
|
||||
while (bs && bs->drv && bs->implicit) {
|
||||
bs = backing_bs(bs);
|
||||
}
|
||||
|
||||
info->device = g_strdup(blk_name(blk));
|
||||
info->type = g_strdup("unknown");
|
||||
info->locked = blk_dev_is_medium_locked(blk);
|
||||
info->removable = blk_dev_has_removable_media(blk);
|
||||
|
||||
qdev = blk_get_attached_dev_id(blk);
|
||||
if (qdev && *qdev) {
|
||||
info->has_qdev = true;
|
||||
info->qdev = qdev;
|
||||
} else {
|
||||
g_free(qdev);
|
||||
}
|
||||
|
||||
if (blk_dev_has_tray(blk)) {
|
||||
info->has_tray_open = true;
|
||||
info->tray_open = blk_dev_is_tray_open(blk);
|
||||
@@ -381,6 +357,10 @@ static void bdrv_query_info(BlockBackend *blk, BlockInfo **p_info,
|
||||
qapi_free_BlockInfo(info);
|
||||
}
|
||||
|
||||
static BlockStats *bdrv_query_stats(BlockBackend *blk,
|
||||
const BlockDriverState *bs,
|
||||
bool query_backing);
|
||||
|
||||
static void bdrv_query_blk_stats(BlockDeviceStats *ds, BlockBackend *blk)
|
||||
{
|
||||
BlockAcctStats *stats = blk_get_stats(blk);
|
||||
@@ -448,41 +428,44 @@ static void bdrv_query_blk_stats(BlockDeviceStats *ds, BlockBackend *blk)
|
||||
}
|
||||
}
|
||||
|
||||
static BlockStats *bdrv_query_bds_stats(BlockDriverState *bs,
|
||||
bool blk_level)
|
||||
static void bdrv_query_bds_stats(BlockStats *s, const BlockDriverState *bs,
|
||||
bool query_backing)
|
||||
{
|
||||
BlockStats *s = NULL;
|
||||
|
||||
s = g_malloc0(sizeof(*s));
|
||||
s->stats = g_malloc0(sizeof(*s->stats));
|
||||
|
||||
if (!bs) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Skip automatically inserted nodes that the user isn't aware of in
|
||||
* a BlockBackend-level command. Stay at the exact node for a node-level
|
||||
* command. */
|
||||
while (blk_level && bs->drv && bs->implicit) {
|
||||
bs = backing_bs(bs);
|
||||
assert(bs);
|
||||
}
|
||||
|
||||
if (bdrv_get_node_name(bs)[0]) {
|
||||
s->has_node_name = true;
|
||||
s->node_name = g_strdup(bdrv_get_node_name(bs));
|
||||
}
|
||||
|
||||
s->stats->wr_highest_offset = stat64_get(&bs->wr_highest_offset);
|
||||
s->stats->wr_highest_offset = bs->wr_highest_offset;
|
||||
|
||||
if (bs->file) {
|
||||
s->has_parent = true;
|
||||
s->parent = bdrv_query_bds_stats(bs->file->bs, blk_level);
|
||||
s->parent = bdrv_query_stats(NULL, bs->file->bs, query_backing);
|
||||
}
|
||||
|
||||
if (blk_level && bs->backing) {
|
||||
if (query_backing && bs->backing) {
|
||||
s->has_backing = true;
|
||||
s->backing = bdrv_query_bds_stats(bs->backing->bs, blk_level);
|
||||
s->backing = bdrv_query_stats(NULL, bs->backing->bs, query_backing);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static BlockStats *bdrv_query_stats(BlockBackend *blk,
|
||||
const BlockDriverState *bs,
|
||||
bool query_backing)
|
||||
{
|
||||
BlockStats *s;
|
||||
|
||||
s = g_malloc0(sizeof(*s));
|
||||
s->stats = g_malloc0(sizeof(*s->stats));
|
||||
|
||||
if (blk) {
|
||||
s->has_device = true;
|
||||
s->device = g_strdup(blk_name(blk));
|
||||
bdrv_query_blk_stats(s->stats, blk);
|
||||
}
|
||||
if (bs) {
|
||||
bdrv_query_bds_stats(s, bs, query_backing);
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -494,14 +477,8 @@ BlockInfoList *qmp_query_block(Error **errp)
|
||||
BlockBackend *blk;
|
||||
Error *local_err = NULL;
|
||||
|
||||
for (blk = blk_all_next(NULL); blk; blk = blk_all_next(blk)) {
|
||||
BlockInfoList *info;
|
||||
|
||||
if (!*blk_name(blk) && !blk_get_attached_dev(blk)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info = g_malloc0(sizeof(*info));
|
||||
for (blk = blk_next(NULL); blk; blk = blk_next(blk)) {
|
||||
BlockInfoList *info = g_malloc0(sizeof(*info));
|
||||
bdrv_query_info(blk, &info->value, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
@@ -517,44 +494,42 @@ BlockInfoList *qmp_query_block(Error **errp)
|
||||
return head;
|
||||
}
|
||||
|
||||
static bool next_query_bds(BlockBackend **blk, BlockDriverState **bs,
|
||||
bool query_nodes)
|
||||
{
|
||||
if (query_nodes) {
|
||||
*bs = bdrv_next_node(*bs);
|
||||
return !!*bs;
|
||||
}
|
||||
|
||||
*blk = blk_next(*blk);
|
||||
*bs = *blk ? blk_bs(*blk) : NULL;
|
||||
|
||||
return !!*blk;
|
||||
}
|
||||
|
||||
BlockStatsList *qmp_query_blockstats(bool has_query_nodes,
|
||||
bool query_nodes,
|
||||
Error **errp)
|
||||
{
|
||||
BlockStatsList *head = NULL, **p_next = &head;
|
||||
BlockBackend *blk;
|
||||
BlockDriverState *bs;
|
||||
BlockBackend *blk = NULL;
|
||||
BlockDriverState *bs = NULL;
|
||||
|
||||
/* Just to be safe if query_nodes is not always initialized */
|
||||
if (has_query_nodes && query_nodes) {
|
||||
for (bs = bdrv_next_node(NULL); bs; bs = bdrv_next_node(bs)) {
|
||||
BlockStatsList *info = g_malloc0(sizeof(*info));
|
||||
AioContext *ctx = bdrv_get_aio_context(bs);
|
||||
query_nodes = has_query_nodes && query_nodes;
|
||||
|
||||
aio_context_acquire(ctx);
|
||||
info->value = bdrv_query_bds_stats(bs, false);
|
||||
aio_context_release(ctx);
|
||||
while (next_query_bds(&blk, &bs, query_nodes)) {
|
||||
BlockStatsList *info = g_malloc0(sizeof(*info));
|
||||
AioContext *ctx = blk ? blk_get_aio_context(blk)
|
||||
: bdrv_get_aio_context(bs);
|
||||
|
||||
*p_next = info;
|
||||
p_next = &info->next;
|
||||
}
|
||||
} else {
|
||||
for (blk = blk_next(NULL); blk; blk = blk_next(blk)) {
|
||||
BlockStatsList *info = g_malloc0(sizeof(*info));
|
||||
AioContext *ctx = blk_get_aio_context(blk);
|
||||
BlockStats *s;
|
||||
aio_context_acquire(ctx);
|
||||
info->value = bdrv_query_stats(blk, bs, !query_nodes);
|
||||
aio_context_release(ctx);
|
||||
|
||||
aio_context_acquire(ctx);
|
||||
s = bdrv_query_bds_stats(blk_bs(blk), true);
|
||||
s->has_device = true;
|
||||
s->device = g_strdup(blk_name(blk));
|
||||
bdrv_query_blk_stats(s->stats, blk);
|
||||
aio_context_release(ctx);
|
||||
|
||||
info->value = s;
|
||||
*p_next = info;
|
||||
p_next = &info->next;
|
||||
}
|
||||
*p_next = info;
|
||||
p_next = &info->next;
|
||||
}
|
||||
|
||||
return head;
|
||||
@@ -633,11 +608,9 @@ static void dump_qobject(fprintf_function func_fprintf, void *f,
|
||||
int comp_indent, QObject *obj)
|
||||
{
|
||||
switch (qobject_type(obj)) {
|
||||
case QTYPE_QNUM: {
|
||||
QNum *value = qobject_to_qnum(obj);
|
||||
char *tmp = qnum_to_string(value);
|
||||
func_fprintf(f, "%s", tmp);
|
||||
g_free(tmp);
|
||||
case QTYPE_QINT: {
|
||||
QInt *value = qobject_to_qint(obj);
|
||||
func_fprintf(f, "%" PRId64, qint_get_int(value));
|
||||
break;
|
||||
}
|
||||
case QTYPE_QSTRING: {
|
||||
@@ -655,6 +628,11 @@ static void dump_qobject(fprintf_function func_fprintf, void *f,
|
||||
dump_qlist(func_fprintf, f, comp_indent, value);
|
||||
break;
|
||||
}
|
||||
case QTYPE_QFLOAT: {
|
||||
QFloat *value = qobject_to_qfloat(obj);
|
||||
func_fprintf(f, "%g", qfloat_get_double(value));
|
||||
break;
|
||||
}
|
||||
case QTYPE_QBOOL: {
|
||||
QBool *value = qobject_to_qbool(obj);
|
||||
func_fprintf(f, "%s", qbool_get_bool(value) ? "true" : "false");
|
||||
@@ -713,13 +691,13 @@ void bdrv_image_info_specific_dump(fprintf_function func_fprintf, void *f,
|
||||
ImageInfoSpecific *info_spec)
|
||||
{
|
||||
QObject *obj, *data;
|
||||
Visitor *v = qobject_output_visitor_new(&obj);
|
||||
Visitor *v = qmp_output_visitor_new(&obj);
|
||||
|
||||
visit_type_ImageInfoSpecific(v, NULL, &info_spec, &error_abort);
|
||||
visit_complete(v, &obj);
|
||||
assert(qobject_type(obj) == QTYPE_QDICT);
|
||||
data = qdict_get(qobject_to_qdict(obj), "data");
|
||||
dump_qobject(func_fprintf, f, 1, data);
|
||||
qobject_decref(obj);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
|
||||
+207
-202
@@ -31,10 +31,8 @@
|
||||
#include "qemu/bswap.h"
|
||||
#include <zlib.h>
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "crypto/block.h"
|
||||
#include "migration/blocker.h"
|
||||
#include "block/crypto.h"
|
||||
#include "crypto/cipher.h"
|
||||
#include "migration/migration.h"
|
||||
|
||||
/**************************************************************/
|
||||
/* QEMU COW block driver with compression and encryption support */
|
||||
@@ -79,7 +77,7 @@ typedef struct BDRVQcowState {
|
||||
uint8_t *cluster_cache;
|
||||
uint8_t *cluster_data;
|
||||
uint64_t cluster_cache_offset;
|
||||
QCryptoBlock *crypto; /* Disk encryption format driver */
|
||||
QCryptoCipher *cipher; /* NULL if no key yet */
|
||||
uint32_t crypt_method_header;
|
||||
CoMutex lock;
|
||||
Error *migration_blocker;
|
||||
@@ -99,15 +97,6 @@ static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static QemuOptsList qcow_runtime_opts = {
|
||||
.name = "qcow",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(qcow_runtime_opts.head),
|
||||
.desc = {
|
||||
BLOCK_CRYPTO_OPT_DEF_QCOW_KEY_SECRET("encrypt."),
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
@@ -115,21 +104,6 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
unsigned int len, i, shift;
|
||||
int ret;
|
||||
QCowHeader header;
|
||||
Error *local_err = NULL;
|
||||
QCryptoBlockOpenOptions *crypto_opts = NULL;
|
||||
unsigned int cflags = 0;
|
||||
QDict *encryptopts = NULL;
|
||||
const char *encryptfmt;
|
||||
|
||||
qdict_extract_subqdict(options, &encryptopts, "encrypt.");
|
||||
encryptfmt = qdict_get_try_str(encryptopts, "format");
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = bdrv_pread(bs->file, 0, &header, sizeof(header));
|
||||
if (ret < 0) {
|
||||
@@ -174,6 +148,16 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (header.crypt_method > QCOW_CRYPT_AES) {
|
||||
error_setg(errp, "invalid encryption method in qcow header");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
if (!qcrypto_cipher_supports(QCRYPTO_CIPHER_ALG_AES_128)) {
|
||||
error_setg(errp, "AES cipher not available");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
s->crypt_method_header = header.crypt_method;
|
||||
if (s->crypt_method_header) {
|
||||
if (bdrv_uses_whitelist() &&
|
||||
@@ -189,44 +173,8 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
ret = -ENOSYS;
|
||||
goto fail;
|
||||
}
|
||||
if (s->crypt_method_header == QCOW_CRYPT_AES) {
|
||||
if (encryptfmt && !g_str_equal(encryptfmt, "aes")) {
|
||||
error_setg(errp,
|
||||
"Header reported 'aes' encryption format but "
|
||||
"options specify '%s'", encryptfmt);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
qdict_del(encryptopts, "format");
|
||||
crypto_opts = block_crypto_open_opts_init(
|
||||
Q_CRYPTO_BLOCK_FORMAT_QCOW, encryptopts, errp);
|
||||
if (!crypto_opts) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (flags & BDRV_O_NO_IO) {
|
||||
cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
|
||||
}
|
||||
s->crypto = qcrypto_block_open(crypto_opts, "encrypt.",
|
||||
NULL, NULL, cflags, errp);
|
||||
if (!s->crypto) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
error_setg(errp, "invalid encryption method in qcow header");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
bs->encrypted = true;
|
||||
} else {
|
||||
if (encryptfmt) {
|
||||
error_setg(errp, "No encryption in image header, but options "
|
||||
"specified format '%s'", encryptfmt);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
s->cluster_bits = header.cluster_bits;
|
||||
s->cluster_size = 1 << s->cluster_bits;
|
||||
@@ -303,15 +251,8 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
error_setg(&s->migration_blocker, "The qcow format used by node '%s' "
|
||||
"does not support live migration",
|
||||
bdrv_get_device_or_node_name(bs));
|
||||
ret = migrate_add_blocker(s->migration_blocker, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
error_free(s->migration_blocker);
|
||||
goto fail;
|
||||
}
|
||||
migrate_add_blocker(s->migration_blocker);
|
||||
|
||||
QDECREF(encryptopts);
|
||||
qapi_free_QCryptoBlockOpenOptions(crypto_opts);
|
||||
qemu_co_mutex_init(&s->lock);
|
||||
return 0;
|
||||
|
||||
@@ -320,9 +261,6 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
qemu_vfree(s->l2_cache);
|
||||
g_free(s->cluster_cache);
|
||||
g_free(s->cluster_data);
|
||||
qcrypto_block_free(s->crypto);
|
||||
QDECREF(encryptopts);
|
||||
qapi_free_QCryptoBlockOpenOptions(crypto_opts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -335,6 +273,85 @@ static int qcow_reopen_prepare(BDRVReopenState *state,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcow_set_key(BlockDriverState *bs, const char *key)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
uint8_t keybuf[16];
|
||||
int len, i;
|
||||
Error *err;
|
||||
|
||||
memset(keybuf, 0, 16);
|
||||
len = strlen(key);
|
||||
if (len > 16)
|
||||
len = 16;
|
||||
/* XXX: we could compress the chars to 7 bits to increase
|
||||
entropy */
|
||||
for(i = 0;i < len;i++) {
|
||||
keybuf[i] = key[i];
|
||||
}
|
||||
assert(bs->encrypted);
|
||||
|
||||
qcrypto_cipher_free(s->cipher);
|
||||
s->cipher = qcrypto_cipher_new(
|
||||
QCRYPTO_CIPHER_ALG_AES_128,
|
||||
QCRYPTO_CIPHER_MODE_CBC,
|
||||
keybuf, G_N_ELEMENTS(keybuf),
|
||||
&err);
|
||||
|
||||
if (!s->cipher) {
|
||||
/* XXX would be nice if errors in this method could
|
||||
* be properly propagate to the caller. Would need
|
||||
* the bdrv_set_key() API signature to be fixed. */
|
||||
error_free(err);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The crypt function is compatible with the linux cryptoloop
|
||||
algorithm for < 4 GB images. NOTE: out_buf == in_buf is
|
||||
supported */
|
||||
static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
|
||||
uint8_t *out_buf, const uint8_t *in_buf,
|
||||
int nb_sectors, bool enc, Error **errp)
|
||||
{
|
||||
union {
|
||||
uint64_t ll[2];
|
||||
uint8_t b[16];
|
||||
} ivec;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
for(i = 0; i < nb_sectors; i++) {
|
||||
ivec.ll[0] = cpu_to_le64(sector_num);
|
||||
ivec.ll[1] = 0;
|
||||
if (qcrypto_cipher_setiv(s->cipher,
|
||||
ivec.b, G_N_ELEMENTS(ivec.b),
|
||||
errp) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (enc) {
|
||||
ret = qcrypto_cipher_encrypt(s->cipher,
|
||||
in_buf,
|
||||
out_buf,
|
||||
512,
|
||||
errp);
|
||||
} else {
|
||||
ret = qcrypto_cipher_decrypt(s->cipher,
|
||||
in_buf,
|
||||
out_buf,
|
||||
512,
|
||||
errp);
|
||||
}
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
sector_num++;
|
||||
in_buf += 512;
|
||||
out_buf += 512;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 'allocate' is:
|
||||
*
|
||||
@@ -443,23 +460,22 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
|
||||
/* round to cluster size */
|
||||
cluster_offset = (cluster_offset + s->cluster_size - 1) &
|
||||
~(s->cluster_size - 1);
|
||||
bdrv_truncate(bs->file, cluster_offset + s->cluster_size,
|
||||
PREALLOC_MODE_OFF, NULL);
|
||||
bdrv_truncate(bs->file->bs, cluster_offset + s->cluster_size);
|
||||
/* if encrypted, we must initialize the cluster
|
||||
content which won't be written */
|
||||
if (bs->encrypted &&
|
||||
(n_end - n_start) < s->cluster_sectors) {
|
||||
uint64_t start_sect;
|
||||
assert(s->crypto);
|
||||
assert(s->cipher);
|
||||
start_sect = (offset & ~(s->cluster_size - 1)) >> 9;
|
||||
memset(s->cluster_data + 512, 0x00, 512);
|
||||
for(i = 0; i < s->cluster_sectors; i++) {
|
||||
if (i < n_start || i >= n_end) {
|
||||
Error *err = NULL;
|
||||
memset(s->cluster_data, 0x00, 512);
|
||||
if (qcrypto_block_encrypt(s->crypto, start_sect + i,
|
||||
s->cluster_data,
|
||||
BDRV_SECTOR_SIZE,
|
||||
&err) < 0) {
|
||||
if (encrypt_sectors(s, start_sect + i,
|
||||
s->cluster_data,
|
||||
s->cluster_data + 512, 1,
|
||||
true, &err) < 0) {
|
||||
error_free(err);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
@@ -504,7 +520,7 @@ static int64_t coroutine_fn qcow_co_get_block_status(BlockDriverState *bs,
|
||||
if (!cluster_offset) {
|
||||
return 0;
|
||||
}
|
||||
if ((cluster_offset & QCOW_OFLAG_COMPRESSED) || s->crypto) {
|
||||
if ((cluster_offset & QCOW_OFLAG_COMPRESSED) || s->cipher) {
|
||||
return BDRV_BLOCK_DATA;
|
||||
}
|
||||
cluster_offset |= (index_in_cluster << BDRV_SECTOR_BITS);
|
||||
@@ -635,9 +651,9 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
|
||||
break;
|
||||
}
|
||||
if (bs->encrypted) {
|
||||
assert(s->crypto);
|
||||
if (qcrypto_block_decrypt(s->crypto, sector_num, buf,
|
||||
n * BDRV_SECTOR_SIZE, &err) < 0) {
|
||||
assert(s->cipher);
|
||||
if (encrypt_sectors(s, sector_num, buf, buf,
|
||||
n, false, &err) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@@ -671,7 +687,9 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
int index_in_cluster;
|
||||
uint64_t cluster_offset;
|
||||
const uint8_t *src_buf;
|
||||
int ret = 0, n;
|
||||
uint8_t *cluster_data = NULL;
|
||||
struct iovec hd_iov;
|
||||
QEMUIOVector hd_qiov;
|
||||
uint8_t *buf;
|
||||
@@ -679,9 +697,7 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||
|
||||
s->cluster_cache_offset = -1; /* disable compressed cache */
|
||||
|
||||
/* We must always copy the iov when encrypting, so we
|
||||
* don't modify the original data buffer during encryption */
|
||||
if (bs->encrypted || qiov->niov > 1) {
|
||||
if (qiov->niov > 1) {
|
||||
buf = orig_buf = qemu_try_blockalign(bs, qiov->size);
|
||||
if (buf == NULL) {
|
||||
return -ENOMEM;
|
||||
@@ -710,16 +726,22 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||
}
|
||||
if (bs->encrypted) {
|
||||
Error *err = NULL;
|
||||
assert(s->crypto);
|
||||
if (qcrypto_block_encrypt(s->crypto, sector_num, buf,
|
||||
n * BDRV_SECTOR_SIZE, &err) < 0) {
|
||||
assert(s->cipher);
|
||||
if (!cluster_data) {
|
||||
cluster_data = g_malloc0(s->cluster_size);
|
||||
}
|
||||
if (encrypt_sectors(s, sector_num, cluster_data, buf,
|
||||
n, true, &err) < 0) {
|
||||
error_free(err);
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
src_buf = cluster_data;
|
||||
} else {
|
||||
src_buf = buf;
|
||||
}
|
||||
|
||||
hd_iov.iov_base = (void *)buf;
|
||||
hd_iov.iov_base = (void *)src_buf;
|
||||
hd_iov.iov_len = n * 512;
|
||||
qemu_iovec_init_external(&hd_qiov, &hd_iov, 1);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
@@ -738,7 +760,10 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||
}
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
|
||||
qemu_vfree(orig_buf);
|
||||
if (qiov->niov > 1) {
|
||||
qemu_vfree(orig_buf);
|
||||
}
|
||||
g_free(cluster_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -747,8 +772,8 @@ static void qcow_close(BlockDriverState *bs)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
|
||||
qcrypto_block_free(s->crypto);
|
||||
s->crypto = NULL;
|
||||
qcrypto_cipher_free(s->cipher);
|
||||
s->cipher = NULL;
|
||||
g_free(s->l1_table);
|
||||
qemu_vfree(s->l2_cache);
|
||||
g_free(s->cluster_cache);
|
||||
@@ -765,35 +790,17 @@ static int qcow_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
uint8_t *tmp;
|
||||
int64_t total_size = 0;
|
||||
char *backing_file = NULL;
|
||||
int flags = 0;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
BlockBackend *qcow_blk;
|
||||
char *encryptfmt = NULL;
|
||||
QDict *options;
|
||||
QDict *encryptopts = NULL;
|
||||
QCryptoBlockCreateOptions *crypto_opts = NULL;
|
||||
QCryptoBlock *crypto = NULL;
|
||||
|
||||
/* Read out options */
|
||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
if (total_size == 0) {
|
||||
error_setg(errp, "Image size is too small, cannot be zero length");
|
||||
ret = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
backing_file = qemu_opt_get_del(opts, BLOCK_OPT_BACKING_FILE);
|
||||
encryptfmt = qemu_opt_get_del(opts, BLOCK_OPT_ENCRYPT_FORMAT);
|
||||
if (encryptfmt) {
|
||||
if (qemu_opt_get(opts, BLOCK_OPT_ENCRYPT)) {
|
||||
error_setg(errp, "Options " BLOCK_OPT_ENCRYPT " and "
|
||||
BLOCK_OPT_ENCRYPT_FORMAT " are mutually exclusive");
|
||||
ret = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
} else if (qemu_opt_get_bool_del(opts, BLOCK_OPT_ENCRYPT, false)) {
|
||||
encryptfmt = g_strdup("aes");
|
||||
if (qemu_opt_get_bool_del(opts, BLOCK_OPT_ENCRYPT, false)) {
|
||||
flags |= BLOCK_FLAG_ENCRYPT;
|
||||
}
|
||||
|
||||
ret = bdrv_create_file(filename, opts, &local_err);
|
||||
@@ -803,8 +810,7 @@ static int qcow_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
}
|
||||
|
||||
qcow_blk = blk_new_open(filename, NULL, NULL,
|
||||
BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
|
||||
&local_err);
|
||||
BDRV_O_RDWR | BDRV_O_PROTOCOL, &local_err);
|
||||
if (qcow_blk == NULL) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EIO;
|
||||
@@ -813,7 +819,7 @@ static int qcow_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
|
||||
blk_set_allow_write_beyond_eof(qcow_blk, true);
|
||||
|
||||
ret = blk_truncate(qcow_blk, 0, PREALLOC_MODE_OFF, errp);
|
||||
ret = blk_truncate(qcow_blk, 0);
|
||||
if (ret < 0) {
|
||||
goto exit;
|
||||
}
|
||||
@@ -832,7 +838,6 @@ static int qcow_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
header_size += backing_filename_len;
|
||||
} else {
|
||||
/* special backing file for vvfat */
|
||||
g_free(backing_file);
|
||||
backing_file = NULL;
|
||||
}
|
||||
header.cluster_bits = 9; /* 512 byte cluster to avoid copying
|
||||
@@ -847,32 +852,8 @@ static int qcow_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
l1_size = (total_size + (1LL << shift) - 1) >> shift;
|
||||
|
||||
header.l1_table_offset = cpu_to_be64(header_size);
|
||||
|
||||
options = qemu_opts_to_qdict(opts, NULL);
|
||||
qdict_extract_subqdict(options, &encryptopts, "encrypt.");
|
||||
QDECREF(options);
|
||||
if (encryptfmt) {
|
||||
if (!g_str_equal(encryptfmt, "aes")) {
|
||||
error_setg(errp, "Unknown encryption format '%s', expected 'aes'",
|
||||
encryptfmt);
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
if (flags & BLOCK_FLAG_ENCRYPT) {
|
||||
header.crypt_method = cpu_to_be32(QCOW_CRYPT_AES);
|
||||
|
||||
crypto_opts = block_crypto_create_opts_init(
|
||||
Q_CRYPTO_BLOCK_FORMAT_QCOW, encryptopts, errp);
|
||||
if (!crypto_opts) {
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
crypto = qcrypto_block_create(crypto_opts, "encrypt.",
|
||||
NULL, NULL, NULL, errp);
|
||||
if (!crypto) {
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
} else {
|
||||
header.crypt_method = cpu_to_be32(QCOW_CRYPT_NONE);
|
||||
}
|
||||
@@ -907,10 +888,6 @@ static int qcow_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
exit:
|
||||
blk_unref(qcow_blk);
|
||||
cleanup:
|
||||
QDECREF(encryptopts);
|
||||
g_free(encryptfmt);
|
||||
qcrypto_block_free(crypto);
|
||||
qapi_free_QCryptoBlockCreateOptions(crypto_opts);
|
||||
g_free(backing_file);
|
||||
return ret;
|
||||
}
|
||||
@@ -925,8 +902,7 @@ static int qcow_make_empty(BlockDriverState *bs)
|
||||
if (bdrv_pwrite_sync(bs->file, s->l1_table_offset, s->l1_table,
|
||||
l1_length) < 0)
|
||||
return -1;
|
||||
ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length,
|
||||
PREALLOC_MODE_OFF, NULL);
|
||||
ret = bdrv_truncate(bs->file->bs, s->l1_table_offset + l1_length);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@@ -937,32 +913,75 @@ static int qcow_make_empty(BlockDriverState *bs)
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct QcowWriteCo {
|
||||
BlockDriverState *bs;
|
||||
int64_t sector_num;
|
||||
const uint8_t *buf;
|
||||
int nb_sectors;
|
||||
int ret;
|
||||
} QcowWriteCo;
|
||||
|
||||
static void qcow_write_co_entry(void *opaque)
|
||||
{
|
||||
QcowWriteCo *co = opaque;
|
||||
QEMUIOVector qiov;
|
||||
|
||||
struct iovec iov = (struct iovec) {
|
||||
.iov_base = (uint8_t*) co->buf,
|
||||
.iov_len = co->nb_sectors * BDRV_SECTOR_SIZE,
|
||||
};
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
co->ret = qcow_co_writev(co->bs, co->sector_num, co->nb_sectors, &qiov);
|
||||
}
|
||||
|
||||
/* Wrapper for non-coroutine contexts */
|
||||
static int qcow_write(BlockDriverState *bs, int64_t sector_num,
|
||||
const uint8_t *buf, int nb_sectors)
|
||||
{
|
||||
Coroutine *co;
|
||||
AioContext *aio_context = bdrv_get_aio_context(bs);
|
||||
QcowWriteCo data = {
|
||||
.bs = bs,
|
||||
.sector_num = sector_num,
|
||||
.buf = buf,
|
||||
.nb_sectors = nb_sectors,
|
||||
.ret = -EINPROGRESS,
|
||||
};
|
||||
co = qemu_coroutine_create(qcow_write_co_entry, &data);
|
||||
qemu_coroutine_enter(co);
|
||||
while (data.ret == -EINPROGRESS) {
|
||||
aio_poll(aio_context, true);
|
||||
}
|
||||
return data.ret;
|
||||
}
|
||||
|
||||
/* XXX: put compressed sectors first, then all the cluster aligned
|
||||
tables to avoid losing bytes in alignment */
|
||||
static coroutine_fn int
|
||||
qcow_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov)
|
||||
static int qcow_write_compressed(BlockDriverState *bs, int64_t sector_num,
|
||||
const uint8_t *buf, int nb_sectors)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
QEMUIOVector hd_qiov;
|
||||
struct iovec iov;
|
||||
z_stream strm;
|
||||
int ret, out_len;
|
||||
uint8_t *buf, *out_buf;
|
||||
uint8_t *out_buf;
|
||||
uint64_t cluster_offset;
|
||||
|
||||
buf = qemu_blockalign(bs, s->cluster_size);
|
||||
if (bytes != s->cluster_size) {
|
||||
if (bytes > s->cluster_size ||
|
||||
offset + bytes != bs->total_sectors << BDRV_SECTOR_BITS)
|
||||
{
|
||||
qemu_vfree(buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (nb_sectors != s->cluster_sectors) {
|
||||
ret = -EINVAL;
|
||||
|
||||
/* Zero-pad last write if image size is not cluster aligned */
|
||||
memset(buf + bytes, 0, s->cluster_size - bytes);
|
||||
if (sector_num + nb_sectors == bs->total_sectors &&
|
||||
nb_sectors < s->cluster_sectors) {
|
||||
uint8_t *pad_buf = qemu_blockalign(bs, s->cluster_size);
|
||||
memset(pad_buf, 0, s->cluster_size);
|
||||
memcpy(pad_buf, buf, nb_sectors * BDRV_SECTOR_SIZE);
|
||||
ret = qcow_write_compressed(bs, sector_num,
|
||||
pad_buf, s->cluster_sectors);
|
||||
qemu_vfree(pad_buf);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
qemu_iovec_to_buf(qiov, 0, buf, qiov->size);
|
||||
|
||||
out_buf = g_malloc(s->cluster_size);
|
||||
|
||||
@@ -993,35 +1012,27 @@ qcow_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
|
||||
|
||||
if (ret != Z_STREAM_END || out_len >= s->cluster_size) {
|
||||
/* could not compress: write normal cluster */
|
||||
ret = qcow_co_writev(bs, offset >> BDRV_SECTOR_BITS,
|
||||
bytes >> BDRV_SECTOR_BITS, qiov);
|
||||
ret = qcow_write(bs, sector_num, buf, s->cluster_sectors);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
goto success;
|
||||
}
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
cluster_offset = get_cluster_offset(bs, offset, 2, out_len, 0, 0);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
if (cluster_offset == 0) {
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
cluster_offset &= s->cluster_offset_mask;
|
||||
} else {
|
||||
cluster_offset = get_cluster_offset(bs, sector_num << 9, 2,
|
||||
out_len, 0, 0);
|
||||
if (cluster_offset == 0) {
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
iov = (struct iovec) {
|
||||
.iov_base = out_buf,
|
||||
.iov_len = out_len,
|
||||
};
|
||||
qemu_iovec_init_external(&hd_qiov, &iov, 1);
|
||||
ret = bdrv_co_pwritev(bs->file, cluster_offset, out_len, &hd_qiov, 0);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
cluster_offset &= s->cluster_offset_mask;
|
||||
ret = bdrv_pwrite(bs->file, cluster_offset, out_buf, out_len);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
success:
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
qemu_vfree(buf);
|
||||
g_free(out_buf);
|
||||
return ret;
|
||||
}
|
||||
@@ -1050,15 +1061,9 @@ static QemuOptsList qcow_create_opts = {
|
||||
{
|
||||
.name = BLOCK_OPT_ENCRYPT,
|
||||
.type = QEMU_OPT_BOOL,
|
||||
.help = "Encrypt the image with format 'aes'. (Deprecated "
|
||||
"in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",
|
||||
.help = "Encrypt the image",
|
||||
.def_value_str = "off"
|
||||
},
|
||||
{
|
||||
.name = BLOCK_OPT_ENCRYPT_FORMAT,
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Encrypt the image, format choices: 'aes'",
|
||||
},
|
||||
BLOCK_CRYPTO_OPT_DEF_QCOW_KEY_SECRET("encrypt."),
|
||||
{ /* end of list */ }
|
||||
}
|
||||
};
|
||||
@@ -1069,7 +1074,6 @@ static BlockDriver bdrv_qcow = {
|
||||
.bdrv_probe = qcow_probe,
|
||||
.bdrv_open = qcow_open,
|
||||
.bdrv_close = qcow_close,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_reopen_prepare = qcow_reopen_prepare,
|
||||
.bdrv_create = qcow_create,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
@@ -1079,8 +1083,9 @@ static BlockDriver bdrv_qcow = {
|
||||
.bdrv_co_writev = qcow_co_writev,
|
||||
.bdrv_co_get_block_status = qcow_co_get_block_status,
|
||||
|
||||
.bdrv_set_key = qcow_set_key,
|
||||
.bdrv_make_empty = qcow_make_empty,
|
||||
.bdrv_co_pwritev_compressed = qcow_co_pwritev_compressed,
|
||||
.bdrv_write_compressed = qcow_write_compressed,
|
||||
.bdrv_get_info = qcow_get_info,
|
||||
|
||||
.create_opts = &qcow_create_opts,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+3
-3
@@ -22,6 +22,7 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* Needed for CONFIG_MADVISE */
|
||||
#include "qemu/osdep.h"
|
||||
#include "block/block_int.h"
|
||||
#include "qemu-common.h"
|
||||
@@ -65,8 +66,7 @@ static inline int qcow2_cache_get_table_idx(BlockDriverState *bs,
|
||||
static void qcow2_cache_table_release(BlockDriverState *bs, Qcow2Cache *c,
|
||||
int i, int num_tables)
|
||||
{
|
||||
/* Using MADV_DONTNEED to discard memory is a Linux-specific feature */
|
||||
#ifdef CONFIG_LINUX
|
||||
#if QEMU_MADV_DONTNEED != QEMU_MADV_INVALID
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
void *t = qcow2_cache_get_table_addr(bs, c, i);
|
||||
int align = getpagesize();
|
||||
@@ -74,7 +74,7 @@ static void qcow2_cache_table_release(BlockDriverState *bs, Qcow2Cache *c,
|
||||
size_t offset = QEMU_ALIGN_UP((uintptr_t) t, align) - (uintptr_t) t;
|
||||
size_t length = QEMU_ALIGN_DOWN(mem_size - offset, align);
|
||||
if (length > 0) {
|
||||
madvise((uint8_t *) t + offset, length, MADV_DONTNEED);
|
||||
qemu_madvise((uint8_t *) t + offset, length, QEMU_MADV_DONTNEED);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+204
-327
@@ -83,9 +83,7 @@ int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
||||
}
|
||||
memset(new_l1_table, 0, align_offset(new_l1_size2, 512));
|
||||
|
||||
if (s->l1_size) {
|
||||
memcpy(new_l1_table, s->l1_table, s->l1_size * sizeof(uint64_t));
|
||||
}
|
||||
memcpy(new_l1_table, s->l1_table, s->l1_size * sizeof(uint64_t));
|
||||
|
||||
/* write new table (align to cluster) */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_L1_GROW_ALLOC_TABLE);
|
||||
@@ -309,19 +307,14 @@ static int count_contiguous_clusters(int nb_clusters, int cluster_size,
|
||||
uint64_t *l2_table, uint64_t stop_flags)
|
||||
{
|
||||
int i;
|
||||
QCow2ClusterType first_cluster_type;
|
||||
uint64_t mask = stop_flags | L2E_OFFSET_MASK | QCOW_OFLAG_COMPRESSED;
|
||||
uint64_t first_entry = be64_to_cpu(l2_table[0]);
|
||||
uint64_t offset = first_entry & mask;
|
||||
|
||||
if (!offset) {
|
||||
if (!offset)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* must be allocated */
|
||||
first_cluster_type = qcow2_get_cluster_type(first_entry);
|
||||
assert(first_cluster_type == QCOW2_CLUSTER_NORMAL ||
|
||||
first_cluster_type == QCOW2_CLUSTER_ZERO_ALLOC);
|
||||
assert(qcow2_get_cluster_type(first_entry) == QCOW2_CLUSTER_NORMAL);
|
||||
|
||||
for (i = 0; i < nb_clusters; i++) {
|
||||
uint64_t l2_entry = be64_to_cpu(l2_table[i]) & mask;
|
||||
@@ -333,21 +326,14 @@ static int count_contiguous_clusters(int nb_clusters, int cluster_size,
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks how many consecutive unallocated clusters in a given L2
|
||||
* table have the same cluster type.
|
||||
*/
|
||||
static int count_contiguous_clusters_unallocated(int nb_clusters,
|
||||
uint64_t *l2_table,
|
||||
QCow2ClusterType wanted_type)
|
||||
static int count_contiguous_clusters_by_type(int nb_clusters,
|
||||
uint64_t *l2_table,
|
||||
int wanted_type)
|
||||
{
|
||||
int i;
|
||||
|
||||
assert(wanted_type == QCOW2_CLUSTER_ZERO_PLAIN ||
|
||||
wanted_type == QCOW2_CLUSTER_UNALLOCATED);
|
||||
for (i = 0; i < nb_clusters; i++) {
|
||||
uint64_t entry = be64_to_cpu(l2_table[i]);
|
||||
QCow2ClusterType type = qcow2_get_cluster_type(entry);
|
||||
int type = qcow2_get_cluster_type(be64_to_cpu(l2_table[i]));
|
||||
|
||||
if (type != wanted_type) {
|
||||
break;
|
||||
@@ -357,21 +343,76 @@ static int count_contiguous_clusters_unallocated(int nb_clusters,
|
||||
return i;
|
||||
}
|
||||
|
||||
static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
|
||||
uint64_t src_cluster_offset,
|
||||
unsigned offset_in_cluster,
|
||||
QEMUIOVector *qiov)
|
||||
/* The crypt function is compatible with the linux cryptoloop
|
||||
algorithm for < 4 GB images. NOTE: out_buf == in_buf is
|
||||
supported */
|
||||
int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
|
||||
uint8_t *out_buf, const uint8_t *in_buf,
|
||||
int nb_sectors, bool enc,
|
||||
Error **errp)
|
||||
{
|
||||
union {
|
||||
uint64_t ll[2];
|
||||
uint8_t b[16];
|
||||
} ivec;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
if (qiov->size == 0) {
|
||||
return 0;
|
||||
for(i = 0; i < nb_sectors; i++) {
|
||||
ivec.ll[0] = cpu_to_le64(sector_num);
|
||||
ivec.ll[1] = 0;
|
||||
if (qcrypto_cipher_setiv(s->cipher,
|
||||
ivec.b, G_N_ELEMENTS(ivec.b),
|
||||
errp) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (enc) {
|
||||
ret = qcrypto_cipher_encrypt(s->cipher,
|
||||
in_buf,
|
||||
out_buf,
|
||||
512,
|
||||
errp);
|
||||
} else {
|
||||
ret = qcrypto_cipher_decrypt(s->cipher,
|
||||
in_buf,
|
||||
out_buf,
|
||||
512,
|
||||
errp);
|
||||
}
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
sector_num++;
|
||||
in_buf += 512;
|
||||
out_buf += 512;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn do_perform_cow(BlockDriverState *bs,
|
||||
uint64_t src_cluster_offset,
|
||||
uint64_t cluster_offset,
|
||||
int offset_in_cluster,
|
||||
int bytes)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
QEMUIOVector qiov;
|
||||
struct iovec iov;
|
||||
int ret;
|
||||
|
||||
iov.iov_len = bytes;
|
||||
iov.iov_base = qemu_try_blockalign(bs, iov.iov_len);
|
||||
if (iov.iov_base == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_COW_READ);
|
||||
|
||||
if (!bs->drv) {
|
||||
return -ENOMEDIUM;
|
||||
ret = -ENOMEDIUM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Call .bdrv_co_readv() directly instead of using the public block-layer
|
||||
@@ -379,63 +420,43 @@ static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
|
||||
* which can lead to deadlock when block layer copy-on-read is enabled.
|
||||
*/
|
||||
ret = bs->drv->bdrv_co_preadv(bs, src_cluster_offset + offset_in_cluster,
|
||||
qiov->size, qiov, 0);
|
||||
bytes, &qiov, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
|
||||
uint64_t src_cluster_offset,
|
||||
uint64_t cluster_offset,
|
||||
unsigned offset_in_cluster,
|
||||
uint8_t *buffer,
|
||||
unsigned bytes)
|
||||
{
|
||||
if (bytes && bs->encrypted) {
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
int64_t sector = (s->crypt_physical_offset ?
|
||||
(cluster_offset + offset_in_cluster) :
|
||||
(src_cluster_offset + offset_in_cluster))
|
||||
if (bs->encrypted) {
|
||||
Error *err = NULL;
|
||||
int64_t sector = (src_cluster_offset + offset_in_cluster)
|
||||
>> BDRV_SECTOR_BITS;
|
||||
assert(s->cipher);
|
||||
assert((offset_in_cluster & ~BDRV_SECTOR_MASK) == 0);
|
||||
assert((bytes & ~BDRV_SECTOR_MASK) == 0);
|
||||
assert(s->crypto);
|
||||
if (qcrypto_block_encrypt(s->crypto, sector, buffer,
|
||||
bytes, NULL) < 0) {
|
||||
return false;
|
||||
if (qcow2_encrypt_sectors(s, sector, iov.iov_base, iov.iov_base,
|
||||
bytes >> BDRV_SECTOR_BITS, true, &err) < 0) {
|
||||
ret = -EIO;
|
||||
error_free(err);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
|
||||
uint64_t cluster_offset,
|
||||
unsigned offset_in_cluster,
|
||||
QEMUIOVector *qiov)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (qiov->size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = qcow2_pre_write_overlap_check(bs, 0,
|
||||
cluster_offset + offset_in_cluster, qiov->size);
|
||||
cluster_offset + offset_in_cluster, bytes);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_COW_WRITE);
|
||||
ret = bdrv_co_pwritev(bs->file, cluster_offset + offset_in_cluster,
|
||||
qiov->size, qiov, 0);
|
||||
bytes, &qiov, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
ret = 0;
|
||||
out:
|
||||
qemu_vfree(iov.iov_base);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -464,7 +485,6 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||
int l1_bits, c;
|
||||
unsigned int offset_in_cluster;
|
||||
uint64_t bytes_available, bytes_needed, nb_clusters;
|
||||
QCow2ClusterType type;
|
||||
int ret;
|
||||
|
||||
offset_in_cluster = offset_into_cluster(s, offset);
|
||||
@@ -487,13 +507,13 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||
|
||||
l1_index = offset >> l1_bits;
|
||||
if (l1_index >= s->l1_size) {
|
||||
type = QCOW2_CLUSTER_UNALLOCATED;
|
||||
ret = QCOW2_CLUSTER_UNALLOCATED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
l2_offset = s->l1_table[l1_index] & L1E_OFFSET_MASK;
|
||||
if (!l2_offset) {
|
||||
type = QCOW2_CLUSTER_UNALLOCATED;
|
||||
ret = QCOW2_CLUSTER_UNALLOCATED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -513,7 +533,7 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||
|
||||
/* find the cluster offset for the given disk offset */
|
||||
|
||||
l2_index = offset_to_l2_index(s, offset);
|
||||
l2_index = (offset >> s->cluster_bits) & (s->l2_size - 1);
|
||||
*cluster_offset = be64_to_cpu(l2_table[l2_index]);
|
||||
|
||||
nb_clusters = size_to_clusters(s, bytes_needed);
|
||||
@@ -522,37 +542,38 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||
* true */
|
||||
assert(nb_clusters <= INT_MAX);
|
||||
|
||||
type = qcow2_get_cluster_type(*cluster_offset);
|
||||
if (s->qcow_version < 3 && (type == QCOW2_CLUSTER_ZERO_PLAIN ||
|
||||
type == QCOW2_CLUSTER_ZERO_ALLOC)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1, "Zero cluster entry found"
|
||||
" in pre-v3 image (L2 offset: %#" PRIx64
|
||||
", L2 index: %#x)", l2_offset, l2_index);
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
switch (type) {
|
||||
ret = qcow2_get_cluster_type(*cluster_offset);
|
||||
switch (ret) {
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
/* Compressed clusters can only be processed one by one */
|
||||
c = 1;
|
||||
*cluster_offset &= L2E_COMPRESSED_OFFSET_SIZE_MASK;
|
||||
break;
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
case QCOW2_CLUSTER_ZERO:
|
||||
if (s->qcow_version < 3) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1, "Zero cluster entry found"
|
||||
" in pre-v3 image (L2 offset: %#" PRIx64
|
||||
", L2 index: %#x)", l2_offset, l2_index);
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
c = count_contiguous_clusters_by_type(nb_clusters, &l2_table[l2_index],
|
||||
QCOW2_CLUSTER_ZERO);
|
||||
*cluster_offset = 0;
|
||||
break;
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
/* how many empty clusters ? */
|
||||
c = count_contiguous_clusters_unallocated(nb_clusters,
|
||||
&l2_table[l2_index], type);
|
||||
c = count_contiguous_clusters_by_type(nb_clusters, &l2_table[l2_index],
|
||||
QCOW2_CLUSTER_UNALLOCATED);
|
||||
*cluster_offset = 0;
|
||||
break;
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
/* how many allocated clusters ? */
|
||||
c = count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||
&l2_table[l2_index], QCOW_OFLAG_ZERO);
|
||||
&l2_table[l2_index], QCOW_OFLAG_ZERO);
|
||||
*cluster_offset &= L2E_OFFSET_MASK;
|
||||
if (offset_into_cluster(s, *cluster_offset)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1,
|
||||
"Cluster allocation offset %#"
|
||||
qcow2_signal_corruption(bs, true, -1, -1, "Data cluster offset %#"
|
||||
PRIx64 " unaligned (L2 offset: %#" PRIx64
|
||||
", L2 index: %#x)", *cluster_offset,
|
||||
l2_offset, l2_index);
|
||||
@@ -579,7 +600,7 @@ out:
|
||||
assert(bytes_available - offset_in_cluster <= UINT_MAX);
|
||||
*bytes = bytes_available - offset_in_cluster;
|
||||
|
||||
return type;
|
||||
return ret;
|
||||
|
||||
fail:
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table);
|
||||
@@ -650,7 +671,7 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
||||
|
||||
/* find the cluster offset for the given disk offset */
|
||||
|
||||
l2_index = offset_to_l2_index(s, offset);
|
||||
l2_index = (offset >> s->cluster_bits) & (s->l2_size - 1);
|
||||
|
||||
*new_l2_table = l2_table;
|
||||
*new_l2_index = l2_index;
|
||||
@@ -718,134 +739,31 @@ uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
||||
return cluster_offset;
|
||||
}
|
||||
|
||||
static int perform_cow(BlockDriverState *bs, QCowL2Meta *m)
|
||||
static int perform_cow(BlockDriverState *bs, QCowL2Meta *m, Qcow2COWRegion *r)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
Qcow2COWRegion *start = &m->cow_start;
|
||||
Qcow2COWRegion *end = &m->cow_end;
|
||||
unsigned buffer_size;
|
||||
unsigned data_bytes = end->offset - (start->offset + start->nb_bytes);
|
||||
bool merge_reads;
|
||||
uint8_t *start_buffer, *end_buffer;
|
||||
QEMUIOVector qiov;
|
||||
int ret;
|
||||
|
||||
assert(start->nb_bytes <= UINT_MAX - end->nb_bytes);
|
||||
assert(start->nb_bytes + end->nb_bytes <= UINT_MAX - data_bytes);
|
||||
assert(start->offset + start->nb_bytes <= end->offset);
|
||||
assert(!m->data_qiov || m->data_qiov->size == data_bytes);
|
||||
|
||||
if (start->nb_bytes == 0 && end->nb_bytes == 0) {
|
||||
if (r->nb_bytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we have to read both the start and end COW regions and the
|
||||
* middle region is not too large then perform just one read
|
||||
* operation */
|
||||
merge_reads = start->nb_bytes && end->nb_bytes && data_bytes <= 16384;
|
||||
if (merge_reads) {
|
||||
buffer_size = start->nb_bytes + data_bytes + end->nb_bytes;
|
||||
} else {
|
||||
/* If we have to do two reads, add some padding in the middle
|
||||
* if necessary to make sure that the end region is optimally
|
||||
* aligned. */
|
||||
size_t align = bdrv_opt_mem_align(bs);
|
||||
assert(align > 0 && align <= UINT_MAX);
|
||||
assert(QEMU_ALIGN_UP(start->nb_bytes, align) <=
|
||||
UINT_MAX - end->nb_bytes);
|
||||
buffer_size = QEMU_ALIGN_UP(start->nb_bytes, align) + end->nb_bytes;
|
||||
}
|
||||
|
||||
/* Reserve a buffer large enough to store all the data that we're
|
||||
* going to read */
|
||||
start_buffer = qemu_try_blockalign(bs, buffer_size);
|
||||
if (start_buffer == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* The part of the buffer where the end region is located */
|
||||
end_buffer = start_buffer + buffer_size - end->nb_bytes;
|
||||
|
||||
qemu_iovec_init(&qiov, 2 + (m->data_qiov ? m->data_qiov->niov : 0));
|
||||
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
/* First we read the existing data from both COW regions. We
|
||||
* either read the whole region in one go, or the start and end
|
||||
* regions separately. */
|
||||
if (merge_reads) {
|
||||
qemu_iovec_add(&qiov, start_buffer, buffer_size);
|
||||
ret = do_perform_cow_read(bs, m->offset, start->offset, &qiov);
|
||||
} else {
|
||||
qemu_iovec_add(&qiov, start_buffer, start->nb_bytes);
|
||||
ret = do_perform_cow_read(bs, m->offset, start->offset, &qiov);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
qemu_iovec_reset(&qiov);
|
||||
qemu_iovec_add(&qiov, end_buffer, end->nb_bytes);
|
||||
ret = do_perform_cow_read(bs, m->offset, end->offset, &qiov);
|
||||
}
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Encrypt the data if necessary before writing it */
|
||||
if (bs->encrypted) {
|
||||
if (!do_perform_cow_encrypt(bs, m->offset, m->alloc_offset,
|
||||
start->offset, start_buffer,
|
||||
start->nb_bytes) ||
|
||||
!do_perform_cow_encrypt(bs, m->offset, m->alloc_offset,
|
||||
end->offset, end_buffer, end->nb_bytes)) {
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* And now we can write everything. If we have the guest data we
|
||||
* can write everything in one single operation */
|
||||
if (m->data_qiov) {
|
||||
qemu_iovec_reset(&qiov);
|
||||
if (start->nb_bytes) {
|
||||
qemu_iovec_add(&qiov, start_buffer, start->nb_bytes);
|
||||
}
|
||||
qemu_iovec_concat(&qiov, m->data_qiov, 0, data_bytes);
|
||||
if (end->nb_bytes) {
|
||||
qemu_iovec_add(&qiov, end_buffer, end->nb_bytes);
|
||||
}
|
||||
/* NOTE: we have a write_aio blkdebug event here followed by
|
||||
* a cow_write one in do_perform_cow_write(), but there's only
|
||||
* one single I/O operation */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO);
|
||||
ret = do_perform_cow_write(bs, m->alloc_offset, start->offset, &qiov);
|
||||
} else {
|
||||
/* If there's no guest data then write both COW regions separately */
|
||||
qemu_iovec_reset(&qiov);
|
||||
qemu_iovec_add(&qiov, start_buffer, start->nb_bytes);
|
||||
ret = do_perform_cow_write(bs, m->alloc_offset, start->offset, &qiov);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
qemu_iovec_reset(&qiov);
|
||||
qemu_iovec_add(&qiov, end_buffer, end->nb_bytes);
|
||||
ret = do_perform_cow_write(bs, m->alloc_offset, end->offset, &qiov);
|
||||
}
|
||||
|
||||
fail:
|
||||
ret = do_perform_cow(bs, m->offset, m->alloc_offset, r->offset, r->nb_bytes);
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Before we update the L2 table to actually point to the new cluster, we
|
||||
* need to be sure that the refcounts have been increased and COW was
|
||||
* handled.
|
||||
*/
|
||||
if (ret == 0) {
|
||||
qcow2_cache_depends_on_flush(s->l2_table_cache);
|
||||
}
|
||||
qcow2_cache_depends_on_flush(s->l2_table_cache);
|
||||
|
||||
qemu_vfree(start_buffer);
|
||||
qemu_iovec_destroy(&qiov);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||
@@ -865,7 +783,12 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||
}
|
||||
|
||||
/* copy content of unmodified sectors */
|
||||
ret = perform_cow(bs, m);
|
||||
ret = perform_cow(bs, m, &m->cow_start);
|
||||
if (ret < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = perform_cow(bs, m, &m->cow_end);
|
||||
if (ret < 0) {
|
||||
goto err;
|
||||
}
|
||||
@@ -910,7 +833,7 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||
* Don't discard clusters that reach a refcount of 0 (e.g. compressed
|
||||
* clusters), the next write will reuse them anyway.
|
||||
*/
|
||||
if (!m->keep_old_clusters && j != 0) {
|
||||
if (j != 0) {
|
||||
for (i = 0; i < j; i++) {
|
||||
qcow2_free_any_clusters(bs, be64_to_cpu(old_cluster[i]), 1,
|
||||
QCOW2_DISCARD_NEVER);
|
||||
@@ -935,7 +858,7 @@ static int count_cow_clusters(BDRVQcow2State *s, int nb_clusters,
|
||||
|
||||
for (i = 0; i < nb_clusters; i++) {
|
||||
uint64_t l2_entry = be64_to_cpu(l2_table[l2_index + i]);
|
||||
QCow2ClusterType cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||
int cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||
|
||||
switch(cluster_type) {
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
@@ -945,8 +868,7 @@ static int count_cow_clusters(BDRVQcow2State *s, int nb_clusters,
|
||||
break;
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
case QCOW2_CLUSTER_ZERO:
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
@@ -1008,7 +930,9 @@ static int handle_dependencies(BlockDriverState *bs, uint64_t guest_offset,
|
||||
if (bytes == 0) {
|
||||
/* Wait for the dependency to complete. We need to recheck
|
||||
* the free/allocated clusters when we continue. */
|
||||
qemu_co_queue_wait(&old_alloc->dependent_requests, &s->lock);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
qemu_co_queue_wait(&old_alloc->dependent_requests);
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
@@ -1208,9 +1132,8 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
||||
uint64_t entry;
|
||||
uint64_t nb_clusters;
|
||||
int ret;
|
||||
bool keep_old_clusters = false;
|
||||
|
||||
uint64_t alloc_cluster_offset = 0;
|
||||
uint64_t alloc_cluster_offset;
|
||||
|
||||
trace_qcow2_handle_alloc(qemu_coroutine_self(), guest_offset, *host_offset,
|
||||
*bytes);
|
||||
@@ -1247,54 +1170,31 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
||||
* wrong with our code. */
|
||||
assert(nb_clusters > 0);
|
||||
|
||||
if (qcow2_get_cluster_type(entry) == QCOW2_CLUSTER_ZERO_ALLOC &&
|
||||
(entry & QCOW_OFLAG_COPIED) &&
|
||||
(!*host_offset ||
|
||||
start_of_cluster(s, *host_offset) == (entry & L2E_OFFSET_MASK)))
|
||||
{
|
||||
/* Try to reuse preallocated zero clusters; contiguous normal clusters
|
||||
* would be fine, too, but count_cow_clusters() above has limited
|
||||
* nb_clusters already to a range of COW clusters */
|
||||
int preallocated_nb_clusters =
|
||||
count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||
&l2_table[l2_index], QCOW_OFLAG_COPIED);
|
||||
assert(preallocated_nb_clusters > 0);
|
||||
|
||||
nb_clusters = preallocated_nb_clusters;
|
||||
alloc_cluster_offset = entry & L2E_OFFSET_MASK;
|
||||
|
||||
/* We want to reuse these clusters, so qcow2_alloc_cluster_link_l2()
|
||||
* should not free them. */
|
||||
keep_old_clusters = true;
|
||||
}
|
||||
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
||||
|
||||
/* Allocate, if necessary at a given offset in the image file */
|
||||
alloc_cluster_offset = start_of_cluster(s, *host_offset);
|
||||
ret = do_alloc_cluster_offset(bs, guest_offset, &alloc_cluster_offset,
|
||||
&nb_clusters);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Can't extend contiguous allocation */
|
||||
if (nb_clusters == 0) {
|
||||
*bytes = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* !*host_offset would overwrite the image header and is reserved for "no
|
||||
* host offset preferred". If 0 was a valid host offset, it'd trigger the
|
||||
* following overlap check; do that now to avoid having an invalid value in
|
||||
* *host_offset. */
|
||||
if (!alloc_cluster_offset) {
|
||||
/* Allocate, if necessary at a given offset in the image file */
|
||||
alloc_cluster_offset = start_of_cluster(s, *host_offset);
|
||||
ret = do_alloc_cluster_offset(bs, guest_offset, &alloc_cluster_offset,
|
||||
&nb_clusters);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Can't extend contiguous allocation */
|
||||
if (nb_clusters == 0) {
|
||||
*bytes = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* !*host_offset would overwrite the image header and is reserved for
|
||||
* "no host offset preferred". If 0 was a valid host offset, it'd
|
||||
* trigger the following overlap check; do that now to avoid having an
|
||||
* invalid value in *host_offset. */
|
||||
if (!alloc_cluster_offset) {
|
||||
ret = qcow2_pre_write_overlap_check(bs, 0, alloc_cluster_offset,
|
||||
nb_clusters * s->cluster_size);
|
||||
assert(ret < 0);
|
||||
goto fail;
|
||||
}
|
||||
ret = qcow2_pre_write_overlap_check(bs, 0, alloc_cluster_offset,
|
||||
nb_clusters * s->cluster_size);
|
||||
assert(ret < 0);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1325,8 +1225,6 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
||||
.offset = start_of_cluster(s, guest_offset),
|
||||
.nb_clusters = nb_clusters,
|
||||
|
||||
.keep_old_clusters = keep_old_clusters,
|
||||
|
||||
.cow_start = {
|
||||
.offset = 0,
|
||||
.nb_bytes = offset_into_cluster(s, guest_offset),
|
||||
@@ -1574,25 +1472,24 @@ static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||
* but rather fall through to the backing file.
|
||||
*/
|
||||
switch (qcow2_get_cluster_type(old_l2_entry)) {
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
if (full_discard || !bs->backing) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
if (full_discard || !bs->backing) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
if (!full_discard) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case QCOW2_CLUSTER_ZERO:
|
||||
if (!full_discard) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
break;
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
break;
|
||||
|
||||
default:
|
||||
abort();
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
/* First remove L2 entries */
|
||||
@@ -1612,36 +1509,37 @@ static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||
return nb_clusters;
|
||||
}
|
||||
|
||||
int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, enum qcow2_discard_type type,
|
||||
bool full_discard)
|
||||
int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset,
|
||||
int nb_sectors, enum qcow2_discard_type type, bool full_discard)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t end_offset = offset + bytes;
|
||||
uint64_t end_offset;
|
||||
uint64_t nb_clusters;
|
||||
int64_t cleared;
|
||||
int ret;
|
||||
|
||||
/* Caller must pass aligned values, except at image end */
|
||||
assert(QEMU_IS_ALIGNED(offset, s->cluster_size));
|
||||
assert(QEMU_IS_ALIGNED(end_offset, s->cluster_size) ||
|
||||
end_offset == bs->total_sectors << BDRV_SECTOR_BITS);
|
||||
end_offset = offset + (nb_sectors << BDRV_SECTOR_BITS);
|
||||
|
||||
nb_clusters = size_to_clusters(s, bytes);
|
||||
/* Round start up and end down */
|
||||
offset = align_offset(offset, s->cluster_size);
|
||||
end_offset = start_of_cluster(s, end_offset);
|
||||
|
||||
if (offset > end_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
nb_clusters = size_to_clusters(s, end_offset - offset);
|
||||
|
||||
s->cache_discards = true;
|
||||
|
||||
/* Each L2 table is handled by its own loop iteration */
|
||||
while (nb_clusters > 0) {
|
||||
cleared = discard_single_l2(bs, offset, nb_clusters, type,
|
||||
full_discard);
|
||||
if (cleared < 0) {
|
||||
ret = cleared;
|
||||
ret = discard_single_l2(bs, offset, nb_clusters, type, full_discard);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nb_clusters -= cleared;
|
||||
offset += (cleared * s->cluster_size);
|
||||
nb_clusters -= ret;
|
||||
offset += (ret * s->cluster_size);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
@@ -1658,14 +1556,13 @@ fail:
|
||||
* clusters.
|
||||
*/
|
||||
static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t nb_clusters, int flags)
|
||||
uint64_t nb_clusters)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t *l2_table;
|
||||
int l2_index;
|
||||
int ret;
|
||||
int i;
|
||||
bool unmap = !!(flags & BDRV_REQ_MAY_UNMAP);
|
||||
|
||||
ret = get_cluster_table(bs, offset, &l2_table, &l2_index);
|
||||
if (ret < 0) {
|
||||
@@ -1678,22 +1575,12 @@ static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||
|
||||
for (i = 0; i < nb_clusters; i++) {
|
||||
uint64_t old_offset;
|
||||
QCow2ClusterType cluster_type;
|
||||
|
||||
old_offset = be64_to_cpu(l2_table[l2_index + i]);
|
||||
|
||||
/*
|
||||
* Minimize L2 changes if the cluster already reads back as
|
||||
* zeroes with correct allocation.
|
||||
*/
|
||||
cluster_type = qcow2_get_cluster_type(old_offset);
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN ||
|
||||
(cluster_type == QCOW2_CLUSTER_ZERO_ALLOC && !unmap)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Update L2 entries */
|
||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
||||
if (cluster_type == QCOW2_CLUSTER_COMPRESSED || unmap) {
|
||||
if (old_offset & QCOW_OFLAG_COMPRESSED) {
|
||||
l2_table[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||
qcow2_free_any_clusters(bs, old_offset, 1, QCOW2_DISCARD_REQUEST);
|
||||
} else {
|
||||
@@ -1706,39 +1593,30 @@ static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||
return nb_clusters;
|
||||
}
|
||||
|
||||
int qcow2_cluster_zeroize(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, int flags)
|
||||
int qcow2_zero_clusters(BlockDriverState *bs, uint64_t offset, int nb_sectors)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t end_offset = offset + bytes;
|
||||
uint64_t nb_clusters;
|
||||
int64_t cleared;
|
||||
int ret;
|
||||
|
||||
/* Caller must pass aligned values, except at image end */
|
||||
assert(QEMU_IS_ALIGNED(offset, s->cluster_size));
|
||||
assert(QEMU_IS_ALIGNED(end_offset, s->cluster_size) ||
|
||||
end_offset == bs->total_sectors << BDRV_SECTOR_BITS);
|
||||
|
||||
/* The zero flag is only supported by version 3 and newer */
|
||||
if (s->qcow_version < 3) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Each L2 table is handled by its own loop iteration */
|
||||
nb_clusters = size_to_clusters(s, bytes);
|
||||
nb_clusters = size_to_clusters(s, nb_sectors << BDRV_SECTOR_BITS);
|
||||
|
||||
s->cache_discards = true;
|
||||
|
||||
while (nb_clusters > 0) {
|
||||
cleared = zero_single_l2(bs, offset, nb_clusters, flags);
|
||||
if (cleared < 0) {
|
||||
ret = cleared;
|
||||
ret = zero_single_l2(bs, offset, nb_clusters);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nb_clusters -= cleared;
|
||||
offset += (cleared * s->cluster_size);
|
||||
nb_clusters -= ret;
|
||||
offset += (ret * s->cluster_size);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
@@ -1822,14 +1700,14 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||
for (j = 0; j < s->l2_size; j++) {
|
||||
uint64_t l2_entry = be64_to_cpu(l2_table[j]);
|
||||
int64_t offset = l2_entry & L2E_OFFSET_MASK;
|
||||
QCow2ClusterType cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||
int cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||
bool preallocated = offset != 0;
|
||||
|
||||
if (cluster_type != QCOW2_CLUSTER_ZERO_PLAIN &&
|
||||
cluster_type != QCOW2_CLUSTER_ZERO_ALLOC) {
|
||||
if (cluster_type != QCOW2_CLUSTER_ZERO) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
if (!preallocated) {
|
||||
if (!bs->backing) {
|
||||
/* not backed; therefore we can simply deallocate the
|
||||
* cluster */
|
||||
@@ -1860,12 +1738,11 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||
}
|
||||
|
||||
if (offset_into_cluster(s, offset)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1,
|
||||
"Cluster allocation offset "
|
||||
qcow2_signal_corruption(bs, true, -1, -1, "Data cluster offset "
|
||||
"%#" PRIx64 " unaligned (L2 offset: %#"
|
||||
PRIx64 ", L2 index: %#x)", offset,
|
||||
l2_offset, j);
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
if (!preallocated) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
@@ -1875,7 +1752,7 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||
|
||||
ret = qcow2_pre_write_overlap_check(bs, 0, offset, s->cluster_size);
|
||||
if (ret < 0) {
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
if (!preallocated) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
@@ -1884,7 +1761,7 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||
|
||||
ret = bdrv_pwrite_zeroes(bs->file, offset, s->cluster_size, 0);
|
||||
if (ret < 0) {
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
if (!preallocated) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
|
||||
+198
-331
@@ -83,16 +83,6 @@ static Qcow2SetRefcountFunc *const set_refcount_funcs[] = {
|
||||
/*********************************************************/
|
||||
/* refcount handling */
|
||||
|
||||
static void update_max_refcount_table_index(BDRVQcow2State *s)
|
||||
{
|
||||
unsigned i = s->refcount_table_size - 1;
|
||||
while (i > 0 && (s->refcount_table[i] & REFT_OFFSET_MASK) == 0) {
|
||||
i--;
|
||||
}
|
||||
/* Set s->max_refcount_table_index to the index of the last used entry */
|
||||
s->max_refcount_table_index = i;
|
||||
}
|
||||
|
||||
int qcow2_refcount_init(BlockDriverState *bs)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
@@ -121,7 +111,6 @@ int qcow2_refcount_init(BlockDriverState *bs)
|
||||
}
|
||||
for(i = 0; i < s->refcount_table_size; i++)
|
||||
be64_to_cpus(&s->refcount_table[i]);
|
||||
update_max_refcount_table_index(s);
|
||||
}
|
||||
return 0;
|
||||
fail:
|
||||
@@ -281,6 +270,25 @@ int qcow2_get_refcount(BlockDriverState *bs, int64_t cluster_index,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rounds the refcount table size up to avoid growing the table for each single
|
||||
* refcount block that is allocated.
|
||||
*/
|
||||
static unsigned int next_refcount_table_size(BDRVQcow2State *s,
|
||||
unsigned int min_size)
|
||||
{
|
||||
unsigned int min_clusters = (min_size >> (s->cluster_bits - 3)) + 1;
|
||||
unsigned int refcount_table_clusters =
|
||||
MAX(1, s->refcount_table_size >> (s->cluster_bits - 3));
|
||||
|
||||
while (min_clusters > refcount_table_clusters) {
|
||||
refcount_table_clusters = (refcount_table_clusters * 3 + 1) / 2;
|
||||
}
|
||||
|
||||
return refcount_table_clusters << (s->cluster_bits - 3);
|
||||
}
|
||||
|
||||
|
||||
/* Checks if two offsets are described by the same refcount block */
|
||||
static int in_same_refcount_block(BDRVQcow2State *s, uint64_t offset_a,
|
||||
uint64_t offset_b)
|
||||
@@ -302,7 +310,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
unsigned int refcount_table_index;
|
||||
int64_t ret;
|
||||
int ret;
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC);
|
||||
|
||||
@@ -377,7 +385,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block,
|
||||
refcount_block);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_block;
|
||||
}
|
||||
|
||||
memset(*refcount_block, 0, s->cluster_size);
|
||||
@@ -392,12 +400,12 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
ret = update_refcount(bs, new_block, s->cluster_size, 1, false,
|
||||
QCOW2_DISCARD_NEVER);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_block;
|
||||
}
|
||||
|
||||
ret = qcow2_cache_flush(bs, s->refcount_block_cache);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_block;
|
||||
}
|
||||
|
||||
/* Initialize the new refcount block only after updating its refcount,
|
||||
@@ -405,7 +413,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block,
|
||||
refcount_block);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_block;
|
||||
}
|
||||
|
||||
memset(*refcount_block, 0, s->cluster_size);
|
||||
@@ -416,7 +424,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache, *refcount_block);
|
||||
ret = qcow2_cache_flush(bs, s->refcount_block_cache);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_block;
|
||||
}
|
||||
|
||||
/* If the refcount table is big enough, just hook the block up there */
|
||||
@@ -427,14 +435,10 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
s->refcount_table_offset + refcount_table_index * sizeof(uint64_t),
|
||||
&data64, sizeof(data64));
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_block;
|
||||
}
|
||||
|
||||
s->refcount_table[refcount_table_index] = new_block;
|
||||
/* If there's a hole in s->refcount_table then it can happen
|
||||
* that refcount_table_index < s->max_refcount_table_index */
|
||||
s->max_refcount_table_index =
|
||||
MAX(s->max_refcount_table_index, refcount_table_index);
|
||||
|
||||
/* The new refcount block may be where the caller intended to put its
|
||||
* data, so let it restart the search. */
|
||||
@@ -471,201 +475,74 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
(new_block >> s->cluster_bits) + 1),
|
||||
s->refcount_block_size);
|
||||
|
||||
if (blocks_used > QCOW_MAX_REFTABLE_SIZE / sizeof(uint64_t)) {
|
||||
return -EFBIG;
|
||||
}
|
||||
|
||||
/* And now we need at least one block more for the new metadata */
|
||||
uint64_t table_size = next_refcount_table_size(s, blocks_used + 1);
|
||||
uint64_t last_table_size;
|
||||
uint64_t blocks_clusters;
|
||||
do {
|
||||
uint64_t table_clusters =
|
||||
size_to_clusters(s, table_size * sizeof(uint64_t));
|
||||
blocks_clusters = 1 +
|
||||
DIV_ROUND_UP(table_clusters, s->refcount_block_size);
|
||||
uint64_t meta_clusters = table_clusters + blocks_clusters;
|
||||
|
||||
last_table_size = table_size;
|
||||
table_size = next_refcount_table_size(s, blocks_used +
|
||||
DIV_ROUND_UP(meta_clusters, s->refcount_block_size));
|
||||
|
||||
} while (last_table_size != table_size);
|
||||
|
||||
#ifdef DEBUG_ALLOC2
|
||||
fprintf(stderr, "qcow2: Grow refcount table %" PRId32 " => %" PRId64 "\n",
|
||||
s->refcount_table_size, table_size);
|
||||
#endif
|
||||
|
||||
/* Create the new refcount table and blocks */
|
||||
uint64_t meta_offset = (blocks_used * s->refcount_block_size) *
|
||||
s->cluster_size;
|
||||
uint64_t table_offset = meta_offset + blocks_clusters * s->cluster_size;
|
||||
uint64_t *new_table = g_try_new0(uint64_t, table_size);
|
||||
void *new_blocks = g_try_malloc0(blocks_clusters * s->cluster_size);
|
||||
|
||||
ret = qcow2_refcount_area(bs, meta_offset, 0, false,
|
||||
refcount_table_index, new_block);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = load_refcount_block(bs, new_block, refcount_block);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* If we were trying to do the initial refcount update for some cluster
|
||||
* allocation, we might have used the same clusters to store newly
|
||||
* allocated metadata. Make the caller search some new space. */
|
||||
return -EAGAIN;
|
||||
|
||||
fail:
|
||||
if (*refcount_block != NULL) {
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, refcount_block);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Starting at @start_offset, this function creates new self-covering refcount
|
||||
* structures: A new refcount table and refcount blocks which cover all of
|
||||
* themselves, and a number of @additional_clusters beyond their end.
|
||||
* @start_offset must be at the end of the image file, that is, there must be
|
||||
* only empty space beyond it.
|
||||
* If @exact_size is false, the refcount table will have 50 % more entries than
|
||||
* necessary so it will not need to grow again soon.
|
||||
* If @new_refblock_offset is not zero, it contains the offset of a refcount
|
||||
* block that should be entered into the new refcount table at index
|
||||
* @new_refblock_index.
|
||||
*
|
||||
* Returns: The offset after the new refcount structures (i.e. where the
|
||||
* @additional_clusters may be placed) on success, -errno on error.
|
||||
*/
|
||||
int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
|
||||
uint64_t additional_clusters, bool exact_size,
|
||||
int new_refblock_index,
|
||||
uint64_t new_refblock_offset)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t total_refblock_count_u64, additional_refblock_count;
|
||||
int total_refblock_count, table_size, area_reftable_index, table_clusters;
|
||||
int i;
|
||||
uint64_t table_offset, block_offset, end_offset;
|
||||
int ret;
|
||||
uint64_t *new_table;
|
||||
|
||||
assert(!(start_offset % s->cluster_size));
|
||||
|
||||
qcow2_refcount_metadata_size(start_offset / s->cluster_size +
|
||||
additional_clusters,
|
||||
s->cluster_size, s->refcount_order,
|
||||
!exact_size, &total_refblock_count_u64);
|
||||
if (total_refblock_count_u64 > QCOW_MAX_REFTABLE_SIZE) {
|
||||
return -EFBIG;
|
||||
}
|
||||
total_refblock_count = total_refblock_count_u64;
|
||||
|
||||
/* Index in the refcount table of the first refcount block to cover the area
|
||||
* of refcount structures we are about to create; we know that
|
||||
* @total_refblock_count can cover @start_offset, so this will definitely
|
||||
* fit into an int. */
|
||||
area_reftable_index = (start_offset / s->cluster_size) /
|
||||
s->refcount_block_size;
|
||||
|
||||
if (exact_size) {
|
||||
table_size = total_refblock_count;
|
||||
} else {
|
||||
table_size = total_refblock_count +
|
||||
DIV_ROUND_UP(total_refblock_count, 2);
|
||||
}
|
||||
/* The qcow2 file can only store the reftable size in number of clusters */
|
||||
table_size = ROUND_UP(table_size, s->cluster_size / sizeof(uint64_t));
|
||||
table_clusters = (table_size * sizeof(uint64_t)) / s->cluster_size;
|
||||
|
||||
if (table_size > QCOW_MAX_REFTABLE_SIZE) {
|
||||
return -EFBIG;
|
||||
}
|
||||
|
||||
new_table = g_try_new0(uint64_t, table_size);
|
||||
|
||||
assert(table_size > 0);
|
||||
if (new_table == NULL) {
|
||||
assert(table_size > 0 && blocks_clusters > 0);
|
||||
if (new_table == NULL || new_blocks == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
goto fail_table;
|
||||
}
|
||||
|
||||
/* Fill the new refcount table */
|
||||
if (table_size > s->max_refcount_table_index) {
|
||||
/* We're actually growing the reftable */
|
||||
memcpy(new_table, s->refcount_table,
|
||||
(s->max_refcount_table_index + 1) * sizeof(uint64_t));
|
||||
} else {
|
||||
/* Improbable case: We're shrinking the reftable. However, the caller
|
||||
* has assured us that there is only empty space beyond @start_offset,
|
||||
* so we can simply drop all of the refblocks that won't fit into the
|
||||
* new reftable. */
|
||||
memcpy(new_table, s->refcount_table, table_size * sizeof(uint64_t));
|
||||
memcpy(new_table, s->refcount_table,
|
||||
s->refcount_table_size * sizeof(uint64_t));
|
||||
new_table[refcount_table_index] = new_block;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < blocks_clusters; i++) {
|
||||
new_table[blocks_used + i] = meta_offset + (i * s->cluster_size);
|
||||
}
|
||||
|
||||
if (new_refblock_offset) {
|
||||
assert(new_refblock_index < total_refblock_count);
|
||||
new_table[new_refblock_index] = new_refblock_offset;
|
||||
/* Fill the refcount blocks */
|
||||
uint64_t table_clusters = size_to_clusters(s, table_size * sizeof(uint64_t));
|
||||
int block = 0;
|
||||
for (i = 0; i < table_clusters + blocks_clusters; i++) {
|
||||
s->set_refcount(new_blocks, block++, 1);
|
||||
}
|
||||
|
||||
/* Count how many new refblocks we have to create */
|
||||
additional_refblock_count = 0;
|
||||
for (i = area_reftable_index; i < total_refblock_count; i++) {
|
||||
if (!new_table[i]) {
|
||||
additional_refblock_count++;
|
||||
}
|
||||
}
|
||||
|
||||
table_offset = start_offset + additional_refblock_count * s->cluster_size;
|
||||
end_offset = table_offset + table_clusters * s->cluster_size;
|
||||
|
||||
/* Fill the refcount blocks, and create new ones, if necessary */
|
||||
block_offset = start_offset;
|
||||
for (i = area_reftable_index; i < total_refblock_count; i++) {
|
||||
void *refblock_data;
|
||||
uint64_t first_offset_covered;
|
||||
|
||||
/* Reuse an existing refblock if possible, create a new one otherwise */
|
||||
if (new_table[i]) {
|
||||
ret = qcow2_cache_get(bs, s->refcount_block_cache, new_table[i],
|
||||
&refblock_data);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
ret = qcow2_cache_get_empty(bs, s->refcount_block_cache,
|
||||
block_offset, &refblock_data);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
memset(refblock_data, 0, s->cluster_size);
|
||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache,
|
||||
refblock_data);
|
||||
|
||||
new_table[i] = block_offset;
|
||||
block_offset += s->cluster_size;
|
||||
}
|
||||
|
||||
/* First host offset covered by this refblock */
|
||||
first_offset_covered = (uint64_t)i * s->refcount_block_size *
|
||||
s->cluster_size;
|
||||
if (first_offset_covered < end_offset) {
|
||||
int j, end_index;
|
||||
|
||||
/* Set the refcount of all of the new refcount structures to 1 */
|
||||
|
||||
if (first_offset_covered < start_offset) {
|
||||
assert(i == area_reftable_index);
|
||||
j = (start_offset - first_offset_covered) / s->cluster_size;
|
||||
assert(j < s->refcount_block_size);
|
||||
} else {
|
||||
j = 0;
|
||||
}
|
||||
|
||||
end_index = MIN((end_offset - first_offset_covered) /
|
||||
s->cluster_size,
|
||||
s->refcount_block_size);
|
||||
|
||||
for (; j < end_index; j++) {
|
||||
/* The caller guaranteed us this space would be empty */
|
||||
assert(s->get_refcount(refblock_data, j) == 0);
|
||||
s->set_refcount(refblock_data, j, 1);
|
||||
}
|
||||
|
||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache,
|
||||
refblock_data);
|
||||
}
|
||||
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock_data);
|
||||
}
|
||||
|
||||
assert(block_offset == table_offset);
|
||||
|
||||
/* Write refcount blocks to disk */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_WRITE_BLOCKS);
|
||||
ret = qcow2_cache_flush(bs, s->refcount_block_cache);
|
||||
ret = bdrv_pwrite_sync(bs->file, meta_offset, new_blocks,
|
||||
blocks_clusters * s->cluster_size);
|
||||
g_free(new_blocks);
|
||||
new_blocks = NULL;
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_table;
|
||||
}
|
||||
|
||||
/* Write refcount table to disk */
|
||||
for (i = 0; i < total_refblock_count; i++) {
|
||||
for(i = 0; i < table_size; i++) {
|
||||
cpu_to_be64s(&new_table[i]);
|
||||
}
|
||||
|
||||
@@ -673,10 +550,10 @@ int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
|
||||
ret = bdrv_pwrite_sync(bs->file, table_offset, new_table,
|
||||
table_size * sizeof(uint64_t));
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_table;
|
||||
}
|
||||
|
||||
for (i = 0; i < total_refblock_count; i++) {
|
||||
for(i = 0; i < table_size; i++) {
|
||||
be64_to_cpus(&new_table[i]);
|
||||
}
|
||||
|
||||
@@ -692,7 +569,7 @@ int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
|
||||
offsetof(QCowHeader, refcount_table_offset),
|
||||
&data, sizeof(data));
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
goto fail_table;
|
||||
}
|
||||
|
||||
/* And switch it in memory */
|
||||
@@ -703,16 +580,28 @@ int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
|
||||
s->refcount_table = new_table;
|
||||
s->refcount_table_size = table_size;
|
||||
s->refcount_table_offset = table_offset;
|
||||
update_max_refcount_table_index(s);
|
||||
|
||||
/* Free old table. */
|
||||
qcow2_free_clusters(bs, old_table_offset, old_table_size * sizeof(uint64_t),
|
||||
QCOW2_DISCARD_OTHER);
|
||||
|
||||
return end_offset;
|
||||
ret = load_refcount_block(bs, new_block, refcount_block);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
fail:
|
||||
/* If we were trying to do the initial refcount update for some cluster
|
||||
* allocation, we might have used the same clusters to store newly
|
||||
* allocated metadata. Make the caller search some new space. */
|
||||
return -EAGAIN;
|
||||
|
||||
fail_table:
|
||||
g_free(new_blocks);
|
||||
g_free(new_table);
|
||||
fail_block:
|
||||
if (*refcount_block != NULL) {
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, refcount_block);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1123,17 +1012,18 @@ void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
||||
}
|
||||
break;
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
if (offset_into_cluster(s, l2_entry & L2E_OFFSET_MASK)) {
|
||||
qcow2_signal_corruption(bs, false, -1, -1,
|
||||
"Cannot free unaligned cluster %#llx",
|
||||
l2_entry & L2E_OFFSET_MASK);
|
||||
} else {
|
||||
qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
|
||||
nb_clusters << s->cluster_bits, type);
|
||||
case QCOW2_CLUSTER_ZERO:
|
||||
if (l2_entry & L2E_OFFSET_MASK) {
|
||||
if (offset_into_cluster(s, l2_entry & L2E_OFFSET_MASK)) {
|
||||
qcow2_signal_corruption(bs, false, -1, -1,
|
||||
"Cannot free unaligned cluster %#llx",
|
||||
l2_entry & L2E_OFFSET_MASK);
|
||||
} else {
|
||||
qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
|
||||
nb_clusters << s->cluster_bits, type);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
break;
|
||||
default:
|
||||
@@ -1153,9 +1043,9 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||
int64_t l1_table_offset, int l1_size, int addend)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t *l1_table, *l2_table, l2_offset, entry, l1_size2, refcount;
|
||||
uint64_t *l1_table, *l2_table, l2_offset, offset, l1_size2, refcount;
|
||||
bool l1_allocated = false;
|
||||
int64_t old_entry, old_l2_offset;
|
||||
int64_t old_offset, old_l2_offset;
|
||||
int i, j, l1_modified = 0, nb_csectors;
|
||||
int ret;
|
||||
|
||||
@@ -1183,16 +1073,15 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < l1_size; i++) {
|
||||
for(i = 0;i < l1_size; i++)
|
||||
be64_to_cpus(&l1_table[i]);
|
||||
}
|
||||
} else {
|
||||
assert(l1_size == s->l1_size);
|
||||
l1_table = s->l1_table;
|
||||
l1_allocated = false;
|
||||
}
|
||||
|
||||
for (i = 0; i < l1_size; i++) {
|
||||
for(i = 0; i < l1_size; i++) {
|
||||
l2_offset = l1_table[i];
|
||||
if (l2_offset) {
|
||||
old_l2_offset = l2_offset;
|
||||
@@ -1212,79 +1101,81 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (j = 0; j < s->l2_size; j++) {
|
||||
for(j = 0; j < s->l2_size; j++) {
|
||||
uint64_t cluster_index;
|
||||
uint64_t offset;
|
||||
|
||||
entry = be64_to_cpu(l2_table[j]);
|
||||
old_entry = entry;
|
||||
entry &= ~QCOW_OFLAG_COPIED;
|
||||
offset = entry & L2E_OFFSET_MASK;
|
||||
offset = be64_to_cpu(l2_table[j]);
|
||||
old_offset = offset;
|
||||
offset &= ~QCOW_OFLAG_COPIED;
|
||||
|
||||
switch (qcow2_get_cluster_type(entry)) {
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
nb_csectors = ((entry >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
if (addend != 0) {
|
||||
ret = update_refcount(bs,
|
||||
(entry & s->cluster_offset_mask) & ~511,
|
||||
switch (qcow2_get_cluster_type(offset)) {
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
nb_csectors = ((offset >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
if (addend != 0) {
|
||||
ret = update_refcount(bs,
|
||||
(offset & s->cluster_offset_mask) & ~511,
|
||||
nb_csectors * 512, abs(addend), addend < 0,
|
||||
QCOW2_DISCARD_SNAPSHOT);
|
||||
if (ret < 0) {
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
/* compressed clusters are never modified */
|
||||
refcount = 2;
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
case QCOW2_CLUSTER_ZERO:
|
||||
if (offset_into_cluster(s, offset & L2E_OFFSET_MASK)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1, "Data "
|
||||
"cluster offset %#llx "
|
||||
"unaligned (L2 offset: %#"
|
||||
PRIx64 ", L2 index: %#x)",
|
||||
offset & L2E_OFFSET_MASK,
|
||||
l2_offset, j);
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
/* compressed clusters are never modified */
|
||||
refcount = 2;
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
if (offset_into_cluster(s, offset)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1, "Cluster "
|
||||
"allocation offset %#" PRIx64
|
||||
" unaligned (L2 offset: %#"
|
||||
PRIx64 ", L2 index: %#x)",
|
||||
offset, l2_offset, j);
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cluster_index = offset >> s->cluster_bits;
|
||||
assert(cluster_index);
|
||||
if (addend != 0) {
|
||||
ret = qcow2_update_cluster_refcount(bs,
|
||||
cluster_index = (offset & L2E_OFFSET_MASK) >> s->cluster_bits;
|
||||
if (!cluster_index) {
|
||||
/* unallocated */
|
||||
refcount = 0;
|
||||
break;
|
||||
}
|
||||
if (addend != 0) {
|
||||
ret = qcow2_update_cluster_refcount(bs,
|
||||
cluster_index, abs(addend), addend < 0,
|
||||
QCOW2_DISCARD_SNAPSHOT);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = qcow2_get_refcount(bs, cluster_index, &refcount);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
ret = qcow2_get_refcount(bs, cluster_index, &refcount);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
refcount = 0;
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
refcount = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
abort();
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
if (refcount == 1) {
|
||||
entry |= QCOW_OFLAG_COPIED;
|
||||
offset |= QCOW_OFLAG_COPIED;
|
||||
}
|
||||
if (entry != old_entry) {
|
||||
if (offset != old_offset) {
|
||||
if (addend > 0) {
|
||||
qcow2_cache_set_dependency(bs, s->l2_table_cache,
|
||||
s->refcount_block_cache);
|
||||
}
|
||||
l2_table[j] = cpu_to_be64(entry);
|
||||
l2_table[j] = cpu_to_be64(offset);
|
||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache,
|
||||
l2_table);
|
||||
}
|
||||
@@ -1418,10 +1309,11 @@ static int realloc_refcount_array(BDRVQcow2State *s, void **array,
|
||||
*
|
||||
* Modifies the number of errors in res.
|
||||
*/
|
||||
int qcow2_inc_refcounts_imrt(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
void **refcount_table,
|
||||
int64_t *refcount_table_size,
|
||||
int64_t offset, int64_t size)
|
||||
static int inc_refcounts(BlockDriverState *bs,
|
||||
BdrvCheckResult *res,
|
||||
void **refcount_table,
|
||||
int64_t *refcount_table_size,
|
||||
int64_t offset, int64_t size)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t start, last, cluster_offset, k, refcount;
|
||||
@@ -1514,9 +1406,8 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
nb_csectors = ((l2_entry >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
l2_entry &= s->cluster_offset_mask;
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res,
|
||||
refcount_table, refcount_table_size,
|
||||
l2_entry & ~511, nb_csectors * 512);
|
||||
ret = inc_refcounts(bs, res, refcount_table, refcount_table_size,
|
||||
l2_entry & ~511, nb_csectors * 512);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
@@ -1534,7 +1425,12 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
}
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
case QCOW2_CLUSTER_ZERO:
|
||||
if ((l2_entry & L2E_OFFSET_MASK) == 0) {
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
{
|
||||
uint64_t offset = l2_entry & L2E_OFFSET_MASK;
|
||||
@@ -1549,9 +1445,8 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
}
|
||||
|
||||
/* Mark cluster as used */
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res,
|
||||
refcount_table, refcount_table_size,
|
||||
offset, s->cluster_size);
|
||||
ret = inc_refcounts(bs, res, refcount_table, refcount_table_size,
|
||||
offset, s->cluster_size);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
@@ -1565,7 +1460,6 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
break;
|
||||
}
|
||||
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
break;
|
||||
|
||||
@@ -1604,8 +1498,8 @@ static int check_refcounts_l1(BlockDriverState *bs,
|
||||
l1_size2 = l1_size * sizeof(uint64_t);
|
||||
|
||||
/* Mark L1 table as used */
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, refcount_table_size,
|
||||
l1_table_offset, l1_size2);
|
||||
ret = inc_refcounts(bs, res, refcount_table, refcount_table_size,
|
||||
l1_table_offset, l1_size2);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
@@ -1634,9 +1528,8 @@ static int check_refcounts_l1(BlockDriverState *bs,
|
||||
if (l2_offset) {
|
||||
/* Mark L2 table as used */
|
||||
l2_offset &= L1E_OFFSET_MASK;
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res,
|
||||
refcount_table, refcount_table_size,
|
||||
l2_offset, s->cluster_size);
|
||||
ret = inc_refcounts(bs, res, refcount_table, refcount_table_size,
|
||||
l2_offset, s->cluster_size);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
@@ -1729,10 +1622,10 @@ static int check_oflag_copied(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
for (j = 0; j < s->l2_size; j++) {
|
||||
uint64_t l2_entry = be64_to_cpu(l2_table[j]);
|
||||
uint64_t data_offset = l2_entry & L2E_OFFSET_MASK;
|
||||
QCow2ClusterType cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||
int cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||
|
||||
if (cluster_type == QCOW2_CLUSTER_NORMAL ||
|
||||
cluster_type == QCOW2_CLUSTER_ZERO_ALLOC) {
|
||||
if ((cluster_type == QCOW2_CLUSTER_NORMAL) ||
|
||||
((cluster_type == QCOW2_CLUSTER_ZERO) && (data_offset != 0))) {
|
||||
ret = qcow2_get_refcount(bs,
|
||||
data_offset >> s->cluster_bits,
|
||||
&refcount);
|
||||
@@ -1819,17 +1712,14 @@ static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
|
||||
if (fix & BDRV_FIX_ERRORS) {
|
||||
int64_t new_nb_clusters;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (offset > INT64_MAX - s->cluster_size) {
|
||||
ret = -EINVAL;
|
||||
goto resize_fail;
|
||||
}
|
||||
|
||||
ret = bdrv_truncate(bs->file, offset + s->cluster_size,
|
||||
PREALLOC_MODE_OFF, &local_err);
|
||||
ret = bdrv_truncate(bs->file->bs, offset + s->cluster_size);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
goto resize_fail;
|
||||
}
|
||||
size = bdrv_getlength(bs->file->bs);
|
||||
@@ -1854,15 +1744,14 @@ static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
}
|
||||
|
||||
res->corruptions_fixed++;
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res,
|
||||
refcount_table, nb_clusters,
|
||||
offset, s->cluster_size);
|
||||
ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
|
||||
offset, s->cluster_size);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
/* No need to check whether the refcount is now greater than 1:
|
||||
* This area was just allocated and zeroed, so it can only be
|
||||
* exactly 1 after qcow2_inc_refcounts_imrt() */
|
||||
* exactly 1 after inc_refcounts() */
|
||||
continue;
|
||||
|
||||
resize_fail:
|
||||
@@ -1877,8 +1766,8 @@ resize_fail:
|
||||
}
|
||||
|
||||
if (offset != 0) {
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters,
|
||||
offset, s->cluster_size);
|
||||
ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
|
||||
offset, s->cluster_size);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@@ -1918,8 +1807,8 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
}
|
||||
|
||||
/* header */
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters,
|
||||
0, s->cluster_size);
|
||||
ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
|
||||
0, s->cluster_size);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@@ -1940,32 +1829,16 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters,
|
||||
s->snapshots_offset, s->snapshots_size);
|
||||
ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
|
||||
s->snapshots_offset, s->snapshots_size);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* refcount data */
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters,
|
||||
s->refcount_table_offset,
|
||||
s->refcount_table_size * sizeof(uint64_t));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* encryption */
|
||||
if (s->crypto_header.length) {
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters,
|
||||
s->crypto_header.offset,
|
||||
s->crypto_header.length);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* bitmaps */
|
||||
ret = qcow2_check_bitmaps_refcounts(bs, res, refcount_table, nb_clusters);
|
||||
ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
|
||||
s->refcount_table_offset,
|
||||
s->refcount_table_size * sizeof(uint64_t));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@@ -2189,8 +2062,6 @@ write_refblocks:
|
||||
* this will leak that range, but we can easily fix that by running
|
||||
* a leak-fixing check after this rebuild operation */
|
||||
reftable_offset = -1;
|
||||
} else {
|
||||
assert(on_disk_reftable);
|
||||
}
|
||||
on_disk_reftable[refblock_index] = refblock_offset;
|
||||
|
||||
@@ -2260,6 +2131,8 @@ write_refblocks:
|
||||
goto write_refblocks;
|
||||
}
|
||||
|
||||
assert(on_disk_reftable);
|
||||
|
||||
for (refblock_index = 0; refblock_index < reftable_size; refblock_index++) {
|
||||
cpu_to_be64s(&on_disk_reftable[refblock_index]);
|
||||
}
|
||||
@@ -2298,7 +2171,6 @@ write_refblocks:
|
||||
s->refcount_table = on_disk_reftable;
|
||||
s->refcount_table_offset = reftable_offset;
|
||||
s->refcount_table_size = reftable_size;
|
||||
update_max_refcount_table_index(s);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -2511,11 +2383,7 @@ int qcow2_check_metadata_overlap(BlockDriverState *bs, int ign, int64_t offset,
|
||||
}
|
||||
|
||||
if ((chk & QCOW2_OL_REFCOUNT_BLOCK) && s->refcount_table) {
|
||||
unsigned last_entry = s->max_refcount_table_index;
|
||||
assert(last_entry < s->refcount_table_size);
|
||||
assert(last_entry + 1 == s->refcount_table_size ||
|
||||
(s->refcount_table[last_entry + 1] & REFT_OFFSET_MASK) == 0);
|
||||
for (i = 0; i <= last_entry; i++) {
|
||||
for (i = 0; i < s->refcount_table_size; i++) {
|
||||
if ((s->refcount_table[i] & REFT_OFFSET_MASK) &&
|
||||
overlaps_with(s->refcount_table[i] & REFT_OFFSET_MASK,
|
||||
s->cluster_size)) {
|
||||
@@ -3003,7 +2871,6 @@ int qcow2_change_refcount_order(BlockDriverState *bs, int refcount_order,
|
||||
/* Now update the rest of the in-memory information */
|
||||
old_reftable = s->refcount_table;
|
||||
s->refcount_table = new_reftable;
|
||||
update_max_refcount_table_index(s);
|
||||
|
||||
s->refcount_bits = 1 << refcount_order;
|
||||
s->refcount_max = UINT64_C(1) << (s->refcount_bits - 1);
|
||||
|
||||
@@ -440,9 +440,10 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
|
||||
|
||||
/* The VM state isn't needed any more in the active L1 table; in fact, it
|
||||
* hurts by causing expensive COW for the next snapshot. */
|
||||
qcow2_cluster_discard(bs, qcow2_vm_state_offset(s),
|
||||
align_offset(sn->vm_state_size, s->cluster_size),
|
||||
QCOW2_DISCARD_NEVER, false);
|
||||
qcow2_discard_clusters(bs, qcow2_vm_state_offset(s),
|
||||
align_offset(sn->vm_state_size, s->cluster_size)
|
||||
>> BDRV_SECTOR_BITS,
|
||||
QCOW2_DISCARD_NEVER, false);
|
||||
|
||||
#ifdef DEBUG_ALLOC
|
||||
{
|
||||
|
||||
+360
-1271
File diff suppressed because it is too large.
Load diff
+17
-92
@@ -25,7 +25,7 @@
|
||||
#ifndef BLOCK_QCOW2_H
|
||||
#define BLOCK_QCOW2_H
|
||||
|
||||
#include "crypto/block.h"
|
||||
#include "crypto/cipher.h"
|
||||
#include "qemu/coroutine.h"
|
||||
|
||||
//#define DEBUG_ALLOC
|
||||
@@ -36,7 +36,6 @@
|
||||
|
||||
#define QCOW_CRYPT_NONE 0
|
||||
#define QCOW_CRYPT_AES 1
|
||||
#define QCOW_CRYPT_LUKS 2
|
||||
|
||||
#define QCOW_MAX_CRYPT_CLUSTERS 32
|
||||
#define QCOW_MAX_SNAPSHOTS 65536
|
||||
@@ -53,10 +52,6 @@
|
||||
* space for snapshot names and IDs */
|
||||
#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS)
|
||||
|
||||
/* Bitmap header extension constraints */
|
||||
#define QCOW2_MAX_BITMAPS 65535
|
||||
#define QCOW2_MAX_BITMAP_DIRECTORY_SIZE (1024 * QCOW2_MAX_BITMAPS)
|
||||
|
||||
/* indicate that the refcount of the referenced cluster is exactly one. */
|
||||
#define QCOW_OFLAG_COPIED (1ULL << 63)
|
||||
/* indicate that the cluster is compressed (they never have the copied flag) */
|
||||
@@ -168,11 +163,6 @@ typedef struct QCowSnapshot {
|
||||
struct Qcow2Cache;
|
||||
typedef struct Qcow2Cache Qcow2Cache;
|
||||
|
||||
typedef struct Qcow2CryptoHeaderExtension {
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
} QEMU_PACKED Qcow2CryptoHeaderExtension;
|
||||
|
||||
typedef struct Qcow2UnknownHeaderExtension {
|
||||
uint32_t magic;
|
||||
uint32_t len;
|
||||
@@ -205,14 +195,6 @@ enum {
|
||||
QCOW2_COMPAT_FEAT_MASK = QCOW2_COMPAT_LAZY_REFCOUNTS,
|
||||
};
|
||||
|
||||
/* Autoclear feature bits */
|
||||
enum {
|
||||
QCOW2_AUTOCLEAR_BITMAPS_BITNR = 0,
|
||||
QCOW2_AUTOCLEAR_BITMAPS = 1 << QCOW2_AUTOCLEAR_BITMAPS_BITNR,
|
||||
|
||||
QCOW2_AUTOCLEAR_MASK = QCOW2_AUTOCLEAR_BITMAPS,
|
||||
};
|
||||
|
||||
enum qcow2_discard_type {
|
||||
QCOW2_DISCARD_NEVER = 0,
|
||||
QCOW2_DISCARD_ALWAYS,
|
||||
@@ -240,13 +222,6 @@ typedef uint64_t Qcow2GetRefcountFunc(const void *refcount_array,
|
||||
typedef void Qcow2SetRefcountFunc(void *refcount_array,
|
||||
uint64_t index, uint64_t value);
|
||||
|
||||
typedef struct Qcow2BitmapHeaderExt {
|
||||
uint32_t nb_bitmaps;
|
||||
uint32_t reserved32;
|
||||
uint64_t bitmap_directory_size;
|
||||
uint64_t bitmap_directory_offset;
|
||||
} QEMU_PACKED Qcow2BitmapHeaderExt;
|
||||
|
||||
typedef struct BDRVQcow2State {
|
||||
int cluster_bits;
|
||||
int cluster_size;
|
||||
@@ -276,27 +251,18 @@ typedef struct BDRVQcow2State {
|
||||
uint64_t *refcount_table;
|
||||
uint64_t refcount_table_offset;
|
||||
uint32_t refcount_table_size;
|
||||
uint32_t max_refcount_table_index; /* Last used entry in refcount_table */
|
||||
uint64_t free_cluster_index;
|
||||
uint64_t free_byte_offset;
|
||||
|
||||
CoMutex lock;
|
||||
|
||||
Qcow2CryptoHeaderExtension crypto_header; /* QCow2 header extension */
|
||||
QCryptoBlockOpenOptions *crypto_opts; /* Disk encryption runtime options */
|
||||
QCryptoBlock *crypto; /* Disk encryption format driver */
|
||||
bool crypt_physical_offset; /* Whether to use virtual or physical offset
|
||||
for encryption initialization vector tweak */
|
||||
QCryptoCipher *cipher; /* current cipher, NULL if no key yet */
|
||||
uint32_t crypt_method_header;
|
||||
uint64_t snapshots_offset;
|
||||
int snapshots_size;
|
||||
unsigned int nb_snapshots;
|
||||
QCowSnapshot *snapshots;
|
||||
|
||||
uint32_t nb_bitmaps;
|
||||
uint64_t bitmap_directory_size;
|
||||
uint64_t bitmap_directory_offset;
|
||||
|
||||
int flags;
|
||||
int qcow_version;
|
||||
bool use_lazy_refcounts;
|
||||
@@ -334,10 +300,10 @@ typedef struct Qcow2COWRegion {
|
||||
* Offset of the COW region in bytes from the start of the first cluster
|
||||
* touched by the request.
|
||||
*/
|
||||
unsigned offset;
|
||||
uint64_t offset;
|
||||
|
||||
/** Number of bytes to copy */
|
||||
unsigned nb_bytes;
|
||||
int nb_bytes;
|
||||
} Qcow2COWRegion;
|
||||
|
||||
/**
|
||||
@@ -355,9 +321,6 @@ typedef struct QCowL2Meta
|
||||
/** Number of newly allocated clusters */
|
||||
int nb_clusters;
|
||||
|
||||
/** Do not free the old clusters */
|
||||
bool keep_old_clusters;
|
||||
|
||||
/**
|
||||
* Requests that overlap with this allocation and wait to be restarted
|
||||
* when the allocating request has completed.
|
||||
@@ -376,26 +339,18 @@ typedef struct QCowL2Meta
|
||||
*/
|
||||
Qcow2COWRegion cow_end;
|
||||
|
||||
/**
|
||||
* The I/O vector with the data from the actual guest write request.
|
||||
* If non-NULL, this is meant to be merged together with the data
|
||||
* from @cow_start and @cow_end into one single write operation.
|
||||
*/
|
||||
QEMUIOVector *data_qiov;
|
||||
|
||||
/** Pointer to next L2Meta of the same write request */
|
||||
struct QCowL2Meta *next;
|
||||
|
||||
QLIST_ENTRY(QCowL2Meta) next_in_flight;
|
||||
} QCowL2Meta;
|
||||
|
||||
typedef enum QCow2ClusterType {
|
||||
enum {
|
||||
QCOW2_CLUSTER_UNALLOCATED,
|
||||
QCOW2_CLUSTER_ZERO_PLAIN,
|
||||
QCOW2_CLUSTER_ZERO_ALLOC,
|
||||
QCOW2_CLUSTER_NORMAL,
|
||||
QCOW2_CLUSTER_COMPRESSED,
|
||||
} QCow2ClusterType;
|
||||
QCOW2_CLUSTER_ZERO
|
||||
};
|
||||
|
||||
typedef enum QCow2MetadataOverlap {
|
||||
QCOW2_OL_MAIN_HEADER_BITNR = 0,
|
||||
@@ -484,15 +439,12 @@ static inline uint64_t qcow2_max_refcount_clusters(BDRVQcow2State *s)
|
||||
return QCOW_MAX_REFTABLE_SIZE >> s->cluster_bits;
|
||||
}
|
||||
|
||||
static inline QCow2ClusterType qcow2_get_cluster_type(uint64_t l2_entry)
|
||||
static inline int qcow2_get_cluster_type(uint64_t l2_entry)
|
||||
{
|
||||
if (l2_entry & QCOW_OFLAG_COMPRESSED) {
|
||||
return QCOW2_CLUSTER_COMPRESSED;
|
||||
} else if (l2_entry & QCOW_OFLAG_ZERO) {
|
||||
if (l2_entry & L2E_OFFSET_MASK) {
|
||||
return QCOW2_CLUSTER_ZERO_ALLOC;
|
||||
}
|
||||
return QCOW2_CLUSTER_ZERO_PLAIN;
|
||||
return QCOW2_CLUSTER_ZERO;
|
||||
} else if (!(l2_entry & L2E_OFFSET_MASK)) {
|
||||
return QCOW2_CLUSTER_UNALLOCATED;
|
||||
} else {
|
||||
@@ -521,14 +473,12 @@ static inline uint64_t refcount_diff(uint64_t r1, uint64_t r2)
|
||||
return r1 > r2 ? r1 - r2 : r2 - r1;
|
||||
}
|
||||
|
||||
// FIXME Need qcow2_ prefix to global functions
|
||||
|
||||
/* qcow2.c functions */
|
||||
int qcow2_backing_read1(BlockDriverState *bs, QEMUIOVector *qiov,
|
||||
int64_t sector_num, int nb_sectors);
|
||||
|
||||
int64_t qcow2_refcount_metadata_size(int64_t clusters, size_t cluster_size,
|
||||
int refcount_order, bool generous_increase,
|
||||
uint64_t *refblock_count);
|
||||
|
||||
int qcow2_mark_dirty(BlockDriverState *bs);
|
||||
int qcow2_mark_corrupt(BlockDriverState *bs);
|
||||
int qcow2_mark_consistent(BlockDriverState *bs);
|
||||
@@ -549,11 +499,6 @@ int qcow2_update_cluster_refcount(BlockDriverState *bs, int64_t cluster_index,
|
||||
uint64_t addend, bool decrease,
|
||||
enum qcow2_discard_type type);
|
||||
|
||||
int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t additional_clusters, bool exact_size,
|
||||
int new_refblock_index,
|
||||
uint64_t new_refblock_offset);
|
||||
|
||||
int64_t qcow2_alloc_clusters(BlockDriverState *bs, uint64_t size);
|
||||
int64_t qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
|
||||
int64_t nb_clusters);
|
||||
@@ -576,10 +521,6 @@ int qcow2_check_metadata_overlap(BlockDriverState *bs, int ign, int64_t offset,
|
||||
int64_t size);
|
||||
int qcow2_pre_write_overlap_check(BlockDriverState *bs, int ign, int64_t offset,
|
||||
int64_t size);
|
||||
int qcow2_inc_refcounts_imrt(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
void **refcount_table,
|
||||
int64_t *refcount_table_size,
|
||||
int64_t offset, int64_t size);
|
||||
|
||||
int qcow2_change_refcount_order(BlockDriverState *bs, int refcount_order,
|
||||
BlockDriverAmendStatusCB *status_cb,
|
||||
@@ -589,9 +530,11 @@ int qcow2_change_refcount_order(BlockDriverState *bs, int refcount_order,
|
||||
int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
||||
bool exact_size);
|
||||
int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index);
|
||||
void qcow2_l2_cache_reset(BlockDriverState *bs);
|
||||
int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset);
|
||||
int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
|
||||
uint8_t *buf, int nb_sectors, bool enc, Error **errp);
|
||||
uint8_t *out_buf, const uint8_t *in_buf,
|
||||
int nb_sectors, bool enc, Error **errp);
|
||||
|
||||
int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||
unsigned int *bytes, uint64_t *cluster_offset);
|
||||
@@ -603,11 +546,9 @@ uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
||||
int compressed_size);
|
||||
|
||||
int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m);
|
||||
int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, enum qcow2_discard_type type,
|
||||
bool full_discard);
|
||||
int qcow2_cluster_zeroize(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, int flags);
|
||||
int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset,
|
||||
int nb_sectors, enum qcow2_discard_type type, bool full_discard);
|
||||
int qcow2_zero_clusters(BlockDriverState *bs, uint64_t offset, int nb_sectors);
|
||||
|
||||
int qcow2_expand_zero_clusters(BlockDriverState *bs,
|
||||
BlockDriverAmendStatusCB *status_cb,
|
||||
@@ -650,20 +591,4 @@ int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||
void **table);
|
||||
void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table);
|
||||
|
||||
/* qcow2-bitmap.c functions */
|
||||
int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
void **refcount_table,
|
||||
int64_t *refcount_table_size);
|
||||
bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error **errp);
|
||||
int qcow2_reopen_bitmaps_rw(BlockDriverState *bs, Error **errp);
|
||||
void qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs, Error **errp);
|
||||
int qcow2_reopen_bitmaps_ro(BlockDriverState *bs, Error **errp);
|
||||
bool qcow2_can_store_new_dirty_bitmap(BlockDriverState *bs,
|
||||
const char *name,
|
||||
uint32_t granularity,
|
||||
Error **errp);
|
||||
void qcow2_remove_persistent_dirty_bitmap(BlockDriverState *bs,
|
||||
const char *name,
|
||||
Error **errp);
|
||||
|
||||
#endif
|
||||
+78
-54
@@ -61,65 +61,36 @@ static unsigned int qed_count_contiguous_clusters(BDRVQEDState *s,
|
||||
return i - index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the offset of a data cluster
|
||||
*
|
||||
* @s: QED state
|
||||
* @request: L2 cache entry
|
||||
* @pos: Byte position in device
|
||||
* @len: Number of bytes (may be shortened on return)
|
||||
* @img_offset: Contains offset in the image file on success
|
||||
*
|
||||
* This function translates a position in the block device to an offset in the
|
||||
* image file. The translated offset or unallocated range in the image file is
|
||||
* reported back in *img_offset and *len.
|
||||
*
|
||||
* If the L2 table exists, request->l2_table points to the L2 table cache entry
|
||||
* and the caller must free the reference when they are finished. The cache
|
||||
* entry is exposed in this way to avoid callers having to read the L2 table
|
||||
* again later during request processing. If request->l2_table is non-NULL it
|
||||
* will be unreferenced before taking on the new cache entry.
|
||||
*
|
||||
* On success QED_CLUSTER_FOUND is returned and img_offset/len are a contiguous
|
||||
* range in the image file.
|
||||
*
|
||||
* On failure QED_CLUSTER_L2 or QED_CLUSTER_L1 is returned for missing L2 or L1
|
||||
* table offset, respectively. len is number of contiguous unallocated bytes.
|
||||
*
|
||||
* Called with table_lock held.
|
||||
*/
|
||||
int coroutine_fn qed_find_cluster(BDRVQEDState *s, QEDRequest *request,
|
||||
uint64_t pos, size_t *len,
|
||||
uint64_t *img_offset)
|
||||
typedef struct {
|
||||
BDRVQEDState *s;
|
||||
uint64_t pos;
|
||||
size_t len;
|
||||
|
||||
QEDRequest *request;
|
||||
|
||||
/* User callback */
|
||||
QEDFindClusterFunc *cb;
|
||||
void *opaque;
|
||||
} QEDFindClusterCB;
|
||||
|
||||
static void qed_find_cluster_cb(void *opaque, int ret)
|
||||
{
|
||||
uint64_t l2_offset;
|
||||
QEDFindClusterCB *find_cluster_cb = opaque;
|
||||
BDRVQEDState *s = find_cluster_cb->s;
|
||||
QEDRequest *request = find_cluster_cb->request;
|
||||
uint64_t offset = 0;
|
||||
size_t len = 0;
|
||||
unsigned int index;
|
||||
unsigned int n;
|
||||
int ret;
|
||||
|
||||
/* Limit length to L2 boundary. Requests are broken up at the L2 boundary
|
||||
* so that a request acts on one L2 table at a time.
|
||||
*/
|
||||
*len = MIN(*len, (((pos >> s->l1_shift) + 1) << s->l1_shift) - pos);
|
||||
|
||||
l2_offset = s->l1_table->offsets[qed_l1_index(s, pos)];
|
||||
if (qed_offset_is_unalloc_cluster(l2_offset)) {
|
||||
*img_offset = 0;
|
||||
return QED_CLUSTER_L1;
|
||||
}
|
||||
if (!qed_check_table_offset(s, l2_offset)) {
|
||||
*img_offset = *len = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = qed_read_l2_table(s, request, l2_offset);
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
index = qed_l2_index(s, pos);
|
||||
n = qed_bytes_to_clusters(s, qed_offset_into_cluster(s, pos) + *len);
|
||||
index = qed_l2_index(s, find_cluster_cb->pos);
|
||||
n = qed_bytes_to_clusters(s,
|
||||
qed_offset_into_cluster(s, find_cluster_cb->pos) +
|
||||
find_cluster_cb->len);
|
||||
n = qed_count_contiguous_clusters(s, request->l2_table->table,
|
||||
index, n, &offset);
|
||||
|
||||
@@ -133,10 +104,63 @@ int coroutine_fn qed_find_cluster(BDRVQEDState *s, QEDRequest *request,
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
*len = MIN(*len,
|
||||
n * s->header.cluster_size - qed_offset_into_cluster(s, pos));
|
||||
len = MIN(find_cluster_cb->len, n * s->header.cluster_size -
|
||||
qed_offset_into_cluster(s, find_cluster_cb->pos));
|
||||
|
||||
out:
|
||||
*img_offset = offset;
|
||||
return ret;
|
||||
find_cluster_cb->cb(find_cluster_cb->opaque, ret, offset, len);
|
||||
g_free(find_cluster_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the offset of a data cluster
|
||||
*
|
||||
* @s: QED state
|
||||
* @request: L2 cache entry
|
||||
* @pos: Byte position in device
|
||||
* @len: Number of bytes
|
||||
* @cb: Completion function
|
||||
* @opaque: User data for completion function
|
||||
*
|
||||
* This function translates a position in the block device to an offset in the
|
||||
* image file. It invokes the cb completion callback to report back the
|
||||
* translated offset or unallocated range in the image file.
|
||||
*
|
||||
* If the L2 table exists, request->l2_table points to the L2 table cache entry
|
||||
* and the caller must free the reference when they are finished. The cache
|
||||
* entry is exposed in this way to avoid callers having to read the L2 table
|
||||
* again later during request processing. If request->l2_table is non-NULL it
|
||||
* will be unreferenced before taking on the new cache entry.
|
||||
*/
|
||||
void qed_find_cluster(BDRVQEDState *s, QEDRequest *request, uint64_t pos,
|
||||
size_t len, QEDFindClusterFunc *cb, void *opaque)
|
||||
{
|
||||
QEDFindClusterCB *find_cluster_cb;
|
||||
uint64_t l2_offset;
|
||||
|
||||
/* Limit length to L2 boundary. Requests are broken up at the L2 boundary
|
||||
* so that a request acts on one L2 table at a time.
|
||||
*/
|
||||
len = MIN(len, (((pos >> s->l1_shift) + 1) << s->l1_shift) - pos);
|
||||
|
||||
l2_offset = s->l1_table->offsets[qed_l1_index(s, pos)];
|
||||
if (qed_offset_is_unalloc_cluster(l2_offset)) {
|
||||
cb(opaque, QED_CLUSTER_L1, 0, len);
|
||||
return;
|
||||
}
|
||||
if (!qed_check_table_offset(s, l2_offset)) {
|
||||
cb(opaque, -EINVAL, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
find_cluster_cb = g_malloc(sizeof(*find_cluster_cb));
|
||||
find_cluster_cb->s = s;
|
||||
find_cluster_cb->pos = pos;
|
||||
find_cluster_cb->len = len;
|
||||
find_cluster_cb->cb = cb;
|
||||
find_cluster_cb->opaque = opaque;
|
||||
find_cluster_cb->request = request;
|
||||
|
||||
qed_read_l2_table(s, request, l2_offset,
|
||||
qed_find_cluster_cb, find_cluster_cb);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* QEMU Enhanced Disk Format
|
||||
*
|
||||
* Copyright IBM, Corp. 2010
|
||||
*
|
||||
* Authors:
|
||||
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qed.h"
|
||||
|
||||
void *gencb_alloc(size_t len, BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
GenericCB *gencb = g_malloc(len);
|
||||
gencb->cb = cb;
|
||||
gencb->opaque = opaque;
|
||||
return gencb;
|
||||
}
|
||||
|
||||
void gencb_complete(void *opaque, int ret)
|
||||
{
|
||||
GenericCB *gencb = opaque;
|
||||
BlockCompletionFunc *cb = gencb->cb;
|
||||
void *user_opaque = gencb->opaque;
|
||||
|
||||
g_free(gencb);
|
||||
cb(user_opaque, ret);
|
||||
}
|
||||
@@ -101,8 +101,6 @@ CachedL2Table *qed_alloc_l2_cache_entry(L2TableCache *l2_cache)
|
||||
/**
|
||||
* Decrease an entry's reference count and free if necessary when the reference
|
||||
* count drops to zero.
|
||||
*
|
||||
* Called with table_lock held.
|
||||
*/
|
||||
void qed_unref_l2_cache_entry(CachedL2Table *entry)
|
||||
{
|
||||
@@ -124,8 +122,6 @@ void qed_unref_l2_cache_entry(CachedL2Table *entry)
|
||||
*
|
||||
* For a cached entry, this function increases the reference count and returns
|
||||
* the entry.
|
||||
*
|
||||
* Called with table_lock held.
|
||||
*/
|
||||
CachedL2Table *qed_find_l2_cache_entry(L2TableCache *l2_cache, uint64_t offset)
|
||||
{
|
||||
@@ -154,8 +150,6 @@ CachedL2Table *qed_find_l2_cache_entry(L2TableCache *l2_cache, uint64_t offset)
|
||||
* N.B. This function steals a reference to the l2_table from the caller so the
|
||||
* caller must obtain a new reference by issuing a call to
|
||||
* qed_find_l2_cache_entry().
|
||||
*
|
||||
* Called with table_lock held.
|
||||
*/
|
||||
void qed_commit_l2_cache_entry(L2TableCache *l2_cache, CachedL2Table *l2_table)
|
||||
{
|
||||
|
||||
+187
-96
@@ -18,43 +18,93 @@
|
||||
#include "qed.h"
|
||||
#include "qemu/bswap.h"
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
static int qed_read_table(BDRVQEDState *s, uint64_t offset, QEDTable *table)
|
||||
{
|
||||
typedef struct {
|
||||
GenericCB gencb;
|
||||
BDRVQEDState *s;
|
||||
QEDTable *table;
|
||||
|
||||
struct iovec iov;
|
||||
QEMUIOVector qiov;
|
||||
int noffsets;
|
||||
int i, ret;
|
||||
} QEDReadTableCB;
|
||||
|
||||
struct iovec iov = {
|
||||
.iov_base = table->offsets,
|
||||
.iov_len = s->header.cluster_size * s->header.table_size,
|
||||
};
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
static void qed_read_table_cb(void *opaque, int ret)
|
||||
{
|
||||
QEDReadTableCB *read_table_cb = opaque;
|
||||
QEDTable *table = read_table_cb->table;
|
||||
int noffsets = read_table_cb->qiov.size / sizeof(uint64_t);
|
||||
int i;
|
||||
|
||||
trace_qed_read_table(s, offset, table);
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
}
|
||||
ret = bdrv_preadv(s->bs->file, offset, &qiov);
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
}
|
||||
if (ret < 0) {
|
||||
/* Handle I/O error */
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Byteswap offsets */
|
||||
noffsets = qiov.size / sizeof(uint64_t);
|
||||
for (i = 0; i < noffsets; i++) {
|
||||
table->offsets[i] = le64_to_cpu(table->offsets[i]);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
/* Completion */
|
||||
trace_qed_read_table_cb(s, table, ret);
|
||||
return ret;
|
||||
trace_qed_read_table_cb(read_table_cb->s, read_table_cb->table, ret);
|
||||
gencb_complete(&read_table_cb->gencb, ret);
|
||||
}
|
||||
|
||||
static void qed_read_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
QEDReadTableCB *read_table_cb = gencb_alloc(sizeof(*read_table_cb),
|
||||
cb, opaque);
|
||||
QEMUIOVector *qiov = &read_table_cb->qiov;
|
||||
|
||||
trace_qed_read_table(s, offset, table);
|
||||
|
||||
read_table_cb->s = s;
|
||||
read_table_cb->table = table;
|
||||
read_table_cb->iov.iov_base = table->offsets,
|
||||
read_table_cb->iov.iov_len = s->header.cluster_size * s->header.table_size,
|
||||
|
||||
qemu_iovec_init_external(qiov, &read_table_cb->iov, 1);
|
||||
bdrv_aio_readv(s->bs->file, offset / BDRV_SECTOR_SIZE, qiov,
|
||||
qiov->size / BDRV_SECTOR_SIZE,
|
||||
qed_read_table_cb, read_table_cb);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GenericCB gencb;
|
||||
BDRVQEDState *s;
|
||||
QEDTable *orig_table;
|
||||
QEDTable *table;
|
||||
bool flush; /* flush after write? */
|
||||
|
||||
struct iovec iov;
|
||||
QEMUIOVector qiov;
|
||||
} QEDWriteTableCB;
|
||||
|
||||
static void qed_write_table_cb(void *opaque, int ret)
|
||||
{
|
||||
QEDWriteTableCB *write_table_cb = opaque;
|
||||
|
||||
trace_qed_write_table_cb(write_table_cb->s,
|
||||
write_table_cb->orig_table,
|
||||
write_table_cb->flush,
|
||||
ret);
|
||||
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_table_cb->flush) {
|
||||
/* We still need to flush first */
|
||||
write_table_cb->flush = false;
|
||||
bdrv_aio_flush(write_table_cb->s->bs, qed_write_table_cb,
|
||||
write_table_cb);
|
||||
return;
|
||||
}
|
||||
|
||||
out:
|
||||
qemu_vfree(write_table_cb->table);
|
||||
gencb_complete(&write_table_cb->gencb, ret);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,19 +116,17 @@ out:
|
||||
* @index: Index of first element
|
||||
* @n: Number of elements
|
||||
* @flush: Whether or not to sync to disk
|
||||
*
|
||||
* Called either from qed_check or with table_lock held.
|
||||
* @cb: Completion function
|
||||
* @opaque: Argument for completion function
|
||||
*/
|
||||
static int qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
static void qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
unsigned int index, unsigned int n, bool flush,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
QEDWriteTableCB *write_table_cb;
|
||||
unsigned int sector_mask = BDRV_SECTOR_SIZE / sizeof(uint64_t) - 1;
|
||||
unsigned int start, end, i;
|
||||
QEDTable *new_table;
|
||||
struct iovec iov;
|
||||
QEMUIOVector qiov;
|
||||
size_t len_bytes;
|
||||
int ret;
|
||||
|
||||
trace_qed_write_table(s, offset, table, index, n);
|
||||
|
||||
@@ -88,120 +136,163 @@ static int qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
|
||||
len_bytes = (end - start) * sizeof(uint64_t);
|
||||
|
||||
new_table = qemu_blockalign(s->bs, len_bytes);
|
||||
iov = (struct iovec) {
|
||||
.iov_base = new_table->offsets,
|
||||
.iov_len = len_bytes,
|
||||
};
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
write_table_cb = gencb_alloc(sizeof(*write_table_cb), cb, opaque);
|
||||
write_table_cb->s = s;
|
||||
write_table_cb->orig_table = table;
|
||||
write_table_cb->flush = flush;
|
||||
write_table_cb->table = qemu_blockalign(s->bs, len_bytes);
|
||||
write_table_cb->iov.iov_base = write_table_cb->table->offsets;
|
||||
write_table_cb->iov.iov_len = len_bytes;
|
||||
qemu_iovec_init_external(&write_table_cb->qiov, &write_table_cb->iov, 1);
|
||||
|
||||
/* Byteswap table */
|
||||
for (i = start; i < end; i++) {
|
||||
uint64_t le_offset = cpu_to_le64(table->offsets[i]);
|
||||
new_table->offsets[i - start] = le_offset;
|
||||
write_table_cb->table->offsets[i - start] = le_offset;
|
||||
}
|
||||
|
||||
/* Adjust for offset into table */
|
||||
offset += start * sizeof(uint64_t);
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
}
|
||||
ret = bdrv_pwritev(s->bs->file, offset, &qiov);
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
}
|
||||
trace_qed_write_table_cb(s, table, flush, ret);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
bdrv_aio_writev(s->bs->file, offset / BDRV_SECTOR_SIZE,
|
||||
&write_table_cb->qiov,
|
||||
write_table_cb->qiov.size / BDRV_SECTOR_SIZE,
|
||||
qed_write_table_cb, write_table_cb);
|
||||
}
|
||||
|
||||
if (flush) {
|
||||
ret = bdrv_flush(s->bs);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
qemu_vfree(new_table);
|
||||
return ret;
|
||||
/**
|
||||
* Propagate return value from async callback
|
||||
*/
|
||||
static void qed_sync_cb(void *opaque, int ret)
|
||||
{
|
||||
*(int *)opaque = ret;
|
||||
}
|
||||
|
||||
int qed_read_l1_table_sync(BDRVQEDState *s)
|
||||
{
|
||||
return qed_read_table(s, s->header.l1_table_offset, s->l1_table);
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_read_table(s, s->header.l1_table_offset,
|
||||
s->l1_table, qed_sync_cb, &ret);
|
||||
while (ret == -EINPROGRESS) {
|
||||
aio_poll(bdrv_get_aio_context(s->bs), true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
int qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n)
|
||||
void qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
BLKDBG_EVENT(s->bs->file, BLKDBG_L1_UPDATE);
|
||||
return qed_write_table(s, s->header.l1_table_offset,
|
||||
s->l1_table, index, n, false);
|
||||
qed_write_table(s, s->header.l1_table_offset,
|
||||
s->l1_table, index, n, false, cb, opaque);
|
||||
}
|
||||
|
||||
int qed_write_l1_table_sync(BDRVQEDState *s, unsigned int index,
|
||||
unsigned int n)
|
||||
{
|
||||
return qed_write_l1_table(s, index, n);
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_write_l1_table(s, index, n, qed_sync_cb, &ret);
|
||||
while (ret == -EINPROGRESS) {
|
||||
aio_poll(bdrv_get_aio_context(s->bs), true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
int qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset)
|
||||
typedef struct {
|
||||
GenericCB gencb;
|
||||
BDRVQEDState *s;
|
||||
uint64_t l2_offset;
|
||||
QEDRequest *request;
|
||||
} QEDReadL2TableCB;
|
||||
|
||||
static void qed_read_l2_table_cb(void *opaque, int ret)
|
||||
{
|
||||
int ret;
|
||||
QEDReadL2TableCB *read_l2_table_cb = opaque;
|
||||
QEDRequest *request = read_l2_table_cb->request;
|
||||
BDRVQEDState *s = read_l2_table_cb->s;
|
||||
CachedL2Table *l2_table = request->l2_table;
|
||||
uint64_t l2_offset = read_l2_table_cb->l2_offset;
|
||||
|
||||
if (ret) {
|
||||
/* can't trust loaded L2 table anymore */
|
||||
qed_unref_l2_cache_entry(l2_table);
|
||||
request->l2_table = NULL;
|
||||
} else {
|
||||
l2_table->offset = l2_offset;
|
||||
|
||||
qed_commit_l2_cache_entry(&s->l2_cache, l2_table);
|
||||
|
||||
/* This is guaranteed to succeed because we just committed the entry
|
||||
* to the cache.
|
||||
*/
|
||||
request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, l2_offset);
|
||||
assert(request->l2_table != NULL);
|
||||
}
|
||||
|
||||
gencb_complete(&read_l2_table_cb->gencb, ret);
|
||||
}
|
||||
|
||||
void qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
QEDReadL2TableCB *read_l2_table_cb;
|
||||
|
||||
qed_unref_l2_cache_entry(request->l2_table);
|
||||
|
||||
/* Check for cached L2 entry */
|
||||
request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, offset);
|
||||
if (request->l2_table) {
|
||||
return 0;
|
||||
cb(opaque, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
request->l2_table = qed_alloc_l2_cache_entry(&s->l2_cache);
|
||||
request->l2_table->table = qed_alloc_table(s);
|
||||
|
||||
read_l2_table_cb = gencb_alloc(sizeof(*read_l2_table_cb), cb, opaque);
|
||||
read_l2_table_cb->s = s;
|
||||
read_l2_table_cb->l2_offset = offset;
|
||||
read_l2_table_cb->request = request;
|
||||
|
||||
BLKDBG_EVENT(s->bs->file, BLKDBG_L2_LOAD);
|
||||
ret = qed_read_table(s, offset, request->l2_table->table);
|
||||
qed_read_table(s, offset, request->l2_table->table,
|
||||
qed_read_l2_table_cb, read_l2_table_cb);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
/* can't trust loaded L2 table anymore */
|
||||
qed_unref_l2_cache_entry(request->l2_table);
|
||||
request->l2_table = NULL;
|
||||
} else {
|
||||
request->l2_table->offset = offset;
|
||||
int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request, uint64_t offset)
|
||||
{
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_commit_l2_cache_entry(&s->l2_cache, request->l2_table);
|
||||
|
||||
/* This is guaranteed to succeed because we just committed the entry
|
||||
* to the cache.
|
||||
*/
|
||||
request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, offset);
|
||||
assert(request->l2_table != NULL);
|
||||
qed_read_l2_table(s, request, offset, qed_sync_cb, &ret);
|
||||
while (ret == -EINPROGRESS) {
|
||||
aio_poll(bdrv_get_aio_context(s->bs), true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request, uint64_t offset)
|
||||
{
|
||||
return qed_read_l2_table(s, request, offset);
|
||||
}
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
int qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
void qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
BLKDBG_EVENT(s->bs->file, BLKDBG_L2_UPDATE);
|
||||
return qed_write_table(s, request->l2_table->offset,
|
||||
request->l2_table->table, index, n, flush);
|
||||
qed_write_table(s, request->l2_table->offset,
|
||||
request->l2_table->table, index, n, flush, cb, opaque);
|
||||
}
|
||||
|
||||
int qed_write_l2_table_sync(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
{
|
||||
return qed_write_l2_table(s, request, index, n, flush);
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_write_l2_table(s, request, index, n, flush, qed_sync_cb, &ret);
|
||||
while (ret == -EINPROGRESS) {
|
||||
aio_poll(bdrv_get_aio_context(s->bs), true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
+488
-427
File diff suppressed because it is too large.
Load diff
+46
-17
@@ -129,7 +129,9 @@ enum {
|
||||
};
|
||||
|
||||
typedef struct QEDAIOCB {
|
||||
BlockDriverState *bs;
|
||||
BlockAIOCB common;
|
||||
QEMUBH *bh;
|
||||
int bh_ret; /* final return status for completion bh */
|
||||
QSIMPLEQ_ENTRY(QEDAIOCB) next; /* next request */
|
||||
int flags; /* QED_AIOCB_* bits ORed together */
|
||||
uint64_t end_pos; /* request end on block device, in bytes */
|
||||
@@ -151,25 +153,18 @@ typedef struct QEDAIOCB {
|
||||
|
||||
typedef struct {
|
||||
BlockDriverState *bs; /* device */
|
||||
uint64_t file_size; /* length of image file, in bytes */
|
||||
|
||||
/* Written only by an allocating write or the timer handler (the latter
|
||||
* while allocating reqs are plugged).
|
||||
*/
|
||||
QEDHeader header; /* always cpu-endian */
|
||||
|
||||
/* Protected by table_lock. */
|
||||
CoMutex table_lock;
|
||||
QEDTable *l1_table;
|
||||
L2TableCache l2_cache; /* l2 table cache */
|
||||
uint32_t table_nelems;
|
||||
uint32_t l1_shift;
|
||||
uint32_t l2_shift;
|
||||
uint32_t l2_mask;
|
||||
uint64_t file_size; /* length of image file, in bytes */
|
||||
|
||||
/* Allocating write request queue */
|
||||
QEDAIOCB *allocating_acb;
|
||||
CoQueue allocating_write_reqs;
|
||||
QSIMPLEQ_HEAD(, QEDAIOCB) allocating_write_reqs;
|
||||
bool allocating_write_reqs_plugged;
|
||||
|
||||
/* Periodic flush and clear need check flag */
|
||||
@@ -183,6 +178,38 @@ enum {
|
||||
QED_CLUSTER_L1, /* cluster missing in L1 */
|
||||
};
|
||||
|
||||
/**
|
||||
* qed_find_cluster() completion callback
|
||||
*
|
||||
* @opaque: User data for completion callback
|
||||
* @ret: QED_CLUSTER_FOUND Success
|
||||
* QED_CLUSTER_L2 Data cluster unallocated in L2
|
||||
* QED_CLUSTER_L1 L2 unallocated in L1
|
||||
* -errno POSIX error occurred
|
||||
* @offset: Data cluster offset
|
||||
* @len: Contiguous bytes starting from cluster offset
|
||||
*
|
||||
* This function is invoked when qed_find_cluster() completes.
|
||||
*
|
||||
* On success ret is QED_CLUSTER_FOUND and offset/len are a contiguous range
|
||||
* in the image file.
|
||||
*
|
||||
* On failure ret is QED_CLUSTER_L2 or QED_CLUSTER_L1 for missing L2 or L1
|
||||
* table offset, respectively. len is number of contiguous unallocated bytes.
|
||||
*/
|
||||
typedef void QEDFindClusterFunc(void *opaque, int ret, uint64_t offset, size_t len);
|
||||
|
||||
/**
|
||||
* Generic callback for chaining async callbacks
|
||||
*/
|
||||
typedef struct {
|
||||
BlockCompletionFunc *cb;
|
||||
void *opaque;
|
||||
} GenericCB;
|
||||
|
||||
void *gencb_alloc(size_t len, BlockCompletionFunc *cb, void *opaque);
|
||||
void gencb_complete(void *opaque, int ret);
|
||||
|
||||
/**
|
||||
* Header functions
|
||||
*/
|
||||
@@ -202,23 +229,25 @@ void qed_commit_l2_cache_entry(L2TableCache *l2_cache, CachedL2Table *l2_table);
|
||||
* Table I/O functions
|
||||
*/
|
||||
int qed_read_l1_table_sync(BDRVQEDState *s);
|
||||
int qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n);
|
||||
void qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int qed_write_l1_table_sync(BDRVQEDState *s, unsigned int index,
|
||||
unsigned int n);
|
||||
int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request,
|
||||
uint64_t offset);
|
||||
int qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset);
|
||||
int qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush);
|
||||
void qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
void qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int qed_write_l2_table_sync(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush);
|
||||
|
||||
/**
|
||||
* Cluster functions
|
||||
*/
|
||||
int coroutine_fn qed_find_cluster(BDRVQEDState *s, QEDRequest *request,
|
||||
uint64_t pos, size_t *len,
|
||||
uint64_t *img_offset);
|
||||
void qed_find_cluster(BDRVQEDState *s, QEDRequest *request, uint64_t pos,
|
||||
size_t len, QEDFindClusterFunc *cb, void *opaque);
|
||||
|
||||
/**
|
||||
* Consistency check
|
||||
|
||||
+215
-237
@@ -19,6 +19,7 @@
|
||||
#include "qapi/qmp/qbool.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qapi/qmp/qint.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
@@ -96,7 +97,7 @@ typedef struct QuorumAIOCB QuorumAIOCB;
|
||||
* $children_count QuorumChildRequest.
|
||||
*/
|
||||
typedef struct QuorumChildRequest {
|
||||
BlockDriverState *bs;
|
||||
BlockAIOCB *aiocb;
|
||||
QEMUIOVector qiov;
|
||||
uint8_t *buf;
|
||||
int ret;
|
||||
@@ -109,12 +110,11 @@ typedef struct QuorumChildRequest {
|
||||
* used to do operations on each children and track overall progress.
|
||||
*/
|
||||
struct QuorumAIOCB {
|
||||
BlockDriverState *bs;
|
||||
Coroutine *co;
|
||||
BlockAIOCB common;
|
||||
|
||||
/* Request metadata */
|
||||
uint64_t offset;
|
||||
uint64_t bytes;
|
||||
uint64_t sector_num;
|
||||
int nb_sectors;
|
||||
|
||||
QEMUIOVector *qiov; /* calling IOV */
|
||||
|
||||
@@ -130,18 +130,50 @@ struct QuorumAIOCB {
|
||||
|
||||
bool is_read;
|
||||
int vote_ret;
|
||||
int children_read; /* how many children have been read from */
|
||||
int child_iter; /* which child to read in fifo pattern */
|
||||
};
|
||||
|
||||
typedef struct QuorumCo {
|
||||
QuorumAIOCB *acb;
|
||||
int idx;
|
||||
} QuorumCo;
|
||||
static bool quorum_vote(QuorumAIOCB *acb);
|
||||
|
||||
static void quorum_aio_cancel(BlockAIOCB *blockacb)
|
||||
{
|
||||
QuorumAIOCB *acb = container_of(blockacb, QuorumAIOCB, common);
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
int i;
|
||||
|
||||
/* cancel all callbacks */
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
if (acb->qcrs[i].aiocb) {
|
||||
bdrv_aio_cancel_async(acb->qcrs[i].aiocb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AIOCBInfo quorum_aiocb_info = {
|
||||
.aiocb_size = sizeof(QuorumAIOCB),
|
||||
.cancel_async = quorum_aio_cancel,
|
||||
};
|
||||
|
||||
static void quorum_aio_finalize(QuorumAIOCB *acb)
|
||||
{
|
||||
int i, ret = 0;
|
||||
|
||||
if (acb->vote_ret) {
|
||||
ret = acb->vote_ret;
|
||||
}
|
||||
|
||||
acb->common.cb(acb->common.opaque, ret);
|
||||
|
||||
if (acb->is_read) {
|
||||
/* on the quorum case acb->child_iter == s->num_children - 1 */
|
||||
for (i = 0; i <= acb->child_iter; i++) {
|
||||
qemu_vfree(acb->qcrs[i].buf);
|
||||
qemu_iovec_destroy(&acb->qcrs[i].qiov);
|
||||
}
|
||||
}
|
||||
|
||||
g_free(acb->qcrs);
|
||||
g_free(acb);
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
|
||||
static bool quorum_sha256_compare(QuorumVoteValue *a, QuorumVoteValue *b)
|
||||
@@ -154,26 +186,30 @@ static bool quorum_64bits_compare(QuorumVoteValue *a, QuorumVoteValue *b)
|
||||
return a->l == b->l;
|
||||
}
|
||||
|
||||
static QuorumAIOCB *quorum_aio_get(BlockDriverState *bs,
|
||||
static QuorumAIOCB *quorum_aio_get(BDRVQuorumState *s,
|
||||
BlockDriverState *bs,
|
||||
QEMUIOVector *qiov,
|
||||
uint64_t offset,
|
||||
uint64_t bytes)
|
||||
uint64_t sector_num,
|
||||
int nb_sectors,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
BDRVQuorumState *s = bs->opaque;
|
||||
QuorumAIOCB *acb = g_new(QuorumAIOCB, 1);
|
||||
QuorumAIOCB *acb = qemu_aio_get(&quorum_aiocb_info, bs, cb, opaque);
|
||||
int i;
|
||||
|
||||
*acb = (QuorumAIOCB) {
|
||||
.co = qemu_coroutine_self(),
|
||||
.bs = bs,
|
||||
.offset = offset,
|
||||
.bytes = bytes,
|
||||
.qiov = qiov,
|
||||
.votes.compare = quorum_sha256_compare,
|
||||
.votes.vote_list = QLIST_HEAD_INITIALIZER(acb.votes.vote_list),
|
||||
};
|
||||
|
||||
acb->common.bs->opaque = s;
|
||||
acb->sector_num = sector_num;
|
||||
acb->nb_sectors = nb_sectors;
|
||||
acb->qiov = qiov;
|
||||
acb->qcrs = g_new0(QuorumChildRequest, s->num_children);
|
||||
acb->count = 0;
|
||||
acb->success_count = 0;
|
||||
acb->rewrite_count = 0;
|
||||
acb->votes.compare = quorum_sha256_compare;
|
||||
QLIST_INIT(&acb->votes.vote_list);
|
||||
acb->is_read = false;
|
||||
acb->vote_ret = 0;
|
||||
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
acb->qcrs[i].buf = NULL;
|
||||
acb->qcrs[i].ret = 0;
|
||||
@@ -183,37 +219,30 @@ static QuorumAIOCB *quorum_aio_get(BlockDriverState *bs,
|
||||
return acb;
|
||||
}
|
||||
|
||||
static void quorum_report_bad(QuorumOpType type, uint64_t offset,
|
||||
uint64_t bytes, char *node_name, int ret)
|
||||
static void quorum_report_bad(QuorumOpType type, uint64_t sector_num,
|
||||
int nb_sectors, char *node_name, int ret)
|
||||
{
|
||||
const char *msg = NULL;
|
||||
int64_t start_sector = offset / BDRV_SECTOR_SIZE;
|
||||
int64_t end_sector = DIV_ROUND_UP(offset + bytes, BDRV_SECTOR_SIZE);
|
||||
|
||||
if (ret < 0) {
|
||||
msg = strerror(-ret);
|
||||
}
|
||||
|
||||
qapi_event_send_quorum_report_bad(type, !!msg, msg, node_name, start_sector,
|
||||
end_sector - start_sector, &error_abort);
|
||||
qapi_event_send_quorum_report_bad(type, !!msg, msg, node_name,
|
||||
sector_num, nb_sectors, &error_abort);
|
||||
}
|
||||
|
||||
static void quorum_report_failure(QuorumAIOCB *acb)
|
||||
{
|
||||
const char *reference = bdrv_get_device_or_node_name(acb->bs);
|
||||
int64_t start_sector = acb->offset / BDRV_SECTOR_SIZE;
|
||||
int64_t end_sector = DIV_ROUND_UP(acb->offset + acb->bytes,
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
qapi_event_send_quorum_failure(reference, start_sector,
|
||||
end_sector - start_sector, &error_abort);
|
||||
const char *reference = bdrv_get_device_or_node_name(acb->common.bs);
|
||||
qapi_event_send_quorum_failure(reference, acb->sector_num,
|
||||
acb->nb_sectors, &error_abort);
|
||||
}
|
||||
|
||||
static int quorum_vote_error(QuorumAIOCB *acb);
|
||||
|
||||
static bool quorum_has_too_much_io_failed(QuorumAIOCB *acb)
|
||||
{
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
|
||||
if (acb->success_count < s->threshold) {
|
||||
acb->vote_ret = quorum_vote_error(acb);
|
||||
@@ -224,7 +253,22 @@ static bool quorum_has_too_much_io_failed(QuorumAIOCB *acb)
|
||||
return false;
|
||||
}
|
||||
|
||||
static int read_fifo_child(QuorumAIOCB *acb);
|
||||
static void quorum_rewrite_aio_cb(void *opaque, int ret)
|
||||
{
|
||||
QuorumAIOCB *acb = opaque;
|
||||
|
||||
/* one less rewrite to do */
|
||||
acb->rewrite_count--;
|
||||
|
||||
/* wait until all rewrite callbacks have completed */
|
||||
if (acb->rewrite_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
quorum_aio_finalize(acb);
|
||||
}
|
||||
|
||||
static BlockAIOCB *read_fifo_child(QuorumAIOCB *acb);
|
||||
|
||||
static void quorum_copy_qiov(QEMUIOVector *dest, QEMUIOVector *source)
|
||||
{
|
||||
@@ -239,11 +283,57 @@ static void quorum_copy_qiov(QEMUIOVector *dest, QEMUIOVector *source)
|
||||
}
|
||||
}
|
||||
|
||||
static void quorum_report_bad_acb(QuorumChildRequest *sacb, int ret)
|
||||
static void quorum_aio_cb(void *opaque, int ret)
|
||||
{
|
||||
QuorumChildRequest *sacb = opaque;
|
||||
QuorumAIOCB *acb = sacb->parent;
|
||||
QuorumOpType type = acb->is_read ? QUORUM_OP_TYPE_READ : QUORUM_OP_TYPE_WRITE;
|
||||
quorum_report_bad(type, acb->offset, acb->bytes, sacb->bs->node_name, ret);
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
bool rewrite = false;
|
||||
|
||||
if (ret == 0) {
|
||||
acb->success_count++;
|
||||
} else {
|
||||
QuorumOpType type;
|
||||
type = acb->is_read ? QUORUM_OP_TYPE_READ : QUORUM_OP_TYPE_WRITE;
|
||||
quorum_report_bad(type, acb->sector_num, acb->nb_sectors,
|
||||
sacb->aiocb->bs->node_name, ret);
|
||||
}
|
||||
|
||||
if (acb->is_read && s->read_pattern == QUORUM_READ_PATTERN_FIFO) {
|
||||
/* We try to read next child in FIFO order if we fail to read */
|
||||
if (ret < 0 && (acb->child_iter + 1) < s->num_children) {
|
||||
acb->child_iter++;
|
||||
read_fifo_child(acb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
quorum_copy_qiov(acb->qiov, &acb->qcrs[acb->child_iter].qiov);
|
||||
}
|
||||
acb->vote_ret = ret;
|
||||
quorum_aio_finalize(acb);
|
||||
return;
|
||||
}
|
||||
|
||||
sacb->ret = ret;
|
||||
acb->count++;
|
||||
assert(acb->count <= s->num_children);
|
||||
assert(acb->success_count <= s->num_children);
|
||||
if (acb->count < s->num_children) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Do the vote on read */
|
||||
if (acb->is_read) {
|
||||
rewrite = quorum_vote(acb);
|
||||
} else {
|
||||
quorum_has_too_much_io_failed(acb);
|
||||
}
|
||||
|
||||
/* if no rewrite is done the code will finish right away */
|
||||
if (!rewrite) {
|
||||
quorum_aio_finalize(acb);
|
||||
}
|
||||
}
|
||||
|
||||
static void quorum_report_bad_versions(BDRVQuorumState *s,
|
||||
@@ -258,31 +348,14 @@ static void quorum_report_bad_versions(BDRVQuorumState *s,
|
||||
continue;
|
||||
}
|
||||
QLIST_FOREACH(item, &version->items, next) {
|
||||
quorum_report_bad(QUORUM_OP_TYPE_READ, acb->offset, acb->bytes,
|
||||
quorum_report_bad(QUORUM_OP_TYPE_READ, acb->sector_num,
|
||||
acb->nb_sectors,
|
||||
s->children[item->index]->bs->node_name, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void quorum_rewrite_entry(void *opaque)
|
||||
{
|
||||
QuorumCo *co = opaque;
|
||||
QuorumAIOCB *acb = co->acb;
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
|
||||
/* Ignore any errors, it's just a correction attempt for already
|
||||
* corrupted data. */
|
||||
bdrv_co_pwritev(s->children[co->idx], acb->offset, acb->bytes,
|
||||
acb->qiov, 0);
|
||||
|
||||
/* Wake up the caller after the last rewrite */
|
||||
acb->rewrite_count--;
|
||||
if (!acb->rewrite_count) {
|
||||
qemu_coroutine_enter_if_inactive(acb->co);
|
||||
}
|
||||
}
|
||||
|
||||
static bool quorum_rewrite_bad_versions(QuorumAIOCB *acb,
|
||||
static bool quorum_rewrite_bad_versions(BDRVQuorumState *s, QuorumAIOCB *acb,
|
||||
QuorumVoteValue *value)
|
||||
{
|
||||
QuorumVoteVersion *version;
|
||||
@@ -301,7 +374,7 @@ static bool quorum_rewrite_bad_versions(QuorumAIOCB *acb,
|
||||
}
|
||||
}
|
||||
|
||||
/* quorum_rewrite_entry will count down this to zero */
|
||||
/* quorum_rewrite_aio_cb will count down this to zero */
|
||||
acb->rewrite_count = count;
|
||||
|
||||
/* now fire the correcting rewrites */
|
||||
@@ -310,14 +383,9 @@ static bool quorum_rewrite_bad_versions(QuorumAIOCB *acb,
|
||||
continue;
|
||||
}
|
||||
QLIST_FOREACH(item, &version->items, next) {
|
||||
Coroutine *co;
|
||||
QuorumCo data = {
|
||||
.acb = acb,
|
||||
.idx = item->index,
|
||||
};
|
||||
|
||||
co = qemu_coroutine_create(quorum_rewrite_entry, &data);
|
||||
qemu_coroutine_enter(co);
|
||||
bdrv_aio_writev(s->children[item->index], acb->sector_num,
|
||||
acb->qiov, acb->nb_sectors, quorum_rewrite_aio_cb,
|
||||
acb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,8 +505,8 @@ static void GCC_FMT_ATTR(2, 3) quorum_err(QuorumAIOCB *acb,
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "quorum: offset=%" PRIu64 " bytes=%" PRIu64 " ",
|
||||
acb->offset, acb->bytes);
|
||||
fprintf(stderr, "quorum: sector_num=%" PRId64 " nb_sectors=%d ",
|
||||
acb->sector_num, acb->nb_sectors);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n");
|
||||
va_end(ap);
|
||||
@@ -449,15 +517,16 @@ static bool quorum_compare(QuorumAIOCB *acb,
|
||||
QEMUIOVector *a,
|
||||
QEMUIOVector *b)
|
||||
{
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
ssize_t offset;
|
||||
|
||||
/* This driver will replace blkverify in this particular case */
|
||||
if (s->is_blkverify) {
|
||||
offset = qemu_iovec_compare(a, b);
|
||||
if (offset != -1) {
|
||||
quorum_err(acb, "contents mismatch at offset %" PRIu64,
|
||||
acb->offset + offset);
|
||||
quorum_err(acb, "contents mismatch in sector %" PRId64,
|
||||
acb->sector_num +
|
||||
(uint64_t)(offset / BDRV_SECTOR_SIZE));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -468,7 +537,7 @@ static bool quorum_compare(QuorumAIOCB *acb,
|
||||
/* Do a vote to get the error code */
|
||||
static int quorum_vote_error(QuorumAIOCB *acb)
|
||||
{
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
QuorumVoteVersion *winner = NULL;
|
||||
QuorumVotes error_votes;
|
||||
QuorumVoteValue result_value;
|
||||
@@ -497,16 +566,17 @@ static int quorum_vote_error(QuorumAIOCB *acb)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void quorum_vote(QuorumAIOCB *acb)
|
||||
static bool quorum_vote(QuorumAIOCB *acb)
|
||||
{
|
||||
bool quorum = true;
|
||||
bool rewrite = false;
|
||||
int i, j, ret;
|
||||
QuorumVoteValue hash;
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
QuorumVoteVersion *winner;
|
||||
|
||||
if (quorum_has_too_much_io_failed(acb)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* get the index of the first successful read */
|
||||
@@ -534,7 +604,7 @@ static void quorum_vote(QuorumAIOCB *acb)
|
||||
/* Every successful read agrees */
|
||||
if (quorum) {
|
||||
quorum_copy_qiov(acb->qiov, &acb->qcrs[i].qiov);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* compute hashes for each successful read, also store indexes */
|
||||
@@ -569,48 +639,20 @@ static void quorum_vote(QuorumAIOCB *acb)
|
||||
|
||||
/* corruption correction is enabled */
|
||||
if (s->rewrite_corrupted) {
|
||||
quorum_rewrite_bad_versions(acb, &winner->value);
|
||||
rewrite = quorum_rewrite_bad_versions(s, acb, &winner->value);
|
||||
}
|
||||
|
||||
free_exit:
|
||||
/* free lists */
|
||||
quorum_free_vote_list(&acb->votes);
|
||||
return rewrite;
|
||||
}
|
||||
|
||||
static void read_quorum_children_entry(void *opaque)
|
||||
static BlockAIOCB *read_quorum_children(QuorumAIOCB *acb)
|
||||
{
|
||||
QuorumCo *co = opaque;
|
||||
QuorumAIOCB *acb = co->acb;
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
int i = co->idx;
|
||||
QuorumChildRequest *sacb = &acb->qcrs[i];
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
int i;
|
||||
|
||||
sacb->bs = s->children[i]->bs;
|
||||
sacb->ret = bdrv_co_preadv(s->children[i], acb->offset, acb->bytes,
|
||||
&acb->qcrs[i].qiov, 0);
|
||||
|
||||
if (sacb->ret == 0) {
|
||||
acb->success_count++;
|
||||
} else {
|
||||
quorum_report_bad_acb(sacb, sacb->ret);
|
||||
}
|
||||
|
||||
acb->count++;
|
||||
assert(acb->count <= s->num_children);
|
||||
assert(acb->success_count <= s->num_children);
|
||||
|
||||
/* Wake up the caller after the last read */
|
||||
if (acb->count == s->num_children) {
|
||||
qemu_coroutine_enter_if_inactive(acb->co);
|
||||
}
|
||||
}
|
||||
|
||||
static int read_quorum_children(QuorumAIOCB *acb)
|
||||
{
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
int i, ret;
|
||||
|
||||
acb->children_read = s->num_children;
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
acb->qcrs[i].buf = qemu_blockalign(s->children[i]->bs, acb->qiov->size);
|
||||
qemu_iovec_init(&acb->qcrs[i].qiov, acb->qiov->niov);
|
||||
@@ -618,131 +660,71 @@ static int read_quorum_children(QuorumAIOCB *acb)
|
||||
}
|
||||
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
Coroutine *co;
|
||||
QuorumCo data = {
|
||||
.acb = acb,
|
||||
.idx = i,
|
||||
};
|
||||
|
||||
co = qemu_coroutine_create(read_quorum_children_entry, &data);
|
||||
qemu_coroutine_enter(co);
|
||||
acb->qcrs[i].aiocb = bdrv_aio_readv(s->children[i], acb->sector_num,
|
||||
&acb->qcrs[i].qiov, acb->nb_sectors,
|
||||
quorum_aio_cb, &acb->qcrs[i]);
|
||||
}
|
||||
|
||||
while (acb->count < s->num_children) {
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
|
||||
/* Do the vote on read */
|
||||
quorum_vote(acb);
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
qemu_vfree(acb->qcrs[i].buf);
|
||||
qemu_iovec_destroy(&acb->qcrs[i].qiov);
|
||||
}
|
||||
|
||||
while (acb->rewrite_count) {
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
|
||||
ret = acb->vote_ret;
|
||||
|
||||
return ret;
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static int read_fifo_child(QuorumAIOCB *acb)
|
||||
static BlockAIOCB *read_fifo_child(QuorumAIOCB *acb)
|
||||
{
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
int n, ret;
|
||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
||||
|
||||
/* We try to read the next child in FIFO order if we failed to read */
|
||||
do {
|
||||
n = acb->children_read++;
|
||||
acb->qcrs[n].bs = s->children[n]->bs;
|
||||
ret = bdrv_co_preadv(s->children[n], acb->offset, acb->bytes,
|
||||
acb->qiov, 0);
|
||||
if (ret < 0) {
|
||||
quorum_report_bad_acb(&acb->qcrs[n], ret);
|
||||
}
|
||||
} while (ret < 0 && acb->children_read < s->num_children);
|
||||
acb->qcrs[acb->child_iter].buf =
|
||||
qemu_blockalign(s->children[acb->child_iter]->bs, acb->qiov->size);
|
||||
qemu_iovec_init(&acb->qcrs[acb->child_iter].qiov, acb->qiov->niov);
|
||||
qemu_iovec_clone(&acb->qcrs[acb->child_iter].qiov, acb->qiov,
|
||||
acb->qcrs[acb->child_iter].buf);
|
||||
acb->qcrs[acb->child_iter].aiocb =
|
||||
bdrv_aio_readv(s->children[acb->child_iter], acb->sector_num,
|
||||
&acb->qcrs[acb->child_iter].qiov, acb->nb_sectors,
|
||||
quorum_aio_cb, &acb->qcrs[acb->child_iter]);
|
||||
|
||||
/* FIXME: rewrite failed children if acb->children_read > 1? */
|
||||
|
||||
return ret;
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static int quorum_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
static BlockAIOCB *quorum_aio_readv(BlockDriverState *bs,
|
||||
int64_t sector_num,
|
||||
QEMUIOVector *qiov,
|
||||
int nb_sectors,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
BDRVQuorumState *s = bs->opaque;
|
||||
QuorumAIOCB *acb = quorum_aio_get(bs, qiov, offset, bytes);
|
||||
int ret;
|
||||
|
||||
QuorumAIOCB *acb = quorum_aio_get(s, bs, qiov, sector_num,
|
||||
nb_sectors, cb, opaque);
|
||||
acb->is_read = true;
|
||||
acb->children_read = 0;
|
||||
|
||||
if (s->read_pattern == QUORUM_READ_PATTERN_QUORUM) {
|
||||
ret = read_quorum_children(acb);
|
||||
} else {
|
||||
ret = read_fifo_child(acb);
|
||||
acb->child_iter = s->num_children - 1;
|
||||
return read_quorum_children(acb);
|
||||
}
|
||||
quorum_aio_finalize(acb);
|
||||
|
||||
return ret;
|
||||
acb->child_iter = 0;
|
||||
return read_fifo_child(acb);
|
||||
}
|
||||
|
||||
static void write_quorum_entry(void *opaque)
|
||||
{
|
||||
QuorumCo *co = opaque;
|
||||
QuorumAIOCB *acb = co->acb;
|
||||
BDRVQuorumState *s = acb->bs->opaque;
|
||||
int i = co->idx;
|
||||
QuorumChildRequest *sacb = &acb->qcrs[i];
|
||||
|
||||
sacb->bs = s->children[i]->bs;
|
||||
sacb->ret = bdrv_co_pwritev(s->children[i], acb->offset, acb->bytes,
|
||||
acb->qiov, 0);
|
||||
if (sacb->ret == 0) {
|
||||
acb->success_count++;
|
||||
} else {
|
||||
quorum_report_bad_acb(sacb, sacb->ret);
|
||||
}
|
||||
acb->count++;
|
||||
assert(acb->count <= s->num_children);
|
||||
assert(acb->success_count <= s->num_children);
|
||||
|
||||
/* Wake up the caller after the last write */
|
||||
if (acb->count == s->num_children) {
|
||||
qemu_coroutine_enter_if_inactive(acb->co);
|
||||
}
|
||||
}
|
||||
|
||||
static int quorum_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||
static BlockAIOCB *quorum_aio_writev(BlockDriverState *bs,
|
||||
int64_t sector_num,
|
||||
QEMUIOVector *qiov,
|
||||
int nb_sectors,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
BDRVQuorumState *s = bs->opaque;
|
||||
QuorumAIOCB *acb = quorum_aio_get(bs, qiov, offset, bytes);
|
||||
int i, ret;
|
||||
QuorumAIOCB *acb = quorum_aio_get(s, bs, qiov, sector_num, nb_sectors,
|
||||
cb, opaque);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
Coroutine *co;
|
||||
QuorumCo data = {
|
||||
.acb = acb,
|
||||
.idx = i,
|
||||
};
|
||||
|
||||
co = qemu_coroutine_create(write_quorum_entry, &data);
|
||||
qemu_coroutine_enter(co);
|
||||
acb->qcrs[i].aiocb = bdrv_aio_writev(s->children[i], sector_num,
|
||||
qiov, nb_sectors, &quorum_aio_cb,
|
||||
&acb->qcrs[i]);
|
||||
}
|
||||
|
||||
while (acb->count < s->num_children) {
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
|
||||
quorum_has_too_much_io_failed(acb);
|
||||
|
||||
ret = acb->vote_ret;
|
||||
quorum_aio_finalize(acb);
|
||||
|
||||
return ret;
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static int64_t quorum_getlength(BlockDriverState *bs)
|
||||
@@ -785,7 +767,8 @@ static coroutine_fn int quorum_co_flush(BlockDriverState *bs)
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
result = bdrv_co_flush(s->children[i]->bs);
|
||||
if (result) {
|
||||
quorum_report_bad(QUORUM_OP_TYPE_FLUSH, 0, 0,
|
||||
quorum_report_bad(QUORUM_OP_TYPE_FLUSH, 0,
|
||||
bdrv_nb_sectors(s->children[i]->bs),
|
||||
s->children[i]->bs->node_name, result);
|
||||
result_value.l = result;
|
||||
quorum_count_vote(&error_votes, &result_value, i);
|
||||
@@ -1030,17 +1013,10 @@ static void quorum_add_child(BlockDriverState *bs, BlockDriverState *child_bs,
|
||||
|
||||
/* We can safely add the child now */
|
||||
bdrv_ref(child_bs);
|
||||
|
||||
child = bdrv_attach_child(bs, child_bs, indexstr, &child_format, errp);
|
||||
if (child == NULL) {
|
||||
s->next_child_index--;
|
||||
bdrv_unref(child_bs);
|
||||
goto out;
|
||||
}
|
||||
child = bdrv_attach_child(bs, child_bs, indexstr, &child_format);
|
||||
s->children = g_renew(BdrvChild *, s->children, s->num_children + 1);
|
||||
s->children[s->num_children++] = child;
|
||||
|
||||
out:
|
||||
bdrv_drained_end(bs);
|
||||
}
|
||||
|
||||
@@ -1094,15 +1070,19 @@ static void quorum_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
children = qlist_new();
|
||||
for (i = 0; i < s->num_children; i++) {
|
||||
QINCREF(s->children[i]->bs->full_open_options);
|
||||
qlist_append(children, s->children[i]->bs->full_open_options);
|
||||
qlist_append_obj(children,
|
||||
QOBJECT(s->children[i]->bs->full_open_options));
|
||||
}
|
||||
|
||||
opts = qdict_new();
|
||||
qdict_put_str(opts, "driver", "quorum");
|
||||
qdict_put_int(opts, QUORUM_OPT_VOTE_THRESHOLD, s->threshold);
|
||||
qdict_put_bool(opts, QUORUM_OPT_BLKVERIFY, s->is_blkverify);
|
||||
qdict_put_bool(opts, QUORUM_OPT_REWRITE, s->rewrite_corrupted);
|
||||
qdict_put(opts, "children", children);
|
||||
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("quorum")));
|
||||
qdict_put_obj(opts, QUORUM_OPT_VOTE_THRESHOLD,
|
||||
QOBJECT(qint_from_int(s->threshold)));
|
||||
qdict_put_obj(opts, QUORUM_OPT_BLKVERIFY,
|
||||
QOBJECT(qbool_from_bool(s->is_blkverify)));
|
||||
qdict_put_obj(opts, QUORUM_OPT_REWRITE,
|
||||
QOBJECT(qbool_from_bool(s->rewrite_corrupted)));
|
||||
qdict_put_obj(opts, "children", QOBJECT(children));
|
||||
|
||||
bs->full_open_options = opts;
|
||||
}
|
||||
@@ -1121,14 +1101,12 @@ static BlockDriver bdrv_quorum = {
|
||||
|
||||
.bdrv_getlength = quorum_getlength,
|
||||
|
||||
.bdrv_co_preadv = quorum_co_preadv,
|
||||
.bdrv_co_pwritev = quorum_co_pwritev,
|
||||
.bdrv_aio_readv = quorum_aio_readv,
|
||||
.bdrv_aio_writev = quorum_aio_writev,
|
||||
|
||||
.bdrv_add_child = quorum_add_child,
|
||||
.bdrv_del_child = quorum_del_child,
|
||||
|
||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
||||
|
||||
.is_filter = true,
|
||||
.bdrv_recurse_is_first_non_filter = quorum_recurse_is_first_non_filter,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -24,6 +24,7 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "block/block_int.h"
|
||||
#include "qemu/module.h"
|
||||
#include "block/raw-aio.h"
|
||||
@@ -31,7 +32,6 @@
|
||||
#include "block/thread-pool.h"
|
||||
#include "qemu/iov.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/util.h"
|
||||
#include <windows.h>
|
||||
#include <winioctl.h>
|
||||
|
||||
@@ -252,8 +252,7 @@ static void raw_probe_alignment(BlockDriverState *bs, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
static void raw_parse_flags(int flags, bool use_aio, int *access_flags,
|
||||
DWORD *overlapped)
|
||||
static void raw_parse_flags(int flags, int *access_flags, DWORD *overlapped)
|
||||
{
|
||||
assert(access_flags != NULL);
|
||||
assert(overlapped != NULL);
|
||||
@@ -265,7 +264,7 @@ static void raw_parse_flags(int flags, bool use_aio, int *access_flags,
|
||||
}
|
||||
|
||||
*overlapped = FILE_ATTRIBUTE_NORMAL;
|
||||
if (use_aio) {
|
||||
if (flags & BDRV_O_NATIVE_AIO) {
|
||||
*overlapped |= FILE_FLAG_OVERLAPPED;
|
||||
}
|
||||
if (flags & BDRV_O_NOCACHE) {
|
||||
@@ -276,7 +275,12 @@ static void raw_parse_flags(int flags, bool use_aio, int *access_flags,
|
||||
static void raw_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
bdrv_parse_filename_strip_prefix(filename, "file:", options);
|
||||
/* The filename does not have to be prefixed by the protocol name, since
|
||||
* "file" is the default protocol; therefore, the return value of this
|
||||
* function call can be ignored. */
|
||||
strstart(filename, "file:", &filename);
|
||||
|
||||
qdict_put_obj(options, "filename", QOBJECT(qstring_from_str(filename)));
|
||||
}
|
||||
|
||||
static QemuOptsList raw_runtime_opts = {
|
||||
@@ -288,35 +292,10 @@ static QemuOptsList raw_runtime_opts = {
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "File name of the image",
|
||||
},
|
||||
{
|
||||
.name = "aio",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "host AIO implementation (threads, native)",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static bool get_aio_option(QemuOpts *opts, int flags, Error **errp)
|
||||
{
|
||||
BlockdevAioOptions aio, aio_default;
|
||||
|
||||
aio_default = (flags & BDRV_O_NATIVE_AIO) ? BLOCKDEV_AIO_OPTIONS_NATIVE
|
||||
: BLOCKDEV_AIO_OPTIONS_THREADS;
|
||||
aio = qapi_enum_parse(BlockdevAioOptions_lookup, qemu_opt_get(opts, "aio"),
|
||||
BLOCKDEV_AIO_OPTIONS__MAX, aio_default, errp);
|
||||
|
||||
switch (aio) {
|
||||
case BLOCKDEV_AIO_OPTIONS_NATIVE:
|
||||
return true;
|
||||
case BLOCKDEV_AIO_OPTIONS_THREADS:
|
||||
return false;
|
||||
default:
|
||||
error_setg(errp, "Invalid AIO option");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
@@ -326,7 +305,6 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
QemuOpts *opts;
|
||||
Error *local_err = NULL;
|
||||
const char *filename;
|
||||
bool use_aio;
|
||||
int ret;
|
||||
|
||||
s->type = FTYPE_FILE;
|
||||
@@ -339,22 +317,9 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (qdict_get_try_bool(options, "locking", false)) {
|
||||
error_setg(errp, "locking=on is not supported on Windows");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
filename = qemu_opt_get(opts, "filename");
|
||||
|
||||
use_aio = get_aio_option(opts, flags, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
raw_parse_flags(flags, use_aio, &access_flags, &overlapped);
|
||||
raw_parse_flags(flags, &access_flags, &overlapped);
|
||||
|
||||
if (filename[0] && filename[1] == ':') {
|
||||
snprintf(s->drive_path, sizeof(s->drive_path), "%c:\\", filename[0]);
|
||||
@@ -373,7 +338,6 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
if (s->hfile == INVALID_HANDLE_VALUE) {
|
||||
int err = GetLastError();
|
||||
|
||||
error_setg_win32(errp, err, "Could not open '%s'", filename);
|
||||
if (err == ERROR_ACCESS_DENIED) {
|
||||
ret = -EACCES;
|
||||
} else {
|
||||
@@ -382,7 +346,7 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (use_aio) {
|
||||
if (flags & BDRV_O_NATIVE_AIO) {
|
||||
s->aio = win32_aio_init();
|
||||
if (s->aio == NULL) {
|
||||
CloseHandle(s->hfile);
|
||||
@@ -461,19 +425,12 @@ static void raw_close(BlockDriverState *bs)
|
||||
}
|
||||
}
|
||||
|
||||
static int raw_truncate(BlockDriverState *bs, int64_t offset,
|
||||
PreallocMode prealloc, Error **errp)
|
||||
static int raw_truncate(BlockDriverState *bs, int64_t offset)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
LONG low, high;
|
||||
DWORD dwPtrLow;
|
||||
|
||||
if (prealloc != PREALLOC_MODE_OFF) {
|
||||
error_setg(errp, "Unsupported preallocation mode '%s'",
|
||||
PreallocMode_lookup[prealloc]);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
low = offset;
|
||||
high = offset >> 32;
|
||||
|
||||
@@ -483,11 +440,11 @@ static int raw_truncate(BlockDriverState *bs, int64_t offset,
|
||||
*/
|
||||
dwPtrLow = SetFilePointer(s->hfile, low, &high, FILE_BEGIN);
|
||||
if (dwPtrLow == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
|
||||
error_setg_win32(errp, GetLastError(), "SetFilePointer error");
|
||||
fprintf(stderr, "SetFilePointer error: %lu\n", GetLastError());
|
||||
return -EIO;
|
||||
}
|
||||
if (SetEndOfFile(s->hfile) == 0) {
|
||||
error_setg_win32(errp, GetLastError(), "SetEndOfFile error");
|
||||
fprintf(stderr, "SetEndOfFile error: %lu\n", GetLastError());
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
@@ -673,7 +630,10 @@ static int hdev_probe_device(const char *filename)
|
||||
static void hdev_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
bdrv_parse_filename_strip_prefix(filename, "host_device:", options);
|
||||
/* The prefix is optional, just as for "file". */
|
||||
strstart(filename, "host_device:", &filename);
|
||||
|
||||
qdict_put_obj(options, "filename", QOBJECT(qstring_from_str(filename)));
|
||||
}
|
||||
|
||||
static int hdev_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
@@ -687,7 +647,6 @@ static int hdev_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
|
||||
Error *local_err = NULL;
|
||||
const char *filename;
|
||||
bool use_aio;
|
||||
|
||||
QemuOpts *opts = qemu_opts_create(&raw_runtime_opts, NULL, 0,
|
||||
&error_abort);
|
||||
@@ -700,16 +659,6 @@ static int hdev_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
|
||||
filename = qemu_opt_get(opts, "filename");
|
||||
|
||||
use_aio = get_aio_option(opts, flags, &local_err);
|
||||
if (!local_err && use_aio) {
|
||||
error_setg(&local_err, "AIO is not supported on Windows host devices");
|
||||
}
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (strstart(filename, "/dev/cdrom", NULL)) {
|
||||
if (find_cdrom(device_name, sizeof(device_name)) < 0) {
|
||||
error_setg(errp, "Could not open CD-ROM drive");
|
||||
@@ -728,7 +677,7 @@ static int hdev_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
}
|
||||
s->type = find_device_type(bs, filename);
|
||||
|
||||
raw_parse_flags(flags, use_aio, &access_flags, &overlapped);
|
||||
raw_parse_flags(flags, &access_flags, &overlapped);
|
||||
|
||||
create_flags = OPEN_EXISTING;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* BlockDriver implementation for "raw" format driver
|
||||
/* BlockDriver implementation for "raw"
|
||||
*
|
||||
* Copyright (C) 2010-2016 Red Hat, Inc.
|
||||
* Copyright (C) 2010, Blue Swirl <blauwirbel@gmail.com>
|
||||
@@ -31,30 +31,6 @@
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/option.h"
|
||||
|
||||
typedef struct BDRVRawState {
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
bool has_size;
|
||||
} BDRVRawState;
|
||||
|
||||
static QemuOptsList raw_runtime_opts = {
|
||||
.name = "raw",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(raw_runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "offset",
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "offset in the disk where the image starts",
|
||||
},
|
||||
{
|
||||
.name = "size",
|
||||
.type = QEMU_OPT_SIZE,
|
||||
.help = "virtual disk size",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static QemuOptsList raw_create_opts = {
|
||||
.name = "raw-create-opts",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(raw_create_opts.head),
|
||||
@@ -68,116 +44,16 @@ static QemuOptsList raw_create_opts = {
|
||||
}
|
||||
};
|
||||
|
||||
static int raw_read_options(QDict *options, BlockDriverState *bs,
|
||||
BDRVRawState *s, Error **errp)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
QemuOpts *opts = NULL;
|
||||
int64_t real_size = 0;
|
||||
int ret;
|
||||
|
||||
real_size = bdrv_getlength(bs->file->bs);
|
||||
if (real_size < 0) {
|
||||
error_setg_errno(errp, -real_size, "Could not get image size");
|
||||
return real_size;
|
||||
}
|
||||
|
||||
opts = qemu_opts_create(&raw_runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
s->offset = qemu_opt_get_size(opts, "offset", 0);
|
||||
if (s->offset > real_size) {
|
||||
error_setg(errp, "Offset (%" PRIu64 ") cannot be greater than "
|
||||
"size of the containing file (%" PRId64 ")",
|
||||
s->offset, real_size);
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (qemu_opt_find(opts, "size") != NULL) {
|
||||
s->size = qemu_opt_get_size(opts, "size", 0);
|
||||
s->has_size = true;
|
||||
} else {
|
||||
s->has_size = false;
|
||||
s->size = real_size - s->offset;
|
||||
}
|
||||
|
||||
/* Check size and offset */
|
||||
if ((real_size - s->offset) < s->size) {
|
||||
error_setg(errp, "The sum of offset (%" PRIu64 ") and size "
|
||||
"(%" PRIu64 ") has to be smaller or equal to the "
|
||||
" actual size of the containing file (%" PRId64 ")",
|
||||
s->offset, s->size, real_size);
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Make sure size is multiple of BDRV_SECTOR_SIZE to prevent rounding
|
||||
* up and leaking out of the specified area. */
|
||||
if (s->has_size && !QEMU_IS_ALIGNED(s->size, BDRV_SECTOR_SIZE)) {
|
||||
error_setg(errp, "Specified size is not multiple of %llu",
|
||||
BDRV_SECTOR_SIZE);
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
|
||||
qemu_opts_del(opts);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int raw_reopen_prepare(BDRVReopenState *reopen_state,
|
||||
BlockReopenQueue *queue, Error **errp)
|
||||
{
|
||||
assert(reopen_state != NULL);
|
||||
assert(reopen_state->bs != NULL);
|
||||
|
||||
reopen_state->opaque = g_new0(BDRVRawState, 1);
|
||||
|
||||
return raw_read_options(
|
||||
reopen_state->options,
|
||||
reopen_state->bs,
|
||||
reopen_state->opaque,
|
||||
errp);
|
||||
}
|
||||
|
||||
static void raw_reopen_commit(BDRVReopenState *state)
|
||||
{
|
||||
BDRVRawState *new_s = state->opaque;
|
||||
BDRVRawState *s = state->bs->opaque;
|
||||
|
||||
memcpy(s, new_s, sizeof(BDRVRawState));
|
||||
|
||||
g_free(state->opaque);
|
||||
state->opaque = NULL;
|
||||
}
|
||||
|
||||
static void raw_reopen_abort(BDRVReopenState *state)
|
||||
{
|
||||
g_free(state->opaque);
|
||||
state->opaque = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov,
|
||||
int flags)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
||||
if (offset > UINT64_MAX - s->offset) {
|
||||
return -EINVAL;
|
||||
}
|
||||
offset += s->offset;
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO);
|
||||
return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
|
||||
}
|
||||
@@ -186,23 +62,11 @@ static int coroutine_fn raw_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov,
|
||||
int flags)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
void *buf = NULL;
|
||||
BlockDriver *drv;
|
||||
QEMUIOVector local_qiov;
|
||||
int ret;
|
||||
|
||||
if (s->has_size && (offset > s->size || bytes > (s->size - offset))) {
|
||||
/* There's not enough space for the data. Don't write anything and just
|
||||
* fail to prevent leaking out of the size specified in options. */
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
if (offset > UINT64_MAX - s->offset) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (bs->probed && offset < BLOCK_PROBE_BUF_SIZE && bytes) {
|
||||
/* Handling partial writes would be a pain - so we just
|
||||
* require that guests have 512-byte request alignment if
|
||||
@@ -237,8 +101,6 @@ static int coroutine_fn raw_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
qiov = &local_qiov;
|
||||
}
|
||||
|
||||
offset += s->offset;
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO);
|
||||
ret = bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
|
||||
|
||||
@@ -255,86 +117,28 @@ static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
|
||||
int nb_sectors, int *pnum,
|
||||
BlockDriverState **file)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
*pnum = nb_sectors;
|
||||
*file = bs->file->bs;
|
||||
sector_num += s->offset / BDRV_SECTOR_SIZE;
|
||||
return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
|
||||
return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID | BDRV_BLOCK_DATA |
|
||||
(sector_num << BDRV_SECTOR_BITS);
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int bytes,
|
||||
int64_t offset, int count,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
if (offset > UINT64_MAX - s->offset) {
|
||||
return -EINVAL;
|
||||
}
|
||||
offset += s->offset;
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, count, flags);
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int bytes)
|
||||
int64_t offset, int count)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
if (offset > UINT64_MAX - s->offset) {
|
||||
return -EINVAL;
|
||||
}
|
||||
offset += s->offset;
|
||||
return bdrv_co_pdiscard(bs->file->bs, offset, bytes);
|
||||
return bdrv_co_pdiscard(bs->file->bs, offset, count);
|
||||
}
|
||||
|
||||
static int64_t raw_getlength(BlockDriverState *bs)
|
||||
{
|
||||
int64_t len;
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
||||
/* Update size. It should not change unless the file was externally
|
||||
* modified. */
|
||||
len = bdrv_getlength(bs->file->bs);
|
||||
if (len < 0) {
|
||||
return len;
|
||||
}
|
||||
|
||||
if (len < s->offset) {
|
||||
s->size = 0;
|
||||
} else {
|
||||
if (s->has_size) {
|
||||
/* Try to honour the size */
|
||||
s->size = MIN(s->size, len - s->offset);
|
||||
} else {
|
||||
s->size = len - s->offset;
|
||||
}
|
||||
}
|
||||
|
||||
return s->size;
|
||||
}
|
||||
|
||||
static BlockMeasureInfo *raw_measure(QemuOpts *opts, BlockDriverState *in_bs,
|
||||
Error **errp)
|
||||
{
|
||||
BlockMeasureInfo *info;
|
||||
int64_t required;
|
||||
|
||||
if (in_bs) {
|
||||
required = bdrv_getlength(in_bs);
|
||||
if (required < 0) {
|
||||
error_setg_errno(errp, -required, "Unable to get image size");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
required = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
info = g_new(BlockMeasureInfo, 1);
|
||||
info->required = required;
|
||||
|
||||
/* Unallocated sectors count towards the file size in raw images */
|
||||
info->fully_allocated = info->required;
|
||||
return info;
|
||||
return bdrv_getlength(bs->file->bs);
|
||||
}
|
||||
|
||||
static int raw_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
||||
@@ -352,24 +156,9 @@ static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
static int raw_truncate(BlockDriverState *bs, int64_t offset,
|
||||
PreallocMode prealloc, Error **errp)
|
||||
static int raw_truncate(BlockDriverState *bs, int64_t offset)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
||||
if (s->has_size) {
|
||||
error_setg(errp, "Cannot resize fixed-size raw disks");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (INT64_MAX - offset < s->offset) {
|
||||
error_setg(errp, "Disk size too large for the chosen offset");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
s->size = offset;
|
||||
offset += s->offset;
|
||||
return bdrv_truncate(bs->file, offset, prealloc, errp);
|
||||
return bdrv_truncate(bs->file->bs, offset);
|
||||
}
|
||||
|
||||
static int raw_media_changed(BlockDriverState *bs)
|
||||
@@ -387,13 +176,12 @@ static void raw_lock_medium(BlockDriverState *bs, bool locked)
|
||||
bdrv_lock_medium(bs->file->bs, locked);
|
||||
}
|
||||
|
||||
static int raw_co_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
|
||||
static BlockAIOCB *raw_aio_ioctl(BlockDriverState *bs,
|
||||
unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
if (s->offset || s->has_size) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
return bdrv_co_ioctl(bs->file->bs, req, buf);
|
||||
return bdrv_aio_ioctl(bs->file->bs, req, buf, cb, opaque);
|
||||
}
|
||||
|
||||
static int raw_has_zero_init(BlockDriverState *bs)
|
||||
@@ -409,15 +197,6 @@ static int raw_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
int ret;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bs->sg = bs->file->bs->sg;
|
||||
bs->supported_write_flags = BDRV_REQ_FUA &
|
||||
bs->file->bs->supported_write_flags;
|
||||
@@ -435,16 +214,6 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
bs->file->bs->filename);
|
||||
}
|
||||
|
||||
ret = raw_read_options(options, bs, s, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (bs->sg && (s->offset || s->has_size)) {
|
||||
error_setg(errp, "Cannot use offset/size with SCSI generic devices");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -462,40 +231,20 @@ static int raw_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||
|
||||
static int raw_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
int ret;
|
||||
|
||||
ret = bdrv_probe_blocksizes(bs->file->bs, bsz);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!QEMU_IS_ALIGNED(s->offset, MAX(bsz->log, bsz->phys))) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return bdrv_probe_blocksizes(bs->file->bs, bsz);
|
||||
}
|
||||
|
||||
static int raw_probe_geometry(BlockDriverState *bs, HDGeometry *geo)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
if (s->offset || s->has_size) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
return bdrv_probe_geometry(bs->file->bs, geo);
|
||||
}
|
||||
|
||||
BlockDriver bdrv_raw = {
|
||||
.format_name = "raw",
|
||||
.instance_size = sizeof(BDRVRawState),
|
||||
.bdrv_probe = &raw_probe,
|
||||
.bdrv_reopen_prepare = &raw_reopen_prepare,
|
||||
.bdrv_reopen_commit = &raw_reopen_commit,
|
||||
.bdrv_reopen_abort = &raw_reopen_abort,
|
||||
.bdrv_open = &raw_open,
|
||||
.bdrv_close = &raw_close,
|
||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
||||
.bdrv_create = &raw_create,
|
||||
.bdrv_co_preadv = &raw_co_preadv,
|
||||
.bdrv_co_pwritev = &raw_co_pwritev,
|
||||
@@ -505,7 +254,6 @@ BlockDriver bdrv_raw = {
|
||||
.bdrv_truncate = &raw_truncate,
|
||||
.bdrv_getlength = &raw_getlength,
|
||||
.has_variable_length = true,
|
||||
.bdrv_measure = &raw_measure,
|
||||
.bdrv_get_info = &raw_get_info,
|
||||
.bdrv_refresh_limits = &raw_refresh_limits,
|
||||
.bdrv_probe_blocksizes = &raw_probe_blocksizes,
|
||||
@@ -513,7 +261,7 @@ BlockDriver bdrv_raw = {
|
||||
.bdrv_media_changed = &raw_media_changed,
|
||||
.bdrv_eject = &raw_eject,
|
||||
.bdrv_lock_medium = &raw_lock_medium,
|
||||
.bdrv_co_ioctl = &raw_co_ioctl,
|
||||
.bdrv_aio_ioctl = &raw_aio_ioctl,
|
||||
.create_opts = &raw_create_opts,
|
||||
.bdrv_has_zero_init = &raw_has_zero_init
|
||||
};
|
||||
+279
-420
@@ -13,14 +13,13 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include <rbd/librbd.h>
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "block/block_int.h"
|
||||
#include "crypto/secret.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
|
||||
#include <rbd/librbd.h>
|
||||
|
||||
/*
|
||||
* When specifying the image filename use:
|
||||
@@ -56,15 +55,13 @@
|
||||
|
||||
#define OBJ_MAX_SIZE (1UL << OBJ_DEFAULT_OBJ_ORDER)
|
||||
|
||||
#define RBD_MAX_CONF_NAME_SIZE 128
|
||||
#define RBD_MAX_CONF_VAL_SIZE 512
|
||||
#define RBD_MAX_CONF_SIZE 1024
|
||||
#define RBD_MAX_POOL_NAME_SIZE 128
|
||||
#define RBD_MAX_SNAP_NAME_SIZE 128
|
||||
#define RBD_MAX_SNAPS 100
|
||||
|
||||
/* The LIBRBD_SUPPORTS_IOVEC is defined in librbd.h */
|
||||
#ifdef LIBRBD_SUPPORTS_IOVEC
|
||||
#define LIBRBD_USE_IOVEC 1
|
||||
#else
|
||||
#define LIBRBD_USE_IOVEC 0
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
RBD_AIO_READ,
|
||||
RBD_AIO_WRITE,
|
||||
@@ -74,6 +71,7 @@ typedef enum {
|
||||
|
||||
typedef struct RBDAIOCB {
|
||||
BlockAIOCB common;
|
||||
QEMUBH *bh;
|
||||
int64_t ret;
|
||||
QEMUIOVector *qiov;
|
||||
char *bounce;
|
||||
@@ -94,29 +92,46 @@ typedef struct BDRVRBDState {
|
||||
rados_t cluster;
|
||||
rados_ioctx_t io_ctx;
|
||||
rbd_image_t image;
|
||||
char *image_name;
|
||||
char name[RBD_MAX_IMAGE_NAME_SIZE];
|
||||
char *snap;
|
||||
} BDRVRBDState;
|
||||
|
||||
static char *qemu_rbd_next_tok(char *src, char delim, char **p)
|
||||
static int qemu_rbd_next_tok(char *dst, int dst_len,
|
||||
char *src, char delim,
|
||||
const char *name,
|
||||
char **p, Error **errp)
|
||||
{
|
||||
int l;
|
||||
char *end;
|
||||
|
||||
*p = NULL;
|
||||
|
||||
for (end = src; *end; ++end) {
|
||||
if (delim != '\0') {
|
||||
for (end = src; *end; ++end) {
|
||||
if (*end == delim) {
|
||||
break;
|
||||
}
|
||||
if (*end == '\\' && end[1] != '\0') {
|
||||
end++;
|
||||
}
|
||||
}
|
||||
if (*end == delim) {
|
||||
break;
|
||||
}
|
||||
if (*end == '\\' && end[1] != '\0') {
|
||||
end++;
|
||||
*p = end + 1;
|
||||
*end = '\0';
|
||||
}
|
||||
}
|
||||
if (*end == delim) {
|
||||
*p = end + 1;
|
||||
*end = '\0';
|
||||
l = strlen(src);
|
||||
if (l >= dst_len) {
|
||||
error_setg(errp, "%s too long", name);
|
||||
return -EINVAL;
|
||||
} else if (l == 0) {
|
||||
error_setg(errp, "%s too short", name);
|
||||
return -EINVAL;
|
||||
}
|
||||
return src;
|
||||
|
||||
pstrcpy(dst, dst_len, src);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qemu_rbd_unescape(char *src)
|
||||
@@ -132,92 +147,87 @@ static void qemu_rbd_unescape(char *src)
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
static void qemu_rbd_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
static int qemu_rbd_parsename(const char *filename,
|
||||
char *pool, int pool_len,
|
||||
char *snap, int snap_len,
|
||||
char *name, int name_len,
|
||||
char *conf, int conf_len,
|
||||
Error **errp)
|
||||
{
|
||||
const char *start;
|
||||
char *p, *buf;
|
||||
QList *keypairs = NULL;
|
||||
char *found_str;
|
||||
int ret;
|
||||
|
||||
if (!strstart(filename, "rbd:", &start)) {
|
||||
error_setg(errp, "File name must start with 'rbd:'");
|
||||
return;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
buf = g_strdup(start);
|
||||
p = buf;
|
||||
*snap = '\0';
|
||||
*conf = '\0';
|
||||
|
||||
found_str = qemu_rbd_next_tok(p, '/', &p);
|
||||
if (!p) {
|
||||
error_setg(errp, "Pool name is required");
|
||||
ret = qemu_rbd_next_tok(pool, pool_len, p,
|
||||
'/', "pool name", &p, errp);
|
||||
if (ret < 0 || !p) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
qemu_rbd_unescape(found_str);
|
||||
qdict_put_str(options, "pool", found_str);
|
||||
qemu_rbd_unescape(pool);
|
||||
|
||||
if (strchr(p, '@')) {
|
||||
found_str = qemu_rbd_next_tok(p, '@', &p);
|
||||
qemu_rbd_unescape(found_str);
|
||||
qdict_put_str(options, "image", found_str);
|
||||
|
||||
found_str = qemu_rbd_next_tok(p, ':', &p);
|
||||
qemu_rbd_unescape(found_str);
|
||||
qdict_put_str(options, "snapshot", found_str);
|
||||
ret = qemu_rbd_next_tok(name, name_len, p,
|
||||
'@', "object name", &p, errp);
|
||||
if (ret < 0) {
|
||||
goto done;
|
||||
}
|
||||
ret = qemu_rbd_next_tok(snap, snap_len, p,
|
||||
':', "snap name", &p, errp);
|
||||
qemu_rbd_unescape(snap);
|
||||
} else {
|
||||
found_str = qemu_rbd_next_tok(p, ':', &p);
|
||||
qemu_rbd_unescape(found_str);
|
||||
qdict_put_str(options, "image", found_str);
|
||||
ret = qemu_rbd_next_tok(name, name_len, p,
|
||||
':', "object name", &p, errp);
|
||||
}
|
||||
if (!p) {
|
||||
qemu_rbd_unescape(name);
|
||||
if (ret < 0 || !p) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* The following are essentially all key/value pairs, and we treat
|
||||
* 'id' and 'conf' a bit special. Key/value pairs may be in any order. */
|
||||
while (p) {
|
||||
char *name, *value;
|
||||
name = qemu_rbd_next_tok(p, '=', &p);
|
||||
if (!p) {
|
||||
error_setg(errp, "conf option %s has no value", name);
|
||||
break;
|
||||
}
|
||||
|
||||
qemu_rbd_unescape(name);
|
||||
|
||||
value = qemu_rbd_next_tok(p, ':', &p);
|
||||
qemu_rbd_unescape(value);
|
||||
|
||||
if (!strcmp(name, "conf")) {
|
||||
qdict_put_str(options, "conf", value);
|
||||
} else if (!strcmp(name, "id")) {
|
||||
qdict_put_str(options, "user", value);
|
||||
} else {
|
||||
/*
|
||||
* We pass these internally to qemu_rbd_set_keypairs(), so
|
||||
* we can get away with the simpler list of [ "key1",
|
||||
* "value1", "key2", "value2" ] rather than a raw dict
|
||||
* { "key1": "value1", "key2": "value2" } where we can't
|
||||
* guarantee order, or even a more correct but complex
|
||||
* [ { "key1": "value1" }, { "key2": "value2" } ]
|
||||
*/
|
||||
if (!keypairs) {
|
||||
keypairs = qlist_new();
|
||||
}
|
||||
qlist_append_str(keypairs, name);
|
||||
qlist_append_str(keypairs, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (keypairs) {
|
||||
qdict_put(options, "=keyvalue-pairs",
|
||||
qobject_to_json(QOBJECT(keypairs)));
|
||||
}
|
||||
ret = qemu_rbd_next_tok(conf, conf_len, p,
|
||||
'\0', "configuration", &p, errp);
|
||||
|
||||
done:
|
||||
g_free(buf);
|
||||
QDECREF(keypairs);
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *qemu_rbd_parse_clientname(const char *conf, char *clientname)
|
||||
{
|
||||
const char *p = conf;
|
||||
|
||||
while (*p) {
|
||||
int len;
|
||||
const char *end = strchr(p, ':');
|
||||
|
||||
if (end) {
|
||||
len = end - p;
|
||||
} else {
|
||||
len = strlen(p);
|
||||
}
|
||||
|
||||
if (strncmp(p, "id=", 3) == 0) {
|
||||
len -= 3;
|
||||
strncpy(clientname, p + 3, len);
|
||||
clientname[len] = '\0';
|
||||
return clientname;
|
||||
}
|
||||
if (end == NULL) {
|
||||
break;
|
||||
}
|
||||
p = end + 1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,129 +250,94 @@ static int qemu_rbd_set_auth(rados_t cluster, const char *secretid,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json,
|
||||
Error **errp)
|
||||
|
||||
static int qemu_rbd_set_conf(rados_t cluster, const char *conf,
|
||||
bool only_read_conf_file,
|
||||
Error **errp)
|
||||
{
|
||||
QList *keypairs;
|
||||
QString *name;
|
||||
QString *value;
|
||||
const char *key;
|
||||
size_t remaining;
|
||||
char *p, *buf;
|
||||
char name[RBD_MAX_CONF_NAME_SIZE];
|
||||
char value[RBD_MAX_CONF_VAL_SIZE];
|
||||
int ret = 0;
|
||||
|
||||
if (!keypairs_json) {
|
||||
return ret;
|
||||
}
|
||||
keypairs = qobject_to_qlist(qobject_from_json(keypairs_json,
|
||||
&error_abort));
|
||||
remaining = qlist_size(keypairs) / 2;
|
||||
assert(remaining);
|
||||
buf = g_strdup(conf);
|
||||
p = buf;
|
||||
|
||||
while (remaining--) {
|
||||
name = qobject_to_qstring(qlist_pop(keypairs));
|
||||
value = qobject_to_qstring(qlist_pop(keypairs));
|
||||
assert(name && value);
|
||||
key = qstring_get_str(name);
|
||||
|
||||
ret = rados_conf_set(cluster, key, qstring_get_str(value));
|
||||
QDECREF(name);
|
||||
QDECREF(value);
|
||||
while (p) {
|
||||
ret = qemu_rbd_next_tok(name, sizeof(name), p,
|
||||
'=', "conf option name", &p, errp);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "invalid conf option %s", key);
|
||||
break;
|
||||
}
|
||||
qemu_rbd_unescape(name);
|
||||
|
||||
if (!p) {
|
||||
error_setg(errp, "conf option %s has no value", name);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = qemu_rbd_next_tok(value, sizeof(value), p,
|
||||
':', "conf option value", &p, errp);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
qemu_rbd_unescape(value);
|
||||
|
||||
if (strcmp(name, "conf") == 0) {
|
||||
/* read the conf file alone, so it doesn't override more
|
||||
specific settings for a particular device */
|
||||
if (only_read_conf_file) {
|
||||
ret = rados_conf_read_file(cluster, value);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error reading conf file %s",
|
||||
value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (strcmp(name, "id") == 0) {
|
||||
/* ignore, this is parsed by qemu_rbd_parse_clientname() */
|
||||
} else if (!only_read_conf_file) {
|
||||
ret = rados_conf_set(cluster, name, value);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "invalid conf option %s", name);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QDECREF(keypairs);
|
||||
g_free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
|
||||
{
|
||||
if (LIBRBD_USE_IOVEC) {
|
||||
RBDAIOCB *acb = rcb->acb;
|
||||
iov_memset(acb->qiov->iov, acb->qiov->niov, offs, 0,
|
||||
acb->qiov->size - offs);
|
||||
} else {
|
||||
memset(rcb->buf + offs, 0, rcb->size - offs);
|
||||
}
|
||||
}
|
||||
|
||||
static QemuOptsList runtime_opts = {
|
||||
.name = "rbd",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "pool",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Rados pool name",
|
||||
},
|
||||
{
|
||||
.name = "image",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Image name in the pool",
|
||||
},
|
||||
{
|
||||
.name = "conf",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Rados config file location",
|
||||
},
|
||||
{
|
||||
.name = "snapshot",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Ceph snapshot name",
|
||||
},
|
||||
{
|
||||
/* maps to 'id' in rados_create() */
|
||||
.name = "user",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Rados id name",
|
||||
},
|
||||
/*
|
||||
* server.* extracted manually, see qemu_rbd_mon_host()
|
||||
*/
|
||||
{
|
||||
.name = "password-secret",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "ID of secret providing the password",
|
||||
},
|
||||
|
||||
/*
|
||||
* Keys for qemu_rbd_parse_filename(), not in the QAPI schema
|
||||
*/
|
||||
{
|
||||
/*
|
||||
* HACK: name starts with '=' so that qemu_opts_parse()
|
||||
* can't set it
|
||||
*/
|
||||
.name = "=keyvalue-pairs",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Legacy rados key/value option parameters",
|
||||
},
|
||||
{
|
||||
.name = "filename",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static int qemu_rbd_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int64_t bytes = 0;
|
||||
int64_t objsize;
|
||||
int obj_order = 0;
|
||||
const char *pool, *image_name, *conf, *user, *keypairs;
|
||||
char pool[RBD_MAX_POOL_NAME_SIZE];
|
||||
char name[RBD_MAX_IMAGE_NAME_SIZE];
|
||||
char snap_buf[RBD_MAX_SNAP_NAME_SIZE];
|
||||
char conf[RBD_MAX_CONF_SIZE];
|
||||
char clientname_buf[RBD_MAX_CONF_SIZE];
|
||||
char *clientname;
|
||||
const char *secretid;
|
||||
rados_t cluster;
|
||||
rados_ioctx_t io_ctx;
|
||||
QDict *options = NULL;
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
secretid = qemu_opt_get(opts, "password-secret");
|
||||
|
||||
if (qemu_rbd_parsename(filename, pool, sizeof(pool),
|
||||
snap_buf, sizeof(snap_buf),
|
||||
name, sizeof(name),
|
||||
conf, sizeof(conf), &local_err) < 0) {
|
||||
error_propagate(errp, local_err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Read out options */
|
||||
bytes = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
@@ -370,86 +345,66 @@ static int qemu_rbd_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||
if (objsize) {
|
||||
if ((objsize - 1) & objsize) { /* not a power of 2? */
|
||||
error_setg(errp, "obj size needs to be power of 2");
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
return -EINVAL;
|
||||
}
|
||||
if (objsize < 4096) {
|
||||
error_setg(errp, "obj size too small");
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
return -EINVAL;
|
||||
}
|
||||
obj_order = ctz32(objsize);
|
||||
}
|
||||
|
||||
options = qdict_new();
|
||||
qemu_rbd_parse_filename(filename, options, &local_err);
|
||||
if (local_err) {
|
||||
ret = -EINVAL;
|
||||
error_propagate(errp, local_err);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Caution: while qdict_get_try_str() is fine, getting non-string
|
||||
* types would require more care. When @options come from -blockdev
|
||||
* or blockdev_add, its members are typed according to the QAPI
|
||||
* schema, but when they come from -drive, they're all QString.
|
||||
*/
|
||||
pool = qdict_get_try_str(options, "pool");
|
||||
conf = qdict_get_try_str(options, "conf");
|
||||
user = qdict_get_try_str(options, "user");
|
||||
image_name = qdict_get_try_str(options, "image");
|
||||
keypairs = qdict_get_try_str(options, "=keyvalue-pairs");
|
||||
|
||||
ret = rados_create(&cluster, user);
|
||||
clientname = qemu_rbd_parse_clientname(conf, clientname_buf);
|
||||
ret = rados_create(&cluster, clientname);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error initializing");
|
||||
goto exit;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* try default location when conf=NULL, but ignore failure */
|
||||
ret = rados_conf_read_file(cluster, conf);
|
||||
if (conf && ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error reading conf file %s", conf);
|
||||
ret = -EIO;
|
||||
goto shutdown;
|
||||
if (strstr(conf, "conf=") == NULL) {
|
||||
/* try default location, but ignore failure */
|
||||
rados_conf_read_file(cluster, NULL);
|
||||
} else if (conf[0] != '\0' &&
|
||||
qemu_rbd_set_conf(cluster, conf, true, &local_err) < 0) {
|
||||
rados_shutdown(cluster);
|
||||
error_propagate(errp, local_err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = qemu_rbd_set_keypairs(cluster, keypairs, errp);
|
||||
if (ret < 0) {
|
||||
ret = -EIO;
|
||||
goto shutdown;
|
||||
if (conf[0] != '\0' &&
|
||||
qemu_rbd_set_conf(cluster, conf, false, &local_err) < 0) {
|
||||
rados_shutdown(cluster);
|
||||
error_propagate(errp, local_err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (qemu_rbd_set_auth(cluster, secretid, errp) < 0) {
|
||||
ret = -EIO;
|
||||
goto shutdown;
|
||||
rados_shutdown(cluster);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = rados_connect(cluster);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error connecting");
|
||||
goto shutdown;
|
||||
rados_shutdown(cluster);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = rados_ioctx_create(cluster, pool, &io_ctx);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error opening pool %s", pool);
|
||||
goto shutdown;
|
||||
rados_shutdown(cluster);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = rbd_create(io_ctx, image_name, bytes, &obj_order);
|
||||
ret = rbd_create(io_ctx, name, bytes, &obj_order);
|
||||
rados_ioctx_destroy(io_ctx);
|
||||
rados_shutdown(cluster);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error rbd create");
|
||||
return ret;
|
||||
}
|
||||
|
||||
rados_ioctx_destroy(io_ctx);
|
||||
|
||||
shutdown:
|
||||
rados_shutdown(cluster);
|
||||
|
||||
exit:
|
||||
QDECREF(options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -473,11 +428,11 @@ static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
||||
}
|
||||
} else {
|
||||
if (r < 0) {
|
||||
qemu_rbd_memset(rcb, 0);
|
||||
memset(rcb->buf, 0, rcb->size);
|
||||
acb->ret = r;
|
||||
acb->error = 1;
|
||||
} else if (r < rcb->size) {
|
||||
qemu_rbd_memset(rcb, r);
|
||||
memset(rcb->buf + r, 0, rcb->size - r);
|
||||
if (!acb->error) {
|
||||
acb->ret = rcb->size;
|
||||
}
|
||||
@@ -488,137 +443,92 @@ static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
||||
|
||||
g_free(rcb);
|
||||
|
||||
if (!LIBRBD_USE_IOVEC) {
|
||||
if (acb->cmd == RBD_AIO_READ) {
|
||||
qemu_iovec_from_buf(acb->qiov, 0, acb->bounce, acb->qiov->size);
|
||||
}
|
||||
qemu_vfree(acb->bounce);
|
||||
if (acb->cmd == RBD_AIO_READ) {
|
||||
qemu_iovec_from_buf(acb->qiov, 0, acb->bounce, acb->qiov->size);
|
||||
}
|
||||
|
||||
qemu_vfree(acb->bounce);
|
||||
acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret));
|
||||
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
|
||||
static char *qemu_rbd_mon_host(QDict *options, Error **errp)
|
||||
{
|
||||
const char **vals = g_new(const char *, qdict_size(options) + 1);
|
||||
char keybuf[32];
|
||||
const char *host, *port;
|
||||
char *rados_str;
|
||||
int i;
|
||||
|
||||
for (i = 0;; i++) {
|
||||
sprintf(keybuf, "server.%d.host", i);
|
||||
host = qdict_get_try_str(options, keybuf);
|
||||
qdict_del(options, keybuf);
|
||||
sprintf(keybuf, "server.%d.port", i);
|
||||
port = qdict_get_try_str(options, keybuf);
|
||||
qdict_del(options, keybuf);
|
||||
if (!host && !port) {
|
||||
break;
|
||||
}
|
||||
if (!host) {
|
||||
error_setg(errp, "Parameter server.%d.host is missing", i);
|
||||
rados_str = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strchr(host, ':')) {
|
||||
vals[i] = port ? g_strdup_printf("[%s]:%s", host, port)
|
||||
: g_strdup_printf("[%s]", host);
|
||||
} else {
|
||||
vals[i] = port ? g_strdup_printf("%s:%s", host, port)
|
||||
: g_strdup(host);
|
||||
}
|
||||
}
|
||||
vals[i] = NULL;
|
||||
|
||||
rados_str = i ? g_strjoinv(";", (char **)vals) : NULL;
|
||||
out:
|
||||
g_strfreev((char **)vals);
|
||||
return rados_str;
|
||||
}
|
||||
/* TODO Convert to fine grained options */
|
||||
static QemuOptsList runtime_opts = {
|
||||
.name = "rbd",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "filename",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Specification of the rbd image",
|
||||
},
|
||||
{
|
||||
.name = "password-secret",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "ID of secret providing the password",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
const char *pool, *snap, *conf, *user, *image_name, *keypairs;
|
||||
const char *secretid, *filename;
|
||||
char pool[RBD_MAX_POOL_NAME_SIZE];
|
||||
char snap_buf[RBD_MAX_SNAP_NAME_SIZE];
|
||||
char conf[RBD_MAX_CONF_SIZE];
|
||||
char clientname_buf[RBD_MAX_CONF_SIZE];
|
||||
char *clientname;
|
||||
const char *secretid;
|
||||
QemuOpts *opts;
|
||||
Error *local_err = NULL;
|
||||
char *mon_host = NULL;
|
||||
const char *filename;
|
||||
int r;
|
||||
|
||||
/* If we are given a filename, parse the filename, with precedence given to
|
||||
* filename encoded options */
|
||||
filename = qdict_get_try_str(options, "filename");
|
||||
if (filename) {
|
||||
warn_report("'filename' option specified. "
|
||||
"This is an unsupported option, and may be deprecated "
|
||||
"in the future");
|
||||
qemu_rbd_parse_filename(filename, options, &local_err);
|
||||
if (local_err) {
|
||||
r = -EINVAL;
|
||||
error_propagate(errp, local_err);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
r = -EINVAL;
|
||||
goto failed_opts;
|
||||
}
|
||||
|
||||
mon_host = qemu_rbd_mon_host(options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
r = -EINVAL;
|
||||
goto failed_opts;
|
||||
qemu_opts_del(opts);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
filename = qemu_opt_get(opts, "filename");
|
||||
secretid = qemu_opt_get(opts, "password-secret");
|
||||
|
||||
pool = qemu_opt_get(opts, "pool");
|
||||
conf = qemu_opt_get(opts, "conf");
|
||||
snap = qemu_opt_get(opts, "snapshot");
|
||||
user = qemu_opt_get(opts, "user");
|
||||
image_name = qemu_opt_get(opts, "image");
|
||||
keypairs = qemu_opt_get(opts, "=keyvalue-pairs");
|
||||
|
||||
if (!pool || !image_name) {
|
||||
error_setg(errp, "Parameters 'pool' and 'image' are required");
|
||||
if (qemu_rbd_parsename(filename, pool, sizeof(pool),
|
||||
snap_buf, sizeof(snap_buf),
|
||||
s->name, sizeof(s->name),
|
||||
conf, sizeof(conf), errp) < 0) {
|
||||
r = -EINVAL;
|
||||
goto failed_opts;
|
||||
}
|
||||
|
||||
r = rados_create(&s->cluster, user);
|
||||
clientname = qemu_rbd_parse_clientname(conf, clientname_buf);
|
||||
r = rados_create(&s->cluster, clientname);
|
||||
if (r < 0) {
|
||||
error_setg_errno(errp, -r, "error initializing");
|
||||
goto failed_opts;
|
||||
}
|
||||
|
||||
s->snap = g_strdup(snap);
|
||||
s->image_name = g_strdup(image_name);
|
||||
|
||||
/* try default location when conf=NULL, but ignore failure */
|
||||
r = rados_conf_read_file(s->cluster, conf);
|
||||
if (conf && r < 0) {
|
||||
error_setg_errno(errp, -r, "error reading conf file %s", conf);
|
||||
goto failed_shutdown;
|
||||
s->snap = NULL;
|
||||
if (snap_buf[0] != '\0') {
|
||||
s->snap = g_strdup(snap_buf);
|
||||
}
|
||||
|
||||
r = qemu_rbd_set_keypairs(s->cluster, keypairs, errp);
|
||||
if (r < 0) {
|
||||
goto failed_shutdown;
|
||||
if (strstr(conf, "conf=") == NULL) {
|
||||
/* try default location, but ignore failure */
|
||||
rados_conf_read_file(s->cluster, NULL);
|
||||
} else if (conf[0] != '\0') {
|
||||
r = qemu_rbd_set_conf(s->cluster, conf, true, errp);
|
||||
if (r < 0) {
|
||||
goto failed_shutdown;
|
||||
}
|
||||
}
|
||||
|
||||
if (mon_host) {
|
||||
r = rados_conf_set(s->cluster, "mon_host", mon_host);
|
||||
if (conf[0] != '\0') {
|
||||
r = qemu_rbd_set_conf(s->cluster, conf, false, errp);
|
||||
if (r < 0) {
|
||||
goto failed_shutdown;
|
||||
}
|
||||
@@ -654,23 +564,13 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto failed_shutdown;
|
||||
}
|
||||
|
||||
/* rbd_open is always r/w */
|
||||
r = rbd_open(s->io_ctx, s->image_name, &s->image, s->snap);
|
||||
r = rbd_open(s->io_ctx, s->name, &s->image, s->snap);
|
||||
if (r < 0) {
|
||||
error_setg_errno(errp, -r, "error reading header from %s",
|
||||
s->image_name);
|
||||
error_setg_errno(errp, -r, "error reading header from %s", s->name);
|
||||
goto failed_open;
|
||||
}
|
||||
|
||||
/* If we are using an rbd snapshot, we must be r/o, otherwise
|
||||
* leave as-is */
|
||||
if (s->snap != NULL) {
|
||||
r = bdrv_set_read_only(bs, true, &local_err);
|
||||
if (r < 0) {
|
||||
error_propagate(errp, local_err);
|
||||
goto failed_open;
|
||||
}
|
||||
}
|
||||
bs->read_only = (s->snap != NULL);
|
||||
|
||||
qemu_opts_del(opts);
|
||||
return 0;
|
||||
@@ -680,34 +580,11 @@ failed_open:
|
||||
failed_shutdown:
|
||||
rados_shutdown(s->cluster);
|
||||
g_free(s->snap);
|
||||
g_free(s->image_name);
|
||||
failed_opts:
|
||||
qemu_opts_del(opts);
|
||||
g_free(mon_host);
|
||||
exit:
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/* Since RBD is currently always opened R/W via the API,
|
||||
* we just need to check if we are using a snapshot or not, in
|
||||
* order to determine if we will allow it to be R/W */
|
||||
static int qemu_rbd_reopen_prepare(BDRVReopenState *state,
|
||||
BlockReopenQueue *queue, Error **errp)
|
||||
{
|
||||
BDRVRBDState *s = state->bs->opaque;
|
||||
int ret = 0;
|
||||
|
||||
if (s->snap && state->flags & BDRV_O_RDWR) {
|
||||
error_setg(errp,
|
||||
"Cannot change node '%s' to r/w when using RBD snapshot",
|
||||
bdrv_get_device_or_node_name(state->bs));
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void qemu_rbd_close(BlockDriverState *bs)
|
||||
{
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
@@ -715,7 +592,6 @@ static void qemu_rbd_close(BlockDriverState *bs)
|
||||
rbd_close(s->image);
|
||||
rados_ioctx_destroy(s->io_ctx);
|
||||
g_free(s->snap);
|
||||
g_free(s->image_name);
|
||||
rados_shutdown(s->cluster);
|
||||
}
|
||||
|
||||
@@ -726,6 +602,7 @@ static const AIOCBInfo rbd_aiocb_info = {
|
||||
static void rbd_finish_bh(void *opaque)
|
||||
{
|
||||
RADOSCB *rcb = opaque;
|
||||
qemu_bh_delete(rcb->acb->bh);
|
||||
qemu_rbd_complete_aio(rcb);
|
||||
}
|
||||
|
||||
@@ -744,8 +621,9 @@ static void rbd_finish_aiocb(rbd_completion_t c, RADOSCB *rcb)
|
||||
rcb->ret = rbd_aio_get_return_value(c);
|
||||
rbd_aio_release(c);
|
||||
|
||||
aio_bh_schedule_oneshot(bdrv_get_aio_context(acb->common.bs),
|
||||
rbd_finish_bh, rcb);
|
||||
acb->bh = aio_bh_new(bdrv_get_aio_context(acb->common.bs),
|
||||
rbd_finish_bh, rcb);
|
||||
qemu_bh_schedule(acb->bh);
|
||||
}
|
||||
|
||||
static int rbd_aio_discard_wrapper(rbd_image_t image,
|
||||
@@ -781,6 +659,7 @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs,
|
||||
RBDAIOCB *acb;
|
||||
RADOSCB *rcb = NULL;
|
||||
rbd_completion_t c;
|
||||
char *buf;
|
||||
int r;
|
||||
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
@@ -789,29 +668,28 @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs,
|
||||
acb->cmd = cmd;
|
||||
acb->qiov = qiov;
|
||||
assert(!qiov || qiov->size == size);
|
||||
|
||||
rcb = g_new(RADOSCB, 1);
|
||||
|
||||
if (!LIBRBD_USE_IOVEC) {
|
||||
if (cmd == RBD_AIO_DISCARD || cmd == RBD_AIO_FLUSH) {
|
||||
acb->bounce = NULL;
|
||||
} else {
|
||||
acb->bounce = qemu_try_blockalign(bs, qiov->size);
|
||||
if (acb->bounce == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
if (cmd == RBD_AIO_DISCARD || cmd == RBD_AIO_FLUSH) {
|
||||
acb->bounce = NULL;
|
||||
} else {
|
||||
acb->bounce = qemu_try_blockalign(bs, qiov->size);
|
||||
if (acb->bounce == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
if (cmd == RBD_AIO_WRITE) {
|
||||
qemu_iovec_to_buf(acb->qiov, 0, acb->bounce, qiov->size);
|
||||
}
|
||||
rcb->buf = acb->bounce;
|
||||
}
|
||||
|
||||
acb->ret = 0;
|
||||
acb->error = 0;
|
||||
acb->s = s;
|
||||
acb->bh = NULL;
|
||||
|
||||
if (cmd == RBD_AIO_WRITE) {
|
||||
qemu_iovec_to_buf(acb->qiov, 0, acb->bounce, qiov->size);
|
||||
}
|
||||
|
||||
buf = acb->bounce;
|
||||
|
||||
rcb = g_new(RADOSCB, 1);
|
||||
rcb->acb = acb;
|
||||
rcb->buf = buf;
|
||||
rcb->s = acb->s;
|
||||
rcb->size = size;
|
||||
r = rbd_aio_create_completion(rcb, (rbd_callback_t) rbd_finish_aiocb, &c);
|
||||
@@ -821,18 +699,10 @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs,
|
||||
|
||||
switch (cmd) {
|
||||
case RBD_AIO_WRITE:
|
||||
#ifdef LIBRBD_SUPPORTS_IOVEC
|
||||
r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, off, c);
|
||||
#else
|
||||
r = rbd_aio_write(s->image, off, size, rcb->buf, c);
|
||||
#endif
|
||||
r = rbd_aio_write(s->image, off, size, buf, c);
|
||||
break;
|
||||
case RBD_AIO_READ:
|
||||
#ifdef LIBRBD_SUPPORTS_IOVEC
|
||||
r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, off, c);
|
||||
#else
|
||||
r = rbd_aio_read(s->image, off, size, rcb->buf, c);
|
||||
#endif
|
||||
r = rbd_aio_read(s->image, off, size, buf, c);
|
||||
break;
|
||||
case RBD_AIO_DISCARD:
|
||||
r = rbd_aio_discard_wrapper(s->image, off, size, c);
|
||||
@@ -847,16 +717,14 @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs,
|
||||
if (r < 0) {
|
||||
goto failed_completion;
|
||||
}
|
||||
|
||||
return &acb->common;
|
||||
|
||||
failed_completion:
|
||||
rbd_aio_release(c);
|
||||
failed:
|
||||
g_free(rcb);
|
||||
if (!LIBRBD_USE_IOVEC) {
|
||||
qemu_vfree(acb->bounce);
|
||||
}
|
||||
|
||||
qemu_vfree(acb->bounce);
|
||||
qemu_aio_unref(acb);
|
||||
return NULL;
|
||||
}
|
||||
@@ -936,21 +804,13 @@ static int64_t qemu_rbd_getlength(BlockDriverState *bs)
|
||||
return info.size;
|
||||
}
|
||||
|
||||
static int qemu_rbd_truncate(BlockDriverState *bs, int64_t offset,
|
||||
PreallocMode prealloc, Error **errp)
|
||||
static int qemu_rbd_truncate(BlockDriverState *bs, int64_t offset)
|
||||
{
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
int r;
|
||||
|
||||
if (prealloc != PREALLOC_MODE_OFF) {
|
||||
error_setg(errp, "Unsupported preallocation mode '%s'",
|
||||
PreallocMode_lookup[prealloc]);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
r = rbd_resize(s->image, offset);
|
||||
if (r < 0) {
|
||||
error_setg_errno(errp, -r, "Failed to resize file");
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1072,11 +932,11 @@ static int qemu_rbd_snap_list(BlockDriverState *bs,
|
||||
#ifdef LIBRBD_SUPPORTS_DISCARD
|
||||
static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset,
|
||||
int bytes,
|
||||
int count,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
return rbd_start_aio(bs, offset, NULL, bytes, cb, opaque,
|
||||
return rbd_start_aio(bs, offset, NULL, count, cb, opaque,
|
||||
RBD_AIO_DISCARD);
|
||||
}
|
||||
#endif
|
||||
@@ -1117,19 +977,18 @@ static QemuOptsList qemu_rbd_create_opts = {
|
||||
};
|
||||
|
||||
static BlockDriver bdrv_rbd = {
|
||||
.format_name = "rbd",
|
||||
.instance_size = sizeof(BDRVRBDState),
|
||||
.bdrv_parse_filename = qemu_rbd_parse_filename,
|
||||
.bdrv_file_open = qemu_rbd_open,
|
||||
.bdrv_close = qemu_rbd_close,
|
||||
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
|
||||
.bdrv_create = qemu_rbd_create,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_get_info = qemu_rbd_getinfo,
|
||||
.create_opts = &qemu_rbd_create_opts,
|
||||
.bdrv_getlength = qemu_rbd_getlength,
|
||||
.bdrv_truncate = qemu_rbd_truncate,
|
||||
.protocol_name = "rbd",
|
||||
.format_name = "rbd",
|
||||
.instance_size = sizeof(BDRVRBDState),
|
||||
.bdrv_needs_filename = true,
|
||||
.bdrv_file_open = qemu_rbd_open,
|
||||
.bdrv_close = qemu_rbd_close,
|
||||
.bdrv_create = qemu_rbd_create,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_get_info = qemu_rbd_getinfo,
|
||||
.create_opts = &qemu_rbd_create_opts,
|
||||
.bdrv_getlength = qemu_rbd_getlength,
|
||||
.bdrv_truncate = qemu_rbd_truncate,
|
||||
.protocol_name = "rbd",
|
||||
|
||||
.bdrv_aio_readv = qemu_rbd_aio_readv,
|
||||
.bdrv_aio_writev = qemu_rbd_aio_writev,
|
||||
|
||||
@@ -1,703 +0,0 @@
|
||||
/*
|
||||
* Replication Block filter
|
||||
*
|
||||
* Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
|
||||
* Copyright (c) 2016 Intel Corporation
|
||||
* Copyright (c) 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Author:
|
||||
* Wen Congyang <wency@cn.fujitsu.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "block/nbd.h"
|
||||
#include "block/blockjob.h"
|
||||
#include "block/block_int.h"
|
||||
#include "block/block_backup.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qapi/error.h"
|
||||
#include "replication.h"
|
||||
|
||||
typedef enum {
|
||||
BLOCK_REPLICATION_NONE, /* block replication is not started */
|
||||
BLOCK_REPLICATION_RUNNING, /* block replication is running */
|
||||
BLOCK_REPLICATION_FAILOVER, /* failover is running in background */
|
||||
BLOCK_REPLICATION_FAILOVER_FAILED, /* failover failed */
|
||||
BLOCK_REPLICATION_DONE, /* block replication is done */
|
||||
} ReplicationStage;
|
||||
|
||||
typedef struct BDRVReplicationState {
|
||||
ReplicationMode mode;
|
||||
ReplicationStage stage;
|
||||
BdrvChild *active_disk;
|
||||
BdrvChild *hidden_disk;
|
||||
BdrvChild *secondary_disk;
|
||||
char *top_id;
|
||||
ReplicationState *rs;
|
||||
Error *blocker;
|
||||
int orig_hidden_flags;
|
||||
int orig_secondary_flags;
|
||||
int error;
|
||||
} BDRVReplicationState;
|
||||
|
||||
static void replication_start(ReplicationState *rs, ReplicationMode mode,
|
||||
Error **errp);
|
||||
static void replication_do_checkpoint(ReplicationState *rs, Error **errp);
|
||||
static void replication_get_error(ReplicationState *rs, Error **errp);
|
||||
static void replication_stop(ReplicationState *rs, bool failover,
|
||||
Error **errp);
|
||||
|
||||
#define REPLICATION_MODE "mode"
|
||||
#define REPLICATION_TOP_ID "top-id"
|
||||
static QemuOptsList replication_runtime_opts = {
|
||||
.name = "replication",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(replication_runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = REPLICATION_MODE,
|
||||
.type = QEMU_OPT_STRING,
|
||||
},
|
||||
{
|
||||
.name = REPLICATION_TOP_ID,
|
||||
.type = QEMU_OPT_STRING,
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static ReplicationOps replication_ops = {
|
||||
.start = replication_start,
|
||||
.checkpoint = replication_do_checkpoint,
|
||||
.get_error = replication_get_error,
|
||||
.stop = replication_stop,
|
||||
};
|
||||
|
||||
static int replication_open(BlockDriverState *bs, QDict *options,
|
||||
int flags, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
Error *local_err = NULL;
|
||||
QemuOpts *opts = NULL;
|
||||
const char *mode;
|
||||
const char *top_id;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
opts = qemu_opts_create(&replication_runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mode = qemu_opt_get(opts, REPLICATION_MODE);
|
||||
if (!mode) {
|
||||
error_setg(&local_err, "Missing the option mode");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!strcmp(mode, "primary")) {
|
||||
s->mode = REPLICATION_MODE_PRIMARY;
|
||||
top_id = qemu_opt_get(opts, REPLICATION_TOP_ID);
|
||||
if (top_id) {
|
||||
error_setg(&local_err, "The primary side does not support option top-id");
|
||||
goto fail;
|
||||
}
|
||||
} else if (!strcmp(mode, "secondary")) {
|
||||
s->mode = REPLICATION_MODE_SECONDARY;
|
||||
top_id = qemu_opt_get(opts, REPLICATION_TOP_ID);
|
||||
s->top_id = g_strdup(top_id);
|
||||
if (!s->top_id) {
|
||||
error_setg(&local_err, "Missing the option top-id");
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
error_setg(&local_err,
|
||||
"The option mode's value should be primary or secondary");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
s->rs = replication_new(bs, &replication_ops);
|
||||
|
||||
ret = 0;
|
||||
|
||||
fail:
|
||||
qemu_opts_del(opts);
|
||||
error_propagate(errp, local_err);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void replication_close(BlockDriverState *bs)
|
||||
{
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
|
||||
if (s->stage == BLOCK_REPLICATION_RUNNING) {
|
||||
replication_stop(s->rs, false, NULL);
|
||||
}
|
||||
if (s->stage == BLOCK_REPLICATION_FAILOVER) {
|
||||
block_job_cancel_sync(s->active_disk->bs->job);
|
||||
}
|
||||
|
||||
if (s->mode == REPLICATION_MODE_SECONDARY) {
|
||||
g_free(s->top_id);
|
||||
}
|
||||
|
||||
replication_remove(s->rs);
|
||||
}
|
||||
|
||||
static void replication_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||
const BdrvChildRole *role,
|
||||
uint64_t perm, uint64_t shared,
|
||||
uint64_t *nperm, uint64_t *nshared)
|
||||
{
|
||||
*nperm = *nshared = BLK_PERM_CONSISTENT_READ \
|
||||
| BLK_PERM_WRITE \
|
||||
| BLK_PERM_WRITE_UNCHANGED;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int64_t replication_getlength(BlockDriverState *bs)
|
||||
{
|
||||
return bdrv_getlength(bs->file->bs);
|
||||
}
|
||||
|
||||
static int replication_get_io_status(BDRVReplicationState *s)
|
||||
{
|
||||
switch (s->stage) {
|
||||
case BLOCK_REPLICATION_NONE:
|
||||
return -EIO;
|
||||
case BLOCK_REPLICATION_RUNNING:
|
||||
return 0;
|
||||
case BLOCK_REPLICATION_FAILOVER:
|
||||
return s->mode == REPLICATION_MODE_PRIMARY ? -EIO : 0;
|
||||
case BLOCK_REPLICATION_FAILOVER_FAILED:
|
||||
return s->mode == REPLICATION_MODE_PRIMARY ? -EIO : 1;
|
||||
case BLOCK_REPLICATION_DONE:
|
||||
/*
|
||||
* active commit job completes, and active disk and secondary_disk
|
||||
* is swapped, so we can operate bs->file directly
|
||||
*/
|
||||
return s->mode == REPLICATION_MODE_PRIMARY ? -EIO : 0;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static int replication_return_value(BDRVReplicationState *s, int ret)
|
||||
{
|
||||
if (s->mode == REPLICATION_MODE_SECONDARY) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
s->error = ret;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static coroutine_fn int replication_co_readv(BlockDriverState *bs,
|
||||
int64_t sector_num,
|
||||
int remaining_sectors,
|
||||
QEMUIOVector *qiov)
|
||||
{
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
BdrvChild *child = s->secondary_disk;
|
||||
BlockJob *job = NULL;
|
||||
CowRequest req;
|
||||
int ret;
|
||||
|
||||
if (s->mode == REPLICATION_MODE_PRIMARY) {
|
||||
/* We only use it to forward primary write requests */
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = replication_get_io_status(s);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (child && child->bs) {
|
||||
job = child->bs->job;
|
||||
}
|
||||
|
||||
if (job) {
|
||||
uint64_t remaining_bytes = remaining_sectors * BDRV_SECTOR_SIZE;
|
||||
|
||||
backup_wait_for_overlapping_requests(child->bs->job,
|
||||
sector_num * BDRV_SECTOR_SIZE,
|
||||
remaining_bytes);
|
||||
backup_cow_request_begin(&req, child->bs->job,
|
||||
sector_num * BDRV_SECTOR_SIZE,
|
||||
remaining_bytes);
|
||||
ret = bdrv_co_readv(bs->file, sector_num, remaining_sectors,
|
||||
qiov);
|
||||
backup_cow_request_end(&req);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = bdrv_co_readv(bs->file, sector_num, remaining_sectors, qiov);
|
||||
out:
|
||||
return replication_return_value(s, ret);
|
||||
}
|
||||
|
||||
static coroutine_fn int replication_co_writev(BlockDriverState *bs,
|
||||
int64_t sector_num,
|
||||
int remaining_sectors,
|
||||
QEMUIOVector *qiov)
|
||||
{
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
QEMUIOVector hd_qiov;
|
||||
uint64_t bytes_done = 0;
|
||||
BdrvChild *top = bs->file;
|
||||
BdrvChild *base = s->secondary_disk;
|
||||
BdrvChild *target;
|
||||
int ret;
|
||||
int64_t n;
|
||||
|
||||
ret = replication_get_io_status(s);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
ret = bdrv_co_writev(top, sector_num,
|
||||
remaining_sectors, qiov);
|
||||
return replication_return_value(s, ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Failover failed, only write to active disk if the sectors
|
||||
* have already been allocated in active disk/hidden disk.
|
||||
*/
|
||||
qemu_iovec_init(&hd_qiov, qiov->niov);
|
||||
while (remaining_sectors > 0) {
|
||||
int64_t count;
|
||||
|
||||
ret = bdrv_is_allocated_above(top->bs, base->bs,
|
||||
sector_num * BDRV_SECTOR_SIZE,
|
||||
remaining_sectors * BDRV_SECTOR_SIZE,
|
||||
&count);
|
||||
if (ret < 0) {
|
||||
goto out1;
|
||||
}
|
||||
|
||||
assert(QEMU_IS_ALIGNED(count, BDRV_SECTOR_SIZE));
|
||||
n = count >> BDRV_SECTOR_BITS;
|
||||
qemu_iovec_reset(&hd_qiov);
|
||||
qemu_iovec_concat(&hd_qiov, qiov, bytes_done, count);
|
||||
|
||||
target = ret ? top : base;
|
||||
ret = bdrv_co_writev(target, sector_num, n, &hd_qiov);
|
||||
if (ret < 0) {
|
||||
goto out1;
|
||||
}
|
||||
|
||||
remaining_sectors -= n;
|
||||
sector_num += n;
|
||||
bytes_done += count;
|
||||
}
|
||||
|
||||
out1:
|
||||
qemu_iovec_destroy(&hd_qiov);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool replication_recurse_is_first_non_filter(BlockDriverState *bs,
|
||||
BlockDriverState *candidate)
|
||||
{
|
||||
return bdrv_recurse_is_first_non_filter(bs->file->bs, candidate);
|
||||
}
|
||||
|
||||
static void secondary_do_checkpoint(BDRVReplicationState *s, Error **errp)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
if (!s->secondary_disk->bs->job) {
|
||||
error_setg(errp, "Backup job was cancelled unexpectedly");
|
||||
return;
|
||||
}
|
||||
|
||||
backup_do_checkpoint(s->secondary_disk->bs->job, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = s->active_disk->bs->drv->bdrv_make_empty(s->active_disk->bs);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Cannot make active disk empty");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = s->hidden_disk->bs->drv->bdrv_make_empty(s->hidden_disk->bs);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Cannot make hidden disk empty");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void reopen_backing_file(BlockDriverState *bs, bool writable,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
BlockReopenQueue *reopen_queue = NULL;
|
||||
int orig_hidden_flags, orig_secondary_flags;
|
||||
int new_hidden_flags, new_secondary_flags;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (writable) {
|
||||
orig_hidden_flags = s->orig_hidden_flags =
|
||||
bdrv_get_flags(s->hidden_disk->bs);
|
||||
new_hidden_flags = (orig_hidden_flags | BDRV_O_RDWR) &
|
||||
~BDRV_O_INACTIVE;
|
||||
orig_secondary_flags = s->orig_secondary_flags =
|
||||
bdrv_get_flags(s->secondary_disk->bs);
|
||||
new_secondary_flags = (orig_secondary_flags | BDRV_O_RDWR) &
|
||||
~BDRV_O_INACTIVE;
|
||||
} else {
|
||||
orig_hidden_flags = (s->orig_hidden_flags | BDRV_O_RDWR) &
|
||||
~BDRV_O_INACTIVE;
|
||||
new_hidden_flags = s->orig_hidden_flags;
|
||||
orig_secondary_flags = (s->orig_secondary_flags | BDRV_O_RDWR) &
|
||||
~BDRV_O_INACTIVE;
|
||||
new_secondary_flags = s->orig_secondary_flags;
|
||||
}
|
||||
|
||||
if (orig_hidden_flags != new_hidden_flags) {
|
||||
reopen_queue = bdrv_reopen_queue(reopen_queue, s->hidden_disk->bs, NULL,
|
||||
new_hidden_flags);
|
||||
}
|
||||
|
||||
if (!(orig_secondary_flags & BDRV_O_RDWR)) {
|
||||
reopen_queue = bdrv_reopen_queue(reopen_queue, s->secondary_disk->bs,
|
||||
NULL, new_secondary_flags);
|
||||
}
|
||||
|
||||
if (reopen_queue) {
|
||||
bdrv_reopen_multiple(bdrv_get_aio_context(bs),
|
||||
reopen_queue, &local_err);
|
||||
error_propagate(errp, local_err);
|
||||
}
|
||||
}
|
||||
|
||||
static void backup_job_cleanup(BlockDriverState *bs)
|
||||
{
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
BlockDriverState *top_bs;
|
||||
|
||||
top_bs = bdrv_lookup_bs(s->top_id, s->top_id, NULL);
|
||||
if (!top_bs) {
|
||||
return;
|
||||
}
|
||||
bdrv_op_unblock_all(top_bs, s->blocker);
|
||||
error_free(s->blocker);
|
||||
reopen_backing_file(bs, false, NULL);
|
||||
}
|
||||
|
||||
static void backup_job_completed(void *opaque, int ret)
|
||||
{
|
||||
BlockDriverState *bs = opaque;
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
|
||||
if (s->stage != BLOCK_REPLICATION_FAILOVER) {
|
||||
/* The backup job is cancelled unexpectedly */
|
||||
s->error = -EIO;
|
||||
}
|
||||
|
||||
backup_job_cleanup(bs);
|
||||
}
|
||||
|
||||
static bool check_top_bs(BlockDriverState *top_bs, BlockDriverState *bs)
|
||||
{
|
||||
BdrvChild *child;
|
||||
|
||||
/* The bs itself is the top_bs */
|
||||
if (top_bs == bs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Iterate over top_bs's children */
|
||||
QLIST_FOREACH(child, &top_bs->children, next) {
|
||||
if (child->bs == bs || check_top_bs(child->bs, bs)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void replication_start(ReplicationState *rs, ReplicationMode mode,
|
||||
Error **errp)
|
||||
{
|
||||
BlockDriverState *bs = rs->opaque;
|
||||
BDRVReplicationState *s;
|
||||
BlockDriverState *top_bs;
|
||||
int64_t active_length, hidden_length, disk_length;
|
||||
AioContext *aio_context;
|
||||
Error *local_err = NULL;
|
||||
BlockJob *job;
|
||||
|
||||
aio_context = bdrv_get_aio_context(bs);
|
||||
aio_context_acquire(aio_context);
|
||||
s = bs->opaque;
|
||||
|
||||
if (s->stage != BLOCK_REPLICATION_NONE) {
|
||||
error_setg(errp, "Block replication is running or done");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->mode != mode) {
|
||||
error_setg(errp, "The parameter mode's value is invalid, needs %d,"
|
||||
" but got %d", s->mode, mode);
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (s->mode) {
|
||||
case REPLICATION_MODE_PRIMARY:
|
||||
break;
|
||||
case REPLICATION_MODE_SECONDARY:
|
||||
s->active_disk = bs->file;
|
||||
if (!s->active_disk || !s->active_disk->bs ||
|
||||
!s->active_disk->bs->backing) {
|
||||
error_setg(errp, "Active disk doesn't have backing file");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
s->hidden_disk = s->active_disk->bs->backing;
|
||||
if (!s->hidden_disk->bs || !s->hidden_disk->bs->backing) {
|
||||
error_setg(errp, "Hidden disk doesn't have backing file");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
s->secondary_disk = s->hidden_disk->bs->backing;
|
||||
if (!s->secondary_disk->bs || !bdrv_has_blk(s->secondary_disk->bs)) {
|
||||
error_setg(errp, "The secondary disk doesn't have block backend");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
/* verify the length */
|
||||
active_length = bdrv_getlength(s->active_disk->bs);
|
||||
hidden_length = bdrv_getlength(s->hidden_disk->bs);
|
||||
disk_length = bdrv_getlength(s->secondary_disk->bs);
|
||||
if (active_length < 0 || hidden_length < 0 || disk_length < 0 ||
|
||||
active_length != hidden_length || hidden_length != disk_length) {
|
||||
error_setg(errp, "Active disk, hidden disk, secondary disk's length"
|
||||
" are not the same");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s->active_disk->bs->drv->bdrv_make_empty ||
|
||||
!s->hidden_disk->bs->drv->bdrv_make_empty) {
|
||||
error_setg(errp,
|
||||
"Active disk or hidden disk doesn't support make_empty");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
/* reopen the backing file in r/w mode */
|
||||
reopen_backing_file(bs, true, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
/* start backup job now */
|
||||
error_setg(&s->blocker,
|
||||
"Block device is in use by internal backup job");
|
||||
|
||||
top_bs = bdrv_lookup_bs(s->top_id, s->top_id, NULL);
|
||||
if (!top_bs || !bdrv_is_root_node(top_bs) ||
|
||||
!check_top_bs(top_bs, bs)) {
|
||||
error_setg(errp, "No top_bs or it is invalid");
|
||||
reopen_backing_file(bs, false, NULL);
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
bdrv_op_block_all(top_bs, s->blocker);
|
||||
bdrv_op_unblock(top_bs, BLOCK_OP_TYPE_DATAPLANE, s->blocker);
|
||||
|
||||
job = backup_job_create(NULL, s->secondary_disk->bs, s->hidden_disk->bs,
|
||||
0, MIRROR_SYNC_MODE_NONE, NULL, false,
|
||||
BLOCKDEV_ON_ERROR_REPORT,
|
||||
BLOCKDEV_ON_ERROR_REPORT, BLOCK_JOB_INTERNAL,
|
||||
backup_job_completed, bs, NULL, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
backup_job_cleanup(bs);
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
block_job_start(job);
|
||||
break;
|
||||
default:
|
||||
aio_context_release(aio_context);
|
||||
abort();
|
||||
}
|
||||
|
||||
s->stage = BLOCK_REPLICATION_RUNNING;
|
||||
|
||||
if (s->mode == REPLICATION_MODE_SECONDARY) {
|
||||
secondary_do_checkpoint(s, errp);
|
||||
}
|
||||
|
||||
s->error = 0;
|
||||
aio_context_release(aio_context);
|
||||
}
|
||||
|
||||
static void replication_do_checkpoint(ReplicationState *rs, Error **errp)
|
||||
{
|
||||
BlockDriverState *bs = rs->opaque;
|
||||
BDRVReplicationState *s;
|
||||
AioContext *aio_context;
|
||||
|
||||
aio_context = bdrv_get_aio_context(bs);
|
||||
aio_context_acquire(aio_context);
|
||||
s = bs->opaque;
|
||||
|
||||
if (s->mode == REPLICATION_MODE_SECONDARY) {
|
||||
secondary_do_checkpoint(s, errp);
|
||||
}
|
||||
aio_context_release(aio_context);
|
||||
}
|
||||
|
||||
static void replication_get_error(ReplicationState *rs, Error **errp)
|
||||
{
|
||||
BlockDriverState *bs = rs->opaque;
|
||||
BDRVReplicationState *s;
|
||||
AioContext *aio_context;
|
||||
|
||||
aio_context = bdrv_get_aio_context(bs);
|
||||
aio_context_acquire(aio_context);
|
||||
s = bs->opaque;
|
||||
|
||||
if (s->stage != BLOCK_REPLICATION_RUNNING) {
|
||||
error_setg(errp, "Block replication is not running");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->error) {
|
||||
error_setg(errp, "I/O error occurred");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
aio_context_release(aio_context);
|
||||
}
|
||||
|
||||
static void replication_done(void *opaque, int ret)
|
||||
{
|
||||
BlockDriverState *bs = opaque;
|
||||
BDRVReplicationState *s = bs->opaque;
|
||||
|
||||
if (ret == 0) {
|
||||
s->stage = BLOCK_REPLICATION_DONE;
|
||||
|
||||
/* refresh top bs's filename */
|
||||
bdrv_refresh_filename(bs);
|
||||
s->active_disk = NULL;
|
||||
s->secondary_disk = NULL;
|
||||
s->hidden_disk = NULL;
|
||||
s->error = 0;
|
||||
} else {
|
||||
s->stage = BLOCK_REPLICATION_FAILOVER_FAILED;
|
||||
s->error = -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static void replication_stop(ReplicationState *rs, bool failover, Error **errp)
|
||||
{
|
||||
BlockDriverState *bs = rs->opaque;
|
||||
BDRVReplicationState *s;
|
||||
AioContext *aio_context;
|
||||
|
||||
aio_context = bdrv_get_aio_context(bs);
|
||||
aio_context_acquire(aio_context);
|
||||
s = bs->opaque;
|
||||
|
||||
if (s->stage != BLOCK_REPLICATION_RUNNING) {
|
||||
error_setg(errp, "Block replication is not running");
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (s->mode) {
|
||||
case REPLICATION_MODE_PRIMARY:
|
||||
s->stage = BLOCK_REPLICATION_DONE;
|
||||
s->error = 0;
|
||||
break;
|
||||
case REPLICATION_MODE_SECONDARY:
|
||||
/*
|
||||
* This BDS will be closed, and the job should be completed
|
||||
* before the BDS is closed, because we will access hidden
|
||||
* disk, secondary disk in backup_job_completed().
|
||||
*/
|
||||
if (s->secondary_disk->bs->job) {
|
||||
block_job_cancel_sync(s->secondary_disk->bs->job);
|
||||
}
|
||||
|
||||
if (!failover) {
|
||||
secondary_do_checkpoint(s, errp);
|
||||
s->stage = BLOCK_REPLICATION_DONE;
|
||||
aio_context_release(aio_context);
|
||||
return;
|
||||
}
|
||||
|
||||
s->stage = BLOCK_REPLICATION_FAILOVER;
|
||||
commit_active_start(NULL, s->active_disk->bs, s->secondary_disk->bs,
|
||||
BLOCK_JOB_INTERNAL, 0, BLOCKDEV_ON_ERROR_REPORT,
|
||||
NULL, replication_done, bs, true, errp);
|
||||
break;
|
||||
default:
|
||||
aio_context_release(aio_context);
|
||||
abort();
|
||||
}
|
||||
aio_context_release(aio_context);
|
||||
}
|
||||
|
||||
BlockDriver bdrv_replication = {
|
||||
.format_name = "replication",
|
||||
.protocol_name = "replication",
|
||||
.instance_size = sizeof(BDRVReplicationState),
|
||||
|
||||
.bdrv_open = replication_open,
|
||||
.bdrv_close = replication_close,
|
||||
.bdrv_child_perm = replication_child_perm,
|
||||
|
||||
.bdrv_getlength = replication_getlength,
|
||||
.bdrv_co_readv = replication_co_readv,
|
||||
.bdrv_co_writev = replication_co_writev,
|
||||
|
||||
.is_filter = true,
|
||||
.bdrv_recurse_is_first_non_filter = replication_recurse_is_first_non_filter,
|
||||
|
||||
.has_variable_length = true,
|
||||
};
|
||||
|
||||
static void bdrv_replication_init(void)
|
||||
{
|
||||
bdrv_register(&bdrv_replication);
|
||||
}
|
||||
|
||||
block_init(bdrv_replication_init);
|
||||
+397
-535
File diff suppressed because it is too large.
Load diff
Loaded 100 of 2919 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user