Compare commits
269 Commits
qemu-2.0.0
...
SLE11-SP4-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aff087cee | ||
|
|
b4f36774b7 | ||
|
|
2531d7ee4d | ||
|
|
6855c034e7 | ||
|
|
51e5a6007e | ||
|
|
e3806f5d57 | ||
|
|
b8c2ea9776 | ||
| 0398a1b258 | |||
|
|
4bf75ff6d6 | ||
|
|
0447550926 | ||
|
|
b2bdec338d | ||
|
|
a586d7d202 | ||
|
|
85dadfc305 | ||
|
|
7b2dfb5d35 | ||
|
|
686fab80bd | ||
|
|
abfdc2e6ff | ||
|
|
d05ce95406 | ||
|
|
2c2261cfcc | ||
|
|
c6616fd654 | ||
|
|
efe2752601 | ||
|
|
6e543d2c12 | ||
|
|
de04929dd2 | ||
|
|
670d5d7fee | ||
|
|
94107cdfae | ||
|
|
394084d080 | ||
|
|
551fc996b5 | ||
|
|
ee23dcb6c1 | ||
|
|
b42aedacc9 | ||
| 215b18c9fc | |||
|
|
402d0d1f0c | ||
|
|
58b487a2bc | ||
|
|
6d9a092479 | ||
|
|
e87058cc54 | ||
|
|
3b75987950 | ||
|
|
37f7413027 | ||
|
|
44a0871d54 | ||
|
|
cb74d9ee43 | ||
|
|
59cdffd5f1 | ||
|
|
18f2150f92 | ||
|
|
0625626e51 | ||
|
|
1a66a3ca79 | ||
|
|
158326e199 | ||
|
|
fd67499caa | ||
|
|
71149b3e14 | ||
|
|
5f5aa07d16 | ||
|
|
3a34ab453f | ||
|
|
80ce3a7403 | ||
|
|
7af0df9343 | ||
|
|
094e9d9a91 | ||
|
|
a819068104 | ||
|
|
2ca9b4d153 | ||
|
|
ff80ec1aab | ||
|
|
4bf7b7da45 | ||
|
|
5a6e91a399 | ||
|
|
e8363b7738 | ||
|
|
dd9169bc43 | ||
|
|
877b642be0 | ||
|
|
c181a409d4 | ||
|
|
5626edc3f9 | ||
|
|
2a57bae0d1 | ||
|
|
cca58015c0 | ||
|
|
a958839822 | ||
|
|
e8a8f9f1c4 | ||
|
|
0f4f9527d0 | ||
|
|
408dc94b92 | ||
|
|
d9593d1734 | ||
|
|
301feb072e | ||
|
|
9848148c9c | ||
|
|
924eda5c4a | ||
|
|
8c9ef11d8a | ||
|
|
d5685a80ce | ||
|
|
5e783ed780 | ||
|
|
b27b5c305e | ||
|
|
87559bfe5a | ||
|
|
211bbf522c | ||
|
|
7bae3c9587 | ||
|
|
949fab98f8 | ||
|
|
ab00d35ba6 | ||
|
|
ccb16b84cd | ||
|
|
7acfa7e9eb | ||
|
|
0f55cd19aa | ||
|
|
05fc570638 | ||
|
|
a9041a3d9c | ||
|
|
9639415b85 | ||
|
|
7028f2bc09 | ||
|
|
573bea06b3 | ||
|
|
b0f69fb75c | ||
|
|
3f5672ec57 | ||
|
|
7eb2402026 | ||
|
|
176d3f3351 | ||
|
|
da96690d12 | ||
|
|
e3bd9029dc | ||
|
|
b4c60b7142 | ||
|
|
57c36784f0 | ||
|
|
3211c90e16 | ||
|
|
ca59c611d6 | ||
|
|
278ffa97d6 | ||
|
|
617ae61fa9 | ||
|
|
593f5a543b | ||
|
|
8118864031 | ||
|
|
b1fe4e34b1 | ||
|
|
a3d9060d83 | ||
|
|
44309c1775 | ||
|
|
f5a19fb649 | ||
|
|
1b65531708 | ||
|
|
cf8285b5f4 | ||
|
|
2509270b3b | ||
|
|
9571fdcc96 | ||
|
|
f01d1c4975 | ||
|
|
33fc1224b0 | ||
|
|
355d1697da | ||
|
|
721dcef81a | ||
|
|
68bdfae5e5 | ||
|
|
698c02a4f7 | ||
|
|
a39e5bb368 | ||
|
|
3d62fd2ba0 | ||
|
|
342e94e056 | ||
|
|
4b1e5f667f | ||
|
|
639373b494 | ||
|
|
5116278b67 | ||
|
|
bf2d7690fa | ||
|
|
c6a1d6e329 | ||
|
|
b9263558d9 | ||
|
|
669fb73c5f | ||
|
|
6ec86bb006 | ||
|
|
90e32d9444 | ||
|
|
63a4d51a78 | ||
|
|
ddd66af037 | ||
|
|
603c7238ff | ||
|
|
2b87f79326 | ||
|
|
3e812e702d | ||
|
|
05061c843c | ||
|
|
3dc60f68ae | ||
|
|
8cfd98d3ae | ||
|
|
a681202d4e | ||
|
|
e3401412dc | ||
|
|
fd5534832f | ||
|
|
4c739e0d6d | ||
|
|
300a4123d8 | ||
|
|
1ec1ef8f31 | ||
|
|
9a716057cd | ||
|
|
3fc0198478 | ||
|
|
7ece92f464 | ||
|
|
d0ccd870a6 | ||
|
|
46fbe7a783 | ||
|
|
383f92e67d | ||
|
|
383017b9bd | ||
|
|
4bbf4e9d8e | ||
|
|
e56ac60924 | ||
|
|
57e3bf66c2 | ||
|
|
50c6b143ff | ||
|
|
61c8e3c532 | ||
|
|
1380543027 | ||
|
|
53b7a865ee | ||
|
|
ed32292df6 | ||
|
|
a9692822d8 | ||
|
|
c173bbff64 | ||
|
|
230c029d16 | ||
|
|
1e5e5a10c3 | ||
|
|
e57cc5a2f9 | ||
|
|
f9cea35a81 | ||
|
|
0ae2b92b29 | ||
|
|
a4d378b1ce | ||
|
|
de8526eefe | ||
|
|
3ab66401c3 | ||
|
|
f6af7357df | ||
|
|
c8af02494d | ||
|
|
1bb261f222 | ||
|
|
f0922ef574 | ||
|
|
a6aa2f9c54 | ||
|
|
bf5b9c24c5 | ||
|
|
63d9acbe99 | ||
|
|
632f4958a0 | ||
|
|
66db770b81 | ||
|
|
363b2fc4f0 | ||
|
|
287b7249b6 | ||
|
|
ea55d53b1b | ||
|
|
8e4eb52196 | ||
|
|
5efb9ade7e | ||
|
|
73aab0ebf2 | ||
|
|
bad6f6bc3f | ||
|
|
e536419507 | ||
|
|
1bb6f0527b | ||
|
|
a45b1c7069 | ||
|
|
827326be7b | ||
|
|
89400a80f5 | ||
|
|
e85b521519 | ||
|
|
f890185392 | ||
|
|
745f6c0ef7 | ||
|
|
0182df5ae5 | ||
|
|
7f28f0f1f6 | ||
|
|
45bbe1fa89 | ||
|
|
06efdc4f4d | ||
|
|
0c70b5ad59 | ||
|
|
b90fd157f7 | ||
|
|
7322cb17fa | ||
|
|
1d7723ffc7 | ||
|
|
67b460a404 | ||
|
|
84247bbe28 | ||
|
|
2ebcc590c9 | ||
|
|
69001b3145 | ||
|
|
3accab7365 | ||
|
|
60259539ee | ||
|
|
93399d0827 | ||
|
|
074dd56a01 | ||
|
|
d10d2510b9 | ||
|
|
5613bda4ac | ||
|
|
c5675a98bb | ||
|
|
e355efd962 | ||
|
|
4d7f4556fc | ||
|
|
0486c27a36 | ||
|
|
57105f7480 | ||
|
|
6e8865313f | ||
|
|
6d0b135a98 | ||
|
|
d89f9ba43b | ||
|
|
46f9071a23 | ||
|
|
f85e082a36 | ||
|
|
da78a1bc7a | ||
|
|
2b92aa36d1 | ||
|
|
e4cce2d3e9 | ||
|
|
d15b1aa30c | ||
|
|
65fe29ec00 | ||
|
|
888e036eb4 | ||
|
|
d019dd928c | ||
|
|
dac077f0e6 | ||
|
|
b09a673164 | ||
|
|
79a4dd4085 | ||
|
|
57e929c19c | ||
|
|
27c71355fb | ||
|
|
283b7de6a5 | ||
|
|
a1cb89f3fe | ||
|
|
68f9df5990 | ||
|
|
0135796271 | ||
|
|
799a34a48b | ||
|
|
8378910554 | ||
|
|
7a238b9fbd | ||
|
|
02493ee490 | ||
|
|
7d47b243d6 | ||
|
|
02ea844746 | ||
|
|
0fcf00b55c | ||
|
|
5610ef5863 | ||
|
|
7a687aed28 | ||
|
|
b91aee5810 | ||
|
|
e09b99b54f | ||
|
|
611c7f2c3a | ||
|
|
4e4566ce78 | ||
|
|
43e00611bc | ||
|
|
3c3de7c6b4 | ||
|
|
b0da310a69 | ||
|
|
d26efd2d39 | ||
|
|
f305d504ab | ||
|
|
d3652a1b28 | ||
|
|
51943504d5 | ||
|
|
4d1cdb9efd | ||
|
|
c3b81e01b8 | ||
|
|
99b1f39bd2 | ||
|
|
f23ab037c7 | ||
|
|
0c918dd600 | ||
|
|
a8b090ef08 | ||
|
|
4a38944326 | ||
|
|
b7ff1a7a00 | ||
|
|
d49fed4c55 | ||
|
|
cebb8ebe41 | ||
|
|
3b39a11cde | ||
|
|
ec9f828341 | ||
|
|
332e93417a | ||
|
|
e6b795f34e | ||
|
|
51968b8503 | ||
|
|
80d8b5da48 |
150
.gitignore
vendored
150
.gitignore
vendored
@@ -1,75 +1,63 @@
|
|||||||
/config-devices.*
|
config-devices.*
|
||||||
/config-all-devices.*
|
config-all-devices.*
|
||||||
/config-all-disas.*
|
config-all-disas.*
|
||||||
/config-host.*
|
config-host.*
|
||||||
/config-target.*
|
config-target.*
|
||||||
/config.status
|
trace/generated-tracers.h
|
||||||
/trace/generated-tracers.h
|
trace/generated-tracers.c
|
||||||
/trace/generated-tracers.c
|
trace/generated-tracers-dtrace.h
|
||||||
/trace/generated-tracers-dtrace.h
|
trace/generated-tracers-dtrace.dtrace
|
||||||
/trace/generated-tracers.dtrace
|
libcacard/trace/generated-tracers.c
|
||||||
/trace/generated-events.h
|
|
||||||
/trace/generated-events.c
|
|
||||||
/trace/generated-ust-provider.h
|
|
||||||
/trace/generated-ust.c
|
|
||||||
/libcacard/trace/generated-tracers.c
|
|
||||||
*-timestamp
|
*-timestamp
|
||||||
/*-softmmu
|
*-softmmu
|
||||||
/*-darwin-user
|
*-darwin-user
|
||||||
/*-linux-user
|
*-linux-user
|
||||||
/*-bsd-user
|
*-bsd-user
|
||||||
libdis*
|
libdis*
|
||||||
libuser
|
libuser
|
||||||
/linux-headers/asm
|
linux-headers/asm
|
||||||
/qga/qapi-generated
|
qapi-generated
|
||||||
/qapi-generated
|
qapi-types.[ch]
|
||||||
/qapi-types.[ch]
|
qapi-visit.[ch]
|
||||||
/qapi-visit.[ch]
|
qmp-commands.h
|
||||||
/qmp-commands.h
|
qmp-marshal.c
|
||||||
/qmp-marshal.c
|
qemu-doc.html
|
||||||
/qemu-doc.html
|
qemu-tech.html
|
||||||
/qemu-tech.html
|
qemu-doc.info
|
||||||
/qemu-doc.info
|
qemu-tech.info
|
||||||
/qemu-tech.info
|
qemu.1
|
||||||
/qemu.1
|
qemu.pod
|
||||||
/qemu.pod
|
qemu-img.1
|
||||||
/qemu-img.1
|
qemu-img.pod
|
||||||
/qemu-img.pod
|
qemu-img
|
||||||
/qemu-img
|
qemu-nbd
|
||||||
/qemu-nbd
|
qemu-nbd.8
|
||||||
/qemu-nbd.8
|
qemu-nbd.pod
|
||||||
/qemu-nbd.pod
|
qemu-options.def
|
||||||
/qemu-options.def
|
qemu-options.texi
|
||||||
/qemu-options.texi
|
qemu-img-cmds.texi
|
||||||
/qemu-img-cmds.texi
|
qemu-img-cmds.h
|
||||||
/qemu-img-cmds.h
|
qemu-io
|
||||||
/qemu-io
|
qemu-ga
|
||||||
/qemu-ga
|
qemu-bridge-helper
|
||||||
/qemu-bridge-helper
|
qemu-monitor.texi
|
||||||
/qemu-monitor.texi
|
vscclient
|
||||||
/qmp-commands.txt
|
QMP/qmp-commands.txt
|
||||||
/vscclient
|
test-coroutine
|
||||||
/test-bitops
|
test-qmp-input-visitor
|
||||||
/test-coroutine
|
test-qmp-output-visitor
|
||||||
/test-int128
|
test-string-input-visitor
|
||||||
/test-opts-visitor
|
test-string-output-visitor
|
||||||
/test-qmp-input-visitor
|
test-visitor-serialization
|
||||||
/test-qmp-output-visitor
|
fsdev/virtfs-proxy-helper
|
||||||
/test-string-input-visitor
|
fsdev/virtfs-proxy-helper.1
|
||||||
/test-string-output-visitor
|
fsdev/virtfs-proxy-helper.pod
|
||||||
/test-visitor-serialization
|
.gdbinit
|
||||||
/fsdev/virtfs-proxy-helper
|
|
||||||
/fsdev/virtfs-proxy-helper.1
|
|
||||||
/fsdev/virtfs-proxy-helper.pod
|
|
||||||
/.gdbinit
|
|
||||||
*.a
|
*.a
|
||||||
*.aux
|
*.aux
|
||||||
*.cp
|
*.cp
|
||||||
*.dvi
|
*.dvi
|
||||||
*.exe
|
*.exe
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.mo
|
|
||||||
*.fn
|
*.fn
|
||||||
*.ky
|
*.ky
|
||||||
*.log
|
*.log
|
||||||
@@ -83,35 +71,29 @@ libuser
|
|||||||
*.tp
|
*.tp
|
||||||
*.vr
|
*.vr
|
||||||
*.d
|
*.d
|
||||||
!/scripts/qemu-guest-agent/fsfreeze-hook.d
|
!scripts/qemu-guest-agent/fsfreeze-hook.d
|
||||||
*.o
|
*.o
|
||||||
*.lo
|
*.lo
|
||||||
*.la
|
*.la
|
||||||
*.pc
|
*.pc
|
||||||
.libs
|
.libs
|
||||||
.sdk
|
|
||||||
*.swp
|
*.swp
|
||||||
*.orig
|
*.orig
|
||||||
.pc
|
.pc
|
||||||
*.gcda
|
|
||||||
*.gcno
|
|
||||||
patches
|
patches
|
||||||
/pc-bios/bios-pq/status
|
pc-bios/bios-pq/status
|
||||||
/pc-bios/vgabios-pq/status
|
pc-bios/vgabios-pq/status
|
||||||
/pc-bios/optionrom/linuxboot.asm
|
pc-bios/optionrom/linuxboot.bin
|
||||||
/pc-bios/optionrom/linuxboot.bin
|
pc-bios/optionrom/linuxboot.raw
|
||||||
/pc-bios/optionrom/linuxboot.raw
|
pc-bios/optionrom/linuxboot.img
|
||||||
/pc-bios/optionrom/linuxboot.img
|
pc-bios/optionrom/multiboot.bin
|
||||||
/pc-bios/optionrom/multiboot.asm
|
pc-bios/optionrom/multiboot.raw
|
||||||
/pc-bios/optionrom/multiboot.bin
|
pc-bios/optionrom/multiboot.img
|
||||||
/pc-bios/optionrom/multiboot.raw
|
pc-bios/optionrom/kvmvapic.bin
|
||||||
/pc-bios/optionrom/multiboot.img
|
pc-bios/optionrom/kvmvapic.raw
|
||||||
/pc-bios/optionrom/kvmvapic.asm
|
pc-bios/optionrom/kvmvapic.img
|
||||||
/pc-bios/optionrom/kvmvapic.bin
|
pc-bios/s390-ccw/s390-ccw.elf
|
||||||
/pc-bios/optionrom/kvmvapic.raw
|
pc-bios/s390-ccw/s390-ccw.img
|
||||||
/pc-bios/optionrom/kvmvapic.img
|
|
||||||
/pc-bios/s390-ccw/s390-ccw.elf
|
|
||||||
/pc-bios/s390-ccw/s390-ccw.img
|
|
||||||
.stgit-*
|
.stgit-*
|
||||||
cscope.*
|
cscope.*
|
||||||
tags
|
tags
|
||||||
|
|||||||
20
.gitmodules
vendored
20
.gitmodules
vendored
@@ -1,30 +1,24 @@
|
|||||||
[submodule "roms/vgabios"]
|
[submodule "roms/vgabios"]
|
||||||
path = roms/vgabios
|
path = roms/vgabios
|
||||||
url = git://git.qemu-project.org/vgabios.git/
|
url = git://git.qemu.org/vgabios.git/
|
||||||
[submodule "roms/seabios"]
|
[submodule "roms/seabios"]
|
||||||
path = roms/seabios
|
path = roms/seabios
|
||||||
url = git://git.qemu-project.org/seabios.git/
|
url = git://git.qemu.org/seabios.git/
|
||||||
[submodule "roms/SLOF"]
|
[submodule "roms/SLOF"]
|
||||||
path = roms/SLOF
|
path = roms/SLOF
|
||||||
url = git://git.qemu-project.org/SLOF.git
|
url = git://git.qemu.org/SLOF.git
|
||||||
[submodule "roms/ipxe"]
|
[submodule "roms/ipxe"]
|
||||||
path = roms/ipxe
|
path = roms/ipxe
|
||||||
url = git://git.qemu-project.org/ipxe.git
|
url = git://git.qemu.org/ipxe.git
|
||||||
[submodule "roms/openbios"]
|
[submodule "roms/openbios"]
|
||||||
path = roms/openbios
|
path = roms/openbios
|
||||||
url = git://git.qemu-project.org/openbios.git
|
url = git://git.qemu.org/openbios.git
|
||||||
[submodule "roms/openhackware"]
|
|
||||||
path = roms/openhackware
|
|
||||||
url = git://git.qemu-project.org/openhackware.git
|
|
||||||
[submodule "roms/qemu-palcode"]
|
[submodule "roms/qemu-palcode"]
|
||||||
path = roms/qemu-palcode
|
path = roms/qemu-palcode
|
||||||
url = git://github.com/rth7680/qemu-palcode.git
|
url = git://repo.or.cz/qemu-palcode.git
|
||||||
[submodule "roms/sgabios"]
|
[submodule "roms/sgabios"]
|
||||||
path = roms/sgabios
|
path = roms/sgabios
|
||||||
url = git://git.qemu-project.org/sgabios.git
|
url = git://git.qemu.org/sgabios.git
|
||||||
[submodule "pixman"]
|
[submodule "pixman"]
|
||||||
path = pixman
|
path = pixman
|
||||||
url = git://anongit.freedesktop.org/pixman
|
url = git://anongit.freedesktop.org/pixman
|
||||||
[submodule "dtc"]
|
|
||||||
path = dtc
|
|
||||||
url = git://git.qemu-project.org/dtc.git
|
|
||||||
|
|||||||
3
.mailmap
3
.mailmap
@@ -2,8 +2,7 @@
|
|||||||
# into proper addresses so that they are counted properly in git shortlog output.
|
# into proper addresses so that they are counted properly in git shortlog output.
|
||||||
#
|
#
|
||||||
Andrzej Zaborowski <balrogg@gmail.com> balrog <balrog@c046a42c-6fe2-441c-8c8c-71466251a162>
|
Andrzej Zaborowski <balrogg@gmail.com> balrog <balrog@c046a42c-6fe2-441c-8c8c-71466251a162>
|
||||||
Anthony Liguori <anthony@codemonkey.ws> aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>
|
Anthony Liguori <aliguori@us.ibm.com> aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>
|
||||||
Anthony Liguori <anthony@codemonkey.ws> Anthony Liguori <aliguori@us.ibm.com>
|
|
||||||
Aurelien Jarno <aurelien@aurel32.net> aurel32 <aurel32@c046a42c-6fe2-441c-8c8c-71466251a162>
|
Aurelien Jarno <aurelien@aurel32.net> aurel32 <aurel32@c046a42c-6fe2-441c-8c8c-71466251a162>
|
||||||
Blue Swirl <blauwirbel@gmail.com> blueswir1 <blueswir1@c046a42c-6fe2-441c-8c8c-71466251a162>
|
Blue Swirl <blauwirbel@gmail.com> blueswir1 <blueswir1@c046a42c-6fe2-441c-8c8c-71466251a162>
|
||||||
Edgar E. Iglesias <edgar.iglesias@gmail.com> edgar_igl <edgar_igl@c046a42c-6fe2-441c-8c8c-71466251a162>
|
Edgar E. Iglesias <edgar.iglesias@gmail.com> edgar_igl <edgar_igl@c046a42c-6fe2-441c-8c8c-71466251a162>
|
||||||
|
|||||||
81
.travis.yml
81
.travis.yml
@@ -1,81 +0,0 @@
|
|||||||
language: c
|
|
||||||
python:
|
|
||||||
- "2.4"
|
|
||||||
compiler:
|
|
||||||
- gcc
|
|
||||||
- clang
|
|
||||||
notifications:
|
|
||||||
irc:
|
|
||||||
channels:
|
|
||||||
- "irc.oftc.net#qemu"
|
|
||||||
on_success: change
|
|
||||||
on_failure: always
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- TEST_CMD="make check"
|
|
||||||
- EXTRA_CONFIG=""
|
|
||||||
# Development packages, EXTRA_PKGS saved for additional builds
|
|
||||||
- CORE_PKGS="libusb-1.0-0-dev libiscsi-dev librados-dev libncurses5-dev"
|
|
||||||
- NET_PKGS="libseccomp-dev libgnutls-dev libssh2-1-dev libspice-server-dev libspice-protocol-dev libnss3-dev"
|
|
||||||
- GUI_PKGS="libgtk-3-dev libvte-2.90-dev libsdl1.2-dev libpng12-dev libpixman-1-dev"
|
|
||||||
- EXTRA_PKGS=""
|
|
||||||
matrix:
|
|
||||||
- TARGETS=alpha-softmmu,alpha-linux-user
|
|
||||||
- TARGETS=arm-softmmu,arm-linux-user
|
|
||||||
- TARGETS=aarch64-softmmu,aarch64-linux-user
|
|
||||||
- TARGETS=cris-softmmu
|
|
||||||
- TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
- TARGETS=lm32-softmmu
|
|
||||||
- TARGETS=m68k-softmmu
|
|
||||||
- TARGETS=microblaze-softmmu,microblazeel-softmmu
|
|
||||||
- TARGETS=mips-softmmu,mips64-softmmu,mips64el-softmmu,mipsel-softmmu
|
|
||||||
- TARGETS=moxie-softmmu
|
|
||||||
- TARGETS=or32-softmmu,
|
|
||||||
- TARGETS=ppc-softmmu,ppc64-softmmu,ppcemb-softmmu
|
|
||||||
- TARGETS=s390x-softmmu
|
|
||||||
- TARGETS=sh4-softmmu,sh4eb-softmmu
|
|
||||||
- TARGETS=sparc-softmmu,sparc64-softmmu
|
|
||||||
- TARGETS=unicore32-softmmu
|
|
||||||
- TARGETS=xtensa-softmmu,xtensaeb-softmmu
|
|
||||||
before_install:
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
- sudo apt-get update -qq
|
|
||||||
- sudo apt-get install -qq ${CORE_PKGS} ${NET_PKGS} ${GUI_PKGS} ${EXTRA_PKGS}
|
|
||||||
script: "./configure --target-list=${TARGETS} ${EXTRA_CONFIG} && make && ${TEST_CMD}"
|
|
||||||
matrix:
|
|
||||||
# We manually include a number of additional build for non-standard bits
|
|
||||||
include:
|
|
||||||
# Debug related options
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_CONFIG="--enable-debug"
|
|
||||||
compiler: gcc
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_CONFIG="--enable-debug --enable-tcg-interpreter"
|
|
||||||
compiler: gcc
|
|
||||||
# All the extra -dev packages
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_PKGS="libaio-dev libcap-ng-dev libattr1-dev libbrlapi-dev uuid-dev libusb-1.0.0-dev"
|
|
||||||
compiler: gcc
|
|
||||||
# Currently configure doesn't force --disable-pie
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_CONFIG="--enable-gprof --enable-gcov --disable-pie"
|
|
||||||
compiler: gcc
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_PKGS="sparse"
|
|
||||||
EXTRA_CONFIG="--enable-sparse"
|
|
||||||
compiler: gcc
|
|
||||||
# All the trace backends (apart from dtrace)
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_CONFIG="--enable-trace-backend=stderr"
|
|
||||||
compiler: gcc
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_CONFIG="--enable-trace-backend=simple"
|
|
||||||
compiler: gcc
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_CONFIG="--enable-trace-backend=ftrace"
|
|
||||||
TEST_CMD=""
|
|
||||||
compiler: gcc
|
|
||||||
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
|
||||||
EXTRA_PKGS="liblttng-ust-dev liburcu-dev"
|
|
||||||
EXTRA_CONFIG="--enable-trace-backend=ust"
|
|
||||||
compiler: gcc
|
|
||||||
@@ -84,10 +84,3 @@ and clarity it comes on a line by itself:
|
|||||||
Rationale: a consistent (except for functions...) bracing style reduces
|
Rationale: a consistent (except for functions...) bracing style reduces
|
||||||
ambiguity and avoids needless churn when lines are added or removed.
|
ambiguity and avoids needless churn when lines are added or removed.
|
||||||
Furthermore, it is the QEMU coding style.
|
Furthermore, it is the QEMU coding style.
|
||||||
|
|
||||||
5. Declarations
|
|
||||||
|
|
||||||
Mixed declarations (interleaving statements and declarations within blocks)
|
|
||||||
are not allowed; declarations should be at the beginning of blocks. In other
|
|
||||||
words, the code should not generate warnings if using GCC's
|
|
||||||
-Wdeclaration-after-statement option.
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
This file documents changes for QEMU releases 0.12 and earlier.
|
This file documents changes for QEMU releases 0.12 and earlier.
|
||||||
For changelog information for later releases, see
|
For changelog information for later releases, see
|
||||||
http://wiki.qemu-project.org/ChangeLog or look at the git history for
|
http://wiki.qemu.org/ChangeLog or look at the git history for
|
||||||
more detailed information.
|
more detailed information.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
26
HACKING
26
HACKING
@@ -40,23 +40,8 @@ speaking, the size of guest memory can always fit into ram_addr_t but
|
|||||||
it would not be correct to store an actual guest physical address in a
|
it would not be correct to store an actual guest physical address in a
|
||||||
ram_addr_t.
|
ram_addr_t.
|
||||||
|
|
||||||
For CPU virtual addresses there are several possible types.
|
Use target_ulong (or abi_ulong) for CPU virtual addresses, however
|
||||||
vaddr is the best type to use to hold a CPU virtual address in
|
devices should not need to use target_ulong.
|
||||||
target-independent code. It is guaranteed to be large enough to hold a
|
|
||||||
virtual address for any target, and it does not change size from target
|
|
||||||
to target. It is always unsigned.
|
|
||||||
target_ulong is a type the size of a virtual address on the CPU; this means
|
|
||||||
it may be 32 or 64 bits depending on which target is being built. It should
|
|
||||||
therefore be used only in target-specific code, and in some
|
|
||||||
performance-critical built-per-target core code such as the TLB code.
|
|
||||||
There is also a signed version, target_long.
|
|
||||||
abi_ulong is for the *-user targets, and represents a type the size of
|
|
||||||
'void *' in that target's ABI. (This may not be the same as the size of a
|
|
||||||
full CPU virtual address in the case of target ABIs which use 32 bit pointers
|
|
||||||
on 64 bit CPUs, like sparc32plus.) Definitions of structures that must match
|
|
||||||
the target's ABI must use this type for anything that on the target is defined
|
|
||||||
to be an 'unsigned long' or a pointer type.
|
|
||||||
There is also a signed version, abi_long.
|
|
||||||
|
|
||||||
Of course, take all of the above with a grain of salt. If you're about
|
Of course, take all of the above with a grain of salt. If you're about
|
||||||
to use some system interface that requires a type like size_t, pid_t or
|
to use some system interface that requires a type like size_t, pid_t or
|
||||||
@@ -93,15 +78,16 @@ avoided.
|
|||||||
Use of the malloc/free/realloc/calloc/valloc/memalign/posix_memalign
|
Use of the malloc/free/realloc/calloc/valloc/memalign/posix_memalign
|
||||||
APIs is not allowed in the QEMU codebase. Instead of these routines,
|
APIs is not allowed in the QEMU codebase. Instead of these routines,
|
||||||
use the GLib memory allocation routines g_malloc/g_malloc0/g_new/
|
use the GLib memory allocation routines g_malloc/g_malloc0/g_new/
|
||||||
g_new0/g_realloc/g_free or QEMU's qemu_memalign/qemu_blockalign/qemu_vfree
|
g_new0/g_realloc/g_free or QEMU's qemu_vmalloc/qemu_memalign/qemu_vfree
|
||||||
APIs.
|
APIs.
|
||||||
|
|
||||||
Please note that g_malloc will exit on allocation failure, so there
|
Please note that g_malloc will exit on allocation failure, so there
|
||||||
is no need to test for failure (as you would have to with malloc).
|
is no need to test for failure (as you would have to with malloc).
|
||||||
Calling g_malloc with a zero size is valid and will return NULL.
|
Calling g_malloc with a zero size is valid and will return NULL.
|
||||||
|
|
||||||
Memory allocated by qemu_memalign or qemu_blockalign must be freed with
|
Memory allocated by qemu_vmalloc or qemu_memalign must be freed with
|
||||||
qemu_vfree, since breaking this will cause problems on Win32.
|
qemu_vfree, since breaking this will cause problems on Win32 and user
|
||||||
|
emulators.
|
||||||
|
|
||||||
4. String manipulation
|
4. String manipulation
|
||||||
|
|
||||||
|
|||||||
15
LICENSE
15
LICENSE
@@ -1,21 +1,16 @@
|
|||||||
The following points clarify the QEMU license:
|
The following points clarify the QEMU license:
|
||||||
|
|
||||||
1) QEMU as a whole is released under the GNU General Public License,
|
1) QEMU as a whole is released under the GNU General Public License
|
||||||
version 2.
|
|
||||||
|
|
||||||
2) Parts of QEMU have specific licenses which are compatible with the
|
2) Parts of QEMU have specific licenses which are compatible with the
|
||||||
GNU General Public License, version 2. Hence each source file contains
|
GNU General Public License. Hence each source file contains its own
|
||||||
its own licensing information. Source files with no licensing information
|
licensing information.
|
||||||
are released under the GNU General Public License, version 2 or (at your
|
|
||||||
option) any later version.
|
|
||||||
|
|
||||||
As of July 2013, contributions under version 2 of the GNU General Public
|
Many hardware device emulation sources are released under the BSD license.
|
||||||
License (and no later version) are only accepted for the following files
|
|
||||||
or directories: bsd-user/, linux-user/, hw/misc/vfio.c, hw/xen/xen_pt*.
|
|
||||||
|
|
||||||
3) The Tiny Code Generator (TCG) is released under the BSD license
|
3) The Tiny Code Generator (TCG) is released under the BSD license
|
||||||
(see license headers in files).
|
(see license headers in files).
|
||||||
|
|
||||||
4) QEMU is a trademark of Fabrice Bellard.
|
4) QEMU is a trademark of Fabrice Bellard.
|
||||||
|
|
||||||
Fabrice Bellard and the QEMU team
|
Fabrice Bellard.
|
||||||
|
|||||||
446
MAINTAINERS
446
MAINTAINERS
@@ -50,7 +50,8 @@ Descriptions of section entries:
|
|||||||
|
|
||||||
General Project Administration
|
General Project Administration
|
||||||
------------------------------
|
------------------------------
|
||||||
M: Anthony Liguori <aliguori@amazon.com>
|
M: Anthony Liguori <aliguori@us.ibm.com>
|
||||||
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
|
|
||||||
Guest CPU cores (TCG):
|
Guest CPU cores (TCG):
|
||||||
----------------------
|
----------------------
|
||||||
@@ -58,131 +59,99 @@ Alpha
|
|||||||
M: Richard Henderson <rth@twiddle.net>
|
M: Richard Henderson <rth@twiddle.net>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-alpha/
|
F: target-alpha/
|
||||||
F: hw/alpha/
|
|
||||||
|
|
||||||
ARM
|
ARM
|
||||||
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
M: Peter Maydell <peter.maydell@linaro.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-arm/
|
F: target-arm/
|
||||||
F: hw/arm/
|
|
||||||
F: hw/cpu/a*mpcore.c
|
|
||||||
|
|
||||||
CRIS
|
CRIS
|
||||||
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-cris/
|
F: target-cris/
|
||||||
F: hw/cris/
|
|
||||||
|
|
||||||
LM32
|
LM32
|
||||||
M: Michael Walle <michael@walle.cc>
|
M: Michael Walle <michael@walle.cc>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-lm32/
|
F: target-lm32/
|
||||||
F: hw/lm32/
|
|
||||||
F: hw/char/lm32_*
|
|
||||||
|
|
||||||
M68K
|
M68K
|
||||||
S: Orphan
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
|
S: Odd Fixes
|
||||||
F: target-m68k/
|
F: target-m68k/
|
||||||
F: hw/m68k/
|
|
||||||
|
|
||||||
MicroBlaze
|
MicroBlaze
|
||||||
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-microblaze/
|
F: target-microblaze/
|
||||||
F: hw/microblaze/
|
|
||||||
|
|
||||||
MIPS
|
MIPS
|
||||||
M: Aurelien Jarno <aurelien@aurel32.net>
|
M: Aurelien Jarno <aurelien@aurel32.net>
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: target-mips/
|
F: target-mips/
|
||||||
F: hw/mips/
|
|
||||||
|
|
||||||
Moxie
|
|
||||||
M: Anthony Green <green@moxielogic.com>
|
|
||||||
S: Maintained
|
|
||||||
F: target-moxie/
|
|
||||||
|
|
||||||
OpenRISC
|
|
||||||
M: Jia Liu <proljc@gmail.com>
|
|
||||||
S: Maintained
|
|
||||||
F: target-openrisc/
|
|
||||||
F: hw/openrisc/
|
|
||||||
|
|
||||||
PowerPC
|
PowerPC
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-ppc/
|
F: target-ppc/
|
||||||
F: hw/ppc/
|
|
||||||
|
|
||||||
S390
|
S390
|
||||||
M: Richard Henderson <rth@twiddle.net>
|
M: Richard Henderson <rth@twiddle.net>
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-s390x/
|
F: target-s390x/
|
||||||
F: hw/s390x/
|
|
||||||
|
|
||||||
SH4
|
SH4
|
||||||
M: Aurelien Jarno <aurelien@aurel32.net>
|
M: Aurelien Jarno <aurelien@aurel32.net>
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: target-sh4/
|
F: target-sh4/
|
||||||
F: hw/sh4/
|
|
||||||
|
|
||||||
SPARC
|
SPARC
|
||||||
M: Blue Swirl <blauwirbel@gmail.com>
|
M: Blue Swirl <blauwirbel@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-sparc/
|
F: target-sparc/
|
||||||
F: hw/sparc/
|
|
||||||
F: hw/sparc64/
|
|
||||||
|
|
||||||
UniCore32
|
UniCore32
|
||||||
M: Guan Xuetao <gxt@mprc.pku.edu.cn>
|
M: Guan Xuetao <gxt@mprc.pku.edu.cn>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-unicore32/
|
F: target-unicore32/
|
||||||
F: hw/unicore32/
|
|
||||||
|
|
||||||
X86
|
X86
|
||||||
M: qemu-devel@nongnu.org
|
M: qemu-devel@nongnu.org
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: target-i386/
|
F: target-i386/
|
||||||
F: hw/i386/
|
|
||||||
|
|
||||||
Xtensa
|
Xtensa
|
||||||
M: Max Filippov <jcmvbkbc@gmail.com>
|
M: Max Filippov <jcmvbkbc@gmail.com>
|
||||||
W: http://wiki.osll.spb.ru/doku.php?id=etc:users:jcmvbkbc:qemu-target-xtensa
|
W: http://wiki.osll.spb.ru/doku.php?id=etc:users:jcmvbkbc:qemu-target-xtensa
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-xtensa/
|
F: target-xtensa/
|
||||||
F: hw/xtensa/
|
|
||||||
|
|
||||||
Guest CPU Cores (KVM):
|
Guest CPU Cores (KVM):
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Overall
|
Overall
|
||||||
M: Paolo Bonzini <pbonzini@redhat.com>
|
M: Gleb Natapov <gleb@redhat.com>
|
||||||
|
M: Marcelo Tosatti <mtosatti@redhat.com>
|
||||||
L: kvm@vger.kernel.org
|
L: kvm@vger.kernel.org
|
||||||
S: Supported
|
S: Supported
|
||||||
F: kvm-*
|
F: kvm-*
|
||||||
F: */kvm.*
|
F: */kvm.*
|
||||||
|
|
||||||
ARM
|
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
|
||||||
S: Maintained
|
|
||||||
F: target-arm/kvm.c
|
|
||||||
|
|
||||||
PPC
|
PPC
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-ppc/kvm.c
|
F: target-ppc/kvm.c
|
||||||
|
|
||||||
S390
|
S390
|
||||||
M: Christian Borntraeger <borntraeger@de.ibm.com>
|
|
||||||
M: Cornelia Huck <cornelia.huck@de.ibm.com>
|
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: target-s390x/kvm.c
|
F: target-s390x/kvm.c
|
||||||
F: hw/intc/s390_flic.[hc]
|
|
||||||
|
|
||||||
X86
|
X86
|
||||||
|
M: Gleb Natapov <gleb@redhat.com>
|
||||||
M: Marcelo Tosatti <mtosatti@redhat.com>
|
M: Marcelo Tosatti <mtosatti@redhat.com>
|
||||||
L: kvm@vger.kernel.org
|
L: kvm@vger.kernel.org
|
||||||
S: Supported
|
S: Supported
|
||||||
@@ -220,171 +189,162 @@ F: *win32*
|
|||||||
|
|
||||||
ARM Machines
|
ARM Machines
|
||||||
------------
|
------------
|
||||||
Allwinner-a10
|
|
||||||
M: Li Guang <lig.fnst@cn.fujitsu.com>
|
|
||||||
S: Maintained
|
|
||||||
F: hw/*/allwinner-a10*
|
|
||||||
F: include/hw/*/allwinner-a10*
|
|
||||||
F: hw/arm/cubieboard.c
|
|
||||||
|
|
||||||
Exynos
|
Exynos
|
||||||
M: Evgeny Voevodin <e.voevodin@samsung.com>
|
M: Evgeny Voevodin <e.voevodin@samsung.com>
|
||||||
M: Maksim Kozlov <m.kozlov@samsung.com>
|
M: Maksim Kozlov <m.kozlov@samsung.com>
|
||||||
M: Igor Mitsyanko <i.mitsyanko@gmail.com>
|
M: Igor Mitsyanko <i.mitsyanko@samsung.com>
|
||||||
M: Dmitry Solodkiy <d.solodkiy@samsung.com>
|
M: Dmitry Solodkiy <d.solodkiy@samsung.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/*/exynos*
|
F: hw/exynos*
|
||||||
|
|
||||||
Calxeda Highbank
|
Calxeda Highbank
|
||||||
M: Mark Langsdorf <mark.langsdorf@calxeda.com>
|
M: Mark Langsdorf <mark.langsdorf@calxeda.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/arm/highbank.c
|
F: hw/highbank.c
|
||||||
F: hw/net/xgmac.c
|
F: hw/xgmac.c
|
||||||
|
|
||||||
Canon DIGIC
|
|
||||||
M: Antony Pavlov <antonynpavlov@gmail.com>
|
|
||||||
S: Maintained
|
|
||||||
F: include/hw/arm/digic.h
|
|
||||||
F: hw/*/digic*
|
|
||||||
|
|
||||||
Gumstix
|
Gumstix
|
||||||
M: qemu-devel@nongnu.org
|
M: qemu-devel@nongnu.org
|
||||||
S: Orphan
|
S: Orphan
|
||||||
F: hw/arm/gumstix.c
|
F: hw/gumstix.c
|
||||||
|
|
||||||
i.MX31
|
i.MX31
|
||||||
M: Peter Chubb <peter.chubb@nicta.com.au>
|
M: Peter Chubb <peter.chubb@nicta.com.au>
|
||||||
S: Odd fixes
|
S: Odd fixes
|
||||||
F: hw/*/imx*
|
F: hw/imx*
|
||||||
F: hw/arm/kzm.c
|
F: hw/kzm.c
|
||||||
|
|
||||||
Integrator CP
|
Integrator CP
|
||||||
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
M: Peter Maydell <peter.maydell@linaro.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/arm/integratorcp.c
|
F: hw/integratorcp.c
|
||||||
|
|
||||||
Mainstone
|
Mainstone
|
||||||
M: qemu-devel@nongnu.org
|
M: qemu-devel@nongnu.org
|
||||||
S: Orphan
|
S: Orphan
|
||||||
F: hw/arm/mainstone.c
|
F: hw/mainstone.c
|
||||||
|
|
||||||
Musicpal
|
Musicpal
|
||||||
M: Jan Kiszka <jan.kiszka@web.de>
|
M: Jan Kiszka <jan.kiszka@web.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/arm/musicpal.c
|
F: hw/musicpal.c
|
||||||
|
|
||||||
nSeries
|
nSeries
|
||||||
M: Andrzej Zaborowski <balrogg@gmail.com>
|
M: Andrzej Zaborowski <balrogg@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/arm/nseries.c
|
F: hw/nseries.c
|
||||||
|
|
||||||
Palm
|
Palm
|
||||||
M: Andrzej Zaborowski <balrogg@gmail.com>
|
M: Andrzej Zaborowski <balrogg@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/arm/palm.c
|
F: hw/palm.c
|
||||||
|
|
||||||
Real View
|
Real View
|
||||||
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
M: Peter Maydell <peter.maydell@linaro.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/arm/realview*
|
F: hw/realview*
|
||||||
|
|
||||||
Spitz
|
Spitz
|
||||||
M: Andrzej Zaborowski <balrogg@gmail.com>
|
M: Andrzej Zaborowski <balrogg@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/arm/spitz.c
|
F: hw/spitz.c
|
||||||
|
|
||||||
Stellaris
|
Stellaris
|
||||||
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
M: Peter Maydell <peter.maydell@linaro.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/*/stellaris*
|
F: hw/stellaris.c
|
||||||
|
|
||||||
Versatile PB
|
Versatile PB
|
||||||
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
M: Peter Maydell <peter.maydell@linaro.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/*/versatile*
|
F: hw/versatilepb.c
|
||||||
|
|
||||||
Xilinx Zynq
|
Xilinx Zynq
|
||||||
M: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
|
M: Peter Crosthwaite <peter.crosthwaite@petalogix.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/arm/xilinx_zynq.c
|
F: hw/xilinx_zynq.c
|
||||||
F: hw/misc/zynq_slcr.c
|
F: hw/zynq_slcr.c
|
||||||
F: hw/*/cadence_*
|
F: hw/cadence_*
|
||||||
F: hw/ssi/xilinx_spips.c
|
F: hw/xilinx_spips.c
|
||||||
|
|
||||||
CRIS Machines
|
CRIS Machines
|
||||||
-------------
|
-------------
|
||||||
Axis Dev88
|
Axis Dev88
|
||||||
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/cris/axis_dev88.c
|
F: hw/axis_dev88.c
|
||||||
F: hw/*/etraxfs_*.c
|
|
||||||
|
etraxfs
|
||||||
|
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
||||||
|
S: Maintained
|
||||||
|
F: hw/etraxfs.c
|
||||||
|
|
||||||
LM32 Machines
|
LM32 Machines
|
||||||
-------------
|
-------------
|
||||||
EVR32 and uclinux BSP
|
EVR32 and uclinux BSP
|
||||||
M: Michael Walle <michael@walle.cc>
|
M: Michael Walle <michael@walle.cc>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/lm32/lm32_boards.c
|
F: hw/lm32_boards.c
|
||||||
|
|
||||||
milkymist
|
milkymist
|
||||||
M: Michael Walle <michael@walle.cc>
|
M: Michael Walle <michael@walle.cc>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/lm32/milkymist.c
|
F: hw/milkymist.c
|
||||||
|
|
||||||
M68K Machines
|
M68K Machines
|
||||||
-------------
|
-------------
|
||||||
an5206
|
an5206
|
||||||
S: Orphan
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
F: hw/m68k/an5206.c
|
S: Maintained
|
||||||
|
F: hw/an5206.c
|
||||||
|
|
||||||
dummy_m68k
|
dummy_m68k
|
||||||
S: Orphan
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
F: hw/m68k/dummy_m68k.c
|
S: Maintained
|
||||||
|
F: hw/dummy_m68k.c
|
||||||
|
|
||||||
mcf5208
|
mcf5208
|
||||||
S: Orphan
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
F: hw/m68k/mcf5208.c
|
S: Maintained
|
||||||
|
F: hw/mcf5208.c
|
||||||
|
|
||||||
MicroBlaze Machines
|
MicroBlaze Machines
|
||||||
-------------------
|
-------------------
|
||||||
petalogix_s3adsp1800
|
petalogix_s3adsp1800
|
||||||
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/microblaze/petalogix_s3adsp1800_mmu.c
|
F: hw/petalogix_s3adsp1800.c
|
||||||
|
|
||||||
petalogix_ml605
|
petalogix_ml605
|
||||||
M: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
|
M: Peter Crosthwaite <peter.crosthwaite@petalogix.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/microblaze/petalogix_ml605_mmu.c
|
F: hw/petalogix_ml605_mmu.c
|
||||||
|
|
||||||
MIPS Machines
|
MIPS Machines
|
||||||
-------------
|
-------------
|
||||||
Jazz
|
Jazz
|
||||||
M: Hervé Poussineau <hpoussin@reactos.org>
|
M: Hervé Poussineau <hpoussin@reactos.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/mips/mips_jazz.c
|
F: hw/mips_jazz.c
|
||||||
|
|
||||||
Malta
|
Malta
|
||||||
M: Aurelien Jarno <aurelien@aurel32.net>
|
M: Aurelien Jarno <aurelien@aurel32.net>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/mips/mips_malta.c
|
F: hw/mips_malta.c
|
||||||
|
|
||||||
Mipssim
|
Mipssim
|
||||||
M: qemu-devel@nongnu.org
|
M: qemu-devel@nongnu.org
|
||||||
S: Orphan
|
S: Orphan
|
||||||
F: hw/mips/mips_mipssim.c
|
F: hw/mips_mipssim.c
|
||||||
|
|
||||||
R4000
|
R4000
|
||||||
M: Aurelien Jarno <aurelien@aurel32.net>
|
M: Aurelien Jarno <aurelien@aurel32.net>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/mips/mips_r4k.c
|
F: hw/mips_r4k.c
|
||||||
|
|
||||||
OpenRISC Machines
|
|
||||||
-----------------
|
|
||||||
or1k-sim
|
|
||||||
M: Jia Liu <proljc@gmail.com>
|
|
||||||
S: Maintained
|
|
||||||
F: hw/openrisc/openrisc_sim.c
|
|
||||||
|
|
||||||
PowerPC Machines
|
PowerPC Machines
|
||||||
----------------
|
----------------
|
||||||
@@ -392,13 +352,13 @@ PowerPC Machines
|
|||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: hw/ppc/ppc405_boards.c
|
F: hw/ppc405_boards.c
|
||||||
|
|
||||||
Bamboo
|
Bamboo
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: hw/ppc/ppc440_bamboo.c
|
F: hw/ppc440_bamboo.c
|
||||||
|
|
||||||
e500
|
e500
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
@@ -414,258 +374,216 @@ M: Scott Wood <scottwood@freescale.com>
|
|||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/ppc/mpc8544ds.c
|
F: hw/ppc/mpc8544ds.c
|
||||||
F: hw/ppc/mpc8544_guts.c
|
F: hw/mpc8544_guts.c
|
||||||
|
|
||||||
New World
|
New World
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/ppc/mac_newworld.c
|
F: hw/ppc/mac_newworld.c
|
||||||
F: hw/pci-host/uninorth.c
|
F: hw/unin_pci.c
|
||||||
F: hw/pci-bridge/dec.[hc]
|
F: hw/dec_pci.[hc]
|
||||||
F: hw/misc/macio/
|
|
||||||
|
|
||||||
Old World
|
Old World
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/ppc/mac_oldworld.c
|
F: hw/ppc/mac_oldworld.c
|
||||||
F: hw/pci-host/grackle.c
|
F: hw/grackle_pci.c
|
||||||
F: hw/misc/macio/
|
|
||||||
|
|
||||||
PReP
|
PReP
|
||||||
M: Andreas Färber <andreas.faerber@web.de>
|
M: Andreas Färber <andreas.faerber@web.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: hw/ppc/prep.c
|
F: hw/ppc/prep.c
|
||||||
F: hw/pci-host/prep.[hc]
|
F: hw/prep_pci.[hc]
|
||||||
F: hw/isa/pc87312.[hc]
|
F: hw/pc87312.[hc]
|
||||||
|
|
||||||
sPAPR
|
sPAPR
|
||||||
|
M: David Gibson <david@gibson.dropbear.id.au>
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/*/spapr*
|
F: hw/spapr*
|
||||||
F: include/hw/*/spapr*
|
|
||||||
F: hw/*/xics*
|
|
||||||
F: include/hw/*/xics*
|
|
||||||
F: pc-bios/spapr-rtas/*
|
|
||||||
|
|
||||||
virtex_ml507
|
virtex_ml507
|
||||||
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: hw/ppc/virtex_ml507.c
|
F: hw/virtex_ml507.c
|
||||||
|
|
||||||
SH4 Machines
|
SH4 Machines
|
||||||
------------
|
------------
|
||||||
R2D
|
R2D
|
||||||
M: Magnus Damm <magnus.damm@gmail.com>
|
M: Magnus Damm <magnus.damm@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/sh4/r2d.c
|
F: hw/r2d.c
|
||||||
|
|
||||||
Shix
|
Shix
|
||||||
M: Magnus Damm <magnus.damm@gmail.com>
|
M: Magnus Damm <magnus.damm@gmail.com>
|
||||||
S: Orphan
|
S: Orphan
|
||||||
F: hw/sh4/shix.c
|
F: hw/shix.c
|
||||||
|
|
||||||
SPARC Machines
|
SPARC Machines
|
||||||
--------------
|
--------------
|
||||||
Sun4m
|
Sun4m
|
||||||
M: Blue Swirl <blauwirbel@gmail.com>
|
M: Blue Swirl <blauwirbel@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/sparc/sun4m.c
|
F: hw/sun4m.c
|
||||||
|
|
||||||
Sun4u
|
Sun4u
|
||||||
M: Blue Swirl <blauwirbel@gmail.com>
|
M: Blue Swirl <blauwirbel@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/sparc64/sun4u.c
|
F: hw/sun4u.c
|
||||||
|
|
||||||
Leon3
|
Leon3
|
||||||
M: Fabien Chouteau <chouteau@adacore.com>
|
M: Fabien Chouteau <chouteau@adacore.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/sparc/leon3.c
|
F: hw/leon3.c
|
||||||
F: hw/*/grlib*
|
F: hw/grlib*
|
||||||
|
|
||||||
S390 Machines
|
S390 Machines
|
||||||
-------------
|
-------------
|
||||||
S390 Virtio
|
S390 Virtio
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/s390x/s390-*.c
|
F: hw/s390-*.c
|
||||||
|
|
||||||
S390 Virtio-ccw
|
|
||||||
M: Cornelia Huck <cornelia.huck@de.ibm.com>
|
|
||||||
M: Christian Borntraeger <borntraeger@de.ibm.com>
|
|
||||||
M: Alexander Graf <agraf@suse.de>
|
|
||||||
S: Supported
|
|
||||||
F: hw/s390x/s390-virtio-ccw.c
|
|
||||||
F: hw/s390x/css.[hc]
|
|
||||||
F: hw/s390x/sclp*.[hc]
|
|
||||||
F: hw/s390x/ipl*.[hc]
|
|
||||||
T: git git://github.com/cohuck/qemu virtio-ccw-upstr
|
|
||||||
|
|
||||||
UniCore32 Machines
|
UniCore32 Machines
|
||||||
-------------
|
-------------
|
||||||
PKUnity-3 SoC initramfs-with-busybox
|
PKUnity-3 SoC initramfs-with-busybox
|
||||||
M: Guan Xuetao <gxt@mprc.pku.edu.cn>
|
M: Guan Xuetao <gxt@mprc.pku.edu.cn>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/*/puv3*
|
F: hw/puv3*
|
||||||
F: hw/unicore32/
|
F: hw/unicore32/
|
||||||
|
|
||||||
X86 Machines
|
X86 Machines
|
||||||
------------
|
------------
|
||||||
PC
|
PC
|
||||||
M: Anthony Liguori <aliguori@amazon.com>
|
M: Anthony Liguori <aliguori@us.ibm.com>
|
||||||
M: Michael S. Tsirkin <mst@redhat.com>
|
|
||||||
S: Supported
|
S: Supported
|
||||||
F: include/hw/i386/
|
F: hw/pc.[ch]
|
||||||
F: hw/i386/
|
F: hw/pc_piix.c
|
||||||
F: hw/pci-host/piix.c
|
|
||||||
F: hw/pci-host/q35.c
|
|
||||||
F: hw/pci-host/pam.c
|
|
||||||
F: include/hw/pci-host/q35.h
|
|
||||||
F: include/hw/pci-host/pam.h
|
|
||||||
F: hw/isa/piix4.c
|
|
||||||
F: hw/isa/lpc_ich9.c
|
|
||||||
F: hw/i2c/smbus_ich9.c
|
|
||||||
F: hw/acpi/piix4.c
|
|
||||||
F: hw/acpi/ich9.c
|
|
||||||
F: include/hw/acpi/ich9.h
|
|
||||||
F: include/hw/acpi/piix.h
|
|
||||||
|
|
||||||
|
|
||||||
Xtensa Machines
|
Xtensa Machines
|
||||||
---------------
|
---------------
|
||||||
sim
|
sim
|
||||||
M: Max Filippov <jcmvbkbc@gmail.com>
|
M: Max Filippov <jcmvbkbc@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/xtensa/xtensa_sim.c
|
F: hw/xtensa_sim.c
|
||||||
|
|
||||||
Avnet LX60
|
Avnet LX60
|
||||||
M: Max Filippov <jcmvbkbc@gmail.com>
|
M: Max Filippov <jcmvbkbc@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/xtensa/xtensa_lx60.c
|
F: hw/xtensa_lx60.c
|
||||||
|
|
||||||
Devices
|
Devices
|
||||||
-------
|
-------
|
||||||
IDE
|
IDE
|
||||||
M: Kevin Wolf <kwolf@redhat.com>
|
M: Kevin Wolf <kwolf@redhat.com>
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: include/hw/ide.h
|
|
||||||
F: hw/ide/
|
F: hw/ide/
|
||||||
|
|
||||||
OMAP
|
OMAP
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
M: Peter Maydell <peter.maydell@linaro.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/*/omap*
|
F: hw/omap*
|
||||||
|
|
||||||
PCI
|
PCI
|
||||||
M: Michael S. Tsirkin <mst@redhat.com>
|
M: Michael S. Tsirkin <mst@redhat.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: include/hw/pci/*
|
|
||||||
F: hw/pci/*
|
F: hw/pci/*
|
||||||
F: hw/acpi/*
|
F: hw/pci*
|
||||||
|
F: hw/piix*
|
||||||
|
|
||||||
ppc4xx
|
ppc4xx
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: hw/ppc/ppc4*.c
|
F: hw/ppc4xx*.[hc]
|
||||||
|
|
||||||
ppce500
|
ppce500
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
M: Scott Wood <scottwood@freescale.com>
|
M: Scott Wood <scottwood@freescale.com>
|
||||||
L: qemu-ppc@nongnu.org
|
L: qemu-ppc@nongnu.org
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/ppc/e500*
|
F: hw/ppce500_*
|
||||||
|
|
||||||
SCSI
|
SCSI
|
||||||
M: Paolo Bonzini <pbonzini@redhat.com>
|
M: Paolo Bonzini <pbonzini@redhat.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: include/hw/scsi*
|
F: hw/virtio-scsi.*
|
||||||
F: hw/scsi/*
|
F: hw/scsi*
|
||||||
T: git git://github.com/bonzini/qemu.git scsi-next
|
T: git git://github.com/bonzini/qemu.git scsi-next
|
||||||
|
|
||||||
LSI53C895A
|
LSI53C895A
|
||||||
S: Orphan
|
M: Paul Brook <paul@codesourcery.com>
|
||||||
F: hw/scsi/lsi53c895a.c
|
S: Odd Fixes
|
||||||
|
F: hw/lsi53c895a.c
|
||||||
|
|
||||||
SSI
|
SSI
|
||||||
M: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
|
M: Peter Crosthwaite <peter.crosthwaite@petalogix.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/ssi/*
|
F: hw/ssi.*
|
||||||
F: hw/block/m25p80.c
|
F: hw/m25p80.c
|
||||||
|
|
||||||
USB
|
USB
|
||||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
M: Gerd Hoffmann <kraxel@redhat.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/usb/*
|
F: hw/usb*
|
||||||
|
|
||||||
VFIO
|
VFIO
|
||||||
M: Alex Williamson <alex.williamson@redhat.com>
|
M: Alex Williamson <alex.williamson@redhat.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/misc/vfio.c
|
F: hw/vfio*
|
||||||
|
|
||||||
vhost
|
vhost
|
||||||
M: Michael S. Tsirkin <mst@redhat.com>
|
M: Michael S. Tsirkin <mst@redhat.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/*/*vhost*
|
F: hw/vhost*
|
||||||
|
|
||||||
virtio
|
virtio
|
||||||
M: Anthony Liguori <aliguori@amazon.com>
|
M: Anthony Liguori <aliguori@us.ibm.com>
|
||||||
M: Michael S. Tsirkin <mst@redhat.com>
|
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/*/virtio*
|
F: hw/virtio*
|
||||||
|
|
||||||
virtio-9p
|
virtio-9p
|
||||||
M: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
|
M: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/9pfs/
|
F: hw/9pfs/
|
||||||
F: fsdev/
|
F: fsdev/
|
||||||
F: tests/virtio-9p-test.c
|
|
||||||
T: git git://github.com/kvaneesh/QEMU.git
|
T: git git://github.com/kvaneesh/QEMU.git
|
||||||
|
|
||||||
virtio-blk
|
virtio-blk
|
||||||
M: Kevin Wolf <kwolf@redhat.com>
|
M: Kevin Wolf <kwolf@redhat.com>
|
||||||
M: Stefan Hajnoczi <stefanha@redhat.com>
|
M: Stefan Hajnoczi <stefanha@redhat.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/block/virtio-blk.c
|
F: hw/virtio-blk*
|
||||||
|
|
||||||
virtio-ccw
|
|
||||||
M: Cornelia Huck <cornelia.huck@de.ibm.com>
|
|
||||||
M: Christian Borntraeger <borntraeger@de.ibm.com>
|
|
||||||
S: Supported
|
|
||||||
F: hw/s390x/virtio-ccw.[hc]
|
|
||||||
T: git git://github.com/cohuck/qemu virtio-ccw-upstr
|
|
||||||
|
|
||||||
virtio-serial
|
virtio-serial
|
||||||
M: Amit Shah <amit.shah@redhat.com>
|
M: Amit Shah <amit.shah@redhat.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: hw/char/virtio-serial-bus.c
|
F: hw/virtio-serial*
|
||||||
F: hw/char/virtio-console.c
|
F: hw/virtio-console*
|
||||||
|
|
||||||
nvme
|
|
||||||
M: Keith Busch <keith.busch@intel.com>
|
|
||||||
S: Supported
|
|
||||||
F: hw/block/nvme*
|
|
||||||
F: tests/nvme-test.c
|
|
||||||
|
|
||||||
Xilinx EDK
|
Xilinx EDK
|
||||||
M: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
|
M: Peter Crosthwaite <peter.crosthwaite@petalogix.com>
|
||||||
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
M: Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/*/xilinx_*
|
F: hw/xilinx_axi*
|
||||||
F: include/hw/xilinx.h
|
F: hw/xilinx_uartlite.c
|
||||||
|
F: hw/xilinx_intc.c
|
||||||
|
F: hw/xilinx_ethlite.c
|
||||||
|
F: hw/xilinx_timer.c
|
||||||
|
F: hw/xilinx.h
|
||||||
|
F: hw/xilinx_spi.c
|
||||||
|
|
||||||
Subsystems
|
Subsystems
|
||||||
----------
|
----------
|
||||||
Audio
|
Audio
|
||||||
M: Vassili Karpov (malc) <av1474@comtv.ru>
|
M: Vassili Karpov (malc) <av1474@comtv.ru>
|
||||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: audio/
|
F: audio/
|
||||||
F: hw/audio/
|
|
||||||
|
|
||||||
Block
|
Block
|
||||||
M: Kevin Wolf <kwolf@redhat.com>
|
M: Kevin Wolf <kwolf@redhat.com>
|
||||||
@@ -673,12 +591,9 @@ M: Stefan Hajnoczi <stefanha@redhat.com>
|
|||||||
S: Supported
|
S: Supported
|
||||||
F: block*
|
F: block*
|
||||||
F: block/
|
F: block/
|
||||||
F: hw/block/
|
|
||||||
T: git git://repo.or.cz/qemu/kevin.git block
|
|
||||||
T: git git://github.com/stefanha/qemu.git block
|
|
||||||
|
|
||||||
Character Devices
|
Character Devices
|
||||||
M: Anthony Liguori <aliguori@amazon.com>
|
M: Anthony Liguori <aliguori@us.ibm.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: qemu-char.c
|
F: qemu-char.c
|
||||||
|
|
||||||
@@ -686,20 +601,14 @@ CPU
|
|||||||
M: Andreas Färber <afaerber@suse.de>
|
M: Andreas Färber <afaerber@suse.de>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: qom/cpu.c
|
F: qom/cpu.c
|
||||||
F: include/qom/cpu.h
|
F: include/qemu/cpu.h
|
||||||
F: target-i386/cpu.c
|
F: target-i386/cpu.c
|
||||||
|
|
||||||
ICC Bus
|
|
||||||
M: Igor Mammedov <imammedo@redhat.com>
|
|
||||||
S: Supported
|
|
||||||
F: include/hw/cpu/icc_bus.h
|
|
||||||
F: hw/cpu/icc_bus.c
|
|
||||||
|
|
||||||
Device Tree
|
Device Tree
|
||||||
M: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
|
M: Peter Crosthwaite <peter.crosthwaite@petalogix.com>
|
||||||
M: Alexander Graf <agraf@suse.de>
|
M: Alexander Graf <agraf@suse.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: device_tree.[ch]
|
F: device-tree.[ch]
|
||||||
|
|
||||||
GDB stub
|
GDB stub
|
||||||
M: qemu-devel@nongnu.org
|
M: qemu-devel@nongnu.org
|
||||||
@@ -710,50 +619,39 @@ F: gdb-xml/
|
|||||||
SPICE
|
SPICE
|
||||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
M: Gerd Hoffmann <kraxel@redhat.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: include/ui/qemu-spice.h
|
F: ui/qemu-spice.h
|
||||||
F: ui/spice-*.c
|
F: ui/spice-*.c
|
||||||
F: audio/spiceaudio.c
|
F: audio/spiceaudio.c
|
||||||
F: hw/display/qxl*
|
F: hw/qxl*
|
||||||
|
|
||||||
Graphics
|
Graphics
|
||||||
M: Anthony Liguori <aliguori@amazon.com>
|
M: Anthony Liguori <aliguori@us.ibm.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: ui/
|
F: ui/
|
||||||
|
|
||||||
Cocoa graphics
|
Cocoa graphics
|
||||||
M: Andreas Färber <andreas.faerber@web.de>
|
M: Andreas Färber <andreas.faerber@web.de>
|
||||||
M: Peter Maydell <peter.maydell@linaro.org>
|
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: ui/cocoa.m
|
F: ui/cocoa.m
|
||||||
|
|
||||||
Main loop
|
Main loop
|
||||||
M: Anthony Liguori <aliguori@amazon.com>
|
M: Anthony Liguori <aliguori@us.ibm.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: vl.c
|
F: vl.c
|
||||||
|
|
||||||
Human Monitor (HMP)
|
Monitor (QMP/HMP)
|
||||||
M: Luiz Capitulino <lcapitulino@redhat.com>
|
M: Luiz Capitulino <lcapitulino@redhat.com>
|
||||||
S: Maintained
|
M: Markus Armbruster <armbru@redhat.com>
|
||||||
|
S: Supported
|
||||||
F: monitor.c
|
F: monitor.c
|
||||||
F: hmp.c
|
|
||||||
F: hmp-commands.hx
|
|
||||||
T: git git://repo.or.cz/qemu/qmp-unstable.git queue/qmp
|
|
||||||
|
|
||||||
Network device layer
|
Network device layer
|
||||||
M: Anthony Liguori <aliguori@amazon.com>
|
M: Anthony Liguori <aliguori@us.ibm.com>
|
||||||
M: Stefan Hajnoczi <stefanha@redhat.com>
|
M: Stefan Hajnoczi <stefanha@redhat.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: net/
|
F: net/
|
||||||
T: git git://github.com/stefanha/qemu.git net
|
T: git git://github.com/stefanha/qemu.git net
|
||||||
|
|
||||||
Netmap network backend
|
|
||||||
M: Luigi Rizzo <rizzo@iet.unipi.it>
|
|
||||||
M: Giuseppe Lettieri <g.lettieri@iet.unipi.it>
|
|
||||||
M: Vincenzo Maffione <v.maffione@gmail.com>
|
|
||||||
W: http://info.iet.unipi.it/~luigi/netmap/
|
|
||||||
S: Maintained
|
|
||||||
F: net/netmap.c
|
|
||||||
|
|
||||||
Network Block Device (NBD)
|
Network Block Device (NBD)
|
||||||
M: Paolo Bonzini <pbonzini@redhat.com>
|
M: Paolo Bonzini <pbonzini@redhat.com>
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
@@ -762,30 +660,6 @@ F: nbd.*
|
|||||||
F: qemu-nbd.c
|
F: qemu-nbd.c
|
||||||
T: git git://github.com/bonzini/qemu.git nbd-next
|
T: git git://github.com/bonzini/qemu.git nbd-next
|
||||||
|
|
||||||
QAPI
|
|
||||||
M: Luiz Capitulino <lcapitulino@redhat.com>
|
|
||||||
M: Michael Roth <mdroth@linux.vnet.ibm.com>
|
|
||||||
S: Maintained
|
|
||||||
F: qapi/
|
|
||||||
T: git git://repo.or.cz/qemu/qmp-unstable.git queue/qmp
|
|
||||||
|
|
||||||
QAPI Schema
|
|
||||||
M: Eric Blake <eblake@redhat.com>
|
|
||||||
M: Luiz Capitulino <lcapitulino@redhat.com>
|
|
||||||
M: Markus Armbruster <armbru@redhat.com>
|
|
||||||
S: Supported
|
|
||||||
F: qapi-schema.json
|
|
||||||
T: git git://repo.or.cz/qemu/qmp-unstable.git queue/qmp
|
|
||||||
|
|
||||||
QMP
|
|
||||||
M: Luiz Capitulino <lcapitulino@redhat.com>
|
|
||||||
S: Maintained
|
|
||||||
F: qmp.c
|
|
||||||
F: monitor.c
|
|
||||||
F: qmp-commands.hx
|
|
||||||
F: QMP/
|
|
||||||
T: git git://repo.or.cz/qemu/qmp-unstable.git queue/qmp
|
|
||||||
|
|
||||||
SLIRP
|
SLIRP
|
||||||
M: Jan Kiszka <jan.kiszka@siemens.com>
|
M: Jan Kiszka <jan.kiszka@siemens.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
@@ -806,12 +680,6 @@ M: Blue Swirl <blauwirbel@gmail.com>
|
|||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: scripts/checkpatch.pl
|
F: scripts/checkpatch.pl
|
||||||
|
|
||||||
Seccomp
|
|
||||||
M: Eduardo Otubo <otubo@linux.vnet.ibm.com>
|
|
||||||
S: Supported
|
|
||||||
F: qemu-seccomp.c
|
|
||||||
F: include/sysemu/seccomp.h
|
|
||||||
|
|
||||||
Usermode Emulation
|
Usermode Emulation
|
||||||
------------------
|
------------------
|
||||||
BSD user
|
BSD user
|
||||||
@@ -828,21 +696,19 @@ Tiny Code Generator (TCG)
|
|||||||
-------------------------
|
-------------------------
|
||||||
Common code
|
Common code
|
||||||
M: qemu-devel@nongnu.org
|
M: qemu-devel@nongnu.org
|
||||||
M: Richard Henderson <rth@twiddle.net>
|
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: tcg/
|
F: tcg/
|
||||||
|
|
||||||
AArch64 target
|
|
||||||
M: Claudio Fontana <claudio.fontana@huawei.com>
|
|
||||||
M: Claudio Fontana <claudio.fontana@gmail.com>
|
|
||||||
S: Maintained
|
|
||||||
F: tcg/aarch64/
|
|
||||||
|
|
||||||
ARM target
|
ARM target
|
||||||
M: Andrzej Zaborowski <balrogg@gmail.com>
|
M: Andrzej Zaborowski <balrogg@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: tcg/arm/
|
F: tcg/arm/
|
||||||
|
|
||||||
|
HPPA target
|
||||||
|
M: Richard Henderson <rth@twiddle.net>
|
||||||
|
S: Maintained
|
||||||
|
F: tcg/hppa/
|
||||||
|
|
||||||
i386 target
|
i386 target
|
||||||
M: qemu-devel@nongnu.org
|
M: qemu-devel@nongnu.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
@@ -883,73 +749,25 @@ TCI target
|
|||||||
M: Stefan Weil <sw@weilnetz.de>
|
M: Stefan Weil <sw@weilnetz.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: tcg/tci/
|
F: tcg/tci/
|
||||||
F: tci.c
|
|
||||||
|
|
||||||
Stable branches
|
Stable branches
|
||||||
---------------
|
---------------
|
||||||
Stable 1.0
|
Stable 1.0
|
||||||
L: qemu-stable@nongnu.org
|
L: qemu-stable@nongnu.org
|
||||||
T: git git://git.qemu-project.org/qemu-stable-1.0.git
|
T: git git://git.qemu.org/qemu-stable-1.0.git
|
||||||
S: Orphan
|
S: Orphan
|
||||||
|
|
||||||
Stable 0.15
|
Stable 0.15
|
||||||
L: qemu-stable@nongnu.org
|
L: qemu-stable@nongnu.org
|
||||||
M: Andreas Färber <afaerber@suse.de>
|
T: git git://git.qemu.org/qemu-stable-0.15.git
|
||||||
T: git git://git.qemu-project.org/qemu-stable-0.15.git
|
S: Orphan
|
||||||
S: Supported
|
|
||||||
|
|
||||||
Stable 0.14
|
Stable 0.14
|
||||||
L: qemu-stable@nongnu.org
|
L: qemu-stable@nongnu.org
|
||||||
T: git git://git.qemu-project.org/qemu-stable-0.14.git
|
T: git git://git.qemu.org/qemu-stable-0.14.git
|
||||||
S: Orphan
|
S: Orphan
|
||||||
|
|
||||||
Stable 0.10
|
Stable 0.10
|
||||||
L: qemu-stable@nongnu.org
|
L: qemu-stable@nongnu.org
|
||||||
T: git git://git.qemu-project.org/qemu-stable-0.10.git
|
T: git git://git.qemu.org/qemu-stable-0.10.git
|
||||||
S: Orphan
|
S: Orphan
|
||||||
|
|
||||||
Block drivers
|
|
||||||
-------------
|
|
||||||
VMDK
|
|
||||||
M: Fam Zheng <famz@redhat.com>
|
|
||||||
S: Supported
|
|
||||||
F: block/vmdk.c
|
|
||||||
|
|
||||||
RBD
|
|
||||||
M: Josh Durgin <josh.durgin@inktank.com>
|
|
||||||
S: Supported
|
|
||||||
F: block/rbd.c
|
|
||||||
|
|
||||||
Sheepdog
|
|
||||||
M: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
|
|
||||||
M: Liu Yuan <namei.unix@gmail.com>
|
|
||||||
L: sheepdog@lists.wpkg.org
|
|
||||||
S: Supported
|
|
||||||
F: block/sheepdog.c
|
|
||||||
|
|
||||||
VHDX
|
|
||||||
M: Jeff Cody <jcody@redhat.com>
|
|
||||||
S: Supported
|
|
||||||
F: block/vhdx*
|
|
||||||
|
|
||||||
VDI
|
|
||||||
M: Stefan Weil <sw@weilnetz.de>
|
|
||||||
S: Maintained
|
|
||||||
F: block/vdi.c
|
|
||||||
|
|
||||||
iSCSI
|
|
||||||
M: Ronnie Sahlberg <ronniesahlberg@gmail.com>
|
|
||||||
M: Paolo Bonzini <pbonzini@redhat.com>
|
|
||||||
M: Peter Lieven <pl@kamp.de>
|
|
||||||
S: Supported
|
|
||||||
F: block/iscsi.c
|
|
||||||
|
|
||||||
NFS
|
|
||||||
M: Peter Lieven <pl@kamp.de>
|
|
||||||
S: Maintained
|
|
||||||
F: block/nfs.c
|
|
||||||
|
|
||||||
SSH
|
|
||||||
M: Richard W.M. Jones <rjones@redhat.com>
|
|
||||||
S: Supported
|
|
||||||
F: block/ssh.c
|
|
||||||
|
|||||||
201
Makefile
201
Makefile
@@ -19,23 +19,10 @@ seems to have been used for an in-tree build. You can fix this by running \
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CONFIG_SOFTMMU := $(if $(filter %-softmmu,$(TARGET_DIRS)),y)
|
|
||||||
CONFIG_USER_ONLY := $(if $(filter %-user,$(TARGET_DIRS)),y)
|
|
||||||
CONFIG_ALL=y
|
|
||||||
-include config-all-devices.mak
|
|
||||||
-include config-all-disas.mak
|
|
||||||
|
|
||||||
include $(SRC_PATH)/rules.mak
|
include $(SRC_PATH)/rules.mak
|
||||||
config-host.mak: $(SRC_PATH)/configure
|
config-host.mak: $(SRC_PATH)/configure
|
||||||
@echo $@ is out-of-date, running configure
|
@echo $@ is out-of-date, running configure
|
||||||
@# TODO: The next lines include code which supports a smooth
|
@sed -n "/.*Configured with/s/[^:]*: //p" $@ | sh
|
||||||
@# transition from old configurations without config.status.
|
|
||||||
@# This code can be removed after QEMU 1.7.
|
|
||||||
@if test -x config.status; then \
|
|
||||||
./config.status; \
|
|
||||||
else \
|
|
||||||
sed -n "/.*Configured with/s/[^:]*: //p" $@ | sh; \
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
config-host.mak:
|
config-host.mak:
|
||||||
ifneq ($(filter-out %clean,$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
ifneq ($(filter-out %clean,$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
||||||
@@ -48,20 +35,12 @@ GENERATED_HEADERS = config-host.h qemu-options.def
|
|||||||
GENERATED_HEADERS += qmp-commands.h qapi-types.h qapi-visit.h
|
GENERATED_HEADERS += qmp-commands.h qapi-types.h qapi-visit.h
|
||||||
GENERATED_SOURCES += qmp-marshal.c qapi-types.c qapi-visit.c
|
GENERATED_SOURCES += qmp-marshal.c qapi-types.c qapi-visit.c
|
||||||
|
|
||||||
GENERATED_HEADERS += trace/generated-events.h
|
|
||||||
GENERATED_SOURCES += trace/generated-events.c
|
|
||||||
|
|
||||||
GENERATED_HEADERS += trace/generated-tracers.h
|
GENERATED_HEADERS += trace/generated-tracers.h
|
||||||
ifeq ($(TRACE_BACKEND),dtrace)
|
ifeq ($(TRACE_BACKEND),dtrace)
|
||||||
GENERATED_HEADERS += trace/generated-tracers-dtrace.h
|
GENERATED_HEADERS += trace/generated-tracers-dtrace.h
|
||||||
endif
|
endif
|
||||||
GENERATED_SOURCES += trace/generated-tracers.c
|
GENERATED_SOURCES += trace/generated-tracers.c
|
||||||
|
|
||||||
ifeq ($(TRACE_BACKEND),ust)
|
|
||||||
GENERATED_HEADERS += trace/generated-ust-provider.h
|
|
||||||
GENERATED_SOURCES += trace/generated-ust.c
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Don't try to regenerate Makefile or configure
|
# Don't try to regenerate Makefile or configure
|
||||||
# We don't generate any of them
|
# We don't generate any of them
|
||||||
Makefile: ;
|
Makefile: ;
|
||||||
@@ -77,7 +56,7 @@ LIBS+=-lz $(LIBS_TOOLS)
|
|||||||
HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
|
HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
|
||||||
|
|
||||||
ifdef BUILD_DOCS
|
ifdef BUILD_DOCS
|
||||||
DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 qmp-commands.txt
|
DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 QMP/qmp-commands.txt
|
||||||
ifdef CONFIG_VIRTFS
|
ifdef CONFIG_VIRTFS
|
||||||
DOCS+=fsdev/virtfs-proxy-helper.1
|
DOCS+=fsdev/virtfs-proxy-helper.1
|
||||||
endif
|
endif
|
||||||
@@ -87,17 +66,14 @@ endif
|
|||||||
|
|
||||||
SUBDIR_MAKEFLAGS=$(if $(V),,--no-print-directory) BUILD_DIR=$(BUILD_DIR)
|
SUBDIR_MAKEFLAGS=$(if $(V),,--no-print-directory) BUILD_DIR=$(BUILD_DIR)
|
||||||
SUBDIR_DEVICES_MAK=$(patsubst %, %/config-devices.mak, $(TARGET_DIRS))
|
SUBDIR_DEVICES_MAK=$(patsubst %, %/config-devices.mak, $(TARGET_DIRS))
|
||||||
SUBDIR_DEVICES_MAK_DEP=$(patsubst %, %-config-devices.mak.d, $(TARGET_DIRS))
|
SUBDIR_DEVICES_MAK_DEP=$(patsubst %, %/config-devices.mak.d, $(TARGET_DIRS))
|
||||||
|
|
||||||
ifeq ($(SUBDIR_DEVICES_MAK),)
|
ifeq ($(SUBDIR_DEVICES_MAK),)
|
||||||
config-all-devices.mak:
|
config-all-devices.mak:
|
||||||
$(call quiet-command,echo '# no devices' > $@," GEN $@")
|
$(call quiet-command,echo '# no devices' > $@," GEN $@")
|
||||||
else
|
else
|
||||||
config-all-devices.mak: $(SUBDIR_DEVICES_MAK)
|
config-all-devices.mak: $(SUBDIR_DEVICES_MAK)
|
||||||
$(call quiet-command, sed -n \
|
$(call quiet-command,cat $(SUBDIR_DEVICES_MAK) | grep =y | sort -u > $@," GEN $@")
|
||||||
's|^\([^=]*\)=\(.*\)$$|\1:=$$(findstring y,$$(\1)\2)|p' \
|
|
||||||
$(SUBDIR_DEVICES_MAK) | sort -u > $@, \
|
|
||||||
" GEN $@")
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
-include $(SUBDIR_DEVICES_MAK_DEP)
|
-include $(SUBDIR_DEVICES_MAK_DEP)
|
||||||
@@ -125,31 +101,21 @@ endif
|
|||||||
defconfig:
|
defconfig:
|
||||||
rm -f config-all-devices.mak $(SUBDIR_DEVICES_MAK)
|
rm -f config-all-devices.mak $(SUBDIR_DEVICES_MAK)
|
||||||
|
|
||||||
|
-include config-all-devices.mak
|
||||||
|
-include config-all-disas.mak
|
||||||
|
CONFIG_SOFTMMU := $(if $(filter %-softmmu,$(TARGET_DIRS)),y)
|
||||||
|
CONFIG_USER_ONLY := $(if $(filter %-user,$(TARGET_DIRS)),y)
|
||||||
|
CONFIG_ALL=y
|
||||||
|
|
||||||
ifneq ($(wildcard config-host.mak),)
|
ifneq ($(wildcard config-host.mak),)
|
||||||
include $(SRC_PATH)/Makefile.objs
|
include $(SRC_PATH)/Makefile.objs
|
||||||
endif
|
|
||||||
|
|
||||||
dummy := $(call unnest-vars,, \
|
|
||||||
stub-obj-y \
|
|
||||||
util-obj-y \
|
|
||||||
qga-obj-y \
|
|
||||||
block-obj-y \
|
|
||||||
block-obj-m \
|
|
||||||
common-obj-y \
|
|
||||||
common-obj-m)
|
|
||||||
|
|
||||||
ifneq ($(wildcard config-host.mak),)
|
|
||||||
include $(SRC_PATH)/tests/Makefile
|
include $(SRC_PATH)/tests/Makefile
|
||||||
endif
|
endif
|
||||||
ifeq ($(CONFIG_SMARTCARD_NSS),y)
|
ifeq ($(CONFIG_SMARTCARD_NSS),y)
|
||||||
include $(SRC_PATH)/libcacard/Makefile
|
include $(SRC_PATH)/libcacard/Makefile
|
||||||
endif
|
endif
|
||||||
|
|
||||||
all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all modules
|
all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all
|
||||||
|
|
||||||
vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS)
|
|
||||||
|
|
||||||
vl.o: QEMU_CFLAGS+=$(SDL_CFLAGS)
|
|
||||||
|
|
||||||
config-host.h: config-host.h-timestamp
|
config-host.h: config-host.h-timestamp
|
||||||
config-host.h-timestamp: config-host.mak
|
config-host.h-timestamp: config-host.mak
|
||||||
@@ -157,10 +123,6 @@ qemu-options.def: $(SRC_PATH)/qemu-options.hx
|
|||||||
$(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))
|
SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
|
||||||
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
|
||||||
|
|
||||||
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
|
|
||||||
$(SOFTMMU_SUBDIR_RULES): config-all-devices.mak
|
|
||||||
|
|
||||||
subdir-%:
|
subdir-%:
|
||||||
$(call quiet-command,$(MAKE) $(SUBDIR_MAKEFLAGS) -C $* V="$(V)" TARGET_DIR="$*/" all,)
|
$(call quiet-command,$(MAKE) $(SUBDIR_MAKEFLAGS) -C $* V="$(V)" TARGET_DIR="$*/" all,)
|
||||||
@@ -174,16 +136,6 @@ pixman/Makefile: $(SRC_PATH)/pixman/configure
|
|||||||
$(SRC_PATH)/pixman/configure:
|
$(SRC_PATH)/pixman/configure:
|
||||||
(cd $(SRC_PATH)/pixman; autoreconf -v --install)
|
(cd $(SRC_PATH)/pixman; autoreconf -v --install)
|
||||||
|
|
||||||
DTC_MAKE_ARGS=-I$(SRC_PATH)/dtc VPATH=$(SRC_PATH)/dtc -C dtc V="$(V)" LIBFDT_srcdir=$(SRC_PATH)/dtc/libfdt
|
|
||||||
DTC_CFLAGS=$(CFLAGS) $(QEMU_CFLAGS)
|
|
||||||
DTC_CPPFLAGS=-I$(BUILD_DIR)/dtc -I$(SRC_PATH)/dtc -I$(SRC_PATH)/dtc/libfdt
|
|
||||||
|
|
||||||
subdir-dtc:dtc/libfdt dtc/tests
|
|
||||||
$(call quiet-command,$(MAKE) $(DTC_MAKE_ARGS) CPPFLAGS="$(DTC_CPPFLAGS)" CFLAGS="$(DTC_CFLAGS)" LDFLAGS="$(LDFLAGS)" ARFLAGS="$(ARFLAGS)" CC="$(CC)" AR="$(AR)" LD="$(LD)" $(SUBDIR_MAKEFLAGS) libfdt/libfdt.a,)
|
|
||||||
|
|
||||||
dtc/%:
|
|
||||||
mkdir -p $@
|
|
||||||
|
|
||||||
$(SUBDIR_RULES): libqemuutil.a libqemustub.a $(common-obj-y)
|
$(SUBDIR_RULES): libqemuutil.a libqemustub.a $(common-obj-y)
|
||||||
|
|
||||||
ROMSUBDIR_RULES=$(patsubst %,romsubdir-%, $(ROMS))
|
ROMSUBDIR_RULES=$(patsubst %,romsubdir-%, $(ROMS))
|
||||||
@@ -196,21 +148,17 @@ recurse-all: $(SUBDIR_RULES) $(ROMSUBDIR_RULES)
|
|||||||
|
|
||||||
bt-host.o: QEMU_CFLAGS += $(BLUEZ_CFLAGS)
|
bt-host.o: QEMU_CFLAGS += $(BLUEZ_CFLAGS)
|
||||||
|
|
||||||
$(BUILD_DIR)/version.o: $(SRC_PATH)/version.rc $(BUILD_DIR)/config-host.h | $(BUILD_DIR)/version.lo
|
version.o: $(SRC_PATH)/version.rc config-host.h
|
||||||
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<," RC version.o")
|
$(call quiet-command,$(WINDRES) -I. -o $@ $<," RC $(TARGET_DIR)$@")
|
||||||
$(BUILD_DIR)/version.lo: $(SRC_PATH)/version.rc $(BUILD_DIR)/config-host.h
|
|
||||||
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<," RC version.lo")
|
|
||||||
|
|
||||||
Makefile: $(version-obj-y) $(version-lobj-y)
|
version-obj-$(CONFIG_WIN32) += version.o
|
||||||
|
Makefile: $(version-obj-y)
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Build libraries
|
# Build libraries
|
||||||
|
|
||||||
libqemustub.a: $(stub-obj-y)
|
libqemustub.a: $(stub-obj-y)
|
||||||
libqemuutil.a: $(util-obj-y) qapi-types.o qapi-visit.o
|
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)'
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
@@ -218,7 +166,7 @@ qemu-img.o: qemu-img-cmds.h
|
|||||||
|
|
||||||
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) libqemuutil.a libqemustub.a
|
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||||
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) libqemuutil.a libqemustub.a
|
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||||
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) libqemuutil.a libqemustub.a
|
qemu-io$(EXESUF): qemu-io.o cmd.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||||
|
|
||||||
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o
|
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o
|
||||||
|
|
||||||
@@ -247,10 +195,10 @@ $(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
|||||||
|
|
||||||
qapi-types.c qapi-types.h :\
|
qapi-types.c qapi-types.h :\
|
||||||
$(SRC_PATH)/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
$(SRC_PATH)/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 "." -b < $<, " GEN $@")
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py $(gen-out-type) -o "." < $<, " GEN $@")
|
||||||
qapi-visit.c qapi-visit.h :\
|
qapi-visit.c qapi-visit.h :\
|
||||||
$(SRC_PATH)/qapi-schema.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
|
$(SRC_PATH)/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 "." -b < $<, " GEN $@")
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py $(gen-out-type) -o "." < $<, " GEN $@")
|
||||||
qmp-commands.h qmp-marshal.c :\
|
qmp-commands.h qmp-marshal.c :\
|
||||||
$(SRC_PATH)/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
$(SRC_PATH)/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py $(gen-out-type) -m -o "." < $<, " GEN $@")
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py $(gen-out-type) -m -o "." < $<, " GEN $@")
|
||||||
@@ -265,10 +213,10 @@ clean:
|
|||||||
# avoid old build problems by removing potentially incorrect old files
|
# 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 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 qemu-options.def
|
||||||
find . \( -name '*.l[oa]' -o -name '*.so' -o -name '*.dll' -o -name '*.mo' -o -name '*.[oda]' \) -type f -exec rm {} +
|
find . -name '*.[oda]' -type f -exec rm -f {} +
|
||||||
rm -f $(filter-out %.tlb,$(TOOLS)) $(HELPERS-y) qemu-ga TAGS cscope.* *.pod *~ */*~
|
find . -name '*.l[oa]' -type f -exec rm -f {} +
|
||||||
rm -f fsdev/*.pod
|
rm -f $(TOOLS) $(HELPERS-y) qemu-ga TAGS cscope.* *.pod *~ */*~
|
||||||
rm -rf .libs */.libs
|
rm -Rf .libs
|
||||||
rm -f qemu-img-cmds.h
|
rm -f qemu-img-cmds.h
|
||||||
@# May not be present in GENERATED_HEADERS
|
@# May not be present in GENERATED_HEADERS
|
||||||
rm -f trace/generated-tracers-dtrace.dtrace*
|
rm -f trace/generated-tracers-dtrace.dtrace*
|
||||||
@@ -277,6 +225,7 @@ clean:
|
|||||||
rm -f $(foreach f,$(GENERATED_SOURCES),$(f) $(f)-timestamp)
|
rm -f $(foreach f,$(GENERATED_SOURCES),$(f) $(f)-timestamp)
|
||||||
rm -rf qapi-generated
|
rm -rf qapi-generated
|
||||||
rm -rf qga/qapi-generated
|
rm -rf qga/qapi-generated
|
||||||
|
$(MAKE) -C tests/tcg clean
|
||||||
for d in $(ALL_SUBDIRS); do \
|
for d in $(ALL_SUBDIRS); do \
|
||||||
if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \
|
if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \
|
||||||
rm -f $$d/qemu-options.def; \
|
rm -f $$d/qemu-options.def; \
|
||||||
@@ -292,7 +241,6 @@ qemu-%.tar.bz2:
|
|||||||
distclean: clean
|
distclean: clean
|
||||||
rm -f config-host.mak config-host.h* config-host.ld $(DOCS) qemu-options.texi qemu-img-cmds.texi qemu-monitor.texi
|
rm -f config-host.mak config-host.h* config-host.ld $(DOCS) qemu-options.texi qemu-img-cmds.texi qemu-monitor.texi
|
||||||
rm -f config-all-devices.mak config-all-disas.mak
|
rm -f config-all-devices.mak config-all-disas.mak
|
||||||
rm -f po/*.mo
|
|
||||||
rm -f roms/seabios/config.mak roms/vgabios/config.mak
|
rm -f roms/seabios/config.mak roms/vgabios/config.mak
|
||||||
rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.cps qemu-doc.dvi
|
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.fn qemu-doc.fns qemu-doc.info qemu-doc.ky qemu-doc.kys
|
||||||
@@ -304,29 +252,24 @@ distclean: clean
|
|||||||
for d in $(TARGET_DIRS); do \
|
for d in $(TARGET_DIRS); do \
|
||||||
rm -rf $$d || exit 1 ; \
|
rm -rf $$d || exit 1 ; \
|
||||||
done
|
done
|
||||||
rm -Rf .sdk
|
|
||||||
if test -f pixman/config.log; then make -C pixman distclean; fi
|
if test -f pixman/config.log; then make -C pixman distclean; fi
|
||||||
if test -f dtc/version_gen.h; then make $(DTC_MAKE_ARGS) clean; fi
|
|
||||||
|
|
||||||
KEYMAPS=da en-gb et fr fr-ch is lt modifiers no pt-br sv \
|
KEYMAPS=da en-gb et fr fr-ch is lt modifiers no pt-br sv \
|
||||||
ar de en-us fi fr-be hr it lv nl pl ru th \
|
ar de en-us fi fr-be hr it lv nl pl ru th \
|
||||||
common de-ch es fo fr-ca hu ja mk nl-be pt sl tr \
|
common de-ch es fo fr-ca hu ja mk nl-be pt sl tr \
|
||||||
bepo cz
|
bepo
|
||||||
|
|
||||||
ifdef INSTALL_BLOBS
|
ifdef INSTALL_BLOBS
|
||||||
BLOBS=bios.bin bios-256k.bin sgabios.bin vgabios.bin vgabios-cirrus.bin \
|
BLOBS=bios.bin sgabios.bin vgabios.bin vgabios-cirrus.bin \
|
||||||
vgabios-stdvga.bin vgabios-vmware.bin vgabios-qxl.bin \
|
vgabios-stdvga.bin vgabios-vmware.bin vgabios-qxl.bin \
|
||||||
acpi-dsdt.aml q35-acpi-dsdt.aml \
|
acpi-dsdt.aml q35-acpi-dsdt.aml \
|
||||||
ppc_rom.bin openbios-sparc32 openbios-sparc64 openbios-ppc QEMU,tcx.bin QEMU,cgthree.bin \
|
ppc_rom.bin openbios-sparc32 openbios-sparc64 openbios-ppc \
|
||||||
pxe-e1000.rom pxe-eepro100.rom pxe-ne2k_pci.rom \
|
pxe-e1000.rom pxe-eepro100.rom pxe-ne2k_pci.rom \
|
||||||
pxe-pcnet.rom pxe-rtl8139.rom pxe-virtio.rom \
|
pxe-pcnet.rom pxe-rtl8139.rom pxe-virtio.rom \
|
||||||
efi-e1000.rom efi-eepro100.rom efi-ne2k_pci.rom \
|
qemu-icon.bmp \
|
||||||
efi-pcnet.rom efi-rtl8139.rom efi-virtio.rom \
|
|
||||||
qemu-icon.bmp qemu_logo_no_text.svg \
|
|
||||||
bamboo.dtb petalogix-s3adsp1800.dtb petalogix-ml605.dtb \
|
bamboo.dtb petalogix-s3adsp1800.dtb petalogix-ml605.dtb \
|
||||||
multiboot.bin linuxboot.bin kvmvapic.bin \
|
multiboot.bin linuxboot.bin kvmvapic.bin \
|
||||||
s390-zipl.rom \
|
s390-zipl.rom \
|
||||||
s390-ccw.img \
|
|
||||||
spapr-rtas.bin slof.bin \
|
spapr-rtas.bin slof.bin \
|
||||||
palcode-clipper
|
palcode-clipper
|
||||||
else
|
else
|
||||||
@@ -336,16 +279,13 @@ endif
|
|||||||
install-doc: $(DOCS)
|
install-doc: $(DOCS)
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
|
||||||
$(INSTALL_DATA) qemu-doc.html qemu-tech.html "$(DESTDIR)$(qemu_docdir)"
|
$(INSTALL_DATA) qemu-doc.html qemu-tech.html "$(DESTDIR)$(qemu_docdir)"
|
||||||
$(INSTALL_DATA) qmp-commands.txt "$(DESTDIR)$(qemu_docdir)"
|
$(INSTALL_DATA) QMP/qmp-commands.txt "$(DESTDIR)$(qemu_docdir)"
|
||||||
ifdef CONFIG_POSIX
|
ifdef CONFIG_POSIX
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
||||||
$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DATA) qemu.1 qemu-img.1 "$(DESTDIR)$(mandir)/man1"
|
||||||
ifneq ($(TOOLS),)
|
|
||||||
$(INSTALL_DATA) qemu-img.1 "$(DESTDIR)$(mandir)/man1"
|
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8"
|
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8"
|
||||||
$(INSTALL_DATA) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8"
|
$(INSTALL_DATA) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8"
|
||||||
endif
|
endif
|
||||||
endif
|
|
||||||
ifdef CONFIG_VIRTFS
|
ifdef CONFIG_VIRTFS
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
||||||
$(INSTALL_DATA) fsdev/virtfs-proxy-helper.1 "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DATA) fsdev/virtfs-proxy-helper.1 "$(DESTDIR)$(mandir)/man1"
|
||||||
@@ -354,31 +294,17 @@ endif
|
|||||||
install-datadir:
|
install-datadir:
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)"
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)"
|
||||||
|
|
||||||
install-localstatedir:
|
|
||||||
ifdef CONFIG_POSIX
|
|
||||||
ifneq (,$(findstring qemu-ga,$(TOOLS)))
|
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_localstatedir)"/run
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
install-confdir:
|
install-confdir:
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_confdir)"
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_confdir)"
|
||||||
|
|
||||||
install-sysconfig: install-datadir install-confdir
|
install-sysconfig: install-datadir install-confdir
|
||||||
$(INSTALL_DATA) $(SRC_PATH)/sysconfigs/target/target-x86_64.conf "$(DESTDIR)$(qemu_confdir)"
|
$(INSTALL_DATA) $(SRC_PATH)/sysconfigs/target/target-x86_64.conf "$(DESTDIR)$(qemu_confdir)"
|
||||||
|
|
||||||
install: all $(if $(BUILD_DOCS),install-doc) install-sysconfig \
|
install: all $(if $(BUILD_DOCS),install-doc) install-sysconfig install-datadir
|
||||||
install-datadir install-localstatedir
|
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(bindir)"
|
$(INSTALL_DIR) "$(DESTDIR)$(bindir)"
|
||||||
ifneq ($(TOOLS),)
|
ifneq ($(TOOLS),)
|
||||||
$(INSTALL_PROG) $(STRIP_OPT) $(TOOLS) "$(DESTDIR)$(bindir)"
|
$(INSTALL_PROG) $(STRIP_OPT) $(TOOLS) "$(DESTDIR)$(bindir)"
|
||||||
endif
|
endif
|
||||||
ifneq ($(CONFIG_MODULES),)
|
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_moddir)"
|
|
||||||
for s in $(patsubst %.mo,%$(DSOSUF),$(modules-m)); do \
|
|
||||||
$(INSTALL_PROG) $(STRIP_OPT) $$s "$(DESTDIR)$(qemu_moddir)/$${s//\//-}"; \
|
|
||||||
done
|
|
||||||
endif
|
|
||||||
ifneq ($(HELPERS-y),)
|
ifneq ($(HELPERS-y),)
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(libexecdir)"
|
$(INSTALL_DIR) "$(DESTDIR)$(libexecdir)"
|
||||||
$(INSTALL_PROG) $(STRIP_OPT) $(HELPERS-y) "$(DESTDIR)$(libexecdir)"
|
$(INSTALL_PROG) $(STRIP_OPT) $(HELPERS-y) "$(DESTDIR)$(libexecdir)"
|
||||||
@@ -387,16 +313,13 @@ ifneq ($(BLOBS),)
|
|||||||
set -e; for x in $(BLOBS); do \
|
set -e; for x in $(BLOBS); do \
|
||||||
$(INSTALL_DATA) $(SRC_PATH)/pc-bios/$$x "$(DESTDIR)$(qemu_datadir)"; \
|
$(INSTALL_DATA) $(SRC_PATH)/pc-bios/$$x "$(DESTDIR)$(qemu_datadir)"; \
|
||||||
done
|
done
|
||||||
endif
|
|
||||||
ifeq ($(CONFIG_GTK),y)
|
|
||||||
$(MAKE) -C po $@
|
|
||||||
endif
|
endif
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)/keymaps"
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)/keymaps"
|
||||||
set -e; for x in $(KEYMAPS); do \
|
set -e; for x in $(KEYMAPS); do \
|
||||||
$(INSTALL_DATA) $(SRC_PATH)/pc-bios/keymaps/$$x "$(DESTDIR)$(qemu_datadir)/keymaps"; \
|
$(INSTALL_DATA) $(SRC_PATH)/pc-bios/keymaps/$$x "$(DESTDIR)$(qemu_datadir)/keymaps"; \
|
||||||
done
|
done
|
||||||
for d in $(TARGET_DIRS); do \
|
for d in $(TARGET_DIRS); do \
|
||||||
$(MAKE) $(SUBDIR_MAKEFLAGS) TARGET_DIR=$$d/ -C $$d $@ || exit 1 ; \
|
$(MAKE) -C $$d $@ || exit 1 ; \
|
||||||
done
|
done
|
||||||
|
|
||||||
# various test targets
|
# various test targets
|
||||||
@@ -405,8 +328,7 @@ test speed: all
|
|||||||
|
|
||||||
.PHONY: TAGS
|
.PHONY: TAGS
|
||||||
TAGS:
|
TAGS:
|
||||||
rm -f $@
|
find "$(SRC_PATH)" -name '*.[hc]' -print0 | xargs -0 etags
|
||||||
find "$(SRC_PATH)" -name '*.[hc]' -exec etags --append {} +
|
|
||||||
|
|
||||||
cscope:
|
cscope:
|
||||||
rm -f ./cscope.*
|
rm -f ./cscope.*
|
||||||
@@ -436,7 +358,7 @@ qemu-options.texi: $(SRC_PATH)/qemu-options.hx
|
|||||||
qemu-monitor.texi: $(SRC_PATH)/hmp-commands.hx
|
qemu-monitor.texi: $(SRC_PATH)/hmp-commands.hx
|
||||||
$(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
|
QMP/qmp-commands.txt: $(SRC_PATH)/qmp-commands.hx
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -q < $< > $@," GEN $@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -q < $< > $@," GEN $@")
|
||||||
|
|
||||||
qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx
|
qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx
|
||||||
@@ -475,61 +397,6 @@ qemu-doc.dvi qemu-doc.html qemu-doc.info qemu-doc.pdf: \
|
|||||||
qemu-img.texi qemu-nbd.texi qemu-options.texi \
|
qemu-img.texi qemu-nbd.texi qemu-options.texi \
|
||||||
qemu-monitor.texi qemu-img-cmds.texi
|
qemu-monitor.texi qemu-img-cmds.texi
|
||||||
|
|
||||||
ifdef CONFIG_WIN32
|
|
||||||
|
|
||||||
INSTALLER = qemu-setup-$(VERSION)$(EXESUF)
|
|
||||||
|
|
||||||
nsisflags = -V2 -NOCD
|
|
||||||
|
|
||||||
ifneq ($(wildcard $(SRC_PATH)/dll),)
|
|
||||||
ifeq ($(ARCH),x86_64)
|
|
||||||
# 64 bit executables
|
|
||||||
DLL_PATH = $(SRC_PATH)/dll/w64
|
|
||||||
nsisflags += -DW64
|
|
||||||
else
|
|
||||||
# 32 bit executables
|
|
||||||
DLL_PATH = $(SRC_PATH)/dll/w32
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: installer
|
|
||||||
installer: $(INSTALLER)
|
|
||||||
|
|
||||||
INSTDIR=/tmp/qemu-nsis
|
|
||||||
|
|
||||||
$(INSTALLER): $(SRC_PATH)/qemu.nsi
|
|
||||||
make install prefix=${INSTDIR}
|
|
||||||
ifdef SIGNCODE
|
|
||||||
(cd ${INSTDIR}; \
|
|
||||||
for i in *.exe; do \
|
|
||||||
$(SIGNCODE) $${i}; \
|
|
||||||
done \
|
|
||||||
)
|
|
||||||
endif # SIGNCODE
|
|
||||||
(cd ${INSTDIR}; \
|
|
||||||
for i in qemu-system-*.exe; do \
|
|
||||||
arch=$${i%.exe}; \
|
|
||||||
arch=$${arch#qemu-system-}; \
|
|
||||||
echo Section \"$$arch\" Section_$$arch; \
|
|
||||||
echo SetOutPath \"\$$INSTDIR\"; \
|
|
||||||
echo File \"\$${BINDIR}\\$$i\"; \
|
|
||||||
echo SectionEnd; \
|
|
||||||
done \
|
|
||||||
) >${INSTDIR}/system-emulations.nsh
|
|
||||||
makensis $(nsisflags) \
|
|
||||||
$(if $(BUILD_DOCS),-DCONFIG_DOCUMENTATION="y") \
|
|
||||||
$(if $(CONFIG_GTK),-DCONFIG_GTK="y") \
|
|
||||||
-DBINDIR="${INSTDIR}" \
|
|
||||||
$(if $(DLL_PATH),-DDLLDIR="$(DLL_PATH)") \
|
|
||||||
-DSRCDIR="$(SRC_PATH)" \
|
|
||||||
-DOUTFILE="$(INSTALLER)" \
|
|
||||||
$(SRC_PATH)/qemu.nsi
|
|
||||||
rm -r ${INSTDIR}
|
|
||||||
ifdef SIGNCODE
|
|
||||||
$(SIGNCODE) $(INSTALLER)
|
|
||||||
endif # SIGNCODE
|
|
||||||
endif # CONFIG_WIN
|
|
||||||
|
|
||||||
# Add a dependency on the generated files, so that they are always
|
# Add a dependency on the generated files, so that they are always
|
||||||
# rebuilt before other object files
|
# rebuilt before other object files
|
||||||
ifneq ($(filter-out %clean,$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
ifneq ($(filter-out %clean,$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ block-obj-$(CONFIG_POSIX) += aio-posix.o
|
|||||||
block-obj-$(CONFIG_WIN32) += aio-win32.o
|
block-obj-$(CONFIG_WIN32) += aio-win32.o
|
||||||
block-obj-y += block/
|
block-obj-y += block/
|
||||||
block-obj-y += qapi-types.o qapi-visit.o
|
block-obj-y += qapi-types.o qapi-visit.o
|
||||||
block-obj-y += qemu-io-cmds.o
|
|
||||||
|
|
||||||
block-obj-y += qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o
|
block-obj-y += qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o
|
||||||
block-obj-y += qemu-coroutine-sleep.o
|
block-obj-y += qemu-coroutine-sleep.o
|
||||||
block-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
|
block-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
|
||||||
|
|
||||||
block-obj-m = block/
|
ifeq ($(CONFIG_VIRTIO)$(CONFIG_VIRTFS)$(CONFIG_PCI),yyy)
|
||||||
|
# Lots of the fsdev/9pcode is pulled in by vl.c via qemu_fsdev_add.
|
||||||
|
# only pull in the actual virtio-9p device if we also enabled virtio.
|
||||||
|
CONFIG_REALLY_VIRTFS=y
|
||||||
|
endif
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# smartcard
|
# smartcard
|
||||||
@@ -30,7 +32,6 @@ libcacard-y += libcacard/vcard.o libcacard/vreader.o
|
|||||||
libcacard-y += libcacard/vcard_emul_nss.o
|
libcacard-y += libcacard/vcard_emul_nss.o
|
||||||
libcacard-y += libcacard/vcard_emul_type.o
|
libcacard-y += libcacard/vcard_emul_type.o
|
||||||
libcacard-y += libcacard/card_7816.o
|
libcacard-y += libcacard/card_7816.o
|
||||||
libcacard-y += libcacard/vcardt.o
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Target independent part of system emulation. The long term path is to
|
# Target independent part of system emulation. The long term path is to
|
||||||
@@ -38,19 +39,15 @@ libcacard-y += libcacard/vcardt.o
|
|||||||
# single QEMU executable should support all CPUs and machines.
|
# single QEMU executable should support all CPUs and machines.
|
||||||
|
|
||||||
ifeq ($(CONFIG_SOFTMMU),y)
|
ifeq ($(CONFIG_SOFTMMU),y)
|
||||||
common-obj-y = blockdev.o blockdev-nbd.o block/
|
common-obj-y = $(block-obj-y) blockdev.o blockdev-nbd.o block/
|
||||||
common-obj-y += iothread.o
|
|
||||||
common-obj-y += net/
|
common-obj-y += net/
|
||||||
common-obj-y += qdev-monitor.o device-hotplug.o
|
common-obj-y += readline.o
|
||||||
common-obj-$(CONFIG_WIN32) += os-win32.o
|
common-obj-$(CONFIG_WIN32) += os-win32.o
|
||||||
common-obj-$(CONFIG_POSIX) += os-posix.o
|
common-obj-$(CONFIG_POSIX) += os-posix.o
|
||||||
|
|
||||||
common-obj-$(CONFIG_LINUX) += fsdev/
|
common-obj-$(CONFIG_LINUX) += fsdev/
|
||||||
|
|
||||||
common-obj-y += migration.o migration-tcp.o
|
common-obj-y += migration.o migration-tcp.o
|
||||||
common-obj-y += vmstate.o
|
|
||||||
common-obj-y += qemu-file.o
|
|
||||||
common-obj-$(CONFIG_RDMA) += migration-rdma.o
|
|
||||||
common-obj-y += qemu-char.o #aio.o
|
common-obj-y += qemu-char.o #aio.o
|
||||||
common-obj-y += block-migration.o
|
common-obj-y += block-migration.o
|
||||||
common-obj-y += page_cache.o xbzrle.o
|
common-obj-y += page_cache.o xbzrle.o
|
||||||
@@ -66,8 +63,8 @@ common-obj-y += ui/
|
|||||||
common-obj-y += bt-host.o bt-vhci.o
|
common-obj-y += bt-host.o bt-vhci.o
|
||||||
|
|
||||||
common-obj-y += dma-helpers.o
|
common-obj-y += dma-helpers.o
|
||||||
|
common-obj-y += qtest.o
|
||||||
common-obj-y += vl.o
|
common-obj-y += vl.o
|
||||||
common-obj-y += tpm.o
|
|
||||||
|
|
||||||
common-obj-$(CONFIG_SLIRP) += slirp/
|
common-obj-$(CONFIG_SLIRP) += slirp/
|
||||||
|
|
||||||
@@ -80,15 +77,10 @@ common-obj-$(CONFIG_SMARTCARD_NSS) += $(libcacard-y)
|
|||||||
######################################################################
|
######################################################################
|
||||||
# qapi
|
# qapi
|
||||||
|
|
||||||
common-obj-y += qmp-marshal.o
|
common-obj-y += qmp-marshal.o qapi-visit.o qapi-types.o
|
||||||
common-obj-y += qmp.o hmp.o
|
common-obj-y += qmp.o hmp.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# some qapi visitors are used by both system and user emulation:
|
|
||||||
|
|
||||||
common-obj-y += qapi-visit.o qapi-types.o
|
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# Target-independent parts used in system and user emulation
|
# Target-independent parts used in system and user emulation
|
||||||
common-obj-y += qemu-log.o
|
common-obj-y += qemu-log.o
|
||||||
@@ -97,15 +89,23 @@ common-obj-y += hw/
|
|||||||
common-obj-y += qom/
|
common-obj-y += qom/
|
||||||
common-obj-y += disas/
|
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
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# guest agent
|
# guest agent
|
||||||
|
|
||||||
# FIXME: a few definitions from qapi-types.o/qapi-visit.o are needed
|
# FIXME: a few definitions from qapi-types.o/qapi-visit.o are needed
|
||||||
# by libqemuutil.a. These should be moved to a separate .json schema.
|
# by libqemuutil.a. These should be moved to a separate .json schema.
|
||||||
qga-obj-y = qga/ qapi-types.o qapi-visit.o
|
qga-obj-y = qga/ qapi-types.o qapi-visit.o
|
||||||
qga-vss-dll-obj-y = qga/
|
|
||||||
|
vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS)
|
||||||
|
|
||||||
|
vl.o: QEMU_CFLAGS+=$(SDL_CFLAGS)
|
||||||
|
|
||||||
|
QEMU_CFLAGS+=$(GLIB_CFLAGS)
|
||||||
|
|
||||||
|
nested-vars += \
|
||||||
|
stub-obj-y \
|
||||||
|
util-obj-y \
|
||||||
|
qga-obj-y \
|
||||||
|
block-obj-y \
|
||||||
|
common-obj-y
|
||||||
|
dummy := $(call unnest-vars)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# -*- Mode: makefile -*-
|
# -*- Mode: makefile -*-
|
||||||
|
|
||||||
include ../config-host.mak
|
include ../config-host.mak
|
||||||
include config-target.mak
|
|
||||||
include config-devices.mak
|
include config-devices.mak
|
||||||
|
include config-target.mak
|
||||||
include $(SRC_PATH)/rules.mak
|
include $(SRC_PATH)/rules.mak
|
||||||
|
|
||||||
$(call set-vpath, $(SRC_PATH))
|
$(call set-vpath, $(SRC_PATH))
|
||||||
@@ -15,14 +15,14 @@ QEMU_CFLAGS+=-I$(SRC_PATH)/include
|
|||||||
|
|
||||||
ifdef CONFIG_USER_ONLY
|
ifdef CONFIG_USER_ONLY
|
||||||
# user emulator name
|
# user emulator name
|
||||||
QEMU_PROG=qemu-$(TARGET_NAME)
|
QEMU_PROG=qemu-$(TARGET_ARCH2)
|
||||||
else
|
else
|
||||||
# system emulator name
|
# system emulator name
|
||||||
ifneq (,$(findstring -mwindows,$(libs_softmmu)))
|
ifneq (,$(findstring -mwindows,$(LIBS)))
|
||||||
# Terminate program name with a 'w' because the linker builds a windows executable.
|
# Terminate program name with a 'w' because the linker builds a windows executable.
|
||||||
QEMU_PROGW=qemu-system-$(TARGET_NAME)w$(EXESUF)
|
QEMU_PROGW=qemu-system-$(TARGET_ARCH2)w$(EXESUF)
|
||||||
endif # windows executable
|
endif # windows executable
|
||||||
QEMU_PROG=qemu-system-$(TARGET_NAME)$(EXESUF)
|
QEMU_PROG=qemu-system-$(TARGET_ARCH2)$(EXESUF)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PROGS=$(QEMU_PROG)
|
PROGS=$(QEMU_PROG)
|
||||||
@@ -31,11 +31,15 @@ PROGS+=$(QEMU_PROGW)
|
|||||||
endif
|
endif
|
||||||
STPFILES=
|
STPFILES=
|
||||||
|
|
||||||
|
ifndef CONFIG_HAIKU
|
||||||
|
LIBS+=-lm
|
||||||
|
endif
|
||||||
|
|
||||||
config-target.h: config-target.h-timestamp
|
config-target.h: config-target.h-timestamp
|
||||||
config-target.h-timestamp: config-target.mak
|
config-target.h-timestamp: config-target.mak
|
||||||
|
|
||||||
ifdef CONFIG_TRACE_SYSTEMTAP
|
ifdef CONFIG_TRACE_SYSTEMTAP
|
||||||
stap: $(QEMU_PROG).stp-installed $(QEMU_PROG).stp
|
stap: $(QEMU_PROG).stp
|
||||||
|
|
||||||
ifdef CONFIG_USER_ONLY
|
ifdef CONFIG_USER_ONLY
|
||||||
TARGET_TYPE=user
|
TARGET_TYPE=user
|
||||||
@@ -43,24 +47,14 @@ else
|
|||||||
TARGET_TYPE=system
|
TARGET_TYPE=system
|
||||||
endif
|
endif
|
||||||
|
|
||||||
$(QEMU_PROG).stp-installed: $(SRC_PATH)/trace-events
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--format=stap \
|
|
||||||
--backend=$(TRACE_BACKEND) \
|
|
||||||
--binary=$(bindir)/$(QEMU_PROG) \
|
|
||||||
--target-name=$(TARGET_NAME) \
|
|
||||||
--target-type=$(TARGET_TYPE) \
|
|
||||||
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG).stp-installed")
|
|
||||||
|
|
||||||
$(QEMU_PROG).stp: $(SRC_PATH)/trace-events
|
$(QEMU_PROG).stp: $(SRC_PATH)/trace-events
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
$(call quiet-command,$(TRACETOOL) \
|
||||||
--format=stap \
|
--format=stap \
|
||||||
--backend=$(TRACE_BACKEND) \
|
--backend=$(TRACE_BACKEND) \
|
||||||
--binary=$(realpath .)/$(QEMU_PROG) \
|
--binary=$(bindir)/$(QEMU_PROG) \
|
||||||
--target-name=$(TARGET_NAME) \
|
--target-arch=$(TARGET_ARCH) \
|
||||||
--target-type=$(TARGET_TYPE) \
|
--target-type=$(TARGET_TYPE) \
|
||||||
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG).stp")
|
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG).stp")
|
||||||
|
|
||||||
else
|
else
|
||||||
stap:
|
stap:
|
||||||
endif
|
endif
|
||||||
@@ -79,8 +73,7 @@ obj-$(CONFIG_TCG_INTERPRETER) += disas/tci.o
|
|||||||
obj-y += fpu/softfloat.o
|
obj-y += fpu/softfloat.o
|
||||||
obj-y += target-$(TARGET_BASE_ARCH)/
|
obj-y += target-$(TARGET_BASE_ARCH)/
|
||||||
obj-y += disas.o
|
obj-y += disas.o
|
||||||
obj-$(call notempty,$(TARGET_XML_FILES)) += gdbstub-xml.o
|
obj-$(CONFIG_GDBSTUB_XML) += gdbstub-xml.o
|
||||||
obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
|
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
# Linux user emulator target
|
# Linux user emulator target
|
||||||
@@ -99,7 +92,7 @@ endif #CONFIG_LINUX_USER
|
|||||||
|
|
||||||
ifdef CONFIG_BSD_USER
|
ifdef CONFIG_BSD_USER
|
||||||
|
|
||||||
QEMU_CFLAGS+=-I$(SRC_PATH)/bsd-user -I$(SRC_PATH)/bsd-user/$(TARGET_ABI_DIR)
|
QEMU_CFLAGS+=-I$(SRC_PATH)/bsd-user -I$(SRC_PATH)/bsd-user/$(TARGET_ARCH)
|
||||||
|
|
||||||
obj-y += bsd-user/
|
obj-y += bsd-user/
|
||||||
obj-y += gdbstub.o user-exec.o
|
obj-y += gdbstub.o user-exec.o
|
||||||
@@ -109,27 +102,36 @@ endif #CONFIG_BSD_USER
|
|||||||
#########################################################
|
#########################################################
|
||||||
# System emulator target
|
# System emulator target
|
||||||
ifdef CONFIG_SOFTMMU
|
ifdef CONFIG_SOFTMMU
|
||||||
|
CONFIG_NO_PCI = $(if $(subst n,,$(CONFIG_PCI)),n,y)
|
||||||
|
CONFIG_NO_KVM = $(if $(subst n,,$(CONFIG_KVM)),n,y)
|
||||||
|
CONFIG_NO_XEN = $(if $(subst n,,$(CONFIG_XEN)),n,y)
|
||||||
|
CONFIG_NO_GET_MEMORY_MAPPING = $(if $(subst n,,$(CONFIG_HAVE_GET_MEMORY_MAPPING)),n,y)
|
||||||
|
CONFIG_NO_CORE_DUMP = $(if $(subst n,,$(CONFIG_HAVE_CORE_DUMP)),n,y)
|
||||||
|
|
||||||
obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o
|
obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o
|
||||||
obj-y += qtest.o
|
|
||||||
obj-y += hw/
|
obj-y += hw/
|
||||||
obj-$(CONFIG_FDT) += device_tree.o
|
|
||||||
obj-$(CONFIG_KVM) += kvm-all.o
|
obj-$(CONFIG_KVM) += kvm-all.o
|
||||||
|
obj-$(CONFIG_NO_KVM) += kvm-stub.o
|
||||||
obj-y += memory.o savevm.o cputlb.o
|
obj-y += memory.o savevm.o cputlb.o
|
||||||
obj-y += memory_mapping.o
|
obj-$(CONFIG_HAVE_GET_MEMORY_MAPPING) += memory_mapping.o
|
||||||
obj-y += dump.o
|
obj-$(CONFIG_HAVE_CORE_DUMP) += dump.o
|
||||||
LIBS+=$(libs_softmmu)
|
obj-$(CONFIG_NO_GET_MEMORY_MAPPING) += memory_mapping-stub.o
|
||||||
|
obj-$(CONFIG_NO_CORE_DUMP) += dump-stub.o
|
||||||
|
LIBS+=-lz
|
||||||
|
|
||||||
# xen support
|
# xen support
|
||||||
obj-$(CONFIG_XEN) += xen-all.o xen-mapcache.o
|
obj-$(CONFIG_XEN) += xen-all.o xen-mapcache.o
|
||||||
obj-$(call lnot,$(CONFIG_XEN)) += xen-stub.o
|
obj-$(CONFIG_NO_XEN) += xen-stub.o
|
||||||
|
|
||||||
# Hardware support
|
# Hardware support
|
||||||
ifeq ($(TARGET_NAME), sparc64)
|
ifeq ($(TARGET_ARCH), sparc64)
|
||||||
obj-y += hw/sparc64/
|
obj-y += hw/sparc64/
|
||||||
else
|
else
|
||||||
obj-y += hw/$(TARGET_BASE_ARCH)/
|
obj-y += hw/$(TARGET_BASE_ARCH)/
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
main.o: QEMU_CFLAGS+=$(GPROF_CFLAGS)
|
||||||
|
|
||||||
GENERATED_HEADERS += hmp-commands.h qmp-commands-old.h
|
GENERATED_HEADERS += hmp-commands.h qmp-commands-old.h
|
||||||
|
|
||||||
endif # CONFIG_SOFTMMU
|
endif # CONFIG_SOFTMMU
|
||||||
@@ -137,30 +139,13 @@ endif # CONFIG_SOFTMMU
|
|||||||
# Workaround for http://gcc.gnu.org/PR55489, see configure.
|
# Workaround for http://gcc.gnu.org/PR55489, see configure.
|
||||||
%/translate.o: QEMU_CFLAGS += $(TRANSLATE_OPT_CFLAGS)
|
%/translate.o: QEMU_CFLAGS += $(TRANSLATE_OPT_CFLAGS)
|
||||||
|
|
||||||
dummy := $(call unnest-vars,,obj-y)
|
nested-vars += obj-y
|
||||||
|
|
||||||
# we are making another call to unnest-vars with different vars, protect obj-y,
|
# This resolves all nested paths, so it must come last
|
||||||
# it can be overriden in subdir Makefile.objs
|
|
||||||
obj-y-save := $(obj-y)
|
|
||||||
|
|
||||||
block-obj-y :=
|
|
||||||
common-obj-y :=
|
|
||||||
include $(SRC_PATH)/Makefile.objs
|
include $(SRC_PATH)/Makefile.objs
|
||||||
dummy := $(call unnest-vars,.., \
|
|
||||||
block-obj-y \
|
|
||||||
block-obj-m \
|
|
||||||
common-obj-y \
|
|
||||||
common-obj-m)
|
|
||||||
|
|
||||||
# Now restore obj-y
|
all-obj-y = $(obj-y)
|
||||||
obj-y := $(obj-y-save)
|
all-obj-y += $(addprefix ../, $(common-obj-y))
|
||||||
|
|
||||||
all-obj-y = $(obj-y) $(common-obj-y)
|
|
||||||
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y)
|
|
||||||
|
|
||||||
ifndef CONFIG_HAIKU
|
|
||||||
LIBS+=-lm
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifdef QEMU_PROGW
|
ifdef QEMU_PROGW
|
||||||
# The linker builds a windows executable. Make also a console executable.
|
# The linker builds a windows executable. Make also a console executable.
|
||||||
@@ -199,7 +184,7 @@ endif
|
|||||||
endif
|
endif
|
||||||
ifdef CONFIG_TRACE_SYSTEMTAP
|
ifdef CONFIG_TRACE_SYSTEMTAP
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset"
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset"
|
||||||
$(INSTALL_DATA) $(QEMU_PROG).stp-installed "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset/$(QEMU_PROG).stp"
|
$(INSTALL_DATA) $(QEMU_PROG).stp "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
GENERATED_HEADERS += config-target.h
|
GENERATED_HEADERS += config-target.h
|
||||||
|
|||||||
88
QMP/README
Normal file
88
QMP/README
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
QEMU Monitor Protocol
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The QEMU Monitor Protocol (QMP) allows applications to communicate with
|
||||||
|
QEMU's Monitor.
|
||||||
|
|
||||||
|
QMP is JSON[1] based and currently has the following features:
|
||||||
|
|
||||||
|
- Lightweight, text-based, easy to parse data format
|
||||||
|
- Asynchronous messages support (ie. events)
|
||||||
|
- Capabilities Negotiation
|
||||||
|
|
||||||
|
For detailed information on QMP's usage, please, refer to the following files:
|
||||||
|
|
||||||
|
o qmp-spec.txt QEMU Monitor Protocol current specification
|
||||||
|
o qmp-commands.txt QMP supported commands (auto-generated at build-time)
|
||||||
|
o qmp-events.txt List of available asynchronous events
|
||||||
|
|
||||||
|
There is also a simple Python script called 'qmp-shell' available.
|
||||||
|
|
||||||
|
IMPORTANT: It's strongly recommended to read the 'Stability Considerations'
|
||||||
|
section in the qmp-commands.txt file before making any serious use of QMP.
|
||||||
|
|
||||||
|
|
||||||
|
[1] http://www.json.org
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
To enable QMP, you need a QEMU monitor instance in "control mode". There are
|
||||||
|
two ways of doing this.
|
||||||
|
|
||||||
|
The simplest one is using the '-qmp' command-line option. The following
|
||||||
|
example makes QMP available on localhost port 4444:
|
||||||
|
|
||||||
|
$ qemu [...] -qmp tcp:localhost:4444,server
|
||||||
|
|
||||||
|
However, in order to have more complex combinations, like multiple monitors,
|
||||||
|
the '-mon' command-line option should be used along with the '-chardev' one.
|
||||||
|
For instance, the following example creates one user monitor on stdio and one
|
||||||
|
QMP monitor on localhost port 4444.
|
||||||
|
|
||||||
|
$ qemu [...] -chardev stdio,id=mon0 -mon chardev=mon0,mode=readline \
|
||||||
|
-chardev socket,id=mon1,host=localhost,port=4444,server \
|
||||||
|
-mon chardev=mon1,mode=control
|
||||||
|
|
||||||
|
Please, refer to QEMU's manpage for more information.
|
||||||
|
|
||||||
|
Simple Testing
|
||||||
|
--------------
|
||||||
|
|
||||||
|
To manually test QMP one can connect with telnet and issue commands by hand:
|
||||||
|
|
||||||
|
$ telnet localhost 4444
|
||||||
|
Trying 127.0.0.1...
|
||||||
|
Connected to localhost.
|
||||||
|
Escape character is '^]'.
|
||||||
|
{"QMP": {"version": {"qemu": {"micro": 50, "minor": 13, "major": 0}, "package": ""}, "capabilities": []}}
|
||||||
|
{ "execute": "qmp_capabilities" }
|
||||||
|
{"return": {}}
|
||||||
|
{ "execute": "query-version" }
|
||||||
|
{"return": {"qemu": {"micro": 50, "minor": 13, "major": 0}, "package": ""}}
|
||||||
|
|
||||||
|
Development Process
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
When changing QMP's interface (by adding new commands, events or modifying
|
||||||
|
existing ones) it's mandatory to update the relevant documentation, which is
|
||||||
|
one (or more) of the files listed in the 'Introduction' section*.
|
||||||
|
|
||||||
|
Also, it's strongly recommended to send the documentation patch first, before
|
||||||
|
doing any code change. This is so because:
|
||||||
|
|
||||||
|
1. Avoids the code dictating the interface
|
||||||
|
|
||||||
|
2. Review can improve your interface. Letting that happen before
|
||||||
|
you implement it can save you work.
|
||||||
|
|
||||||
|
* The qmp-commands.txt file is generated from the qmp-commands.hx one, which
|
||||||
|
is the file that should be edited.
|
||||||
|
|
||||||
|
Homepage
|
||||||
|
--------
|
||||||
|
|
||||||
|
http://wiki.qemu.org/QMP
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
# $ qemu-ga-client fsfreeze freeze
|
# $ qemu-ga-client fsfreeze freeze
|
||||||
# 2 filesystems frozen
|
# 2 filesystems frozen
|
||||||
#
|
#
|
||||||
# See also: http://wiki.qemu-project.org/Features/QAPI/GuestAgent
|
# See also: http://wiki.qemu.org/Features/QAPI/GuestAgent
|
||||||
#
|
#
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
@@ -267,9 +267,7 @@ def main(address, cmd, args):
|
|||||||
print('Hint: qemu is not running?')
|
print('Hint: qemu is not running?')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if cmd == 'fsfreeze' and args[0] == 'freeze':
|
if cmd != 'ping':
|
||||||
client.sync(60)
|
|
||||||
elif cmd != 'ping':
|
|
||||||
client.sync()
|
client.sync()
|
||||||
|
|
||||||
globals()['_cmd_' + cmd](client, args)
|
globals()['_cmd_' + cmd](client, args)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
QEMU Machine Protocol Events
|
QEMU Monitor Protocol Events
|
||||||
============================
|
============================
|
||||||
|
|
||||||
BALLOON_CHANGE
|
BALLOON_CHANGE
|
||||||
@@ -18,28 +18,6 @@ Example:
|
|||||||
"data": { "actual": 944766976 },
|
"data": { "actual": 944766976 },
|
||||||
"timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
|
"timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
|
||||||
|
|
||||||
BLOCK_IMAGE_CORRUPTED
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Emitted when a disk image is being marked corrupt.
|
|
||||||
|
|
||||||
Data:
|
|
||||||
|
|
||||||
- "device": Device name (json-string)
|
|
||||||
- "msg": Informative message (e.g., reason for the corruption) (json-string)
|
|
||||||
- "offset": If the corruption resulted from an image access, this is the access
|
|
||||||
offset into the image (json-int)
|
|
||||||
- "size": If the corruption resulted from an image access, this is the access
|
|
||||||
size (json-int)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
{ "event": "BLOCK_IMAGE_CORRUPTED",
|
|
||||||
"data": { "device": "ide0-hd0",
|
|
||||||
"msg": "Prevented active L1 table overwrite", "offset": 196608,
|
|
||||||
"size": 65536 },
|
|
||||||
"timestamp": { "seconds": 1378126126, "microseconds": 966463 } }
|
|
||||||
|
|
||||||
BLOCK_IO_ERROR
|
BLOCK_IO_ERROR
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@@ -158,24 +136,6 @@ Example:
|
|||||||
Note: The "ready to complete" status is always reset by a BLOCK_JOB_ERROR
|
Note: The "ready to complete" status is always reset by a BLOCK_JOB_ERROR
|
||||||
event.
|
event.
|
||||||
|
|
||||||
DEVICE_DELETED
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Emitted whenever the device removal completion is acknowledged
|
|
||||||
by the guest.
|
|
||||||
At this point, it's safe to reuse the specified device ID.
|
|
||||||
Device removal can be initiated by the guest or by HMP/QMP commands.
|
|
||||||
|
|
||||||
Data:
|
|
||||||
|
|
||||||
- "device": device name (json-string, optional)
|
|
||||||
- "path": device path (json-string)
|
|
||||||
|
|
||||||
{ "event": "DEVICE_DELETED",
|
|
||||||
"data": { "device": "virtio-net-pci-0",
|
|
||||||
"path": "/machine/peripheral/virtio-net-pci-0" },
|
|
||||||
"timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
|
|
||||||
|
|
||||||
DEVICE_TRAY_MOVED
|
DEVICE_TRAY_MOVED
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@@ -194,76 +154,6 @@ Data:
|
|||||||
},
|
},
|
||||||
"timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
|
"timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
|
||||||
|
|
||||||
GUEST_PANICKED
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Emitted when guest OS panic is detected.
|
|
||||||
|
|
||||||
Data:
|
|
||||||
|
|
||||||
- "action": Action that has been taken (json-string, currently always "pause").
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
{ "event": "GUEST_PANICKED",
|
|
||||||
"data": { "action": "pause" } }
|
|
||||||
|
|
||||||
NIC_RX_FILTER_CHANGED
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The event is emitted once until the query command is executed,
|
|
||||||
the first event will always be emitted.
|
|
||||||
|
|
||||||
Data:
|
|
||||||
|
|
||||||
- "name": net client name (json-string)
|
|
||||||
- "path": device path (json-string)
|
|
||||||
|
|
||||||
{ "event": "NIC_RX_FILTER_CHANGED",
|
|
||||||
"data": { "name": "vnet0",
|
|
||||||
"path": "/machine/peripheral/vnet0/virtio-backend" },
|
|
||||||
"timestamp": { "seconds": 1368697518, "microseconds": 326866 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
QUORUM_FAILURE
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Emitted by the Quorum block driver if it fails to establish a quorum.
|
|
||||||
|
|
||||||
Data:
|
|
||||||
|
|
||||||
- "reference": device name if defined else node name.
|
|
||||||
- "sector-num": Number of the first sector of the failed read operation.
|
|
||||||
- "sector-count": Failed read operation sector count.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
{ "event": "QUORUM_FAILURE",
|
|
||||||
"data": { "reference": "usr1", "sector-num": 345435, "sector-count": 5 },
|
|
||||||
"timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
|
|
||||||
|
|
||||||
QUORUM_REPORT_BAD
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Emitted to report a corruption of a Quorum file.
|
|
||||||
|
|
||||||
Data:
|
|
||||||
|
|
||||||
- "error": Error message (json-string, optional)
|
|
||||||
Only present on failure. This field contains a human-readable
|
|
||||||
error message. There are no semantics other than that the
|
|
||||||
block layer reported an error and clients should not try to
|
|
||||||
interpret the error string.
|
|
||||||
- "node-name": The graph node name of the block driver state.
|
|
||||||
- "sector-num": Number of the first sector of the failed read operation.
|
|
||||||
- "sector-count": Failed read operation sector count.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
{ "event": "QUORUM_REPORT_BAD",
|
|
||||||
"data": { "node-name": "1.raw", "sector-num": 345435, "sector-count": 5 },
|
|
||||||
"timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
|
|
||||||
|
|
||||||
RESET
|
RESET
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -295,8 +185,7 @@ Emitted when the guest changes the RTC time.
|
|||||||
|
|
||||||
Data:
|
Data:
|
||||||
|
|
||||||
- "offset": Offset between base RTC clock (as specified by -rtc base), and
|
- "offset": delta against the host UTC in seconds (json-number)
|
||||||
new RTC clock value (json-number)
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -518,7 +407,7 @@ Data: None.
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
{ "event": "WAKEUP",
|
{ "event": "WATCHDOG",
|
||||||
"timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
|
"timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
|
||||||
|
|
||||||
WATCHDOG
|
WATCHDOG
|
||||||
@@ -31,7 +31,6 @@
|
|||||||
# (QEMU)
|
# (QEMU)
|
||||||
|
|
||||||
import qmp
|
import qmp
|
||||||
import json
|
|
||||||
import readline
|
import readline
|
||||||
import sys
|
import sys
|
||||||
import pprint
|
import pprint
|
||||||
@@ -100,41 +99,16 @@ class QMPShell(qmp.QEMUMonitorProtocol):
|
|||||||
for arg in cmdargs[1:]:
|
for arg in cmdargs[1:]:
|
||||||
opt = arg.split('=')
|
opt = arg.split('=')
|
||||||
try:
|
try:
|
||||||
if(len(opt) > 2):
|
|
||||||
opt[1] = '='.join(opt[1:])
|
|
||||||
value = int(opt[1])
|
value = int(opt[1])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if opt[1] == 'true':
|
|
||||||
value = True
|
|
||||||
elif opt[1] == 'false':
|
|
||||||
value = False
|
|
||||||
elif opt[1].startswith('{'):
|
|
||||||
value = json.loads(opt[1])
|
|
||||||
else:
|
|
||||||
value = opt[1]
|
value = opt[1]
|
||||||
optpath = opt[0].split('.')
|
qmpcmd['arguments'][opt[0]] = value
|
||||||
parent = qmpcmd['arguments']
|
|
||||||
curpath = []
|
|
||||||
for p in optpath[:-1]:
|
|
||||||
curpath.append(p)
|
|
||||||
d = parent.get(p, {})
|
|
||||||
if type(d) is not dict:
|
|
||||||
raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
|
|
||||||
parent[p] = d
|
|
||||||
parent = d
|
|
||||||
if optpath[-1] in parent:
|
|
||||||
if type(parent[optpath[-1]]) is dict:
|
|
||||||
raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
|
|
||||||
else:
|
|
||||||
raise QMPShellError('Cannot set "%s" multiple times' % opt[0])
|
|
||||||
parent[optpath[-1]] = value
|
|
||||||
return qmpcmd
|
return qmpcmd
|
||||||
|
|
||||||
def _execute_cmd(self, cmdline):
|
def _execute_cmd(self, cmdline):
|
||||||
try:
|
try:
|
||||||
qmpcmd = self.__build_cmd(cmdline)
|
qmpcmd = self.__build_cmd(cmdline)
|
||||||
except Exception, e:
|
except:
|
||||||
print 'Error while parsing command line: %s' % e
|
|
||||||
print 'command format: <command-name> ',
|
print 'command format: <command-name> ',
|
||||||
print '[arg-name1=arg1] ... [arg-nameN=argN]'
|
print '[arg-name1=arg1] ... [arg-nameN=argN]'
|
||||||
return True
|
return True
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
QEMU Machine Protocol Specification
|
QEMU Monitor Protocol Specification - Version 0.1
|
||||||
|
|
||||||
1. Introduction
|
1. Introduction
|
||||||
===============
|
===============
|
||||||
|
|
||||||
This document specifies the QEMU Machine Protocol (QMP), a JSON-based protocol
|
This document specifies the QEMU Monitor Protocol (QMP), a JSON-based protocol
|
||||||
which is available for applications to operate QEMU at the machine-level.
|
which is available for applications to control QEMU at the machine-level.
|
||||||
|
|
||||||
|
To enable QMP support, QEMU has to be run in "control mode". This is done by
|
||||||
|
starting QEMU with the appropriate command-line options. Please, refer to the
|
||||||
|
QEMU manual page for more information.
|
||||||
|
|
||||||
2. Protocol Specification
|
2. Protocol Specification
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
This section details the protocol format. For the purpose of this document
|
This section details the protocol format. For the purpose of this document
|
||||||
"Client" is any application which is using QMP to communicate with QEMU and
|
"Client" is any application which is communicating with QEMU in control mode,
|
||||||
"Server" is QEMU itself.
|
and "Server" is QEMU itself.
|
||||||
|
|
||||||
JSON data structures, when mentioned in this document, are always in the
|
JSON data structures, when mentioned in this document, are always in the
|
||||||
following format:
|
following format:
|
||||||
@@ -43,14 +47,14 @@ that the connection has been successfully established and that the Server is
|
|||||||
ready for capabilities negotiation (for more information refer to section
|
ready for capabilities negotiation (for more information refer to section
|
||||||
'4. Capabilities Negotiation').
|
'4. Capabilities Negotiation').
|
||||||
|
|
||||||
The greeting message format is:
|
The format is:
|
||||||
|
|
||||||
{ "QMP": { "version": json-object, "capabilities": json-array } }
|
{ "QMP": { "version": json-object, "capabilities": json-array } }
|
||||||
|
|
||||||
Where,
|
Where,
|
||||||
|
|
||||||
- The "version" member contains the Server's version information (the format
|
- The "version" member contains the Server's version information (the format
|
||||||
is the same of the query-version command)
|
is the same of the 'query-version' command)
|
||||||
- The "capabilities" member specify the availability of features beyond the
|
- The "capabilities" member specify the availability of features beyond the
|
||||||
baseline specification
|
baseline specification
|
||||||
|
|
||||||
@@ -79,7 +83,10 @@ of a command execution: success or error.
|
|||||||
2.4.1 success
|
2.4.1 success
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The format of a success response is:
|
The success response is issued when the command execution has finished
|
||||||
|
without errors.
|
||||||
|
|
||||||
|
The format is:
|
||||||
|
|
||||||
{ "return": json-object, "id": json-value }
|
{ "return": json-object, "id": json-value }
|
||||||
|
|
||||||
@@ -89,12 +96,15 @@ The format of a success response is:
|
|||||||
in a per-command basis or an empty json-object if the command does not
|
in a per-command basis or an empty json-object if the command does not
|
||||||
return data
|
return data
|
||||||
- The "id" member contains the transaction identification associated
|
- The "id" member contains the transaction identification associated
|
||||||
with the command execution if issued by the Client
|
with the command execution (if issued by the Client)
|
||||||
|
|
||||||
2.4.2 error
|
2.4.2 error
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The format of an error response is:
|
The error response is issued when the command execution could not be
|
||||||
|
completed because of an error condition.
|
||||||
|
|
||||||
|
The format is:
|
||||||
|
|
||||||
{ "error": { "class": json-string, "desc": json-string }, "id": json-value }
|
{ "error": { "class": json-string, "desc": json-string }, "id": json-value }
|
||||||
|
|
||||||
@@ -104,7 +114,7 @@ The format of an error response is:
|
|||||||
- The "desc" member is a human-readable error message. Clients should
|
- The "desc" member is a human-readable error message. Clients should
|
||||||
not attempt to parse this message.
|
not attempt to parse this message.
|
||||||
- The "id" member contains the transaction identification associated with
|
- The "id" member contains the transaction identification associated with
|
||||||
the command execution if issued by the Client
|
the command execution (if issued by the Client)
|
||||||
|
|
||||||
NOTE: Some errors can occur before the Server is able to read the "id" member,
|
NOTE: Some errors can occur before the Server is able to read the "id" member,
|
||||||
in these cases the "id" member will not be part of the error response, even
|
in these cases the "id" member will not be part of the error response, even
|
||||||
@@ -114,9 +124,9 @@ if provided by the client.
|
|||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
As a result of state changes, the Server may send messages unilaterally
|
As a result of state changes, the Server may send messages unilaterally
|
||||||
to the Client at any time. They are called "asynchronous events".
|
to the Client at any time. They are called 'asynchronous events'.
|
||||||
|
|
||||||
The format of asynchronous events is:
|
The format is:
|
||||||
|
|
||||||
{ "event": json-string, "data": json-object,
|
{ "event": json-string, "data": json-object,
|
||||||
"timestamp": { "seconds": json-number, "microseconds": json-number } }
|
"timestamp": { "seconds": json-number, "microseconds": json-number } }
|
||||||
@@ -137,13 +147,12 @@ qmp-events.txt file.
|
|||||||
===============
|
===============
|
||||||
|
|
||||||
This section provides some examples of real QMP usage, in all of them
|
This section provides some examples of real QMP usage, in all of them
|
||||||
"C" stands for "Client" and "S" stands for "Server".
|
'C' stands for 'Client' and 'S' stands for 'Server'.
|
||||||
|
|
||||||
3.1 Server greeting
|
3.1 Server greeting
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
S: { "QMP": { "version": { "qemu": { "micro": 50, "minor": 6, "major": 1 },
|
S: {"QMP": {"version": {"qemu": "0.12.50", "package": ""}, "capabilities": []}}
|
||||||
"package": ""}, "capabilities": []}}
|
|
||||||
|
|
||||||
3.2 Simple 'stop' execution
|
3.2 Simple 'stop' execution
|
||||||
---------------------------
|
---------------------------
|
||||||
@@ -166,8 +175,8 @@ S: { "error": { "class": "GenericError", "desc": "Invalid JSON syntax" } }
|
|||||||
3.5 Powerdown event
|
3.5 Powerdown event
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
S: { "timestamp": { "seconds": 1258551470, "microseconds": 802384 },
|
S: {"timestamp": {"seconds": 1258551470, "microseconds": 802384}, "event":
|
||||||
"event": "POWERDOWN" }
|
"POWERDOWN"}
|
||||||
|
|
||||||
4. Capabilities Negotiation
|
4. Capabilities Negotiation
|
||||||
----------------------------
|
----------------------------
|
||||||
@@ -175,17 +184,17 @@ S: { "timestamp": { "seconds": 1258551470, "microseconds": 802384 },
|
|||||||
When a Client successfully establishes a connection, the Server is in
|
When a Client successfully establishes a connection, the Server is in
|
||||||
Capabilities Negotiation mode.
|
Capabilities Negotiation mode.
|
||||||
|
|
||||||
In this mode only the qmp_capabilities command is allowed to run, all
|
In this mode only the 'qmp_capabilities' command is allowed to run, all
|
||||||
other commands will return the CommandNotFound error. Asynchronous
|
other commands will return the CommandNotFound error. Asynchronous messages
|
||||||
messages are not delivered either.
|
are not delivered either.
|
||||||
|
|
||||||
Clients should use the qmp_capabilities command to enable capabilities
|
Clients should use the 'qmp_capabilities' command to enable capabilities
|
||||||
advertised in the Server's greeting (section '2.2 Server Greeting') they
|
advertised in the Server's greeting (section '2.2 Server Greeting') they
|
||||||
support.
|
support.
|
||||||
|
|
||||||
When the qmp_capabilities command is issued, and if it does not return an
|
When the 'qmp_capabilities' command is issued, and if it does not return an
|
||||||
error, the Server enters in Command mode where capabilities changes take
|
error, the Server enters in Command mode where capabilities changes take
|
||||||
effect, all commands (except qmp_capabilities) are allowed and asynchronous
|
effect, all commands (except 'qmp_capabilities') are allowed and asynchronous
|
||||||
messages are delivered.
|
messages are delivered.
|
||||||
|
|
||||||
5 Compatibility Considerations
|
5 Compatibility Considerations
|
||||||
@@ -236,7 +245,7 @@ arguments, errors, asynchronous events, and so forth.
|
|||||||
|
|
||||||
Any new names downstream wishes to add must begin with '__'. To
|
Any new names downstream wishes to add must begin with '__'. To
|
||||||
ensure compatibility with other downstreams, it is strongly
|
ensure compatibility with other downstreams, it is strongly
|
||||||
recommended that you prefix your downstream names with '__RFQDN_' where
|
recommended that you prefix your downstram names with '__RFQDN_' where
|
||||||
RFQDN is a valid, reverse fully qualified domain name which you
|
RFQDN is a valid, reverse fully qualified domain name which you
|
||||||
control. For example, a qemu-kvm specific monitor command would be:
|
control. For example, a qemu-kvm specific monitor command would be:
|
||||||
|
|
||||||
@@ -171,12 +171,7 @@ class QEMUMonitorProtocol:
|
|||||||
pass
|
pass
|
||||||
self.__sock.setblocking(1)
|
self.__sock.setblocking(1)
|
||||||
if not self.__events and wait:
|
if not self.__events and wait:
|
||||||
ret = self.__json_read(only_event=True)
|
self.__json_read(only_event=True)
|
||||||
if ret == None:
|
|
||||||
# We are in blocking mode, if don't get anything, something
|
|
||||||
# went wrong
|
|
||||||
raise QMPConnectError("Error while reading from socket")
|
|
||||||
|
|
||||||
return self.__events
|
return self.__events
|
||||||
|
|
||||||
def clear_events(self):
|
def clear_events(self):
|
||||||
@@ -193,9 +188,3 @@ class QEMUMonitorProtocol:
|
|||||||
|
|
||||||
def settimeout(self, timeout):
|
def settimeout(self, timeout):
|
||||||
self.__sock.settimeout(timeout)
|
self.__sock.settimeout(timeout)
|
||||||
|
|
||||||
def get_sock_fd(self):
|
|
||||||
return self.__sock.fileno()
|
|
||||||
|
|
||||||
def is_scm_available(self):
|
|
||||||
return self.__sock.family == socket.AF_UNIX
|
|
||||||
2
README
2
README
@@ -1,3 +1,3 @@
|
|||||||
Read the documentation in qemu-doc.html or on http://wiki.qemu-project.org
|
Read the documentation in qemu-doc.html or on http://wiki.qemu.org
|
||||||
|
|
||||||
- QEMU team
|
- QEMU team
|
||||||
|
|||||||
37
TODO
Normal file
37
TODO
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
General:
|
||||||
|
-------
|
||||||
|
- cycle counter for all archs
|
||||||
|
- cpu_interrupt() win32/SMP fix
|
||||||
|
- merge PIC spurious interrupt patch
|
||||||
|
- warning for OS/2: must not use 128 MB memory (merge bochs cmos patch ?)
|
||||||
|
- config file (at least for windows/Mac OS X)
|
||||||
|
- update doc: PCI infos.
|
||||||
|
- basic VGA optimizations
|
||||||
|
- better code fetch
|
||||||
|
- do not resize vga if invalid size.
|
||||||
|
- TLB code protection support for PPC
|
||||||
|
- disable SMC handling for ARM/SPARC/PPC (not finished)
|
||||||
|
- see undefined flags for BTx insn
|
||||||
|
- keyboard output buffer filling timing emulation
|
||||||
|
- tests for each target CPU
|
||||||
|
- fix all remaining thread lock issues (must put TBs in a specific invalid
|
||||||
|
state, find a solution for tb_flush()).
|
||||||
|
|
||||||
|
ppc specific:
|
||||||
|
------------
|
||||||
|
- TLB invalidate not needed if msr_pr changes
|
||||||
|
- enable shift optimizations ?
|
||||||
|
|
||||||
|
linux-user specific:
|
||||||
|
-------------------
|
||||||
|
- remove threading support as it cannot work at this point
|
||||||
|
- improve IPC syscalls
|
||||||
|
- more syscalls (in particular all 64 bit ones, IPCs, fix 64 bit
|
||||||
|
issues, fix 16 bit uid issues)
|
||||||
|
- use kernel traps for unaligned accesses on ARM ?
|
||||||
|
|
||||||
|
|
||||||
|
lower priority:
|
||||||
|
--------------
|
||||||
|
- int15 ah=86: use better timing
|
||||||
|
- use -msoft-float on ARM
|
||||||
167
aio-posix.c
167
aio-posix.c
@@ -23,8 +23,8 @@ struct AioHandler
|
|||||||
GPollFD pfd;
|
GPollFD pfd;
|
||||||
IOHandler *io_read;
|
IOHandler *io_read;
|
||||||
IOHandler *io_write;
|
IOHandler *io_write;
|
||||||
|
AioFlushHandler *io_flush;
|
||||||
int deleted;
|
int deleted;
|
||||||
int pollfds_idx;
|
|
||||||
void *opaque;
|
void *opaque;
|
||||||
QLIST_ENTRY(AioHandler) node;
|
QLIST_ENTRY(AioHandler) node;
|
||||||
};
|
};
|
||||||
@@ -46,6 +46,7 @@ void aio_set_fd_handler(AioContext *ctx,
|
|||||||
int fd,
|
int fd,
|
||||||
IOHandler *io_read,
|
IOHandler *io_read,
|
||||||
IOHandler *io_write,
|
IOHandler *io_write,
|
||||||
|
AioFlushHandler *io_flush,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
@@ -82,11 +83,11 @@ void aio_set_fd_handler(AioContext *ctx,
|
|||||||
/* Update handler with latest information */
|
/* Update handler with latest information */
|
||||||
node->io_read = io_read;
|
node->io_read = io_read;
|
||||||
node->io_write = io_write;
|
node->io_write = io_write;
|
||||||
|
node->io_flush = io_flush;
|
||||||
node->opaque = opaque;
|
node->opaque = opaque;
|
||||||
node->pollfds_idx = -1;
|
|
||||||
|
|
||||||
node->pfd.events = (io_read ? G_IO_IN | G_IO_HUP | G_IO_ERR : 0);
|
node->pfd.events = (io_read ? G_IO_IN | G_IO_HUP : 0);
|
||||||
node->pfd.events |= (io_write ? G_IO_OUT | G_IO_ERR : 0);
|
node->pfd.events |= (io_write ? G_IO_OUT : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
aio_notify(ctx);
|
aio_notify(ctx);
|
||||||
@@ -94,10 +95,12 @@ void aio_set_fd_handler(AioContext *ctx,
|
|||||||
|
|
||||||
void aio_set_event_notifier(AioContext *ctx,
|
void aio_set_event_notifier(AioContext *ctx,
|
||||||
EventNotifier *notifier,
|
EventNotifier *notifier,
|
||||||
EventNotifierHandler *io_read)
|
EventNotifierHandler *io_read,
|
||||||
|
AioFlushEventNotifierHandler *io_flush)
|
||||||
{
|
{
|
||||||
aio_set_fd_handler(ctx, event_notifier_get_fd(notifier),
|
aio_set_fd_handler(ctx, event_notifier_get_fd(notifier),
|
||||||
(IOHandler *)io_read, NULL, notifier);
|
(IOHandler *)io_read, NULL,
|
||||||
|
(AioFlushHandler *)io_flush, notifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool aio_pending(AioContext *ctx)
|
bool aio_pending(AioContext *ctx)
|
||||||
@@ -107,6 +110,13 @@ bool aio_pending(AioContext *ctx)
|
|||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
int revents;
|
int revents;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: right now we cannot get G_IO_HUP and G_IO_ERR because
|
||||||
|
* main-loop.c is still select based (due to the slirp legacy).
|
||||||
|
* If main-loop.c ever switches to poll, G_IO_ERR should be
|
||||||
|
* tested too. Dispatching G_IO_ERR to both handlers should be
|
||||||
|
* okay, since handlers need to be ready for spurious wakeups.
|
||||||
|
*/
|
||||||
revents = node->pfd.revents & node->pfd.events;
|
revents = node->pfd.revents & node->pfd.events;
|
||||||
if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read) {
|
if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read) {
|
||||||
return true;
|
return true;
|
||||||
@@ -119,12 +129,30 @@ bool aio_pending(AioContext *ctx)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool aio_dispatch(AioContext *ctx)
|
bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
{
|
{
|
||||||
|
static struct timeval tv0;
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
bool progress = false;
|
fd_set rdfds, wrfds;
|
||||||
|
int max_fd = -1;
|
||||||
|
int ret;
|
||||||
|
bool busy, progress;
|
||||||
|
|
||||||
|
progress = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* If there are callbacks left that have been queued, we need to call then.
|
||||||
|
* 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 qemu_aio_wait loops).
|
||||||
|
*/
|
||||||
|
if (aio_bh_poll(ctx)) {
|
||||||
|
blocking = false;
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Then dispatch any pending callbacks from the GSource.
|
||||||
|
*
|
||||||
* We have to walk very carefully in case qemu_aio_set_fd_handler is
|
* We have to walk very carefully in case qemu_aio_set_fd_handler is
|
||||||
* called while we're walking.
|
* called while we're walking.
|
||||||
*/
|
*/
|
||||||
@@ -138,19 +166,12 @@ static bool aio_dispatch(AioContext *ctx)
|
|||||||
revents = node->pfd.revents & node->pfd.events;
|
revents = node->pfd.revents & node->pfd.events;
|
||||||
node->pfd.revents = 0;
|
node->pfd.revents = 0;
|
||||||
|
|
||||||
if (!node->deleted &&
|
/* See comment in aio_pending. */
|
||||||
(revents & (G_IO_IN | G_IO_HUP | G_IO_ERR)) &&
|
if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read) {
|
||||||
node->io_read) {
|
|
||||||
node->io_read(node->opaque);
|
node->io_read(node->opaque);
|
||||||
|
|
||||||
/* aio_notify() does not count as progress */
|
|
||||||
if (node->opaque != &ctx->notifier) {
|
|
||||||
progress = true;
|
progress = true;
|
||||||
}
|
}
|
||||||
}
|
if (revents & (G_IO_OUT | G_IO_ERR) && node->io_write) {
|
||||||
if (!node->deleted &&
|
|
||||||
(revents & (G_IO_OUT | G_IO_ERR)) &&
|
|
||||||
node->io_write) {
|
|
||||||
node->io_write(node->opaque);
|
node->io_write(node->opaque);
|
||||||
progress = true;
|
progress = true;
|
||||||
}
|
}
|
||||||
@@ -166,77 +187,83 @@ static bool aio_dispatch(AioContext *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Run our timers */
|
|
||||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
|
||||||
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool aio_poll(AioContext *ctx, bool blocking)
|
|
||||||
{
|
|
||||||
AioHandler *node;
|
|
||||||
int ret;
|
|
||||||
bool progress;
|
|
||||||
|
|
||||||
progress = false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 qemu_aio_wait loops).
|
|
||||||
*/
|
|
||||||
if (aio_bh_poll(ctx)) {
|
|
||||||
blocking = false;
|
|
||||||
progress = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aio_dispatch(ctx)) {
|
|
||||||
progress = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress && !blocking) {
|
if (progress && !blocking) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->walking_handlers++;
|
ctx->walking_handlers++;
|
||||||
|
|
||||||
g_array_set_size(ctx->pollfds, 0);
|
FD_ZERO(&rdfds);
|
||||||
|
FD_ZERO(&wrfds);
|
||||||
|
|
||||||
/* fill pollfds */
|
/* fill fd sets */
|
||||||
|
busy = false;
|
||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
node->pollfds_idx = -1;
|
/* If there aren't pending AIO operations, don't invoke callbacks.
|
||||||
if (!node->deleted && node->pfd.events) {
|
* Otherwise, if there are no AIO requests, qemu_aio_wait() would
|
||||||
GPollFD pfd = {
|
* wait indefinitely.
|
||||||
.fd = node->pfd.fd,
|
*/
|
||||||
.events = node->pfd.events,
|
if (!node->deleted && node->io_flush) {
|
||||||
};
|
if (node->io_flush(node->opaque) == 0) {
|
||||||
node->pollfds_idx = ctx->pollfds->len;
|
continue;
|
||||||
g_array_append_val(ctx->pollfds, pfd);
|
}
|
||||||
|
busy = true;
|
||||||
|
}
|
||||||
|
if (!node->deleted && node->io_read) {
|
||||||
|
FD_SET(node->pfd.fd, &rdfds);
|
||||||
|
max_fd = MAX(max_fd, node->pfd.fd + 1);
|
||||||
|
}
|
||||||
|
if (!node->deleted && node->io_write) {
|
||||||
|
FD_SET(node->pfd.fd, &wrfds);
|
||||||
|
max_fd = MAX(max_fd, node->pfd.fd + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->walking_handlers--;
|
ctx->walking_handlers--;
|
||||||
|
|
||||||
|
/* No AIO operations? Get us out of here */
|
||||||
|
if (!busy) {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
/* wait until next event */
|
/* wait until next event */
|
||||||
ret = qemu_poll_ns((GPollFD *)ctx->pollfds->data,
|
ret = select(max_fd, &rdfds, &wrfds, NULL, blocking ? NULL : &tv0);
|
||||||
ctx->pollfds->len,
|
|
||||||
blocking ? timerlistgroup_deadline_ns(&ctx->tlg) : 0);
|
|
||||||
|
|
||||||
/* if we have any readable fds, dispatch event */
|
/* if we have any readable fds, dispatch event */
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
/* we have to walk very carefully in case
|
||||||
if (node->pollfds_idx != -1) {
|
* qemu_aio_set_fd_handler is called while we're walking */
|
||||||
GPollFD *pfd = &g_array_index(ctx->pollfds, GPollFD,
|
node = QLIST_FIRST(&ctx->aio_handlers);
|
||||||
node->pollfds_idx);
|
while (node) {
|
||||||
node->pfd.revents = pfd->revents;
|
AioHandler *tmp;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Run dispatch even if there were no readable fds to run timers */
|
ctx->walking_handlers++;
|
||||||
if (aio_dispatch(ctx)) {
|
|
||||||
|
if (!node->deleted &&
|
||||||
|
FD_ISSET(node->pfd.fd, &rdfds) &&
|
||||||
|
node->io_read) {
|
||||||
|
node->io_read(node->opaque);
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
if (!node->deleted &&
|
||||||
|
FD_ISSET(node->pfd.fd, &wrfds) &&
|
||||||
|
node->io_write) {
|
||||||
|
node->io_write(node->opaque);
|
||||||
progress = true;
|
progress = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return progress;
|
tmp = node;
|
||||||
|
node = QLIST_NEXT(node, node);
|
||||||
|
|
||||||
|
ctx->walking_handlers--;
|
||||||
|
|
||||||
|
if (!ctx->walking_handlers && tmp->deleted) {
|
||||||
|
QLIST_REMOVE(tmp, node);
|
||||||
|
g_free(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(progress || busy);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
54
aio-win32.c
54
aio-win32.c
@@ -23,6 +23,7 @@
|
|||||||
struct AioHandler {
|
struct AioHandler {
|
||||||
EventNotifier *e;
|
EventNotifier *e;
|
||||||
EventNotifierHandler *io_notify;
|
EventNotifierHandler *io_notify;
|
||||||
|
AioFlushEventNotifierHandler *io_flush;
|
||||||
GPollFD pfd;
|
GPollFD pfd;
|
||||||
int deleted;
|
int deleted;
|
||||||
QLIST_ENTRY(AioHandler) node;
|
QLIST_ENTRY(AioHandler) node;
|
||||||
@@ -30,7 +31,8 @@ struct AioHandler {
|
|||||||
|
|
||||||
void aio_set_event_notifier(AioContext *ctx,
|
void aio_set_event_notifier(AioContext *ctx,
|
||||||
EventNotifier *e,
|
EventNotifier *e,
|
||||||
EventNotifierHandler *io_notify)
|
EventNotifierHandler *io_notify,
|
||||||
|
AioFlushEventNotifierHandler *io_flush)
|
||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ void aio_set_event_notifier(AioContext *ctx,
|
|||||||
}
|
}
|
||||||
/* Update handler with latest information */
|
/* Update handler with latest information */
|
||||||
node->io_notify = io_notify;
|
node->io_notify = io_notify;
|
||||||
|
node->io_flush = io_flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
aio_notify(ctx);
|
aio_notify(ctx);
|
||||||
@@ -93,9 +96,8 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
HANDLE events[MAXIMUM_WAIT_OBJECTS + 1];
|
HANDLE events[MAXIMUM_WAIT_OBJECTS + 1];
|
||||||
bool progress;
|
bool busy, progress;
|
||||||
int count;
|
int count;
|
||||||
int timeout;
|
|
||||||
|
|
||||||
progress = false;
|
progress = false;
|
||||||
|
|
||||||
@@ -109,9 +111,6 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
progress = true;
|
progress = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Run timers */
|
|
||||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Then dispatch any pending callbacks from the GSource.
|
* Then dispatch any pending callbacks from the GSource.
|
||||||
*
|
*
|
||||||
@@ -127,12 +126,8 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
if (node->pfd.revents && node->io_notify) {
|
if (node->pfd.revents && node->io_notify) {
|
||||||
node->pfd.revents = 0;
|
node->pfd.revents = 0;
|
||||||
node->io_notify(node->e);
|
node->io_notify(node->e);
|
||||||
|
|
||||||
/* aio_notify() does not count as progress */
|
|
||||||
if (node->e != &ctx->notifier) {
|
|
||||||
progress = true;
|
progress = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tmp = node;
|
tmp = node;
|
||||||
node = QLIST_NEXT(node, node);
|
node = QLIST_NEXT(node, node);
|
||||||
@@ -152,8 +147,19 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
ctx->walking_handlers++;
|
ctx->walking_handlers++;
|
||||||
|
|
||||||
/* fill fd sets */
|
/* fill fd sets */
|
||||||
|
busy = false;
|
||||||
count = 0;
|
count = 0;
|
||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
|
/* If there aren't pending AIO operations, don't invoke callbacks.
|
||||||
|
* Otherwise, if there are no AIO requests, qemu_aio_wait() would
|
||||||
|
* wait indefinitely.
|
||||||
|
*/
|
||||||
|
if (!node->deleted && node->io_flush) {
|
||||||
|
if (node->io_flush(node->e) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
busy = true;
|
||||||
|
}
|
||||||
if (!node->deleted && node->io_notify) {
|
if (!node->deleted && node->io_notify) {
|
||||||
events[count++] = event_notifier_get_handle(node->e);
|
events[count++] = event_notifier_get_handle(node->e);
|
||||||
}
|
}
|
||||||
@@ -161,13 +167,15 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
|
|
||||||
ctx->walking_handlers--;
|
ctx->walking_handlers--;
|
||||||
|
|
||||||
|
/* No AIO operations? Get us out of here */
|
||||||
|
if (!busy) {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
/* wait until next event */
|
/* wait until next event */
|
||||||
while (count > 0) {
|
while (count > 0) {
|
||||||
int ret;
|
int timeout = blocking ? INFINITE : 0;
|
||||||
|
int ret = WaitForMultipleObjects(count, events, FALSE, timeout);
|
||||||
timeout = blocking ?
|
|
||||||
qemu_timeout_ns_to_ms(timerlistgroup_deadline_ns(&ctx->tlg)) : 0;
|
|
||||||
ret = WaitForMultipleObjects(count, events, FALSE, timeout);
|
|
||||||
|
|
||||||
/* if we have any signaled events, dispatch event */
|
/* if we have any signaled events, dispatch event */
|
||||||
if ((DWORD) (ret - WAIT_OBJECT_0) >= count) {
|
if ((DWORD) (ret - WAIT_OBJECT_0) >= count) {
|
||||||
@@ -188,12 +196,8 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
event_notifier_get_handle(node->e) == events[ret - WAIT_OBJECT_0] &&
|
event_notifier_get_handle(node->e) == events[ret - WAIT_OBJECT_0] &&
|
||||||
node->io_notify) {
|
node->io_notify) {
|
||||||
node->io_notify(node->e);
|
node->io_notify(node->e);
|
||||||
|
|
||||||
/* aio_notify() does not count as progress */
|
|
||||||
if (node->e != &ctx->notifier) {
|
|
||||||
progress = true;
|
progress = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tmp = node;
|
tmp = node;
|
||||||
node = QLIST_NEXT(node, node);
|
node = QLIST_NEXT(node, node);
|
||||||
@@ -210,14 +214,6 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
events[ret - WAIT_OBJECT_0] = events[--count];
|
events[ret - WAIT_OBJECT_0] = events[--count];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blocking) {
|
assert(progress || busy);
|
||||||
/* Run the timers a second time. We do this because otherwise aio_wait
|
return true;
|
||||||
* will not note progress - and will stop a drain early - if we have
|
|
||||||
* a timer that was not ready to run entering g_poll but is ready
|
|
||||||
* after g_poll. This will only do anything if a timer has expired.
|
|
||||||
*/
|
|
||||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return progress;
|
|
||||||
}
|
}
|
||||||
|
|||||||
619
arch_init.c
619
arch_init.c
File diff suppressed because it is too large
Load Diff
88
async.c
88
async.c
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/aio.h"
|
#include "block/aio.h"
|
||||||
#include "block/thread-pool.h"
|
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
|
|
||||||
/***********************************************************/
|
/***********************************************************/
|
||||||
@@ -47,16 +46,11 @@ QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
|||||||
bh->ctx = ctx;
|
bh->ctx = ctx;
|
||||||
bh->cb = cb;
|
bh->cb = cb;
|
||||||
bh->opaque = opaque;
|
bh->opaque = opaque;
|
||||||
qemu_mutex_lock(&ctx->bh_lock);
|
|
||||||
bh->next = ctx->first_bh;
|
bh->next = ctx->first_bh;
|
||||||
/* Make sure that the members are ready before putting bh into list */
|
|
||||||
smp_wmb();
|
|
||||||
ctx->first_bh = bh;
|
ctx->first_bh = bh;
|
||||||
qemu_mutex_unlock(&ctx->bh_lock);
|
|
||||||
return bh;
|
return bh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Multiple occurrences of aio_bh_poll cannot be called concurrently */
|
|
||||||
int aio_bh_poll(AioContext *ctx)
|
int aio_bh_poll(AioContext *ctx)
|
||||||
{
|
{
|
||||||
QEMUBH *bh, **bhp, *next;
|
QEMUBH *bh, **bhp, *next;
|
||||||
@@ -66,15 +60,9 @@ int aio_bh_poll(AioContext *ctx)
|
|||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
for (bh = ctx->first_bh; bh; 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;
|
next = bh->next;
|
||||||
if (!bh->deleted && bh->scheduled) {
|
if (!bh->deleted && bh->scheduled) {
|
||||||
bh->scheduled = 0;
|
bh->scheduled = 0;
|
||||||
/* Paired with write barrier in bh schedule to ensure reading for
|
|
||||||
* idle & callbacks coming after bh's scheduling.
|
|
||||||
*/
|
|
||||||
smp_rmb();
|
|
||||||
if (!bh->idle)
|
if (!bh->idle)
|
||||||
ret = 1;
|
ret = 1;
|
||||||
bh->idle = 0;
|
bh->idle = 0;
|
||||||
@@ -86,7 +74,6 @@ int aio_bh_poll(AioContext *ctx)
|
|||||||
|
|
||||||
/* remove deleted bhs */
|
/* remove deleted bhs */
|
||||||
if (!ctx->walking_bh) {
|
if (!ctx->walking_bh) {
|
||||||
qemu_mutex_lock(&ctx->bh_lock);
|
|
||||||
bhp = &ctx->first_bh;
|
bhp = &ctx->first_bh;
|
||||||
while (*bhp) {
|
while (*bhp) {
|
||||||
bh = *bhp;
|
bh = *bhp;
|
||||||
@@ -97,7 +84,6 @@ int aio_bh_poll(AioContext *ctx)
|
|||||||
bhp = &bh->next;
|
bhp = &bh->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qemu_mutex_unlock(&ctx->bh_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -107,38 +93,24 @@ void qemu_bh_schedule_idle(QEMUBH *bh)
|
|||||||
{
|
{
|
||||||
if (bh->scheduled)
|
if (bh->scheduled)
|
||||||
return;
|
return;
|
||||||
bh->idle = 1;
|
|
||||||
/* Make sure that idle & any writes needed by the callback are done
|
|
||||||
* before the locations are read in the aio_bh_poll.
|
|
||||||
*/
|
|
||||||
smp_wmb();
|
|
||||||
bh->scheduled = 1;
|
bh->scheduled = 1;
|
||||||
|
bh->idle = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void qemu_bh_schedule(QEMUBH *bh)
|
void qemu_bh_schedule(QEMUBH *bh)
|
||||||
{
|
{
|
||||||
if (bh->scheduled)
|
if (bh->scheduled)
|
||||||
return;
|
return;
|
||||||
bh->idle = 0;
|
|
||||||
/* Make sure that idle & any writes needed by the callback are done
|
|
||||||
* before the locations are read in the aio_bh_poll.
|
|
||||||
*/
|
|
||||||
smp_wmb();
|
|
||||||
bh->scheduled = 1;
|
bh->scheduled = 1;
|
||||||
|
bh->idle = 0;
|
||||||
aio_notify(bh->ctx);
|
aio_notify(bh->ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* This func is async.
|
|
||||||
*/
|
|
||||||
void qemu_bh_cancel(QEMUBH *bh)
|
void qemu_bh_cancel(QEMUBH *bh)
|
||||||
{
|
{
|
||||||
bh->scheduled = 0;
|
bh->scheduled = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This func is async.The bottom half will do the delete action at the finial
|
|
||||||
* end.
|
|
||||||
*/
|
|
||||||
void qemu_bh_delete(QEMUBH *bh)
|
void qemu_bh_delete(QEMUBH *bh)
|
||||||
{
|
{
|
||||||
bh->scheduled = 0;
|
bh->scheduled = 0;
|
||||||
@@ -150,10 +122,7 @@ aio_ctx_prepare(GSource *source, gint *timeout)
|
|||||||
{
|
{
|
||||||
AioContext *ctx = (AioContext *) source;
|
AioContext *ctx = (AioContext *) source;
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
int deadline;
|
|
||||||
|
|
||||||
/* We assume there is no timeout already supplied */
|
|
||||||
*timeout = -1;
|
|
||||||
for (bh = ctx->first_bh; bh; bh = bh->next) {
|
for (bh = ctx->first_bh; bh; bh = bh->next) {
|
||||||
if (!bh->deleted && bh->scheduled) {
|
if (!bh->deleted && bh->scheduled) {
|
||||||
if (bh->idle) {
|
if (bh->idle) {
|
||||||
@@ -169,14 +138,6 @@ aio_ctx_prepare(GSource *source, gint *timeout)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline = qemu_timeout_ns_to_ms(timerlistgroup_deadline_ns(&ctx->tlg));
|
|
||||||
if (deadline == 0) {
|
|
||||||
*timeout = 0;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
*timeout = qemu_soonest_timeout(*timeout, deadline);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +152,7 @@ aio_ctx_check(GSource *source)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return aio_pending(ctx) || (timerlistgroup_deadline_ns(&ctx->tlg) == 0);
|
return aio_pending(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@@ -211,13 +172,8 @@ aio_ctx_finalize(GSource *source)
|
|||||||
{
|
{
|
||||||
AioContext *ctx = (AioContext *) source;
|
AioContext *ctx = (AioContext *) source;
|
||||||
|
|
||||||
thread_pool_free(ctx->thread_pool);
|
aio_set_event_notifier(ctx, &ctx->notifier, NULL, NULL);
|
||||||
aio_set_event_notifier(ctx, &ctx->notifier, NULL);
|
|
||||||
event_notifier_cleanup(&ctx->notifier);
|
event_notifier_cleanup(&ctx->notifier);
|
||||||
rfifolock_destroy(&ctx->lock);
|
|
||||||
qemu_mutex_destroy(&ctx->bh_lock);
|
|
||||||
g_array_free(ctx->pollfds, TRUE);
|
|
||||||
timerlistgroup_deinit(&ctx->tlg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static GSourceFuncs aio_source_funcs = {
|
static GSourceFuncs aio_source_funcs = {
|
||||||
@@ -233,43 +189,19 @@ GSource *aio_get_g_source(AioContext *ctx)
|
|||||||
return &ctx->source;
|
return &ctx->source;
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadPool *aio_get_thread_pool(AioContext *ctx)
|
|
||||||
{
|
|
||||||
if (!ctx->thread_pool) {
|
|
||||||
ctx->thread_pool = thread_pool_new(ctx);
|
|
||||||
}
|
|
||||||
return ctx->thread_pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
void aio_notify(AioContext *ctx)
|
void aio_notify(AioContext *ctx)
|
||||||
{
|
{
|
||||||
event_notifier_set(&ctx->notifier);
|
event_notifier_set(&ctx->notifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void aio_timerlist_notify(void *opaque)
|
|
||||||
{
|
|
||||||
aio_notify(opaque);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aio_rfifolock_cb(void *opaque)
|
|
||||||
{
|
|
||||||
/* Kick owner thread in case they are blocked in aio_poll() */
|
|
||||||
aio_notify(opaque);
|
|
||||||
}
|
|
||||||
|
|
||||||
AioContext *aio_context_new(void)
|
AioContext *aio_context_new(void)
|
||||||
{
|
{
|
||||||
AioContext *ctx;
|
AioContext *ctx;
|
||||||
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
|
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
|
||||||
ctx->pollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
|
|
||||||
ctx->thread_pool = NULL;
|
|
||||||
qemu_mutex_init(&ctx->bh_lock);
|
|
||||||
rfifolock_init(&ctx->lock, aio_rfifolock_cb, ctx);
|
|
||||||
event_notifier_init(&ctx->notifier, false);
|
event_notifier_init(&ctx->notifier, false);
|
||||||
aio_set_event_notifier(ctx, &ctx->notifier,
|
aio_set_event_notifier(ctx, &ctx->notifier,
|
||||||
(EventNotifierHandler *)
|
(EventNotifierHandler *)
|
||||||
event_notifier_test_and_clear);
|
event_notifier_test_and_clear, NULL);
|
||||||
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
@@ -283,13 +215,3 @@ void aio_context_unref(AioContext *ctx)
|
|||||||
{
|
{
|
||||||
g_source_unref(&ctx->source);
|
g_source_unref(&ctx->source);
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_context_acquire(AioContext *ctx)
|
|
||||||
{
|
|
||||||
rfifolock_lock(&ctx->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void aio_context_release(AioContext *ctx)
|
|
||||||
{
|
|
||||||
rfifolock_unlock(&ctx->lock);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ static struct {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
.period = { .hertz = 100 },
|
.period = { .hertz = 250 },
|
||||||
.plive = 0,
|
.plive = 0,
|
||||||
.log_to_monitor = 0,
|
.log_to_monitor = 0,
|
||||||
.try_poll_in = 1,
|
.try_poll_in = 1,
|
||||||
@@ -1124,11 +1124,10 @@ static int audio_is_timer_needed (void)
|
|||||||
static void audio_reset_timer (AudioState *s)
|
static void audio_reset_timer (AudioState *s)
|
||||||
{
|
{
|
||||||
if (audio_is_timer_needed ()) {
|
if (audio_is_timer_needed ()) {
|
||||||
timer_mod (s->ts,
|
qemu_mod_timer (s->ts, qemu_get_clock_ns (vm_clock) + 1);
|
||||||
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
timer_del (s->ts);
|
qemu_del_timer (s->ts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1835,7 +1834,7 @@ static void audio_init (void)
|
|||||||
QLIST_INIT (&s->cap_head);
|
QLIST_INIT (&s->cap_head);
|
||||||
atexit (audio_atexit);
|
atexit (audio_atexit);
|
||||||
|
|
||||||
s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
|
s->ts = qemu_new_timer_ns (vm_clock, audio_timer, s);
|
||||||
if (!s->ts) {
|
if (!s->ts) {
|
||||||
hw_error("Could not create audio timer\n");
|
hw_error("Could not create audio timer\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,13 +243,38 @@ static inline int audio_ring_dist (int dst, int src, int len)
|
|||||||
return (dst >= src) ? (dst - src) : (len - src + dst);
|
return (dst >= src) ? (dst - src) : (len - src + dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define dolog(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__)
|
static void GCC_ATTR dolog (const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt);
|
||||||
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
#define ldebug(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__)
|
static void GCC_ATTR ldebug (const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt);
|
||||||
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
#define ldebug(fmt, ...) (void)0
|
#if defined NDEBUG && defined __GNUC__
|
||||||
|
#define ldebug(...)
|
||||||
|
#elif defined NDEBUG && defined _MSC_VER
|
||||||
|
#define ldebug __noop
|
||||||
|
#else
|
||||||
|
static void GCC_ATTR ldebug (const char *fmt, ...)
|
||||||
|
{
|
||||||
|
(void) fmt;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef GCC_ATTR
|
||||||
|
|
||||||
#define AUDIO_STRINGIFY_(n) #n
|
#define AUDIO_STRINGIFY_(n) #n
|
||||||
#define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n)
|
#define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* public domain */
|
/* public domain */
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
#define AUDIO_CAP "win-int"
|
#define AUDIO_CAP "win-int"
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|||||||
@@ -348,6 +348,7 @@ void mixeng_clear (struct st_sample *buf, int len)
|
|||||||
|
|
||||||
void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol)
|
void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol)
|
||||||
{
|
{
|
||||||
|
#ifdef CONFIG_MIXEMU
|
||||||
if (vol->mute) {
|
if (vol->mute) {
|
||||||
mixeng_clear (buf, len);
|
mixeng_clear (buf, len);
|
||||||
return;
|
return;
|
||||||
@@ -363,4 +364,9 @@ void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol)
|
|||||||
#endif
|
#endif
|
||||||
buf += 1;
|
buf += 1;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
(void) buf;
|
||||||
|
(void) len;
|
||||||
|
(void) vol;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
#define IN_T glue (glue (ITYPE, BSIZE), _t)
|
#define IN_T glue (glue (ITYPE, BSIZE), _t)
|
||||||
|
|
||||||
#ifdef FLOAT_MIXENG
|
#ifdef FLOAT_MIXENG
|
||||||
static inline mixeng_real glue (conv_, ET) (IN_T v)
|
static mixeng_real inline glue (conv_, ET) (IN_T v)
|
||||||
{
|
{
|
||||||
IN_T nv = ENDIAN_CONVERT (v);
|
IN_T nv = ENDIAN_CONVERT (v);
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ static inline mixeng_real glue (conv_, ET) (IN_T v)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline IN_T glue (clip_, ET) (mixeng_real v)
|
static IN_T inline glue (clip_, ET) (mixeng_real v)
|
||||||
{
|
{
|
||||||
if (v >= 0.5) {
|
if (v >= 0.5) {
|
||||||
return IN_MAX;
|
return IN_MAX;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ static int no_run_out (HWVoiceOut *hw, int live)
|
|||||||
int64_t ticks;
|
int64_t ticks;
|
||||||
int64_t bytes;
|
int64_t bytes;
|
||||||
|
|
||||||
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
now = qemu_get_clock_ns (vm_clock);
|
||||||
ticks = now - no->old_ticks;
|
ticks = now - no->old_ticks;
|
||||||
bytes = muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
bytes = muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
||||||
bytes = audio_MIN (bytes, INT_MAX);
|
bytes = audio_MIN (bytes, INT_MAX);
|
||||||
@@ -102,7 +102,7 @@ static int no_run_in (HWVoiceIn *hw)
|
|||||||
int samples = 0;
|
int samples = 0;
|
||||||
|
|
||||||
if (dead) {
|
if (dead) {
|
||||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
int64_t now = qemu_get_clock_ns (vm_clock);
|
||||||
int64_t ticks = now - no->old_ticks;
|
int64_t ticks = now - no->old_ticks;
|
||||||
int64_t bytes =
|
int64_t bytes =
|
||||||
muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
||||||
|
|||||||
@@ -25,7 +25,11 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
#ifdef __OpenBSD__
|
||||||
|
#include <soundcard.h>
|
||||||
|
#else
|
||||||
#include <sys/soundcard.h>
|
#include <sys/soundcard.h>
|
||||||
|
#endif
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
#include "qemu/host-utils.h"
|
#include "qemu/host-utils.h"
|
||||||
@@ -849,10 +853,6 @@ static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|||||||
|
|
||||||
static void *oss_audio_init (void)
|
static void *oss_audio_init (void)
|
||||||
{
|
{
|
||||||
if (access(conf.devpath_in, R_OK | W_OK) < 0 ||
|
|
||||||
access(conf.devpath_out, R_OK | W_OK) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return &conf;
|
return &conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -547,11 +547,11 @@ static int qpa_init_out (HWVoiceOut *hw, struct audsettings *as)
|
|||||||
ss.rate = as->freq;
|
ss.rate = as->freq;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* qemu audio tick runs at 100 Hz (by default), so processing
|
* qemu audio tick runs at 250 Hz (by default), so processing
|
||||||
* data chunks worth 10 ms of sound should be a good fit.
|
* data chunks worth 4 ms of sound should be a good fit.
|
||||||
*/
|
*/
|
||||||
ba.tlength = pa_usec_to_bytes (10 * 1000, &ss);
|
ba.tlength = pa_usec_to_bytes (4 * 1000, &ss);
|
||||||
ba.minreq = pa_usec_to_bytes (5 * 1000, &ss);
|
ba.minreq = pa_usec_to_bytes (2 * 1000, &ss);
|
||||||
ba.maxlength = -1;
|
ba.maxlength = -1;
|
||||||
ba.prebuf = -1;
|
ba.prebuf = -1;
|
||||||
|
|
||||||
|
|||||||
@@ -25,17 +25,8 @@
|
|||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "audio_int.h"
|
#include "audio_int.h"
|
||||||
|
|
||||||
#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
|
#define LINE_IN_SAMPLES 1024
|
||||||
#define LINE_OUT_SAMPLES (480 * 4)
|
#define LINE_OUT_SAMPLES 1024
|
||||||
#else
|
|
||||||
#define LINE_OUT_SAMPLES (256 * 4)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3
|
|
||||||
#define LINE_IN_SAMPLES (480 * 4)
|
|
||||||
#else
|
|
||||||
#define LINE_IN_SAMPLES (256 * 4)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct SpiceRateCtl {
|
typedef struct SpiceRateCtl {
|
||||||
int64_t start_ticks;
|
int64_t start_ticks;
|
||||||
@@ -90,7 +81,7 @@ static void spice_audio_fini (void *opaque)
|
|||||||
static void rate_start (SpiceRateCtl *rate)
|
static void rate_start (SpiceRateCtl *rate)
|
||||||
{
|
{
|
||||||
memset (rate, 0, sizeof (*rate));
|
memset (rate, 0, sizeof (*rate));
|
||||||
rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
rate->start_ticks = qemu_get_clock_ns (vm_clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate)
|
static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate)
|
||||||
@@ -100,7 +91,7 @@ static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate)
|
|||||||
int64_t bytes;
|
int64_t bytes;
|
||||||
int64_t samples;
|
int64_t samples;
|
||||||
|
|
||||||
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
now = qemu_get_clock_ns (vm_clock);
|
||||||
ticks = now - rate->start_ticks;
|
ticks = now - rate->start_ticks;
|
||||||
bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ());
|
bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ());
|
||||||
samples = (bytes - rate->bytes_sent) >> info->shift;
|
samples = (bytes - rate->bytes_sent) >> info->shift;
|
||||||
@@ -120,11 +111,7 @@ static int line_out_init (HWVoiceOut *hw, struct audsettings *as)
|
|||||||
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
|
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
|
||||||
struct audsettings settings;
|
struct audsettings settings;
|
||||||
|
|
||||||
#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
|
|
||||||
settings.freq = spice_server_get_best_playback_rate(NULL);
|
|
||||||
#else
|
|
||||||
settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ;
|
settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ;
|
||||||
#endif
|
|
||||||
settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN;
|
settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN;
|
||||||
settings.fmt = AUD_FMT_S16;
|
settings.fmt = AUD_FMT_S16;
|
||||||
settings.endianness = AUDIO_HOST_ENDIANNESS;
|
settings.endianness = AUDIO_HOST_ENDIANNESS;
|
||||||
@@ -135,9 +122,6 @@ static int line_out_init (HWVoiceOut *hw, struct audsettings *as)
|
|||||||
|
|
||||||
out->sin.base.sif = &playback_sif.base;
|
out->sin.base.sif = &playback_sif.base;
|
||||||
qemu_spice_add_interface (&out->sin.base);
|
qemu_spice_add_interface (&out->sin.base);
|
||||||
#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
|
|
||||||
spice_server_set_playback_rate(&out->sin, settings.freq);
|
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,11 +232,7 @@ static int line_in_init (HWVoiceIn *hw, struct audsettings *as)
|
|||||||
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
|
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
|
||||||
struct audsettings settings;
|
struct audsettings settings;
|
||||||
|
|
||||||
#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3
|
|
||||||
settings.freq = spice_server_get_best_record_rate(NULL);
|
|
||||||
#else
|
|
||||||
settings.freq = SPICE_INTERFACE_RECORD_FREQ;
|
settings.freq = SPICE_INTERFACE_RECORD_FREQ;
|
||||||
#endif
|
|
||||||
settings.nchannels = SPICE_INTERFACE_RECORD_CHAN;
|
settings.nchannels = SPICE_INTERFACE_RECORD_CHAN;
|
||||||
settings.fmt = AUD_FMT_S16;
|
settings.fmt = AUD_FMT_S16;
|
||||||
settings.endianness = AUDIO_HOST_ENDIANNESS;
|
settings.endianness = AUDIO_HOST_ENDIANNESS;
|
||||||
@@ -263,9 +243,6 @@ static int line_in_init (HWVoiceIn *hw, struct audsettings *as)
|
|||||||
|
|
||||||
in->sin.base.sif = &record_sif.base;
|
in->sin.base.sif = &record_sif.base;
|
||||||
qemu_spice_add_interface (&in->sin.base);
|
qemu_spice_add_interface (&in->sin.base);
|
||||||
#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3
|
|
||||||
spice_server_set_record_rate(&in->sin, settings.freq);
|
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ static int wav_run_out (HWVoiceOut *hw, int live)
|
|||||||
int rpos, decr, samples;
|
int rpos, decr, samples;
|
||||||
uint8_t *dst;
|
uint8_t *dst;
|
||||||
struct st_sample *src;
|
struct st_sample *src;
|
||||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
int64_t now = qemu_get_clock_ns (vm_clock);
|
||||||
int64_t ticks = now - wav->old_ticks;
|
int64_t ticks = now - wav->old_ticks;
|
||||||
int64_t bytes =
|
int64_t bytes =
|
||||||
muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
||||||
|
|||||||
@@ -1,8 +1,2 @@
|
|||||||
common-obj-y += rng.o rng-egd.o
|
common-obj-y += rng.o rng-egd.o
|
||||||
common-obj-$(CONFIG_POSIX) += rng-random.o
|
common-obj-$(CONFIG_POSIX) += rng-random.o
|
||||||
|
|
||||||
common-obj-y += msmouse.o
|
|
||||||
common-obj-$(CONFIG_BRLAPI) += baum.o
|
|
||||||
$(obj)/baum.o: QEMU_CFLAGS += $(SDL_CFLAGS)
|
|
||||||
|
|
||||||
common-obj-$(CONFIG_TPM) += tpm.o
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sysemu/rng.h"
|
#include "qemu/rng.h"
|
||||||
#include "sysemu/char.h"
|
#include "char/char.h"
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "hw/qdev.h" /* just for DEFINE_PROP_CHR */
|
#include "hw/qdev.h" /* just for DEFINE_PROP_CHR */
|
||||||
|
|
||||||
@@ -91,14 +91,12 @@ static int rng_egd_chr_can_read(void *opaque)
|
|||||||
static void rng_egd_chr_read(void *opaque, const uint8_t *buf, int size)
|
static void rng_egd_chr_read(void *opaque, const uint8_t *buf, int size)
|
||||||
{
|
{
|
||||||
RngEgd *s = RNG_EGD(opaque);
|
RngEgd *s = RNG_EGD(opaque);
|
||||||
size_t buf_offset = 0;
|
|
||||||
|
|
||||||
while (size > 0 && s->requests) {
|
while (size > 0 && s->requests) {
|
||||||
RngRequest *req = s->requests->data;
|
RngRequest *req = s->requests->data;
|
||||||
int len = MIN(size, req->size - req->offset);
|
int len = MIN(size, req->size - req->offset);
|
||||||
|
|
||||||
memcpy(req->data + req->offset, buf + buf_offset, len);
|
memcpy(req->data + req->offset, buf, len);
|
||||||
buf_offset += len;
|
|
||||||
req->offset += len;
|
req->offset += len;
|
||||||
size -= len;
|
size -= len;
|
||||||
|
|
||||||
@@ -151,11 +149,6 @@ static void rng_egd_opened(RngBackend *b, Error **errp)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qemu_chr_fe_claim(s->chr) != 0) {
|
|
||||||
error_set(errp, QERR_DEVICE_IN_USE, s->chr_name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIXME we should resubmit pending requests when the CDS reconnects. */
|
/* FIXME we should resubmit pending requests when the CDS reconnects. */
|
||||||
qemu_chr_add_handlers(s->chr, rng_egd_chr_can_read, rng_egd_chr_read,
|
qemu_chr_add_handlers(s->chr, rng_egd_chr_can_read, rng_egd_chr_read,
|
||||||
NULL, s);
|
NULL, s);
|
||||||
@@ -169,6 +162,7 @@ static void rng_egd_set_chardev(Object *obj, const char *value, Error **errp)
|
|||||||
if (b->opened) {
|
if (b->opened) {
|
||||||
error_set(errp, QERR_PERMISSION_DENIED);
|
error_set(errp, QERR_PERMISSION_DENIED);
|
||||||
} else {
|
} else {
|
||||||
|
g_free(s->chr_name);
|
||||||
s->chr_name = g_strdup(value);
|
s->chr_name = g_strdup(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +191,6 @@ static void rng_egd_finalize(Object *obj)
|
|||||||
|
|
||||||
if (s->chr) {
|
if (s->chr) {
|
||||||
qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, NULL);
|
qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, NULL);
|
||||||
qemu_chr_fe_release(s->chr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(s->chr_name);
|
g_free(s->chr_name);
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sysemu/rng-random.h"
|
#include "qemu/rng-random.h"
|
||||||
#include "sysemu/rng.h"
|
#include "qemu/rng.h"
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
|
|
||||||
@@ -78,8 +78,9 @@ static void rng_random_opened(RngBackend *b, Error **errp)
|
|||||||
"filename", "a valid filename");
|
"filename", "a valid filename");
|
||||||
} else {
|
} else {
|
||||||
s->fd = qemu_open(s->filename, O_RDONLY | O_NONBLOCK);
|
s->fd = qemu_open(s->filename, O_RDONLY | O_NONBLOCK);
|
||||||
|
|
||||||
if (s->fd == -1) {
|
if (s->fd == -1) {
|
||||||
error_setg_file_open(errp, errno, s->filename);
|
error_set(errp, QERR_OPEN_FILE_FAILED, s->filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,15 +124,15 @@ static void rng_random_init(Object *obj)
|
|||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
s->filename = g_strdup("/dev/random");
|
s->filename = g_strdup("/dev/random");
|
||||||
s->fd = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_random_finalize(Object *obj)
|
static void rng_random_finalize(Object *obj)
|
||||||
{
|
{
|
||||||
RndRandom *s = RNG_RANDOM(obj);
|
RndRandom *s = RNG_RANDOM(obj);
|
||||||
|
|
||||||
if (s->fd != -1) {
|
|
||||||
qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
|
qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (s->fd != -1) {
|
||||||
qemu_close(s->fd);
|
qemu_close(s->fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,8 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sysemu/rng.h"
|
#include "qemu/rng.h"
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "qom/object_interfaces.h"
|
|
||||||
|
|
||||||
void rng_backend_request_entropy(RngBackend *s, size_t size,
|
void rng_backend_request_entropy(RngBackend *s, size_t size,
|
||||||
EntropyReceiveFunc *receive_entropy,
|
EntropyReceiveFunc *receive_entropy,
|
||||||
@@ -41,9 +40,9 @@ static bool rng_backend_prop_get_opened(Object *obj, Error **errp)
|
|||||||
return s->opened;
|
return s->opened;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_backend_complete(UserCreatable *uc, Error **errp)
|
void rng_backend_open(RngBackend *s, Error **errp)
|
||||||
{
|
{
|
||||||
object_property_set_bool(OBJECT(uc), true, "opened", errp);
|
object_property_set_bool(OBJECT(s), true, "opened", errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_backend_prop_set_opened(Object *obj, bool value, Error **errp)
|
static void rng_backend_prop_set_opened(Object *obj, bool value, Error **errp)
|
||||||
@@ -77,25 +76,13 @@ static void rng_backend_init(Object *obj)
|
|||||||
NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_backend_class_init(ObjectClass *oc, void *data)
|
|
||||||
{
|
|
||||||
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
|
||||||
|
|
||||||
ucc->complete = rng_backend_complete;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const TypeInfo rng_backend_info = {
|
static const TypeInfo rng_backend_info = {
|
||||||
.name = TYPE_RNG_BACKEND,
|
.name = TYPE_RNG_BACKEND,
|
||||||
.parent = TYPE_OBJECT,
|
.parent = TYPE_OBJECT,
|
||||||
.instance_size = sizeof(RngBackend),
|
.instance_size = sizeof(RngBackend),
|
||||||
.instance_init = rng_backend_init,
|
.instance_init = rng_backend_init,
|
||||||
.class_size = sizeof(RngBackendClass),
|
.class_size = sizeof(RngBackendClass),
|
||||||
.class_init = rng_backend_class_init,
|
|
||||||
.abstract = true,
|
.abstract = true,
|
||||||
.interfaces = (InterfaceInfo[]) {
|
|
||||||
{ TYPE_USER_CREATABLE },
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void register_types(void)
|
static void register_types(void)
|
||||||
|
|||||||
190
backends/tpm.c
190
backends/tpm.c
@@ -1,190 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU TPM Backend
|
|
||||||
*
|
|
||||||
* Copyright IBM, Corp. 2013
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Stefan Berger <stefanb@us.ibm.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.
|
|
||||||
*
|
|
||||||
* Based on backends/rng.c by Anthony Liguori
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "sysemu/tpm_backend.h"
|
|
||||||
#include "qapi/qmp/qerror.h"
|
|
||||||
#include "sysemu/tpm.h"
|
|
||||||
#include "qemu/thread.h"
|
|
||||||
#include "sysemu/tpm_backend_int.h"
|
|
||||||
|
|
||||||
enum TpmType tpm_backend_get_type(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->type;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *tpm_backend_get_desc(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->desc();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_destroy(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->destroy(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
int tpm_backend_init(TPMBackend *s, TPMState *state,
|
|
||||||
TPMRecvDataCB *datacb)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->init(s, state, datacb);
|
|
||||||
}
|
|
||||||
|
|
||||||
int tpm_backend_startup_tpm(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->startup_tpm(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tpm_backend_had_startup_error(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->had_startup_error(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t tpm_backend_realloc_buffer(TPMBackend *s, TPMSizedBuffer *sb)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->realloc_buffer(sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_deliver_request(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
k->ops->deliver_request(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_reset(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
k->ops->reset(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_cancel_cmd(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
k->ops->cancel_cmd(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tpm_backend_get_tpm_established_flag(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->get_tpm_established_flag(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool tpm_backend_prop_get_opened(Object *obj, Error **errp)
|
|
||||||
{
|
|
||||||
TPMBackend *s = TPM_BACKEND(obj);
|
|
||||||
|
|
||||||
return s->opened;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_open(TPMBackend *s, Error **errp)
|
|
||||||
{
|
|
||||||
object_property_set_bool(OBJECT(s), true, "opened", errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tpm_backend_prop_set_opened(Object *obj, bool value, Error **errp)
|
|
||||||
{
|
|
||||||
TPMBackend *s = TPM_BACKEND(obj);
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
if (value == s->opened) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value && s->opened) {
|
|
||||||
error_set(errp, QERR_PERMISSION_DENIED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k->opened) {
|
|
||||||
k->opened(s, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!error_is_set(errp)) {
|
|
||||||
s->opened = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tpm_backend_instance_init(Object *obj)
|
|
||||||
{
|
|
||||||
object_property_add_bool(obj, "opened",
|
|
||||||
tpm_backend_prop_get_opened,
|
|
||||||
tpm_backend_prop_set_opened,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_thread_deliver_request(TPMBackendThread *tbt)
|
|
||||||
{
|
|
||||||
g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_PROCESS_CMD, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_thread_create(TPMBackendThread *tbt,
|
|
||||||
GFunc func, gpointer user_data)
|
|
||||||
{
|
|
||||||
if (!tbt->pool) {
|
|
||||||
tbt->pool = g_thread_pool_new(func, user_data, 1, TRUE, NULL);
|
|
||||||
g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_INIT, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_thread_end(TPMBackendThread *tbt)
|
|
||||||
{
|
|
||||||
if (tbt->pool) {
|
|
||||||
g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_END, NULL);
|
|
||||||
g_thread_pool_free(tbt->pool, FALSE, TRUE);
|
|
||||||
tbt->pool = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
|
|
||||||
GFunc func, gpointer user_data)
|
|
||||||
{
|
|
||||||
if (!tbt->pool) {
|
|
||||||
tpm_backend_thread_create(tbt, func, user_data);
|
|
||||||
} else {
|
|
||||||
g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_TPM_RESET,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const TypeInfo tpm_backend_info = {
|
|
||||||
.name = TYPE_TPM_BACKEND,
|
|
||||||
.parent = TYPE_OBJECT,
|
|
||||||
.instance_size = sizeof(TPMBackend),
|
|
||||||
.instance_init = tpm_backend_instance_init,
|
|
||||||
.class_size = sizeof(TPMBackendClass),
|
|
||||||
.abstract = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void register_types(void)
|
|
||||||
{
|
|
||||||
type_register_static(&tpm_backend_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_init(register_types);
|
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
#define BLK_MIG_FLAG_DEVICE_BLOCK 0x01
|
#define BLK_MIG_FLAG_DEVICE_BLOCK 0x01
|
||||||
#define BLK_MIG_FLAG_EOS 0x02
|
#define BLK_MIG_FLAG_EOS 0x02
|
||||||
#define BLK_MIG_FLAG_PROGRESS 0x04
|
#define BLK_MIG_FLAG_PROGRESS 0x04
|
||||||
#define BLK_MIG_FLAG_ZERO_BLOCK 0x08
|
|
||||||
|
|
||||||
#define MAX_IS_ALLOCATED_SEARCH 65536
|
#define MAX_IS_ALLOCATED_SEARCH 65536
|
||||||
|
|
||||||
@@ -44,25 +43,19 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct BlkMigDevState {
|
typedef struct BlkMigDevState {
|
||||||
/* Written during setup phase. Can be read without a lock. */
|
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
int shared_base;
|
|
||||||
int64_t total_sectors;
|
|
||||||
QSIMPLEQ_ENTRY(BlkMigDevState) entry;
|
|
||||||
|
|
||||||
/* Only used by migration thread. Does not need a lock. */
|
|
||||||
int bulk_completed;
|
int bulk_completed;
|
||||||
|
int shared_base;
|
||||||
int64_t cur_sector;
|
int64_t cur_sector;
|
||||||
int64_t cur_dirty;
|
int64_t cur_dirty;
|
||||||
|
|
||||||
/* Protected by block migration lock. */
|
|
||||||
unsigned long *aio_bitmap;
|
|
||||||
int64_t completed_sectors;
|
int64_t completed_sectors;
|
||||||
BdrvDirtyBitmap *dirty_bitmap;
|
int64_t total_sectors;
|
||||||
|
int64_t dirty;
|
||||||
|
QSIMPLEQ_ENTRY(BlkMigDevState) entry;
|
||||||
|
unsigned long *aio_bitmap;
|
||||||
} BlkMigDevState;
|
} BlkMigDevState;
|
||||||
|
|
||||||
typedef struct BlkMigBlock {
|
typedef struct BlkMigBlock {
|
||||||
/* Only used by migration thread. */
|
|
||||||
uint8_t *buf;
|
uint8_t *buf;
|
||||||
BlkMigDevState *bmds;
|
BlkMigDevState *bmds;
|
||||||
int64_t sector;
|
int64_t sector;
|
||||||
@@ -70,77 +63,39 @@ typedef struct BlkMigBlock {
|
|||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
QEMUIOVector qiov;
|
QEMUIOVector qiov;
|
||||||
BlockDriverAIOCB *aiocb;
|
BlockDriverAIOCB *aiocb;
|
||||||
|
|
||||||
/* Protected by block migration lock. */
|
|
||||||
int ret;
|
int ret;
|
||||||
QSIMPLEQ_ENTRY(BlkMigBlock) entry;
|
QSIMPLEQ_ENTRY(BlkMigBlock) entry;
|
||||||
} BlkMigBlock;
|
} BlkMigBlock;
|
||||||
|
|
||||||
typedef struct BlkMigState {
|
typedef struct BlkMigState {
|
||||||
/* Written during setup phase. Can be read without a lock. */
|
|
||||||
int blk_enable;
|
int blk_enable;
|
||||||
int shared_base;
|
int shared_base;
|
||||||
QSIMPLEQ_HEAD(bmds_list, BlkMigDevState) bmds_list;
|
QSIMPLEQ_HEAD(bmds_list, BlkMigDevState) bmds_list;
|
||||||
int64_t total_sector_sum;
|
|
||||||
bool zero_blocks;
|
|
||||||
|
|
||||||
/* Protected by lock. */
|
|
||||||
QSIMPLEQ_HEAD(blk_list, BlkMigBlock) blk_list;
|
QSIMPLEQ_HEAD(blk_list, BlkMigBlock) blk_list;
|
||||||
int submitted;
|
int submitted;
|
||||||
int read_done;
|
int read_done;
|
||||||
|
|
||||||
/* Only used by migration thread. Does not need a lock. */
|
|
||||||
int transferred;
|
int transferred;
|
||||||
|
int64_t total_sector_sum;
|
||||||
int prev_progress;
|
int prev_progress;
|
||||||
int bulk_completed;
|
int bulk_completed;
|
||||||
|
long double prev_time_offset;
|
||||||
/* Lock must be taken _inside_ the iothread lock. */
|
|
||||||
QemuMutex lock;
|
|
||||||
} BlkMigState;
|
} BlkMigState;
|
||||||
|
|
||||||
static BlkMigState block_mig_state;
|
static BlkMigState block_mig_state;
|
||||||
|
|
||||||
static void blk_mig_lock(void)
|
|
||||||
{
|
|
||||||
qemu_mutex_lock(&block_mig_state.lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void blk_mig_unlock(void)
|
|
||||||
{
|
|
||||||
qemu_mutex_unlock(&block_mig_state.lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Must run outside of the iothread lock during the bulk phase,
|
|
||||||
* or the VM will stall.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void blk_send(QEMUFile *f, BlkMigBlock * blk)
|
static void blk_send(QEMUFile *f, BlkMigBlock * blk)
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
uint64_t flags = BLK_MIG_FLAG_DEVICE_BLOCK;
|
|
||||||
|
|
||||||
if (block_mig_state.zero_blocks &&
|
|
||||||
buffer_is_zero(blk->buf, BLOCK_SIZE)) {
|
|
||||||
flags |= BLK_MIG_FLAG_ZERO_BLOCK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* sector number and flags */
|
/* sector number and flags */
|
||||||
qemu_put_be64(f, (blk->sector << BDRV_SECTOR_BITS)
|
qemu_put_be64(f, (blk->sector << BDRV_SECTOR_BITS)
|
||||||
| flags);
|
| BLK_MIG_FLAG_DEVICE_BLOCK);
|
||||||
|
|
||||||
/* device name */
|
/* device name */
|
||||||
len = strlen(blk->bmds->bs->device_name);
|
len = strlen(blk->bmds->bs->device_name);
|
||||||
qemu_put_byte(f, len);
|
qemu_put_byte(f, len);
|
||||||
qemu_put_buffer(f, (uint8_t *)blk->bmds->bs->device_name, len);
|
qemu_put_buffer(f, (uint8_t *)blk->bmds->bs->device_name, len);
|
||||||
|
|
||||||
/* if a block is zero we need to flush here since the network
|
|
||||||
* bandwidth is now a lot higher than the storage device bandwidth.
|
|
||||||
* thus if we queue zero blocks we slow down the migration */
|
|
||||||
if (flags & BLK_MIG_FLAG_ZERO_BLOCK) {
|
|
||||||
qemu_fflush(f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_put_buffer(f, blk->buf, BLOCK_SIZE);
|
qemu_put_buffer(f, blk->buf, BLOCK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,11 +109,9 @@ uint64_t blk_mig_bytes_transferred(void)
|
|||||||
BlkMigDevState *bmds;
|
BlkMigDevState *bmds;
|
||||||
uint64_t sum = 0;
|
uint64_t sum = 0;
|
||||||
|
|
||||||
blk_mig_lock();
|
|
||||||
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
||||||
sum += bmds->completed_sectors;
|
sum += bmds->completed_sectors;
|
||||||
}
|
}
|
||||||
blk_mig_unlock();
|
|
||||||
return sum << BDRV_SECTOR_BITS;
|
return sum << BDRV_SECTOR_BITS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,9 +131,6 @@ uint64_t blk_mig_bytes_total(void)
|
|||||||
return sum << BDRV_SECTOR_BITS;
|
return sum << BDRV_SECTOR_BITS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Called with migration lock held. */
|
|
||||||
|
|
||||||
static int bmds_aio_inflight(BlkMigDevState *bmds, int64_t sector)
|
static int bmds_aio_inflight(BlkMigDevState *bmds, int64_t sector)
|
||||||
{
|
{
|
||||||
int64_t chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK;
|
int64_t chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK;
|
||||||
@@ -193,8 +143,6 @@ static int bmds_aio_inflight(BlkMigDevState *bmds, int64_t sector)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with migration lock held. */
|
|
||||||
|
|
||||||
static void bmds_set_aio_inflight(BlkMigDevState *bmds, int64_t sector_num,
|
static void bmds_set_aio_inflight(BlkMigDevState *bmds, int64_t sector_num,
|
||||||
int nb_sectors, int set)
|
int nb_sectors, int set)
|
||||||
{
|
{
|
||||||
@@ -229,26 +177,23 @@ static void alloc_aio_bitmap(BlkMigDevState *bmds)
|
|||||||
bmds->aio_bitmap = g_malloc0(bitmap_size);
|
bmds->aio_bitmap = g_malloc0(bitmap_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Never hold migration lock when yielding to the main loop! */
|
|
||||||
|
|
||||||
static void blk_mig_read_cb(void *opaque, int ret)
|
static void blk_mig_read_cb(void *opaque, int ret)
|
||||||
{
|
{
|
||||||
|
long double curr_time = qemu_get_clock_ns(rt_clock);
|
||||||
BlkMigBlock *blk = opaque;
|
BlkMigBlock *blk = opaque;
|
||||||
|
|
||||||
blk_mig_lock();
|
|
||||||
blk->ret = ret;
|
blk->ret = ret;
|
||||||
|
|
||||||
|
block_mig_state.prev_time_offset = curr_time;
|
||||||
|
|
||||||
QSIMPLEQ_INSERT_TAIL(&block_mig_state.blk_list, blk, entry);
|
QSIMPLEQ_INSERT_TAIL(&block_mig_state.blk_list, blk, entry);
|
||||||
bmds_set_aio_inflight(blk->bmds, blk->sector, blk->nr_sectors, 0);
|
bmds_set_aio_inflight(blk->bmds, blk->sector, blk->nr_sectors, 0);
|
||||||
|
|
||||||
block_mig_state.submitted--;
|
block_mig_state.submitted--;
|
||||||
block_mig_state.read_done++;
|
block_mig_state.read_done++;
|
||||||
assert(block_mig_state.submitted >= 0);
|
assert(block_mig_state.submitted >= 0);
|
||||||
blk_mig_unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with no lock taken. */
|
|
||||||
|
|
||||||
static int mig_save_device_bulk(QEMUFile *f, BlkMigDevState *bmds)
|
static int mig_save_device_bulk(QEMUFile *f, BlkMigDevState *bmds)
|
||||||
{
|
{
|
||||||
int64_t total_sectors = bmds->total_sectors;
|
int64_t total_sectors = bmds->total_sectors;
|
||||||
@@ -258,13 +203,11 @@ static int mig_save_device_bulk(QEMUFile *f, BlkMigDevState *bmds)
|
|||||||
int nr_sectors;
|
int nr_sectors;
|
||||||
|
|
||||||
if (bmds->shared_base) {
|
if (bmds->shared_base) {
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
while (cur_sector < total_sectors &&
|
while (cur_sector < total_sectors &&
|
||||||
!bdrv_is_allocated(bs, cur_sector, MAX_IS_ALLOCATED_SEARCH,
|
!bdrv_is_allocated(bs, cur_sector, MAX_IS_ALLOCATED_SEARCH,
|
||||||
&nr_sectors)) {
|
&nr_sectors)) {
|
||||||
cur_sector += nr_sectors;
|
cur_sector += nr_sectors;
|
||||||
}
|
}
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cur_sector >= total_sectors) {
|
if (cur_sector >= total_sectors) {
|
||||||
@@ -293,38 +236,26 @@ static int mig_save_device_bulk(QEMUFile *f, BlkMigDevState *bmds)
|
|||||||
blk->iov.iov_len = nr_sectors * BDRV_SECTOR_SIZE;
|
blk->iov.iov_len = nr_sectors * BDRV_SECTOR_SIZE;
|
||||||
qemu_iovec_init_external(&blk->qiov, &blk->iov, 1);
|
qemu_iovec_init_external(&blk->qiov, &blk->iov, 1);
|
||||||
|
|
||||||
blk_mig_lock();
|
if (block_mig_state.submitted == 0) {
|
||||||
block_mig_state.submitted++;
|
block_mig_state.prev_time_offset = qemu_get_clock_ns(rt_clock);
|
||||||
blk_mig_unlock();
|
}
|
||||||
|
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov,
|
blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov,
|
||||||
nr_sectors, blk_mig_read_cb, blk);
|
nr_sectors, blk_mig_read_cb, blk);
|
||||||
|
block_mig_state.submitted++;
|
||||||
|
|
||||||
bdrv_reset_dirty(bs, cur_sector, nr_sectors);
|
bdrv_reset_dirty(bs, cur_sector, nr_sectors);
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
|
|
||||||
bmds->cur_sector = cur_sector + nr_sectors;
|
bmds->cur_sector = cur_sector + nr_sectors;
|
||||||
|
|
||||||
return (bmds->cur_sector >= total_sectors);
|
return (bmds->cur_sector >= total_sectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
static void set_dirty_tracking(int enable)
|
||||||
|
|
||||||
static void set_dirty_tracking(void)
|
|
||||||
{
|
{
|
||||||
BlkMigDevState *bmds;
|
BlkMigDevState *bmds;
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
||||||
bmds->dirty_bitmap = bdrv_create_dirty_bitmap(bmds->bs, BLOCK_SIZE);
|
bdrv_set_dirty_tracking(bmds->bs, enable ? BLOCK_SIZE : 0);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unset_dirty_tracking(void)
|
|
||||||
{
|
|
||||||
BlkMigDevState *bmds;
|
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
|
||||||
bdrv_release_dirty_bitmap(bmds->bs, bmds->dirty_bitmap);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,8 +277,8 @@ static void init_blk_migration_it(void *opaque, BlockDriverState *bs)
|
|||||||
bmds->completed_sectors = 0;
|
bmds->completed_sectors = 0;
|
||||||
bmds->shared_base = block_mig_state.shared_base;
|
bmds->shared_base = block_mig_state.shared_base;
|
||||||
alloc_aio_bitmap(bmds);
|
alloc_aio_bitmap(bmds);
|
||||||
|
drive_get_ref(drive_get_by_blockdev(bs));
|
||||||
bdrv_set_in_use(bs, 1);
|
bdrv_set_in_use(bs, 1);
|
||||||
bdrv_ref(bs);
|
|
||||||
|
|
||||||
block_mig_state.total_sector_sum += sectors;
|
block_mig_state.total_sector_sum += sectors;
|
||||||
|
|
||||||
@@ -370,13 +301,10 @@ static void init_blk_migration(QEMUFile *f)
|
|||||||
block_mig_state.total_sector_sum = 0;
|
block_mig_state.total_sector_sum = 0;
|
||||||
block_mig_state.prev_progress = -1;
|
block_mig_state.prev_progress = -1;
|
||||||
block_mig_state.bulk_completed = 0;
|
block_mig_state.bulk_completed = 0;
|
||||||
block_mig_state.zero_blocks = migrate_zero_blocks();
|
|
||||||
|
|
||||||
bdrv_iterate(init_blk_migration_it, NULL);
|
bdrv_iterate(init_blk_migration_it, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with no lock taken. */
|
|
||||||
|
|
||||||
static int blk_mig_save_bulked_block(QEMUFile *f)
|
static int blk_mig_save_bulked_block(QEMUFile *f)
|
||||||
{
|
{
|
||||||
int64_t completed_sector_sum = 0;
|
int64_t completed_sector_sum = 0;
|
||||||
@@ -423,8 +351,6 @@ static void blk_mig_reset_dirty_cursor(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
|
||||||
|
|
||||||
static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds,
|
static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds,
|
||||||
int is_async)
|
int is_async)
|
||||||
{
|
{
|
||||||
@@ -435,14 +361,10 @@ static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds,
|
|||||||
int ret = -EIO;
|
int ret = -EIO;
|
||||||
|
|
||||||
for (sector = bmds->cur_dirty; sector < bmds->total_sectors;) {
|
for (sector = bmds->cur_dirty; sector < bmds->total_sectors;) {
|
||||||
blk_mig_lock();
|
|
||||||
if (bmds_aio_inflight(bmds, sector)) {
|
if (bmds_aio_inflight(bmds, sector)) {
|
||||||
blk_mig_unlock();
|
|
||||||
bdrv_drain_all();
|
bdrv_drain_all();
|
||||||
} else {
|
|
||||||
blk_mig_unlock();
|
|
||||||
}
|
}
|
||||||
if (bdrv_get_dirty(bmds->bs, bmds->dirty_bitmap, sector)) {
|
if (bdrv_get_dirty(bmds->bs, sector)) {
|
||||||
|
|
||||||
if (total_sectors - sector < BDRV_SECTORS_PER_DIRTY_CHUNK) {
|
if (total_sectors - sector < BDRV_SECTORS_PER_DIRTY_CHUNK) {
|
||||||
nr_sectors = total_sectors - sector;
|
nr_sectors = total_sectors - sector;
|
||||||
@@ -460,13 +382,14 @@ static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds,
|
|||||||
blk->iov.iov_len = nr_sectors * BDRV_SECTOR_SIZE;
|
blk->iov.iov_len = nr_sectors * BDRV_SECTOR_SIZE;
|
||||||
qemu_iovec_init_external(&blk->qiov, &blk->iov, 1);
|
qemu_iovec_init_external(&blk->qiov, &blk->iov, 1);
|
||||||
|
|
||||||
|
if (block_mig_state.submitted == 0) {
|
||||||
|
block_mig_state.prev_time_offset = qemu_get_clock_ns(rt_clock);
|
||||||
|
}
|
||||||
|
|
||||||
blk->aiocb = bdrv_aio_readv(bmds->bs, sector, &blk->qiov,
|
blk->aiocb = bdrv_aio_readv(bmds->bs, sector, &blk->qiov,
|
||||||
nr_sectors, blk_mig_read_cb, blk);
|
nr_sectors, blk_mig_read_cb, blk);
|
||||||
|
|
||||||
blk_mig_lock();
|
|
||||||
block_mig_state.submitted++;
|
block_mig_state.submitted++;
|
||||||
bmds_set_aio_inflight(bmds, sector, nr_sectors, 1);
|
bmds_set_aio_inflight(bmds, sector, nr_sectors, 1);
|
||||||
blk_mig_unlock();
|
|
||||||
} else {
|
} else {
|
||||||
ret = bdrv_read(bmds->bs, sector, blk->buf, nr_sectors);
|
ret = bdrv_read(bmds->bs, sector, blk->buf, nr_sectors);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -494,9 +417,7 @@ error:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken.
|
/* return value:
|
||||||
*
|
|
||||||
* return value:
|
|
||||||
* 0: too much data for max_downtime
|
* 0: too much data for max_downtime
|
||||||
* 1: few enough data for max_downtime
|
* 1: few enough data for max_downtime
|
||||||
*/
|
*/
|
||||||
@@ -515,8 +436,6 @@ static int blk_mig_save_dirty_block(QEMUFile *f, int is_async)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with no locks taken. */
|
|
||||||
|
|
||||||
static int flush_blks(QEMUFile *f)
|
static int flush_blks(QEMUFile *f)
|
||||||
{
|
{
|
||||||
BlkMigBlock *blk;
|
BlkMigBlock *blk;
|
||||||
@@ -526,7 +445,6 @@ static int flush_blks(QEMUFile *f)
|
|||||||
__FUNCTION__, block_mig_state.submitted, block_mig_state.read_done,
|
__FUNCTION__, block_mig_state.submitted, block_mig_state.read_done,
|
||||||
block_mig_state.transferred);
|
block_mig_state.transferred);
|
||||||
|
|
||||||
blk_mig_lock();
|
|
||||||
while ((blk = QSIMPLEQ_FIRST(&block_mig_state.blk_list)) != NULL) {
|
while ((blk = QSIMPLEQ_FIRST(&block_mig_state.blk_list)) != NULL) {
|
||||||
if (qemu_file_rate_limit(f)) {
|
if (qemu_file_rate_limit(f)) {
|
||||||
break;
|
break;
|
||||||
@@ -535,12 +453,9 @@ static int flush_blks(QEMUFile *f)
|
|||||||
ret = blk->ret;
|
ret = blk->ret;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
blk_send(f, blk);
|
||||||
|
|
||||||
QSIMPLEQ_REMOVE_HEAD(&block_mig_state.blk_list, entry);
|
QSIMPLEQ_REMOVE_HEAD(&block_mig_state.blk_list, entry);
|
||||||
blk_mig_unlock();
|
|
||||||
blk_send(f, blk);
|
|
||||||
blk_mig_lock();
|
|
||||||
|
|
||||||
g_free(blk->buf);
|
g_free(blk->buf);
|
||||||
g_free(blk);
|
g_free(blk);
|
||||||
|
|
||||||
@@ -548,7 +463,6 @@ static int flush_blks(QEMUFile *f)
|
|||||||
block_mig_state.transferred++;
|
block_mig_state.transferred++;
|
||||||
assert(block_mig_state.read_done >= 0);
|
assert(block_mig_state.read_done >= 0);
|
||||||
}
|
}
|
||||||
blk_mig_unlock();
|
|
||||||
|
|
||||||
DPRINTF("%s Exit submitted %d read_done %d transferred %d\n", __FUNCTION__,
|
DPRINTF("%s Exit submitted %d read_done %d transferred %d\n", __FUNCTION__,
|
||||||
block_mig_state.submitted, block_mig_state.read_done,
|
block_mig_state.submitted, block_mig_state.read_done,
|
||||||
@@ -556,22 +470,18 @@ static int flush_blks(QEMUFile *f)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
|
||||||
|
|
||||||
static int64_t get_remaining_dirty(void)
|
static int64_t get_remaining_dirty(void)
|
||||||
{
|
{
|
||||||
BlkMigDevState *bmds;
|
BlkMigDevState *bmds;
|
||||||
int64_t dirty = 0;
|
int64_t dirty = 0;
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
||||||
dirty += bdrv_get_dirty_count(bmds->bs, bmds->dirty_bitmap);
|
dirty += bdrv_get_dirty_count(bmds->bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dirty << BDRV_SECTOR_BITS;
|
return dirty << BDRV_SECTOR_BITS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
|
||||||
|
|
||||||
static void blk_mig_cleanup(void)
|
static void blk_mig_cleanup(void)
|
||||||
{
|
{
|
||||||
BlkMigDevState *bmds;
|
BlkMigDevState *bmds;
|
||||||
@@ -579,13 +489,12 @@ static void blk_mig_cleanup(void)
|
|||||||
|
|
||||||
bdrv_drain_all();
|
bdrv_drain_all();
|
||||||
|
|
||||||
unset_dirty_tracking();
|
set_dirty_tracking(0);
|
||||||
|
|
||||||
blk_mig_lock();
|
|
||||||
while ((bmds = QSIMPLEQ_FIRST(&block_mig_state.bmds_list)) != NULL) {
|
while ((bmds = QSIMPLEQ_FIRST(&block_mig_state.bmds_list)) != NULL) {
|
||||||
QSIMPLEQ_REMOVE_HEAD(&block_mig_state.bmds_list, entry);
|
QSIMPLEQ_REMOVE_HEAD(&block_mig_state.bmds_list, entry);
|
||||||
bdrv_set_in_use(bmds->bs, 0);
|
bdrv_set_in_use(bmds->bs, 0);
|
||||||
bdrv_unref(bmds->bs);
|
drive_put_ref(drive_get_by_blockdev(bmds->bs));
|
||||||
g_free(bmds->aio_bitmap);
|
g_free(bmds->aio_bitmap);
|
||||||
g_free(bmds);
|
g_free(bmds);
|
||||||
}
|
}
|
||||||
@@ -595,7 +504,6 @@ static void blk_mig_cleanup(void)
|
|||||||
g_free(blk->buf);
|
g_free(blk->buf);
|
||||||
g_free(blk);
|
g_free(blk);
|
||||||
}
|
}
|
||||||
blk_mig_unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void block_migration_cancel(void *opaque)
|
static void block_migration_cancel(void *opaque)
|
||||||
@@ -610,18 +518,22 @@ static int block_save_setup(QEMUFile *f, void *opaque)
|
|||||||
DPRINTF("Enter save live setup submitted %d transferred %d\n",
|
DPRINTF("Enter save live setup submitted %d transferred %d\n",
|
||||||
block_mig_state.submitted, block_mig_state.transferred);
|
block_mig_state.submitted, block_mig_state.transferred);
|
||||||
|
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
init_blk_migration(f);
|
init_blk_migration(f);
|
||||||
|
|
||||||
/* start track dirty blocks */
|
/* start track dirty blocks */
|
||||||
set_dirty_tracking();
|
set_dirty_tracking(1);
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
|
|
||||||
ret = flush_blks(f);
|
ret = flush_blks(f);
|
||||||
|
if (ret) {
|
||||||
|
blk_mig_cleanup();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
blk_mig_reset_dirty_cursor();
|
blk_mig_reset_dirty_cursor();
|
||||||
|
|
||||||
qemu_put_be64(f, BLK_MIG_FLAG_EOS);
|
qemu_put_be64(f, BLK_MIG_FLAG_EOS);
|
||||||
|
|
||||||
return ret;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int block_save_iterate(QEMUFile *f, void *opaque)
|
static int block_save_iterate(QEMUFile *f, void *opaque)
|
||||||
@@ -634,54 +546,46 @@ static int block_save_iterate(QEMUFile *f, void *opaque)
|
|||||||
|
|
||||||
ret = flush_blks(f);
|
ret = flush_blks(f);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
blk_mig_cleanup();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
blk_mig_reset_dirty_cursor();
|
blk_mig_reset_dirty_cursor();
|
||||||
|
|
||||||
/* control the rate of transfer */
|
/* control the rate of transfer */
|
||||||
blk_mig_lock();
|
|
||||||
while ((block_mig_state.submitted +
|
while ((block_mig_state.submitted +
|
||||||
block_mig_state.read_done) * BLOCK_SIZE <
|
block_mig_state.read_done) * BLOCK_SIZE <
|
||||||
qemu_file_get_rate_limit(f)) {
|
qemu_file_get_rate_limit(f)) {
|
||||||
blk_mig_unlock();
|
|
||||||
if (block_mig_state.bulk_completed == 0) {
|
if (block_mig_state.bulk_completed == 0) {
|
||||||
/* first finish the bulk phase */
|
/* first finish the bulk phase */
|
||||||
if (blk_mig_save_bulked_block(f) == 0) {
|
if (blk_mig_save_bulked_block(f) == 0) {
|
||||||
/* finished saving bulk on all devices */
|
/* finished saving bulk on all devices */
|
||||||
block_mig_state.bulk_completed = 1;
|
block_mig_state.bulk_completed = 1;
|
||||||
}
|
}
|
||||||
ret = 0;
|
|
||||||
} else {
|
} else {
|
||||||
/* Always called with iothread lock taken for
|
|
||||||
* simplicity, block_save_complete also calls it.
|
|
||||||
*/
|
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
ret = blk_mig_save_dirty_block(f, 1);
|
ret = blk_mig_save_dirty_block(f, 1);
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
}
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
blk_mig_lock();
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
/* no more dirty blocks */
|
/* no more dirty blocks */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blk_mig_unlock();
|
}
|
||||||
|
if (ret < 0) {
|
||||||
|
blk_mig_cleanup();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
ret = flush_blks(f);
|
ret = flush_blks(f);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
blk_mig_cleanup();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_put_be64(f, BLK_MIG_FLAG_EOS);
|
qemu_put_be64(f, BLK_MIG_FLAG_EOS);
|
||||||
|
|
||||||
return qemu_ftell(f) - last_ftell;
|
return qemu_ftell(f) - last_ftell;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
|
||||||
|
|
||||||
static int block_save_complete(QEMUFile *f, void *opaque)
|
static int block_save_complete(QEMUFile *f, void *opaque)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
@@ -691,6 +595,7 @@ static int block_save_complete(QEMUFile *f, void *opaque)
|
|||||||
|
|
||||||
ret = flush_blks(f);
|
ret = flush_blks(f);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
blk_mig_cleanup();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,17 +603,16 @@ static int block_save_complete(QEMUFile *f, void *opaque)
|
|||||||
|
|
||||||
/* we know for sure that save bulk is completed and
|
/* we know for sure that save bulk is completed and
|
||||||
all async read completed */
|
all async read completed */
|
||||||
blk_mig_lock();
|
|
||||||
assert(block_mig_state.submitted == 0);
|
assert(block_mig_state.submitted == 0);
|
||||||
blk_mig_unlock();
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
ret = blk_mig_save_dirty_block(f, 0);
|
ret = blk_mig_save_dirty_block(f, 0);
|
||||||
|
} while (ret == 0);
|
||||||
|
|
||||||
|
blk_mig_cleanup();
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
} while (ret == 0);
|
|
||||||
|
|
||||||
/* report completion */
|
/* report completion */
|
||||||
qemu_put_be64(f, (100 << BDRV_SECTOR_BITS) | BLK_MIG_FLAG_PROGRESS);
|
qemu_put_be64(f, (100 << BDRV_SECTOR_BITS) | BLK_MIG_FLAG_PROGRESS);
|
||||||
|
|
||||||
@@ -716,18 +620,13 @@ static int block_save_complete(QEMUFile *f, void *opaque)
|
|||||||
|
|
||||||
qemu_put_be64(f, BLK_MIG_FLAG_EOS);
|
qemu_put_be64(f, BLK_MIG_FLAG_EOS);
|
||||||
|
|
||||||
blk_mig_cleanup();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t block_save_pending(QEMUFile *f, void *opaque, uint64_t max_size)
|
static uint64_t block_save_pending(QEMUFile *f, void *opaque, uint64_t max_size)
|
||||||
{
|
{
|
||||||
/* Estimate pending number of bytes to send */
|
/* Estimate pending number of bytes to send */
|
||||||
uint64_t pending;
|
uint64_t pending = get_remaining_dirty() +
|
||||||
|
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
blk_mig_lock();
|
|
||||||
pending = get_remaining_dirty() +
|
|
||||||
block_mig_state.submitted * BLOCK_SIZE +
|
block_mig_state.submitted * BLOCK_SIZE +
|
||||||
block_mig_state.read_done * BLOCK_SIZE;
|
block_mig_state.read_done * BLOCK_SIZE;
|
||||||
|
|
||||||
@@ -735,8 +634,6 @@ static uint64_t block_save_pending(QEMUFile *f, void *opaque, uint64_t max_size)
|
|||||||
if (pending == 0 && !block_mig_state.bulk_completed) {
|
if (pending == 0 && !block_mig_state.bulk_completed) {
|
||||||
pending = BLOCK_SIZE;
|
pending = BLOCK_SIZE;
|
||||||
}
|
}
|
||||||
blk_mig_unlock();
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
|
|
||||||
DPRINTF("Enter save live pending %" PRIu64 "\n", pending);
|
DPRINTF("Enter save live pending %" PRIu64 "\n", pending);
|
||||||
return pending;
|
return pending;
|
||||||
@@ -789,16 +686,12 @@ static int block_load(QEMUFile *f, void *opaque, int version_id)
|
|||||||
nr_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK;
|
nr_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & BLK_MIG_FLAG_ZERO_BLOCK) {
|
|
||||||
ret = bdrv_write_zeroes(bs, addr, nr_sectors,
|
|
||||||
BDRV_REQ_MAY_UNMAP);
|
|
||||||
} else {
|
|
||||||
buf = g_malloc(BLOCK_SIZE);
|
buf = g_malloc(BLOCK_SIZE);
|
||||||
|
|
||||||
qemu_get_buffer(f, buf, BLOCK_SIZE);
|
qemu_get_buffer(f, buf, BLOCK_SIZE);
|
||||||
ret = bdrv_write(bs, addr, buf, nr_sectors);
|
ret = bdrv_write(bs, addr, buf, nr_sectors);
|
||||||
g_free(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
g_free(buf);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -852,7 +745,6 @@ void blk_mig_init(void)
|
|||||||
{
|
{
|
||||||
QSIMPLEQ_INIT(&block_mig_state.bmds_list);
|
QSIMPLEQ_INIT(&block_mig_state.bmds_list);
|
||||||
QSIMPLEQ_INIT(&block_mig_state.blk_list);
|
QSIMPLEQ_INIT(&block_mig_state.blk_list);
|
||||||
qemu_mutex_init(&block_mig_state.lock);
|
|
||||||
|
|
||||||
register_savevm_live(NULL, "block", 0, 1, &savevm_block_handlers,
|
register_savevm_live(NULL, "block", 0, 1, &savevm_block_handlers,
|
||||||
&block_mig_state);
|
&block_mig_state);
|
||||||
|
|||||||
@@ -1,39 +1,24 @@
|
|||||||
block-obj-y += raw_bsd.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vvfat.o
|
block-obj-y += raw.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.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 += 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.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
|
||||||
block-obj-y += qed-check.o
|
block-obj-y += qed-check.o
|
||||||
block-obj-$(CONFIG_VHDX) += vhdx.o vhdx-endian.o vhdx-log.o
|
|
||||||
block-obj-$(CONFIG_QUORUM) += quorum.o
|
|
||||||
block-obj-y += parallels.o blkdebug.o blkverify.o
|
block-obj-y += parallels.o blkdebug.o blkverify.o
|
||||||
block-obj-y += snapshot.o qapi.o
|
|
||||||
block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
|
block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
|
||||||
block-obj-$(CONFIG_POSIX) += raw-posix.o
|
block-obj-$(CONFIG_POSIX) += raw-posix.o
|
||||||
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
||||||
|
|
||||||
ifeq ($(CONFIG_POSIX),y)
|
ifeq ($(CONFIG_POSIX),y)
|
||||||
block-obj-y += nbd.o nbd-client.o sheepdog.o
|
block-obj-y += nbd.o sheepdog.o
|
||||||
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
|
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
|
||||||
block-obj-$(CONFIG_LIBNFS) += nfs.o
|
|
||||||
block-obj-$(CONFIG_CURL) += curl.o
|
block-obj-$(CONFIG_CURL) += curl.o
|
||||||
block-obj-$(CONFIG_RBD) += rbd.o
|
block-obj-$(CONFIG_RBD) += rbd.o
|
||||||
block-obj-$(CONFIG_GLUSTERFS) += gluster.o
|
block-obj-$(CONFIG_GLUSTERFS) += gluster.o
|
||||||
block-obj-$(CONFIG_LIBSSH2) += ssh.o
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
common-obj-y += stream.o
|
common-obj-y += stream.o
|
||||||
common-obj-y += commit.o
|
common-obj-y += commit.o
|
||||||
common-obj-y += mirror.o
|
common-obj-y += mirror.o
|
||||||
common-obj-y += backup.o
|
block-obj-y += dictzip.o
|
||||||
|
block-obj-y += tar.o
|
||||||
|
|
||||||
iscsi.o-cflags := $(LIBISCSI_CFLAGS)
|
$(obj)/curl.o: QEMU_CFLAGS+=$(CURL_CFLAGS)
|
||||||
iscsi.o-libs := $(LIBISCSI_LIBS)
|
|
||||||
curl.o-cflags := $(CURL_CFLAGS)
|
|
||||||
curl.o-libs := $(CURL_LIBS)
|
|
||||||
rbd.o-cflags := $(RBD_CFLAGS)
|
|
||||||
rbd.o-libs := $(RBD_LIBS)
|
|
||||||
gluster.o-cflags := $(GLUSTERFS_CFLAGS)
|
|
||||||
gluster.o-libs := $(GLUSTERFS_LIBS)
|
|
||||||
ssh.o-cflags := $(LIBSSH2_CFLAGS)
|
|
||||||
ssh.o-libs := $(LIBSSH2_LIBS)
|
|
||||||
qcow.o-libs := -lz
|
|
||||||
linux-aio.o-libs := -laio
|
|
||||||
|
|||||||
392
block/backup.c
392
block/backup.c
@@ -1,392 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU backup
|
|
||||||
*
|
|
||||||
* Copyright (C) 2013 Proxmox Server Solutions
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Dietmar Maurer (dietmar@proxmox.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 <stdio.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "trace.h"
|
|
||||||
#include "block/block.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "block/blockjob.h"
|
|
||||||
#include "qemu/ratelimit.h"
|
|
||||||
|
|
||||||
#define BACKUP_CLUSTER_BITS 16
|
|
||||||
#define BACKUP_CLUSTER_SIZE (1 << BACKUP_CLUSTER_BITS)
|
|
||||||
#define BACKUP_SECTORS_PER_CLUSTER (BACKUP_CLUSTER_SIZE / BDRV_SECTOR_SIZE)
|
|
||||||
|
|
||||||
#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;
|
|
||||||
BlockDriverState *target;
|
|
||||||
MirrorSyncMode sync_mode;
|
|
||||||
RateLimit limit;
|
|
||||||
BlockdevOnError on_source_error;
|
|
||||||
BlockdevOnError on_target_error;
|
|
||||||
CoRwlock flush_rwlock;
|
|
||||||
uint64_t sectors_read;
|
|
||||||
HBitmap *bitmap;
|
|
||||||
QLIST_HEAD(, CowRequest) inflight_reqs;
|
|
||||||
} BackupBlockJob;
|
|
||||||
|
|
||||||
/* 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,
|
|
||||||
int64_t end)
|
|
||||||
{
|
|
||||||
CowRequest *req;
|
|
||||||
bool retry;
|
|
||||||
|
|
||||||
do {
|
|
||||||
retry = false;
|
|
||||||
QLIST_FOREACH(req, &job->inflight_reqs, list) {
|
|
||||||
if (end > req->start && start < req->end) {
|
|
||||||
qemu_co_queue_wait(&req->wait_queue);
|
|
||||||
retry = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (retry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep track of an in-flight request */
|
|
||||||
static void cow_request_begin(CowRequest *req, BackupBlockJob *job,
|
|
||||||
int64_t start, int64_t end)
|
|
||||||
{
|
|
||||||
req->start = start;
|
|
||||||
req->end = end;
|
|
||||||
qemu_co_queue_init(&req->wait_queue);
|
|
||||||
QLIST_INSERT_HEAD(&job->inflight_reqs, req, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forget about a completed request */
|
|
||||||
static void cow_request_end(CowRequest *req)
|
|
||||||
{
|
|
||||||
QLIST_REMOVE(req, list);
|
|
||||||
qemu_co_queue_restart_all(&req->wait_queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn backup_do_cow(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors,
|
|
||||||
bool *error_is_read)
|
|
||||||
{
|
|
||||||
BackupBlockJob *job = (BackupBlockJob *)bs->job;
|
|
||||||
CowRequest cow_request;
|
|
||||||
struct iovec iov;
|
|
||||||
QEMUIOVector bounce_qiov;
|
|
||||||
void *bounce_buffer = NULL;
|
|
||||||
int ret = 0;
|
|
||||||
int64_t start, end;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
qemu_co_rwlock_rdlock(&job->flush_rwlock);
|
|
||||||
|
|
||||||
start = sector_num / BACKUP_SECTORS_PER_CLUSTER;
|
|
||||||
end = DIV_ROUND_UP(sector_num + nb_sectors, BACKUP_SECTORS_PER_CLUSTER);
|
|
||||||
|
|
||||||
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++) {
|
|
||||||
if (hbitmap_get(job->bitmap, start)) {
|
|
||||||
trace_backup_do_cow_skip(job, start);
|
|
||||||
continue; /* already copied */
|
|
||||||
}
|
|
||||||
|
|
||||||
trace_backup_do_cow_process(job, start);
|
|
||||||
|
|
||||||
n = MIN(BACKUP_SECTORS_PER_CLUSTER,
|
|
||||||
job->common.len / BDRV_SECTOR_SIZE -
|
|
||||||
start * BACKUP_SECTORS_PER_CLUSTER);
|
|
||||||
|
|
||||||
if (!bounce_buffer) {
|
|
||||||
bounce_buffer = qemu_blockalign(bs, BACKUP_CLUSTER_SIZE);
|
|
||||||
}
|
|
||||||
iov.iov_base = bounce_buffer;
|
|
||||||
iov.iov_len = n * BDRV_SECTOR_SIZE;
|
|
||||||
qemu_iovec_init_external(&bounce_qiov, &iov, 1);
|
|
||||||
|
|
||||||
ret = bdrv_co_readv(bs, start * BACKUP_SECTORS_PER_CLUSTER, n,
|
|
||||||
&bounce_qiov);
|
|
||||||
if (ret < 0) {
|
|
||||||
trace_backup_do_cow_read_fail(job, start, ret);
|
|
||||||
if (error_is_read) {
|
|
||||||
*error_is_read = true;
|
|
||||||
}
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer_is_zero(iov.iov_base, iov.iov_len)) {
|
|
||||||
ret = bdrv_co_write_zeroes(job->target,
|
|
||||||
start * BACKUP_SECTORS_PER_CLUSTER,
|
|
||||||
n, BDRV_REQ_MAY_UNMAP);
|
|
||||||
} else {
|
|
||||||
ret = bdrv_co_writev(job->target,
|
|
||||||
start * BACKUP_SECTORS_PER_CLUSTER, n,
|
|
||||||
&bounce_qiov);
|
|
||||||
}
|
|
||||||
if (ret < 0) {
|
|
||||||
trace_backup_do_cow_write_fail(job, start, ret);
|
|
||||||
if (error_is_read) {
|
|
||||||
*error_is_read = false;
|
|
||||||
}
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
hbitmap_set(job->bitmap, start, 1);
|
|
||||||
|
|
||||||
/* 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->sectors_read += n;
|
|
||||||
job->common.offset += n * BDRV_SECTOR_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (bounce_buffer) {
|
|
||||||
qemu_vfree(bounce_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
cow_request_end(&cow_request);
|
|
||||||
|
|
||||||
trace_backup_do_cow_return(job, sector_num, nb_sectors, ret);
|
|
||||||
|
|
||||||
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn backup_before_write_notify(
|
|
||||||
NotifierWithReturn *notifier,
|
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
BdrvTrackedRequest *req = opaque;
|
|
||||||
int64_t sector_num = req->offset >> BDRV_SECTOR_BITS;
|
|
||||||
int nb_sectors = req->bytes >> BDRV_SECTOR_BITS;
|
|
||||||
|
|
||||||
assert((req->offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
assert((req->bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
|
|
||||||
return backup_do_cow(req->bs, sector_num, nb_sectors, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|
||||||
{
|
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
|
||||||
|
|
||||||
if (speed < 0) {
|
|
||||||
error_set(errp, QERR_INVALID_PARAMETER, "speed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_iostatus_reset(BlockJob *job)
|
|
||||||
{
|
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
|
||||||
|
|
||||||
bdrv_iostatus_reset(s->target);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const BlockJobDriver backup_job_driver = {
|
|
||||||
.instance_size = sizeof(BackupBlockJob),
|
|
||||||
.job_type = BLOCK_JOB_TYPE_BACKUP,
|
|
||||||
.set_speed = backup_set_speed,
|
|
||||||
.iostatus_reset = backup_iostatus_reset,
|
|
||||||
};
|
|
||||||
|
|
||||||
static BlockErrorAction backup_error_action(BackupBlockJob *job,
|
|
||||||
bool read, int error)
|
|
||||||
{
|
|
||||||
if (read) {
|
|
||||||
return block_job_error_action(&job->common, job->common.bs,
|
|
||||||
job->on_source_error, true, error);
|
|
||||||
} else {
|
|
||||||
return block_job_error_action(&job->common, job->target,
|
|
||||||
job->on_target_error, false, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void coroutine_fn backup_run(void *opaque)
|
|
||||||
{
|
|
||||||
BackupBlockJob *job = opaque;
|
|
||||||
BlockDriverState *bs = job->common.bs;
|
|
||||||
BlockDriverState *target = job->target;
|
|
||||||
BlockdevOnError on_target_error = job->on_target_error;
|
|
||||||
NotifierWithReturn before_write = {
|
|
||||||
.notify = backup_before_write_notify,
|
|
||||||
};
|
|
||||||
int64_t start, end;
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
QLIST_INIT(&job->inflight_reqs);
|
|
||||||
qemu_co_rwlock_init(&job->flush_rwlock);
|
|
||||||
|
|
||||||
start = 0;
|
|
||||||
end = DIV_ROUND_UP(job->common.len / BDRV_SECTOR_SIZE,
|
|
||||||
BACKUP_SECTORS_PER_CLUSTER);
|
|
||||||
|
|
||||||
job->bitmap = hbitmap_alloc(end, 0);
|
|
||||||
|
|
||||||
bdrv_set_enable_write_cache(target, true);
|
|
||||||
bdrv_set_on_error(target, on_target_error, on_target_error);
|
|
||||||
bdrv_iostatus_enable(target);
|
|
||||||
|
|
||||||
bdrv_add_before_write_notifier(bs, &before_write);
|
|
||||||
|
|
||||||
if (job->sync_mode == MIRROR_SYNC_MODE_NONE) {
|
|
||||||
while (!block_job_is_cancelled(&job->common)) {
|
|
||||||
/* Yield until the job is cancelled. We just let our before_write
|
|
||||||
* notify callback service CoW requests. */
|
|
||||||
job->common.busy = false;
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
job->common.busy = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
|
||||||
for (; start < end; start++) {
|
|
||||||
bool error_is_read;
|
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* we need to yield so that qemu_aio_flush() returns.
|
|
||||||
* (without, VM does not reboot)
|
|
||||||
*/
|
|
||||||
if (job->common.speed) {
|
|
||||||
uint64_t delay_ns = ratelimit_calculate_delay(
|
|
||||||
&job->limit, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
|
|
||||||
int i, n;
|
|
||||||
int alloced = 0;
|
|
||||||
|
|
||||||
/* Check to see if these blocks are already in the
|
|
||||||
* backing file. */
|
|
||||||
|
|
||||||
for (i = 0; i < BACKUP_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.
|
|
||||||
* For that reason we must verify each sector in the
|
|
||||||
* backup cluster length. We end up copying more than
|
|
||||||
* needed but at some point that is always the case. */
|
|
||||||
alloced =
|
|
||||||
bdrv_is_allocated(bs,
|
|
||||||
start * BACKUP_SECTORS_PER_CLUSTER + i,
|
|
||||||
BACKUP_SECTORS_PER_CLUSTER - i, &n);
|
|
||||||
i += n;
|
|
||||||
|
|
||||||
if (alloced == 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the above loop never found any sectors that are in
|
|
||||||
* the topmost image, skip this backup. */
|
|
||||||
if (alloced == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* FULL sync mode we copy the whole drive. */
|
|
||||||
ret = backup_do_cow(bs, start * BACKUP_SECTORS_PER_CLUSTER,
|
|
||||||
BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
|
|
||||||
if (ret < 0) {
|
|
||||||
/* Depending on error action, fail now or retry cluster */
|
|
||||||
BlockErrorAction action =
|
|
||||||
backup_error_action(job, error_is_read, -ret);
|
|
||||||
if (action == BDRV_ACTION_REPORT) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
start--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notifier_with_return_remove(&before_write);
|
|
||||||
|
|
||||||
/* wait until pending backup_do_cow() calls have completed */
|
|
||||||
qemu_co_rwlock_wrlock(&job->flush_rwlock);
|
|
||||||
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
|
||||||
|
|
||||||
hbitmap_free(job->bitmap);
|
|
||||||
|
|
||||||
bdrv_iostatus_disable(target);
|
|
||||||
bdrv_unref(target);
|
|
||||||
|
|
||||||
block_job_completed(&job->common, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
|
||||||
int64_t speed, MirrorSyncMode sync_mode,
|
|
||||||
BlockdevOnError on_source_error,
|
|
||||||
BlockdevOnError on_target_error,
|
|
||||||
BlockDriverCompletionFunc *cb, void *opaque,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int64_t len;
|
|
||||||
|
|
||||||
assert(bs);
|
|
||||||
assert(target);
|
|
||||||
assert(cb);
|
|
||||||
|
|
||||||
if ((on_source_error == BLOCKDEV_ON_ERROR_STOP ||
|
|
||||||
on_source_error == BLOCKDEV_ON_ERROR_ENOSPC) &&
|
|
||||||
!bdrv_iostatus_is_enabled(bs)) {
|
|
||||||
error_set(errp, QERR_INVALID_PARAMETER, "on-source-error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = bdrv_getlength(bs);
|
|
||||||
if (len < 0) {
|
|
||||||
error_setg_errno(errp, -len, "unable to get length for '%s'",
|
|
||||||
bdrv_get_device_name(bs));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BackupBlockJob *job = block_job_create(&backup_job_driver, bs, speed,
|
|
||||||
cb, opaque, errp);
|
|
||||||
if (!job) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
job->on_source_error = on_source_error;
|
|
||||||
job->on_target_error = on_target_error;
|
|
||||||
job->target = target;
|
|
||||||
job->sync_mode = sync_mode;
|
|
||||||
job->common.len = len;
|
|
||||||
job->common.co = qemu_coroutine_create(backup_run);
|
|
||||||
qemu_coroutine_enter(job->common.co, job);
|
|
||||||
}
|
|
||||||
188
block/blkdebug.c
188
block/blkdebug.c
@@ -168,7 +168,6 @@ static const char *event_names[BLKDBG_EVENT_MAX] = {
|
|||||||
|
|
||||||
[BLKDBG_REFTABLE_LOAD] = "reftable_load",
|
[BLKDBG_REFTABLE_LOAD] = "reftable_load",
|
||||||
[BLKDBG_REFTABLE_GROW] = "reftable_grow",
|
[BLKDBG_REFTABLE_GROW] = "reftable_grow",
|
||||||
[BLKDBG_REFTABLE_UPDATE] = "reftable_update",
|
|
||||||
|
|
||||||
[BLKDBG_REFBLOCK_LOAD] = "refblock_load",
|
[BLKDBG_REFBLOCK_LOAD] = "refblock_load",
|
||||||
[BLKDBG_REFBLOCK_UPDATE] = "refblock_update",
|
[BLKDBG_REFBLOCK_UPDATE] = "refblock_update",
|
||||||
@@ -183,17 +182,6 @@ static const char *event_names[BLKDBG_EVENT_MAX] = {
|
|||||||
[BLKDBG_CLUSTER_ALLOC] = "cluster_alloc",
|
[BLKDBG_CLUSTER_ALLOC] = "cluster_alloc",
|
||||||
[BLKDBG_CLUSTER_ALLOC_BYTES] = "cluster_alloc_bytes",
|
[BLKDBG_CLUSTER_ALLOC_BYTES] = "cluster_alloc_bytes",
|
||||||
[BLKDBG_CLUSTER_FREE] = "cluster_free",
|
[BLKDBG_CLUSTER_FREE] = "cluster_free",
|
||||||
|
|
||||||
[BLKDBG_FLUSH_TO_OS] = "flush_to_os",
|
|
||||||
[BLKDBG_FLUSH_TO_DISK] = "flush_to_disk",
|
|
||||||
|
|
||||||
[BLKDBG_PWRITEV_RMW_HEAD] = "pwritev_rmw.head",
|
|
||||||
[BLKDBG_PWRITEV_RMW_AFTER_HEAD] = "pwritev_rmw.after_head",
|
|
||||||
[BLKDBG_PWRITEV_RMW_TAIL] = "pwritev_rmw.tail",
|
|
||||||
[BLKDBG_PWRITEV_RMW_AFTER_TAIL] = "pwritev_rmw.after_tail",
|
|
||||||
[BLKDBG_PWRITEV] = "pwritev",
|
|
||||||
[BLKDBG_PWRITEV_ZERO] = "pwritev_zero",
|
|
||||||
[BLKDBG_PWRITEV_DONE] = "pwritev_done",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int get_event_by_name(const char *name, BlkDebugEvent *event)
|
static int get_event_by_name(const char *name, BlkDebugEvent *event)
|
||||||
@@ -279,33 +267,24 @@ static void remove_rule(BlkdebugRule *rule)
|
|||||||
g_free(rule);
|
g_free(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int read_config(BDRVBlkdebugState *s, const char *filename,
|
static int read_config(BDRVBlkdebugState *s, const char *filename)
|
||||||
QDict *options, Error **errp)
|
|
||||||
{
|
{
|
||||||
FILE *f = NULL;
|
FILE *f;
|
||||||
int ret;
|
int ret;
|
||||||
struct add_rule_data d;
|
struct add_rule_data d;
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
if (filename) {
|
/* Allow usage without config file */
|
||||||
|
if (!*filename) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
f = fopen(filename, "r");
|
f = fopen(filename, "r");
|
||||||
if (f == NULL) {
|
if (f == NULL) {
|
||||||
error_setg_errno(errp, errno, "Could not read blkdebug config file");
|
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = qemu_config_parse(f, config_groups, filename);
|
ret = qemu_config_parse(f, config_groups, filename);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg(errp, "Could not parse blkdebug config file");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_config_parse_qdict(options, config_groups, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,124 +299,50 @@ static int read_config(BDRVBlkdebugState *s, const char *filename,
|
|||||||
fail:
|
fail:
|
||||||
qemu_opts_reset(&inject_error_opts);
|
qemu_opts_reset(&inject_error_opts);
|
||||||
qemu_opts_reset(&set_state_opts);
|
qemu_opts_reset(&set_state_opts);
|
||||||
if (f) {
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Valid blkdebug filenames look like blkdebug:path/to/config:path/to/image */
|
/* Valid blkdebug filenames look like blkdebug:path/to/config:path/to/image */
|
||||||
static void blkdebug_parse_filename(const char *filename, QDict *options,
|
static int blkdebug_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
const char *c;
|
|
||||||
|
|
||||||
/* Parse the blkdebug: prefix */
|
|
||||||
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(options, "x-image", qstring_from_str(filename));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse config file path */
|
|
||||||
c = strchr(filename, ':');
|
|
||||||
if (c == NULL) {
|
|
||||||
error_setg(errp, "blkdebug requires both config file and image path");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c != filename) {
|
|
||||||
QString *config_path;
|
|
||||||
config_path = qstring_from_substr(filename, 0, c - filename - 1);
|
|
||||||
qdict_put(options, "config", config_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO Allow multi-level nesting and set file.filename here */
|
|
||||||
filename = c + 1;
|
|
||||||
qdict_put(options, "x-image", qstring_from_str(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
|
||||||
.name = "blkdebug",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = "config",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Path to the configuration file",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "x-image",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "[internal use only, will be removed]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "align",
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Required alignment in bytes",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
QemuOpts *opts;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
const char *config;
|
|
||||||
uint64_t align;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
char *config, *c;
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
/* Parse the blkdebug: prefix */
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
if (strncmp(filename, "blkdebug:", strlen("blkdebug:"))) {
|
||||||
if (local_err) {
|
return -EINVAL;
|
||||||
error_propagate(errp, local_err);
|
}
|
||||||
ret = -EINVAL;
|
filename += strlen("blkdebug:");
|
||||||
goto out;
|
|
||||||
|
/* Read rules from config file */
|
||||||
|
c = strchr(filename, ':');
|
||||||
|
if (c == NULL) {
|
||||||
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read rules from config file or command line options */
|
config = g_strdup(filename);
|
||||||
config = qemu_opt_get(opts, "config");
|
config[c - filename] = '\0';
|
||||||
ret = read_config(s, config, options, errp);
|
ret = read_config(s, config);
|
||||||
if (ret) {
|
g_free(config);
|
||||||
goto out;
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
filename = c + 1;
|
||||||
|
|
||||||
/* Set initial state */
|
/* Set initial state */
|
||||||
s->state = 1;
|
s->state = 1;
|
||||||
|
|
||||||
/* Open the backing file */
|
/* Open the backing file */
|
||||||
assert(bs->file == NULL);
|
ret = bdrv_file_open(&bs->file, filename, flags);
|
||||||
ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-image"), options, "image",
|
|
||||||
flags | BDRV_O_PROTOCOL, false, &local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set request alignment */
|
|
||||||
align = qemu_opt_get_size(opts, "align", bs->request_alignment);
|
|
||||||
if (align > 0 && align < INT_MAX && !(align & (align - 1))) {
|
|
||||||
bs->request_alignment = align;
|
|
||||||
} else {
|
|
||||||
error_setg(errp, "Invalid alignment");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail_unref;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = 0;
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
fail_unref:
|
|
||||||
bdrv_unref(bs->file);
|
|
||||||
out:
|
|
||||||
qemu_opts_del(opts);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void error_callback_bh(void *opaque)
|
static void error_callback_bh(void *opaque)
|
||||||
{
|
{
|
||||||
struct BlkdebugAIOCB *acb = opaque;
|
struct BlkdebugAIOCB *acb = opaque;
|
||||||
@@ -632,9 +537,9 @@ static int blkdebug_debug_breakpoint(BlockDriverState *bs, const char *event,
|
|||||||
static int blkdebug_debug_resume(BlockDriverState *bs, const char *tag)
|
static int blkdebug_debug_resume(BlockDriverState *bs, const char *tag)
|
||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
BlkdebugSuspendedReq *r, *next;
|
BlkdebugSuspendedReq *r;
|
||||||
|
|
||||||
QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, next) {
|
QLIST_FOREACH(r, &s->suspended_reqs, next) {
|
||||||
if (!strcmp(r->tag, tag)) {
|
if (!strcmp(r->tag, tag)) {
|
||||||
qemu_coroutine_enter(r->co, NULL);
|
qemu_coroutine_enter(r->co, NULL);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -643,31 +548,6 @@ static int blkdebug_debug_resume(BlockDriverState *bs, const char *tag)
|
|||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int blkdebug_debug_remove_breakpoint(BlockDriverState *bs,
|
|
||||||
const char *tag)
|
|
||||||
{
|
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
|
||||||
BlkdebugSuspendedReq *r, *r_next;
|
|
||||||
BlkdebugRule *rule, *next;
|
|
||||||
int i, ret = -ENOENT;
|
|
||||||
|
|
||||||
for (i = 0; i < BLKDBG_EVENT_MAX; i++) {
|
|
||||||
QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) {
|
|
||||||
if (rule->action == ACTION_SUSPEND &&
|
|
||||||
!strcmp(rule->options.suspend.tag, tag)) {
|
|
||||||
remove_rule(rule);
|
|
||||||
ret = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, r_next) {
|
|
||||||
if (!strcmp(r->tag, tag)) {
|
|
||||||
qemu_coroutine_enter(r->co, NULL);
|
|
||||||
ret = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool blkdebug_debug_is_suspended(BlockDriverState *bs, const char *tag)
|
static bool blkdebug_debug_is_suspended(BlockDriverState *bs, const char *tag)
|
||||||
{
|
{
|
||||||
@@ -690,9 +570,9 @@ static int64_t blkdebug_getlength(BlockDriverState *bs)
|
|||||||
static BlockDriver bdrv_blkdebug = {
|
static BlockDriver bdrv_blkdebug = {
|
||||||
.format_name = "blkdebug",
|
.format_name = "blkdebug",
|
||||||
.protocol_name = "blkdebug",
|
.protocol_name = "blkdebug",
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVBlkdebugState),
|
.instance_size = sizeof(BDRVBlkdebugState),
|
||||||
|
|
||||||
.bdrv_parse_filename = blkdebug_parse_filename,
|
|
||||||
.bdrv_file_open = blkdebug_open,
|
.bdrv_file_open = blkdebug_open,
|
||||||
.bdrv_close = blkdebug_close,
|
.bdrv_close = blkdebug_close,
|
||||||
.bdrv_getlength = blkdebug_getlength,
|
.bdrv_getlength = blkdebug_getlength,
|
||||||
@@ -702,8 +582,6 @@ static BlockDriver bdrv_blkdebug = {
|
|||||||
|
|
||||||
.bdrv_debug_event = blkdebug_debug_event,
|
.bdrv_debug_event = blkdebug_debug_event,
|
||||||
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
|
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
|
||||||
.bdrv_debug_remove_breakpoint
|
|
||||||
= blkdebug_debug_remove_breakpoint,
|
|
||||||
.bdrv_debug_resume = blkdebug_debug_resume,
|
.bdrv_debug_resume = blkdebug_debug_resume,
|
||||||
.bdrv_debug_is_suspended = blkdebug_debug_is_suspended,
|
.bdrv_debug_is_suspended = blkdebug_debug_is_suspended,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,100 +69,50 @@ static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyAIOCB *acb,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Valid blkverify filenames look like blkverify:path/to/raw_image:path/to/image */
|
/* Valid blkverify filenames look like blkverify:path/to/raw_image:path/to/image */
|
||||||
static void blkverify_parse_filename(const char *filename, QDict *options,
|
static int blkverify_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
const char *c;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
QString *raw_path;
|
int ret;
|
||||||
|
char *raw, *c;
|
||||||
|
|
||||||
/* Parse the blkverify: prefix */
|
/* Parse the blkverify: prefix */
|
||||||
if (!strstart(filename, "blkverify:", &filename)) {
|
if (strncmp(filename, "blkverify:", strlen("blkverify:"))) {
|
||||||
/* There was no prefix; therefore, all options have to be already
|
return -EINVAL;
|
||||||
present in the QDict (except for the filename) */
|
|
||||||
qdict_put(options, "x-image", qstring_from_str(filename));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
filename += strlen("blkverify:");
|
||||||
|
|
||||||
/* Parse the raw image filename */
|
/* Parse the raw image filename */
|
||||||
c = strchr(filename, ':');
|
c = strchr(filename, ':');
|
||||||
if (c == NULL) {
|
if (c == NULL) {
|
||||||
error_setg(errp, "blkverify requires raw copy and original image path");
|
return -EINVAL;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Implement option pass-through and set raw.filename here */
|
raw = g_strdup(filename);
|
||||||
raw_path = qstring_from_substr(filename, 0, c - filename - 1);
|
raw[c - filename] = '\0';
|
||||||
qdict_put(options, "x-raw", raw_path);
|
ret = bdrv_file_open(&bs->file, raw, flags);
|
||||||
|
g_free(raw);
|
||||||
/* TODO Allow multi-level nesting and set file.filename here */
|
|
||||||
filename = c + 1;
|
|
||||||
qdict_put(options, "x-image", qstring_from_str(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
|
||||||
.name = "blkverify",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = "x-raw",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "[internal use only, will be removed]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "x-image",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "[internal use only, will be removed]",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
|
||||||
QemuOpts *opts;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
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 fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open the raw file */
|
|
||||||
assert(bs->file == NULL);
|
|
||||||
ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-raw"), options,
|
|
||||||
"raw", flags | BDRV_O_PROTOCOL, false, &local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
return ret;
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
|
filename = c + 1;
|
||||||
|
|
||||||
/* Open the test file */
|
/* Open the test file */
|
||||||
assert(s->test_file == NULL);
|
s->test_file = bdrv_new("");
|
||||||
ret = bdrv_open_image(&s->test_file, qemu_opt_get(opts, "x-image"), options,
|
ret = bdrv_open(s->test_file, filename, flags, NULL);
|
||||||
"test", flags, false, &local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
bdrv_delete(s->test_file);
|
||||||
s->test_file = NULL;
|
s->test_file = NULL;
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
return 0;
|
||||||
fail:
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blkverify_close(BlockDriverState *bs)
|
static void blkverify_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
bdrv_unref(s->test_file);
|
bdrv_delete(s->test_file);
|
||||||
s->test_file = NULL;
|
s->test_file = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +123,110 @@ static int64_t blkverify_getlength(BlockDriverState *bs)
|
|||||||
return bdrv_getlength(s->test_file);
|
return bdrv_getlength(s->test_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that I/O vector contents are identical
|
||||||
|
*
|
||||||
|
* @a: I/O vector
|
||||||
|
* @b: I/O vector
|
||||||
|
* @ret: Offset to first mismatching byte or -1 if match
|
||||||
|
*/
|
||||||
|
static ssize_t blkverify_iovec_compare(QEMUIOVector *a, QEMUIOVector *b)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
ssize_t offset = 0;
|
||||||
|
|
||||||
|
assert(a->niov == b->niov);
|
||||||
|
for (i = 0; i < a->niov; i++) {
|
||||||
|
size_t len = 0;
|
||||||
|
uint8_t *p = (uint8_t *)a->iov[i].iov_base;
|
||||||
|
uint8_t *q = (uint8_t *)b->iov[i].iov_base;
|
||||||
|
|
||||||
|
assert(a->iov[i].iov_len == b->iov[i].iov_len);
|
||||||
|
while (len < a->iov[i].iov_len && *p++ == *q++) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += len;
|
||||||
|
|
||||||
|
if (len != a->iov[i].iov_len) {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int src_index;
|
||||||
|
struct iovec *src_iov;
|
||||||
|
void *dest_base;
|
||||||
|
} IOVectorSortElem;
|
||||||
|
|
||||||
|
static int sortelem_cmp_src_base(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const IOVectorSortElem *elem_a = a;
|
||||||
|
const IOVectorSortElem *elem_b = b;
|
||||||
|
|
||||||
|
/* Don't overflow */
|
||||||
|
if (elem_a->src_iov->iov_base < elem_b->src_iov->iov_base) {
|
||||||
|
return -1;
|
||||||
|
} else if (elem_a->src_iov->iov_base > elem_b->src_iov->iov_base) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sortelem_cmp_src_index(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const IOVectorSortElem *elem_a = a;
|
||||||
|
const IOVectorSortElem *elem_b = b;
|
||||||
|
|
||||||
|
return elem_a->src_index - elem_b->src_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy contents of I/O vector
|
||||||
|
*
|
||||||
|
* The relative relationships of overlapping iovecs are preserved. This is
|
||||||
|
* necessary to ensure identical semantics in the cloned I/O vector.
|
||||||
|
*/
|
||||||
|
static void blkverify_iovec_clone(QEMUIOVector *dest, const QEMUIOVector *src,
|
||||||
|
void *buf)
|
||||||
|
{
|
||||||
|
IOVectorSortElem sortelems[src->niov];
|
||||||
|
void *last_end;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Sort by source iovecs by base address */
|
||||||
|
for (i = 0; i < src->niov; i++) {
|
||||||
|
sortelems[i].src_index = i;
|
||||||
|
sortelems[i].src_iov = &src->iov[i];
|
||||||
|
}
|
||||||
|
qsort(sortelems, src->niov, sizeof(sortelems[0]), sortelem_cmp_src_base);
|
||||||
|
|
||||||
|
/* Allocate buffer space taking into account overlapping iovecs */
|
||||||
|
last_end = NULL;
|
||||||
|
for (i = 0; i < src->niov; i++) {
|
||||||
|
struct iovec *cur = sortelems[i].src_iov;
|
||||||
|
ptrdiff_t rewind = 0;
|
||||||
|
|
||||||
|
/* Detect overlap */
|
||||||
|
if (last_end && last_end > cur->iov_base) {
|
||||||
|
rewind = last_end - cur->iov_base;
|
||||||
|
}
|
||||||
|
|
||||||
|
sortelems[i].dest_base = buf - rewind;
|
||||||
|
buf += cur->iov_len - MIN(rewind, cur->iov_len);
|
||||||
|
last_end = MAX(cur->iov_base + cur->iov_len, last_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sort by source iovec index and build destination iovec */
|
||||||
|
qsort(sortelems, src->niov, sizeof(sortelems[0]), sortelem_cmp_src_index);
|
||||||
|
for (i = 0; i < src->niov; i++) {
|
||||||
|
qemu_iovec_add(dest, sortelems[i].dest_base, src->iov[i].iov_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static BlkverifyAIOCB *blkverify_aio_get(BlockDriverState *bs, bool is_write,
|
static BlkverifyAIOCB *blkverify_aio_get(BlockDriverState *bs, bool is_write,
|
||||||
int64_t sector_num, QEMUIOVector *qiov,
|
int64_t sector_num, QEMUIOVector *qiov,
|
||||||
int nb_sectors,
|
int nb_sectors,
|
||||||
@@ -236,7 +290,7 @@ static void blkverify_aio_cb(void *opaque, int ret)
|
|||||||
|
|
||||||
static void blkverify_verify_readv(BlkverifyAIOCB *acb)
|
static void blkverify_verify_readv(BlkverifyAIOCB *acb)
|
||||||
{
|
{
|
||||||
ssize_t offset = qemu_iovec_compare(acb->qiov, &acb->raw_qiov);
|
ssize_t offset = blkverify_iovec_compare(acb->qiov, &acb->raw_qiov);
|
||||||
if (offset != -1) {
|
if (offset != -1) {
|
||||||
blkverify_err(acb, "contents mismatch in sector %" PRId64,
|
blkverify_err(acb, "contents mismatch in sector %" PRId64,
|
||||||
acb->sector_num + (int64_t)(offset / BDRV_SECTOR_SIZE));
|
acb->sector_num + (int64_t)(offset / BDRV_SECTOR_SIZE));
|
||||||
@@ -254,7 +308,7 @@ static BlockDriverAIOCB *blkverify_aio_readv(BlockDriverState *bs,
|
|||||||
acb->verify = blkverify_verify_readv;
|
acb->verify = blkverify_verify_readv;
|
||||||
acb->buf = qemu_blockalign(bs->file, qiov->size);
|
acb->buf = qemu_blockalign(bs->file, qiov->size);
|
||||||
qemu_iovec_init(&acb->raw_qiov, acb->qiov->niov);
|
qemu_iovec_init(&acb->raw_qiov, acb->qiov->niov);
|
||||||
qemu_iovec_clone(&acb->raw_qiov, qiov, acb->buf);
|
blkverify_iovec_clone(&acb->raw_qiov, qiov, acb->buf);
|
||||||
|
|
||||||
bdrv_aio_readv(s->test_file, sector_num, qiov, nb_sectors,
|
bdrv_aio_readv(s->test_file, sector_num, qiov, nb_sectors,
|
||||||
blkverify_aio_cb, acb);
|
blkverify_aio_cb, acb);
|
||||||
@@ -288,36 +342,20 @@ static BlockDriverAIOCB *blkverify_aio_flush(BlockDriverState *bs,
|
|||||||
return bdrv_aio_flush(s->test_file, cb, opaque);
|
return bdrv_aio_flush(s->test_file, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool blkverify_recurse_is_first_non_filter(BlockDriverState *bs,
|
|
||||||
BlockDriverState *candidate)
|
|
||||||
{
|
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
|
||||||
|
|
||||||
bool perm = bdrv_recurse_is_first_non_filter(bs->file, candidate);
|
|
||||||
|
|
||||||
if (perm) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bdrv_recurse_is_first_non_filter(s->test_file, candidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriver bdrv_blkverify = {
|
static BlockDriver bdrv_blkverify = {
|
||||||
.format_name = "blkverify",
|
.format_name = "blkverify",
|
||||||
.protocol_name = "blkverify",
|
.protocol_name = "blkverify",
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVBlkverifyState),
|
.instance_size = sizeof(BDRVBlkverifyState),
|
||||||
|
|
||||||
.bdrv_parse_filename = blkverify_parse_filename,
|
.bdrv_getlength = blkverify_getlength,
|
||||||
|
|
||||||
.bdrv_file_open = blkverify_open,
|
.bdrv_file_open = blkverify_open,
|
||||||
.bdrv_close = blkverify_close,
|
.bdrv_close = blkverify_close,
|
||||||
.bdrv_getlength = blkverify_getlength,
|
|
||||||
|
|
||||||
.bdrv_aio_readv = blkverify_aio_readv,
|
.bdrv_aio_readv = blkverify_aio_readv,
|
||||||
.bdrv_aio_writev = blkverify_aio_writev,
|
.bdrv_aio_writev = blkverify_aio_writev,
|
||||||
.bdrv_aio_flush = blkverify_aio_flush,
|
.bdrv_aio_flush = blkverify_aio_flush,
|
||||||
|
|
||||||
.is_filter = true,
|
|
||||||
.bdrv_recurse_is_first_non_filter = blkverify_recurse_is_first_non_filter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_blkverify_init(void)
|
static void bdrv_blkverify_init(void)
|
||||||
|
|||||||
@@ -93,8 +93,7 @@ static int bochs_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bochs_open(BlockDriverState *bs, QDict *options, int flags,
|
static int bochs_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVBochsState *s = bs->opaque;
|
BDRVBochsState *s = bs->opaque;
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
@@ -113,8 +112,7 @@ static int bochs_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
strcmp(bochs.subtype, GROWING_TYPE) ||
|
strcmp(bochs.subtype, GROWING_TYPE) ||
|
||||||
((le32_to_cpu(bochs.version) != HEADER_VERSION) &&
|
((le32_to_cpu(bochs.version) != HEADER_VERSION) &&
|
||||||
(le32_to_cpu(bochs.version) != HEADER_V1))) {
|
(le32_to_cpu(bochs.version) != HEADER_V1))) {
|
||||||
error_setg(errp, "Image not in Bochs format");
|
return -EMEDIUMTYPE;
|
||||||
return -EINVAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (le32_to_cpu(bochs.version) == HEADER_V1) {
|
if (le32_to_cpu(bochs.version) == HEADER_V1) {
|
||||||
@@ -127,7 +125,8 @@ static int bochs_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
* needed for the largest image that bximage can create (~8 TB). */
|
* needed for the largest image that bximage can create (~8 TB). */
|
||||||
s->catalog_size = le32_to_cpu(bochs.catalog);
|
s->catalog_size = le32_to_cpu(bochs.catalog);
|
||||||
if (s->catalog_size > 0x100000) {
|
if (s->catalog_size > 0x100000) {
|
||||||
error_setg(errp, "Catalog size is too large");
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Catalog size is too large");
|
||||||
return -EFBIG;
|
return -EFBIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,16 +148,19 @@ static int bochs_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
|
|
||||||
s->extent_size = le32_to_cpu(bochs.extent);
|
s->extent_size = le32_to_cpu(bochs.extent);
|
||||||
if (s->extent_size == 0) {
|
if (s->extent_size == 0) {
|
||||||
error_setg(errp, "Extent size may not be zero");
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Extent size may not be zero");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
} else if (s->extent_size > 0x800000) {
|
} else if (s->extent_size > 0x800000) {
|
||||||
error_setg(errp, "Extent size %" PRIu32 " is too large",
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Extent size %" PRIu32 " is too large",
|
||||||
s->extent_size);
|
s->extent_size);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->catalog_size < bs->total_sectors / s->extent_size) {
|
if (s->catalog_size < bs->total_sectors / s->extent_size) {
|
||||||
error_setg(errp, "Catalog size is too small for this disk size");
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Catalog size is too small for this disk size");
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ static int cloop_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
static int cloop_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVCloopState *s = bs->opaque;
|
BDRVCloopState *s = bs->opaque;
|
||||||
uint32_t offsets_size, max_compressed_block_size = 1, i;
|
uint32_t offsets_size, max_compressed_block_size = 1, i;
|
||||||
@@ -72,12 +71,14 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
s->block_size = be32_to_cpu(s->block_size);
|
s->block_size = be32_to_cpu(s->block_size);
|
||||||
if (s->block_size % 512) {
|
if (s->block_size % 512) {
|
||||||
error_setg(errp, "block_size %u must be a multiple of 512",
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"block_size %u must be a multiple of 512",
|
||||||
s->block_size);
|
s->block_size);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
if (s->block_size == 0) {
|
if (s->block_size == 0) {
|
||||||
error_setg(errp, "block_size cannot be zero");
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"block_size cannot be zero");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +87,8 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
* need a buffer this big.
|
* need a buffer this big.
|
||||||
*/
|
*/
|
||||||
if (s->block_size > MAX_BLOCK_SIZE) {
|
if (s->block_size > MAX_BLOCK_SIZE) {
|
||||||
error_setg(errp, "block_size %u must be %u MB or less",
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"block_size %u must be %u MB or less",
|
||||||
s->block_size,
|
s->block_size,
|
||||||
MAX_BLOCK_SIZE / (1024 * 1024));
|
MAX_BLOCK_SIZE / (1024 * 1024));
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
@@ -101,7 +103,8 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
/* read offsets */
|
/* read offsets */
|
||||||
if (s->n_blocks > (UINT32_MAX - 1) / sizeof(uint64_t)) {
|
if (s->n_blocks > (UINT32_MAX - 1) / sizeof(uint64_t)) {
|
||||||
/* Prevent integer overflow */
|
/* Prevent integer overflow */
|
||||||
error_setg(errp, "n_blocks %u must be %zu or less",
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"n_blocks %u must be %zu or less",
|
||||||
s->n_blocks,
|
s->n_blocks,
|
||||||
(UINT32_MAX - 1) / sizeof(uint64_t));
|
(UINT32_MAX - 1) / sizeof(uint64_t));
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
@@ -112,7 +115,8 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
* fail or overflows bdrv_pread() size. In practice the 512 MB
|
* fail or overflows bdrv_pread() size. In practice the 512 MB
|
||||||
* offsets[] limit supports 16 TB images at 256 KB block size.
|
* offsets[] limit supports 16 TB images at 256 KB block size.
|
||||||
*/
|
*/
|
||||||
error_setg(errp, "image requires too many offsets, "
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"image requires too many offsets, "
|
||||||
"try increasing block size");
|
"try increasing block size");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
@@ -132,7 +136,8 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (s->offsets[i] < s->offsets[i - 1]) {
|
if (s->offsets[i] < s->offsets[i - 1]) {
|
||||||
error_setg(errp, "offsets not monotonically increasing at "
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"offsets not monotonically increasing at "
|
||||||
"index %u, image file is corrupt", i);
|
"index %u, image file is corrupt", i);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
@@ -146,7 +151,8 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
* ridiculous s->compressed_block allocation.
|
* ridiculous s->compressed_block allocation.
|
||||||
*/
|
*/
|
||||||
if (size > 2 * MAX_BLOCK_SIZE) {
|
if (size > 2 * MAX_BLOCK_SIZE) {
|
||||||
error_setg(errp, "invalid compressed block size at index %u, "
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"invalid compressed block size at index %u, "
|
||||||
"image file is corrupt", i);
|
"image file is corrupt", i);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|||||||
@@ -103,12 +103,12 @@ wait:
|
|||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* with no pending I/O here so that bdrv_drain_all() returns.
|
* with no pending I/O here so that bdrv_drain_all() returns.
|
||||||
*/
|
*/
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&s->common, rt_clock, delay_ns);
|
||||||
if (block_job_is_cancelled(&s->common)) {
|
if (block_job_is_cancelled(&s->common)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Copy if allocated above the base */
|
/* Copy if allocated above the base */
|
||||||
ret = bdrv_is_allocated_above(top, base, sector_num,
|
ret = bdrv_co_is_allocated_above(top, base, sector_num,
|
||||||
COMMIT_BUFFER_SIZE / BDRV_SECTOR_SIZE,
|
COMMIT_BUFFER_SIZE / BDRV_SECTOR_SIZE,
|
||||||
&n);
|
&n);
|
||||||
copy = (ret == 1);
|
copy = (ret == 1);
|
||||||
@@ -173,9 +173,9 @@ static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|||||||
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const BlockJobDriver commit_job_driver = {
|
static BlockJobType commit_job_type = {
|
||||||
.instance_size = sizeof(CommitBlockJob),
|
.instance_size = sizeof(CommitBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
.job_type = "commit",
|
||||||
.set_speed = commit_set_speed,
|
.set_speed = commit_set_speed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -198,7 +198,13 @@ void commit_start(BlockDriverState *bs, BlockDriverState *base,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(top != bs);
|
/* Once we support top == active layer, remove this check */
|
||||||
|
if (top == bs) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Top image as the active layer is currently unsupported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (top == base) {
|
if (top == base) {
|
||||||
error_setg(errp, "Invalid files for merge: top and base are the same");
|
error_setg(errp, "Invalid files for merge: top and base are the same");
|
||||||
return;
|
return;
|
||||||
@@ -232,7 +238,7 @@ void commit_start(BlockDriverState *bs, BlockDriverState *base,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
s = block_job_create(&commit_job_driver, bs, speed, cb, opaque, errp);
|
s = block_job_create(&commit_job_type, bs, speed, cb, opaque, errp);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
186
block/cow.c
186
block/cow.c
@@ -58,8 +58,7 @@ static int cow_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cow_open(BlockDriverState *bs, QDict *options, int flags,
|
static int cow_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVCowState *s = bs->opaque;
|
BDRVCowState *s = bs->opaque;
|
||||||
struct cow_header_v2 cow_header;
|
struct cow_header_v2 cow_header;
|
||||||
@@ -74,8 +73,7 @@ static int cow_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (be32_to_cpu(cow_header.magic) != COW_MAGIC) {
|
if (be32_to_cpu(cow_header.magic) != COW_MAGIC) {
|
||||||
error_setg(errp, "Image not in COW format");
|
ret = -EMEDIUMTYPE;
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +81,7 @@ static int cow_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
char version[64];
|
char version[64];
|
||||||
snprintf(version, sizeof(version),
|
snprintf(version, sizeof(version),
|
||||||
"COW version %d", cow_header.version);
|
"COW version %d", cow_header.version);
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
bs->device_name, "cow", version);
|
bs->device_name, "cow", version);
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
@@ -104,45 +102,42 @@ static int cow_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void cow_set_bits(uint8_t *bitmap, int start, int64_t nb_sectors)
|
/*
|
||||||
|
* XXX(hch): right now these functions are extremely inefficient.
|
||||||
|
* We should just read the whole bitmap we'll need in one go instead.
|
||||||
|
*/
|
||||||
|
static inline int cow_set_bit(BlockDriverState *bs, int64_t bitnum)
|
||||||
{
|
{
|
||||||
int64_t bitnum = start, last = start + nb_sectors;
|
uint64_t offset = sizeof(struct cow_header_v2) + bitnum / 8;
|
||||||
while (bitnum < last) {
|
uint8_t bitmap;
|
||||||
if ((bitnum & 7) == 0 && bitnum + 8 <= last) {
|
int ret;
|
||||||
bitmap[bitnum / 8] = 0xFF;
|
|
||||||
bitnum += 8;
|
ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap));
|
||||||
continue;
|
if (ret < 0) {
|
||||||
}
|
return ret;
|
||||||
bitmap[bitnum/8] |= (1 << (bitnum % 8));
|
|
||||||
bitnum++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BITS_PER_BITMAP_SECTOR (512 * 8)
|
bitmap |= (1 << (bitnum % 8));
|
||||||
|
|
||||||
/* Cannot use bitmap.c on big-endian machines. */
|
ret = bdrv_pwrite_sync(bs->file, offset, &bitmap, sizeof(bitmap));
|
||||||
static int cow_test_bit(int64_t bitnum, const uint8_t *bitmap)
|
if (ret < 0) {
|
||||||
{
|
return ret;
|
||||||
return (bitmap[bitnum / 8] & (1 << (bitnum & 7))) != 0;
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cow_find_streak(const uint8_t *bitmap, int value, int start, int nb_sectors)
|
static inline int is_bit_set(BlockDriverState *bs, int64_t bitnum)
|
||||||
{
|
{
|
||||||
int streak_value = value ? 0xFF : 0;
|
uint64_t offset = sizeof(struct cow_header_v2) + bitnum / 8;
|
||||||
int last = MIN(start + nb_sectors, BITS_PER_BITMAP_SECTOR);
|
uint8_t bitmap;
|
||||||
int bitnum = start;
|
int ret;
|
||||||
while (bitnum < last) {
|
|
||||||
if ((bitnum & 7) == 0 && bitmap[bitnum / 8] == streak_value) {
|
ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap));
|
||||||
bitnum += 8;
|
if (ret < 0) {
|
||||||
continue;
|
return ret;
|
||||||
}
|
}
|
||||||
if (cow_test_bit(bitnum, bitmap) == value) {
|
|
||||||
bitnum++;
|
return !!(bitmap & (1 << (bitnum % 8)));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return MIN(bitnum, last) - start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return true if first block has been changed (ie. current version is
|
/* Return true if first block has been changed (ie. current version is
|
||||||
@@ -151,100 +146,40 @@ static int cow_find_streak(const uint8_t *bitmap, int value, int start, int nb_s
|
|||||||
static int coroutine_fn cow_co_is_allocated(BlockDriverState *bs,
|
static int coroutine_fn cow_co_is_allocated(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors, int *num_same)
|
int64_t sector_num, int nb_sectors, int *num_same)
|
||||||
{
|
{
|
||||||
int64_t bitnum = sector_num + sizeof(struct cow_header_v2) * 8;
|
int changed;
|
||||||
uint64_t offset = (bitnum / 8) & -BDRV_SECTOR_SIZE;
|
|
||||||
bool first = true;
|
|
||||||
int changed = 0, same = 0;
|
|
||||||
|
|
||||||
do {
|
if (nb_sectors == 0) {
|
||||||
int ret;
|
*num_same = nb_sectors;
|
||||||
uint8_t bitmap[BDRV_SECTOR_SIZE];
|
return 0;
|
||||||
|
|
||||||
bitnum &= BITS_PER_BITMAP_SECTOR - 1;
|
|
||||||
int sector_bits = MIN(nb_sectors, BITS_PER_BITMAP_SECTOR - bitnum);
|
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap));
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first) {
|
changed = is_bit_set(bs, sector_num);
|
||||||
changed = cow_test_bit(bitnum, bitmap);
|
if (changed < 0) {
|
||||||
first = false;
|
return 0; /* XXX: how to return I/O errors? */
|
||||||
}
|
}
|
||||||
|
|
||||||
same += cow_find_streak(bitmap, changed, bitnum, nb_sectors);
|
for (*num_same = 1; *num_same < nb_sectors; (*num_same)++) {
|
||||||
|
if (is_bit_set(bs, sector_num + *num_same) != changed)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
bitnum += sector_bits;
|
|
||||||
nb_sectors -= sector_bits;
|
|
||||||
offset += BDRV_SECTOR_SIZE;
|
|
||||||
} while (nb_sectors);
|
|
||||||
|
|
||||||
*num_same = same;
|
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t coroutine_fn cow_co_get_block_status(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors, int *num_same)
|
|
||||||
{
|
|
||||||
BDRVCowState *s = bs->opaque;
|
|
||||||
int ret = cow_co_is_allocated(bs, sector_num, nb_sectors, num_same);
|
|
||||||
int64_t offset = s->cow_sectors_offset + (sector_num << BDRV_SECTOR_BITS);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return (ret ? BDRV_BLOCK_DATA : 0) | offset | BDRV_BLOCK_OFFSET_VALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cow_update_bitmap(BlockDriverState *bs, int64_t sector_num,
|
static int cow_update_bitmap(BlockDriverState *bs, int64_t sector_num,
|
||||||
int nb_sectors)
|
int nb_sectors)
|
||||||
{
|
{
|
||||||
int64_t bitnum = sector_num + sizeof(struct cow_header_v2) * 8;
|
int error = 0;
|
||||||
uint64_t offset = (bitnum / 8) & -BDRV_SECTOR_SIZE;
|
int i;
|
||||||
bool first = true;
|
|
||||||
int sector_bits;
|
|
||||||
|
|
||||||
for ( ; nb_sectors;
|
for (i = 0; i < nb_sectors; i++) {
|
||||||
bitnum += sector_bits,
|
error = cow_set_bit(bs, sector_num + i);
|
||||||
nb_sectors -= sector_bits,
|
if (error) {
|
||||||
offset += BDRV_SECTOR_SIZE) {
|
break;
|
||||||
int ret, set;
|
|
||||||
uint8_t bitmap[BDRV_SECTOR_SIZE];
|
|
||||||
|
|
||||||
bitnum &= BITS_PER_BITMAP_SECTOR - 1;
|
|
||||||
sector_bits = MIN(nb_sectors, BITS_PER_BITMAP_SECTOR - bitnum);
|
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap));
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skip over any already set bits */
|
|
||||||
set = cow_find_streak(bitmap, 1, bitnum, sector_bits);
|
|
||||||
bitnum += set;
|
|
||||||
sector_bits -= set;
|
|
||||||
nb_sectors -= set;
|
|
||||||
if (!sector_bits) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
ret = bdrv_flush(bs->file);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cow_set_bits(bitmap, bitnum, sector_bits);
|
|
||||||
|
|
||||||
ret = bdrv_pwrite(bs->file, offset, &bitmap, sizeof(bitmap));
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn cow_read(BlockDriverState *bs, int64_t sector_num,
|
static int coroutine_fn cow_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
@@ -254,11 +189,7 @@ static int coroutine_fn cow_read(BlockDriverState *bs, int64_t sector_num,
|
|||||||
int ret, n;
|
int ret, n;
|
||||||
|
|
||||||
while (nb_sectors > 0) {
|
while (nb_sectors > 0) {
|
||||||
ret = cow_co_is_allocated(bs, sector_num, nb_sectors, &n);
|
if (bdrv_co_is_allocated(bs, sector_num, nb_sectors, &n)) {
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
if (ret) {
|
|
||||||
ret = bdrv_pread(bs->file,
|
ret = bdrv_pread(bs->file,
|
||||||
s->cow_sectors_offset + sector_num * 512,
|
s->cow_sectors_offset + sector_num * 512,
|
||||||
buf, n * 512);
|
buf, n * 512);
|
||||||
@@ -324,14 +255,12 @@ static void cow_close(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cow_create(const char *filename, QEMUOptionParameter *options,
|
static int cow_create(const char *filename, QEMUOptionParameter *options)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
struct cow_header_v2 cow_header;
|
struct cow_header_v2 cow_header;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int64_t image_sectors = 0;
|
int64_t image_sectors = 0;
|
||||||
const char *image_filename = NULL;
|
const char *image_filename = NULL;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
BlockDriverState *cow_bs;
|
BlockDriverState *cow_bs;
|
||||||
|
|
||||||
@@ -345,17 +274,13 @@ static int cow_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
options++;
|
options++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = bdrv_create_file(filename, options, &local_err);
|
ret = bdrv_create_file(filename, options);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
cow_bs = NULL;
|
ret = bdrv_file_open(&cow_bs, filename, BDRV_O_RDWR);
|
||||||
ret = bdrv_open(&cow_bs, filename, NULL, NULL,
|
|
||||||
BDRV_O_RDWR | BDRV_O_PROTOCOL, NULL, &local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +314,7 @@ static int cow_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
}
|
}
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
bdrv_unref(cow_bs);
|
bdrv_delete(cow_bs);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,11 +340,10 @@ static BlockDriver bdrv_cow = {
|
|||||||
.bdrv_open = cow_open,
|
.bdrv_open = cow_open,
|
||||||
.bdrv_close = cow_close,
|
.bdrv_close = cow_close,
|
||||||
.bdrv_create = cow_create,
|
.bdrv_create = cow_create,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
|
||||||
|
|
||||||
.bdrv_read = cow_co_read,
|
.bdrv_read = cow_co_read,
|
||||||
.bdrv_write = cow_co_write,
|
.bdrv_write = cow_co_write,
|
||||||
.bdrv_co_get_block_status = cow_co_get_block_status,
|
.bdrv_co_is_allocated = cow_co_is_allocated,
|
||||||
|
|
||||||
.create_options = cow_create_options,
|
.create_options = cow_create_options,
|
||||||
};
|
};
|
||||||
|
|||||||
225
block/curl.c
225
block/curl.c
@@ -34,11 +34,6 @@
|
|||||||
#define DPRINTF(fmt, ...) do { } while (0)
|
#define DPRINTF(fmt, ...) do { } while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if LIBCURL_VERSION_NUM >= 0x071000
|
|
||||||
/* The multi interface timer callback was introduced in 7.16.0 */
|
|
||||||
#define NEED_CURL_TIMER_CALLBACK
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PROTOCOLS (CURLPROTO_HTTP | CURLPROTO_HTTPS | \
|
#define PROTOCOLS (CURLPROTO_HTTP | CURLPROTO_HTTPS | \
|
||||||
CURLPROTO_FTP | CURLPROTO_FTPS | \
|
CURLPROTO_FTP | CURLPROTO_FTPS | \
|
||||||
CURLPROTO_TFTP)
|
CURLPROTO_TFTP)
|
||||||
@@ -82,33 +77,15 @@ typedef struct CURLState
|
|||||||
|
|
||||||
typedef struct BDRVCURLState {
|
typedef struct BDRVCURLState {
|
||||||
CURLM *multi;
|
CURLM *multi;
|
||||||
QEMUTimer timer;
|
|
||||||
size_t len;
|
size_t len;
|
||||||
CURLState states[CURL_NUM_STATES];
|
CURLState states[CURL_NUM_STATES];
|
||||||
char *url;
|
char *url;
|
||||||
size_t readahead_size;
|
size_t readahead_size;
|
||||||
bool accept_range;
|
|
||||||
} BDRVCURLState;
|
} BDRVCURLState;
|
||||||
|
|
||||||
static void curl_clean_state(CURLState *s);
|
static void curl_clean_state(CURLState *s);
|
||||||
static void curl_multi_do(void *arg);
|
static void curl_multi_do(void *arg);
|
||||||
|
static int curl_aio_flush(void *opaque);
|
||||||
#ifdef NEED_CURL_TIMER_CALLBACK
|
|
||||||
static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
|
|
||||||
{
|
|
||||||
BDRVCURLState *s = opaque;
|
|
||||||
|
|
||||||
DPRINTF("CURL: timer callback timeout_ms %ld\n", timeout_ms);
|
|
||||||
if (timeout_ms == -1) {
|
|
||||||
timer_del(&s->timer);
|
|
||||||
} else {
|
|
||||||
int64_t timeout_ns = (int64_t)timeout_ms * 1000 * 1000;
|
|
||||||
timer_mod(&s->timer,
|
|
||||||
qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + timeout_ns);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
|
static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
|
||||||
void *s, void *sp)
|
void *s, void *sp)
|
||||||
@@ -116,31 +93,31 @@ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
|
|||||||
DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, fd);
|
DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, fd);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case CURL_POLL_IN:
|
case CURL_POLL_IN:
|
||||||
qemu_aio_set_fd_handler(fd, curl_multi_do, NULL, s);
|
qemu_aio_set_fd_handler(fd, curl_multi_do, NULL, curl_aio_flush, s);
|
||||||
break;
|
break;
|
||||||
case CURL_POLL_OUT:
|
case CURL_POLL_OUT:
|
||||||
qemu_aio_set_fd_handler(fd, NULL, curl_multi_do, s);
|
qemu_aio_set_fd_handler(fd, NULL, curl_multi_do, curl_aio_flush, s);
|
||||||
break;
|
break;
|
||||||
case CURL_POLL_INOUT:
|
case CURL_POLL_INOUT:
|
||||||
qemu_aio_set_fd_handler(fd, curl_multi_do, curl_multi_do, s);
|
qemu_aio_set_fd_handler(fd, curl_multi_do, curl_multi_do,
|
||||||
|
curl_aio_flush, s);
|
||||||
break;
|
break;
|
||||||
case CURL_POLL_REMOVE:
|
case CURL_POLL_REMOVE:
|
||||||
qemu_aio_set_fd_handler(fd, NULL, NULL, NULL);
|
qemu_aio_set_fd_handler(fd, NULL, NULL, NULL, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
static size_t curl_size_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||||
{
|
{
|
||||||
BDRVCURLState *s = opaque;
|
CURLState *s = ((CURLState*)opaque);
|
||||||
size_t realsize = size * nmemb;
|
size_t realsize = size * nmemb;
|
||||||
const char *accept_line = "Accept-Ranges: bytes";
|
size_t fsize;
|
||||||
|
|
||||||
if (realsize >= strlen(accept_line)
|
if(sscanf(ptr, "Content-Length: %zd", &fsize) == 1) {
|
||||||
&& strncmp((char *)ptr, accept_line, strlen(accept_line)) == 0) {
|
s->s->len = fsize;
|
||||||
s->accept_range = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return realsize;
|
return realsize;
|
||||||
@@ -237,10 +214,20 @@ static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len,
|
|||||||
return FIND_RET_NONE;
|
return FIND_RET_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_multi_read(BDRVCURLState *s)
|
static void curl_multi_do(void *arg)
|
||||||
{
|
{
|
||||||
|
BDRVCURLState *s = (BDRVCURLState *)arg;
|
||||||
|
int running;
|
||||||
|
int r;
|
||||||
int msgs_in_queue;
|
int msgs_in_queue;
|
||||||
|
|
||||||
|
if (!s->multi)
|
||||||
|
return;
|
||||||
|
|
||||||
|
do {
|
||||||
|
r = curl_multi_socket_all(s->multi, &running);
|
||||||
|
} while(r == CURLM_CALL_MULTI_PERFORM);
|
||||||
|
|
||||||
/* Try to find done transfers, so we can free the easy
|
/* Try to find done transfers, so we can free the easy
|
||||||
* handle again. */
|
* handle again. */
|
||||||
do {
|
do {
|
||||||
@@ -284,41 +271,6 @@ static void curl_multi_read(BDRVCURLState *s)
|
|||||||
} while(msgs_in_queue);
|
} while(msgs_in_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_multi_do(void *arg)
|
|
||||||
{
|
|
||||||
BDRVCURLState *s = (BDRVCURLState *)arg;
|
|
||||||
int running;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
if (!s->multi) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
r = curl_multi_socket_all(s->multi, &running);
|
|
||||||
} while(r == CURLM_CALL_MULTI_PERFORM);
|
|
||||||
|
|
||||||
curl_multi_read(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void curl_multi_timeout_do(void *arg)
|
|
||||||
{
|
|
||||||
#ifdef NEED_CURL_TIMER_CALLBACK
|
|
||||||
BDRVCURLState *s = (BDRVCURLState *)arg;
|
|
||||||
int running;
|
|
||||||
|
|
||||||
if (!s->multi) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
|
|
||||||
|
|
||||||
curl_multi_read(s);
|
|
||||||
#else
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static CURLState *curl_init_state(BDRVCURLState *s)
|
static CURLState *curl_init_state(BDRVCURLState *s)
|
||||||
{
|
{
|
||||||
CURLState *state = NULL;
|
CURLState *state = NULL;
|
||||||
@@ -388,9 +340,11 @@ static void curl_clean_state(CURLState *s)
|
|||||||
s->in_use = 0;
|
s->in_use = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_parse_filename(const char *filename, QDict *options,
|
static int curl_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
|
BDRVCURLState *s = bs->opaque;
|
||||||
|
CURLState *state = NULL;
|
||||||
|
double d;
|
||||||
|
|
||||||
#define RA_OPTSTR ":readahead="
|
#define RA_OPTSTR ":readahead="
|
||||||
char *file;
|
char *file;
|
||||||
@@ -398,17 +352,19 @@ static void curl_parse_filename(const char *filename, QDict *options,
|
|||||||
const char *ra_val;
|
const char *ra_val;
|
||||||
int parse_state = 0;
|
int parse_state = 0;
|
||||||
|
|
||||||
|
static int inited = 0;
|
||||||
|
|
||||||
file = g_strdup(filename);
|
file = g_strdup(filename);
|
||||||
|
s->readahead_size = READ_AHEAD_SIZE;
|
||||||
|
|
||||||
/* Parse a trailing ":readahead=#:" param, if present. */
|
/* Parse a trailing ":readahead=#:" param, if present. */
|
||||||
ra = file + strlen(file) - 1;
|
ra = file + strlen(file) - 1;
|
||||||
while (ra >= file) {
|
while (ra >= file) {
|
||||||
if (parse_state == 0) {
|
if (parse_state == 0) {
|
||||||
if (*ra == ':') {
|
if (*ra == ':')
|
||||||
parse_state++;
|
parse_state++;
|
||||||
} else {
|
else
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
} else if (parse_state == 1) {
|
} else if (parse_state == 1) {
|
||||||
if (*ra > '9' || *ra < '0') {
|
if (*ra > '9' || *ra < '0') {
|
||||||
char *opt_start = ra - strlen(RA_OPTSTR) + 1;
|
char *opt_start = ra - strlen(RA_OPTSTR) + 1;
|
||||||
@@ -417,129 +373,60 @@ static void curl_parse_filename(const char *filename, QDict *options,
|
|||||||
ra_val = ra + 1;
|
ra_val = ra + 1;
|
||||||
ra -= strlen(RA_OPTSTR) - 1;
|
ra -= strlen(RA_OPTSTR) - 1;
|
||||||
*ra = '\0';
|
*ra = '\0';
|
||||||
qdict_put(options, "readahead", qstring_from_str(ra_val));
|
s->readahead_size = atoi(ra_val);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ra--;
|
ra--;
|
||||||
}
|
}
|
||||||
|
|
||||||
qdict_put(options, "url", qstring_from_str(file));
|
|
||||||
|
|
||||||
g_free(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
|
||||||
.name = "curl",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = "url",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "URL to open",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "readahead",
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Readahead size",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BDRVCURLState *s = bs->opaque;
|
|
||||||
CURLState *state = NULL;
|
|
||||||
QemuOpts *opts;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
const char *file;
|
|
||||||
double d;
|
|
||||||
|
|
||||||
static int inited = 0;
|
|
||||||
|
|
||||||
if (flags & BDRV_O_RDWR) {
|
|
||||||
error_setg(errp, "curl block device does not support writes");
|
|
||||||
return -EROFS;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
goto out_noclean;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->readahead_size = qemu_opt_get_size(opts, "readahead", READ_AHEAD_SIZE);
|
|
||||||
if ((s->readahead_size & 0x1ff) != 0) {
|
if ((s->readahead_size & 0x1ff) != 0) {
|
||||||
error_setg(errp, "HTTP_READAHEAD_SIZE %zd is not a multiple of 512",
|
fprintf(stderr, "HTTP_READAHEAD_SIZE %zd is not a multiple of 512\n",
|
||||||
s->readahead_size);
|
s->readahead_size);
|
||||||
goto out_noclean;
|
goto out_noclean;
|
||||||
}
|
}
|
||||||
|
|
||||||
file = qemu_opt_get(opts, "url");
|
|
||||||
if (file == NULL) {
|
|
||||||
error_setg(errp, "curl block driver requires an 'url' option");
|
|
||||||
goto out_noclean;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inited) {
|
if (!inited) {
|
||||||
curl_global_init(CURL_GLOBAL_ALL);
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
inited = 1;
|
inited = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF("CURL: Opening %s\n", file);
|
DPRINTF("CURL: Opening %s\n", file);
|
||||||
s->url = g_strdup(file);
|
s->url = file;
|
||||||
state = curl_init_state(s);
|
state = curl_init_state(s);
|
||||||
if (!state)
|
if (!state)
|
||||||
goto out_noclean;
|
goto out_noclean;
|
||||||
|
|
||||||
// Get file size
|
// Get file size
|
||||||
|
|
||||||
s->accept_range = false;
|
|
||||||
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1);
|
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1);
|
||||||
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION,
|
curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_size_cb);
|
||||||
curl_header_cb);
|
|
||||||
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s);
|
|
||||||
if (curl_easy_perform(state->curl))
|
if (curl_easy_perform(state->curl))
|
||||||
goto out;
|
goto out;
|
||||||
curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d);
|
curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d);
|
||||||
|
curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_read_cb);
|
||||||
|
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 0);
|
||||||
if (d)
|
if (d)
|
||||||
s->len = (size_t)d;
|
s->len = (size_t)d;
|
||||||
else if(!s->len)
|
else if(!s->len)
|
||||||
goto out;
|
goto out;
|
||||||
if ((!strncasecmp(s->url, "http://", strlen("http://"))
|
|
||||||
|| !strncasecmp(s->url, "https://", strlen("https://")))
|
|
||||||
&& !s->accept_range) {
|
|
||||||
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
|
|
||||||
"Server does not support 'range' (byte ranges).");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
DPRINTF("CURL: Size = %zd\n", s->len);
|
DPRINTF("CURL: Size = %zd\n", s->len);
|
||||||
|
|
||||||
curl_clean_state(state);
|
curl_clean_state(state);
|
||||||
curl_easy_cleanup(state->curl);
|
curl_easy_cleanup(state->curl);
|
||||||
state->curl = NULL;
|
state->curl = NULL;
|
||||||
|
|
||||||
aio_timer_init(bdrv_get_aio_context(bs), &s->timer,
|
|
||||||
QEMU_CLOCK_REALTIME, SCALE_NS,
|
|
||||||
curl_multi_timeout_do, s);
|
|
||||||
|
|
||||||
// Now we know the file exists and its size, so let's
|
// Now we know the file exists and its size, so let's
|
||||||
// initialize the multi interface!
|
// initialize the multi interface!
|
||||||
|
|
||||||
s->multi = curl_multi_init();
|
s->multi = curl_multi_init();
|
||||||
curl_multi_setopt( s->multi, CURLMOPT_SOCKETDATA, s);
|
curl_multi_setopt( s->multi, CURLMOPT_SOCKETDATA, s);
|
||||||
curl_multi_setopt( s->multi, CURLMOPT_SOCKETFUNCTION, curl_sock_cb );
|
curl_multi_setopt( s->multi, CURLMOPT_SOCKETFUNCTION, curl_sock_cb );
|
||||||
#ifdef NEED_CURL_TIMER_CALLBACK
|
|
||||||
curl_multi_setopt(s->multi, CURLMOPT_TIMERDATA, s);
|
|
||||||
curl_multi_setopt(s->multi, CURLMOPT_TIMERFUNCTION, curl_timer_cb);
|
|
||||||
#endif
|
|
||||||
curl_multi_do(s);
|
curl_multi_do(s);
|
||||||
|
|
||||||
qemu_opts_del(opts);
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@@ -547,11 +434,25 @@ out:
|
|||||||
curl_easy_cleanup(state->curl);
|
curl_easy_cleanup(state->curl);
|
||||||
state->curl = NULL;
|
state->curl = NULL;
|
||||||
out_noclean:
|
out_noclean:
|
||||||
g_free(s->url);
|
g_free(file);
|
||||||
qemu_opts_del(opts);
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int curl_aio_flush(void *opaque)
|
||||||
|
{
|
||||||
|
BDRVCURLState *s = opaque;
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
for (i=0; i < CURL_NUM_STATES; i++) {
|
||||||
|
for(j=0; j < CURL_NUM_ACB; j++) {
|
||||||
|
if (s->states[i].acb[j]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void curl_aio_cancel(BlockDriverAIOCB *blockacb)
|
static void curl_aio_cancel(BlockDriverAIOCB *blockacb)
|
||||||
{
|
{
|
||||||
// Do we have to implement canceling? Seems to work without...
|
// Do we have to implement canceling? Seems to work without...
|
||||||
@@ -631,6 +532,12 @@ static BlockDriverAIOCB *curl_aio_readv(BlockDriverState *bs,
|
|||||||
acb->nb_sectors = nb_sectors;
|
acb->nb_sectors = nb_sectors;
|
||||||
|
|
||||||
acb->bh = qemu_bh_new(curl_readv_bh_cb, acb);
|
acb->bh = qemu_bh_new(curl_readv_bh_cb, acb);
|
||||||
|
|
||||||
|
if (!acb->bh) {
|
||||||
|
DPRINTF("CURL: qemu_bh_new failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
qemu_bh_schedule(acb->bh);
|
qemu_bh_schedule(acb->bh);
|
||||||
return &acb->common;
|
return &acb->common;
|
||||||
}
|
}
|
||||||
@@ -655,9 +562,6 @@ static void curl_close(BlockDriverState *bs)
|
|||||||
}
|
}
|
||||||
if (s->multi)
|
if (s->multi)
|
||||||
curl_multi_cleanup(s->multi);
|
curl_multi_cleanup(s->multi);
|
||||||
|
|
||||||
timer_del(&s->timer);
|
|
||||||
|
|
||||||
g_free(s->url);
|
g_free(s->url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,7 +576,6 @@ static BlockDriver bdrv_http = {
|
|||||||
.protocol_name = "http",
|
.protocol_name = "http",
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVCURLState),
|
.instance_size = sizeof(BDRVCURLState),
|
||||||
.bdrv_parse_filename = curl_parse_filename,
|
|
||||||
.bdrv_file_open = curl_open,
|
.bdrv_file_open = curl_open,
|
||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
@@ -685,7 +588,6 @@ static BlockDriver bdrv_https = {
|
|||||||
.protocol_name = "https",
|
.protocol_name = "https",
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVCURLState),
|
.instance_size = sizeof(BDRVCURLState),
|
||||||
.bdrv_parse_filename = curl_parse_filename,
|
|
||||||
.bdrv_file_open = curl_open,
|
.bdrv_file_open = curl_open,
|
||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
@@ -698,7 +600,6 @@ static BlockDriver bdrv_ftp = {
|
|||||||
.protocol_name = "ftp",
|
.protocol_name = "ftp",
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVCURLState),
|
.instance_size = sizeof(BDRVCURLState),
|
||||||
.bdrv_parse_filename = curl_parse_filename,
|
|
||||||
.bdrv_file_open = curl_open,
|
.bdrv_file_open = curl_open,
|
||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
@@ -711,7 +612,6 @@ static BlockDriver bdrv_ftps = {
|
|||||||
.protocol_name = "ftps",
|
.protocol_name = "ftps",
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVCURLState),
|
.instance_size = sizeof(BDRVCURLState),
|
||||||
.bdrv_parse_filename = curl_parse_filename,
|
|
||||||
.bdrv_file_open = curl_open,
|
.bdrv_file_open = curl_open,
|
||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
@@ -724,7 +624,6 @@ static BlockDriver bdrv_tftp = {
|
|||||||
.protocol_name = "tftp",
|
.protocol_name = "tftp",
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVCURLState),
|
.instance_size = sizeof(BDRVCURLState),
|
||||||
.bdrv_parse_filename = curl_parse_filename,
|
|
||||||
.bdrv_file_open = curl_open,
|
.bdrv_file_open = curl_open,
|
||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
|
|||||||
572
block/dictzip.c
Normal file
572
block/dictzip.c
Normal file
@@ -0,0 +1,572 @@
|
|||||||
|
/*
|
||||||
|
* DictZip Block driver for dictzip enabled gzip files
|
||||||
|
*
|
||||||
|
* Use the "dictzip" tool from the "dictd" package to create gzip files that
|
||||||
|
* contain the extra DictZip headers.
|
||||||
|
*
|
||||||
|
* dictzip(1) is a compression program which creates compressed files in the
|
||||||
|
* gzip format (see RFC 1952). However, unlike gzip(1), dictzip(1) compresses
|
||||||
|
* the file in pieces and stores an index to the pieces in the gzip header.
|
||||||
|
* This allows random access to the file at the granularity of the compressed
|
||||||
|
* pieces (currently about 64kB) while maintaining good compression ratios
|
||||||
|
* (within 5% of the expected ratio for dictionary data).
|
||||||
|
* dictd(8) uses files stored in this format.
|
||||||
|
*
|
||||||
|
* For details on DictZip see http://dict.org/.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 Alexander Graf <agraf@suse.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-common.h"
|
||||||
|
#include "block/block_int.h"
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
// #define DEBUG
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define dprintf(fmt, ...) do { printf("dzip: " fmt, ## __VA_ARGS__); } while (0)
|
||||||
|
#else
|
||||||
|
#define dprintf(fmt, ...) do { } while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SECTOR_SIZE 512
|
||||||
|
#define Z_STREAM_COUNT 4
|
||||||
|
#define CACHE_COUNT 20
|
||||||
|
|
||||||
|
/* magic values */
|
||||||
|
|
||||||
|
#define GZ_MAGIC1 0x1f
|
||||||
|
#define GZ_MAGIC2 0x8b
|
||||||
|
#define DZ_MAGIC1 'R'
|
||||||
|
#define DZ_MAGIC2 'A'
|
||||||
|
|
||||||
|
#define GZ_FEXTRA 0x04 /* Optional field (random access index) */
|
||||||
|
#define GZ_FNAME 0x08 /* Original name */
|
||||||
|
#define GZ_COMMENT 0x10 /* Zero-terminated, human-readable comment */
|
||||||
|
#define GZ_FHCRC 0x02 /* Header CRC16 */
|
||||||
|
|
||||||
|
/* offsets */
|
||||||
|
|
||||||
|
#define GZ_ID 0 /* GZ_MAGIC (16bit) */
|
||||||
|
#define GZ_FLG 3 /* FLaGs (see above) */
|
||||||
|
#define GZ_XLEN 10 /* eXtra LENgth (16bit) */
|
||||||
|
#define GZ_SI 12 /* Subfield ID (16bit) */
|
||||||
|
#define GZ_VERSION 16 /* Version for subfield format */
|
||||||
|
#define GZ_CHUNKSIZE 18 /* Chunk size (16bit) */
|
||||||
|
#define GZ_CHUNKCNT 20 /* Number of chunks (16bit) */
|
||||||
|
#define GZ_RNDDATA 22 /* Random access data (16bit) */
|
||||||
|
|
||||||
|
#define GZ_99_CHUNKSIZE 18 /* Chunk size (32bit) */
|
||||||
|
#define GZ_99_CHUNKCNT 22 /* Number of chunks (32bit) */
|
||||||
|
#define GZ_99_FILESIZE 26 /* Size of unpacked file (64bit) */
|
||||||
|
#define GZ_99_RNDDATA 34 /* Random access data (32bit) */
|
||||||
|
|
||||||
|
struct BDRVDictZipState;
|
||||||
|
|
||||||
|
typedef struct DictZipAIOCB {
|
||||||
|
BlockDriverAIOCB common;
|
||||||
|
struct BDRVDictZipState *s;
|
||||||
|
QEMUIOVector *qiov; /* QIOV of the original request */
|
||||||
|
QEMUIOVector *qiov_gz; /* QIOV of the gz subrequest */
|
||||||
|
QEMUBH *bh; /* BH for cache */
|
||||||
|
z_stream *zStream; /* stream to use for decoding */
|
||||||
|
int zStream_id; /* stream id of the above pointer */
|
||||||
|
size_t start; /* offset into the uncompressed file */
|
||||||
|
size_t len; /* uncompressed bytes to read */
|
||||||
|
uint8_t *gzipped; /* the gzipped data */
|
||||||
|
uint8_t *buf; /* cached result */
|
||||||
|
size_t gz_len; /* amount of gzip data */
|
||||||
|
size_t gz_start; /* uncompressed starting point of gzip data */
|
||||||
|
uint64_t offset; /* offset for "start" into the uncompressed chunk */
|
||||||
|
int chunks_len; /* amount of uncompressed data in all gzip data */
|
||||||
|
} DictZipAIOCB;
|
||||||
|
|
||||||
|
typedef struct dict_cache {
|
||||||
|
size_t start;
|
||||||
|
size_t len;
|
||||||
|
uint8_t *buf;
|
||||||
|
} DictCache;
|
||||||
|
|
||||||
|
typedef struct BDRVDictZipState {
|
||||||
|
BlockDriverState *hd;
|
||||||
|
z_stream zStream[Z_STREAM_COUNT];
|
||||||
|
DictCache cache[CACHE_COUNT];
|
||||||
|
int cache_index;
|
||||||
|
uint8_t stream_in_use;
|
||||||
|
uint64_t chunk_len;
|
||||||
|
uint32_t chunk_cnt;
|
||||||
|
uint16_t *chunks;
|
||||||
|
uint32_t *chunks32;
|
||||||
|
uint64_t *offsets;
|
||||||
|
int64_t file_len;
|
||||||
|
} BDRVDictZipState;
|
||||||
|
|
||||||
|
static int dictzip_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||||
|
{
|
||||||
|
if (buf_size < 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* We match on every gzip file */
|
||||||
|
if ((buf[0] == GZ_MAGIC1) && (buf[1] == GZ_MAGIC2))
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int start_zStream(z_stream *zStream)
|
||||||
|
{
|
||||||
|
zStream->zalloc = NULL;
|
||||||
|
zStream->zfree = NULL;
|
||||||
|
zStream->opaque = NULL;
|
||||||
|
zStream->next_in = 0;
|
||||||
|
zStream->avail_in = 0;
|
||||||
|
zStream->next_out = NULL;
|
||||||
|
zStream->avail_out = 0;
|
||||||
|
|
||||||
|
return inflateInit2( zStream, -15 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dictzip_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
const char *err = "Unknown (read error?)";
|
||||||
|
uint8_t magic[2];
|
||||||
|
char buf[100];
|
||||||
|
uint8_t header_flags;
|
||||||
|
uint16_t chunk_len16;
|
||||||
|
uint16_t chunk_cnt16;
|
||||||
|
uint32_t chunk_len32;
|
||||||
|
uint16_t header_ver;
|
||||||
|
uint16_t tmp_short;
|
||||||
|
uint64_t offset;
|
||||||
|
int chunks_len;
|
||||||
|
int headerLength = GZ_XLEN - 1;
|
||||||
|
int rnd_offs;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
const char *fname = filename;
|
||||||
|
|
||||||
|
if (!strncmp(filename, "dzip://", 7))
|
||||||
|
fname += 7;
|
||||||
|
else if (!strncmp(filename, "dzip:", 5))
|
||||||
|
fname += 5;
|
||||||
|
|
||||||
|
ret = bdrv_file_open(&s->hd, fname, flags);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* initialize zlib streams */
|
||||||
|
for (i = 0; i < Z_STREAM_COUNT; i++) {
|
||||||
|
if (start_zStream( &s->zStream[i] ) != Z_OK) {
|
||||||
|
err = s->zStream[i].msg;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gzip header */
|
||||||
|
if (bdrv_pread(s->hd, GZ_ID, &magic, sizeof(magic)) != sizeof(magic))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (!((magic[0] == GZ_MAGIC1) && (magic[1] == GZ_MAGIC2))) {
|
||||||
|
err = "No gzip file";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dzip header */
|
||||||
|
if (bdrv_pread(s->hd, GZ_FLG, &header_flags, 1) != 1)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (!(header_flags & GZ_FEXTRA)) {
|
||||||
|
err = "Not a dictzip file (wrong flags)";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* extra length */
|
||||||
|
if (bdrv_pread(s->hd, GZ_XLEN, &tmp_short, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
headerLength += le16_to_cpu(tmp_short) + 2;
|
||||||
|
|
||||||
|
/* DictZip magic */
|
||||||
|
if (bdrv_pread(s->hd, GZ_SI, &magic, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (magic[0] != DZ_MAGIC1 || magic[1] != DZ_MAGIC2) {
|
||||||
|
err = "Not a dictzip file (missing extra magic)";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DictZip version */
|
||||||
|
if (bdrv_pread(s->hd, GZ_VERSION, &header_ver, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
header_ver = le16_to_cpu(header_ver);
|
||||||
|
|
||||||
|
switch (header_ver) {
|
||||||
|
case 1: /* Normal DictZip */
|
||||||
|
/* number of chunks */
|
||||||
|
if (bdrv_pread(s->hd, GZ_CHUNKSIZE, &chunk_len16, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->chunk_len = le16_to_cpu(chunk_len16);
|
||||||
|
|
||||||
|
/* chunk count */
|
||||||
|
if (bdrv_pread(s->hd, GZ_CHUNKCNT, &chunk_cnt16, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->chunk_cnt = le16_to_cpu(chunk_cnt16);
|
||||||
|
chunks_len = sizeof(short) * s->chunk_cnt;
|
||||||
|
rnd_offs = GZ_RNDDATA;
|
||||||
|
break;
|
||||||
|
case 99: /* Special Alex pigz version */
|
||||||
|
/* number of chunks */
|
||||||
|
if (bdrv_pread(s->hd, GZ_99_CHUNKSIZE, &chunk_len32, 4) != 4)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
dprintf("chunk len [%#x] = %d\n", GZ_99_CHUNKSIZE, chunk_len32);
|
||||||
|
s->chunk_len = le32_to_cpu(chunk_len32);
|
||||||
|
|
||||||
|
/* chunk count */
|
||||||
|
if (bdrv_pread(s->hd, GZ_99_CHUNKCNT, &s->chunk_cnt, 4) != 4)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->chunk_cnt = le32_to_cpu(s->chunk_cnt);
|
||||||
|
|
||||||
|
dprintf("chunk len | count = %d | %d\n", s->chunk_len, s->chunk_cnt);
|
||||||
|
|
||||||
|
/* file size */
|
||||||
|
if (bdrv_pread(s->hd, GZ_99_FILESIZE, &s->file_len, 8) != 8)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->file_len = le64_to_cpu(s->file_len);
|
||||||
|
chunks_len = sizeof(int) * s->chunk_cnt;
|
||||||
|
rnd_offs = GZ_99_RNDDATA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err = "Invalid DictZip version";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* random access data */
|
||||||
|
s->chunks = g_malloc(chunks_len);
|
||||||
|
if (header_ver == 99)
|
||||||
|
s->chunks32 = (uint32_t *)s->chunks;
|
||||||
|
|
||||||
|
if (bdrv_pread(s->hd, rnd_offs, s->chunks, chunks_len) != chunks_len)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
/* orig filename */
|
||||||
|
if (header_flags & GZ_FNAME) {
|
||||||
|
if (bdrv_pread(s->hd, headerLength + 1, buf, sizeof(buf)) != sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
buf[sizeof(buf) - 1] = '\0';
|
||||||
|
headerLength += strlen(buf) + 1;
|
||||||
|
|
||||||
|
if (strlen(buf) == sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
dprintf("filename: %s\n", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* comment field */
|
||||||
|
if (header_flags & GZ_COMMENT) {
|
||||||
|
if (bdrv_pread(s->hd, headerLength, buf, sizeof(buf)) != sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
buf[sizeof(buf) - 1] = '\0';
|
||||||
|
headerLength += strlen(buf) + 1;
|
||||||
|
|
||||||
|
if (strlen(buf) == sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
dprintf("comment: %s\n", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header_flags & GZ_FHCRC)
|
||||||
|
headerLength += 2;
|
||||||
|
|
||||||
|
/* uncompressed file length*/
|
||||||
|
if (!s->file_len) {
|
||||||
|
uint32_t file_len;
|
||||||
|
|
||||||
|
if (bdrv_pread(s->hd, bdrv_getlength(s->hd) - 4, &file_len, 4) != 4)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->file_len = le32_to_cpu(file_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute offsets */
|
||||||
|
s->offsets = g_malloc(sizeof( *s->offsets ) * s->chunk_cnt);
|
||||||
|
|
||||||
|
for (offset = headerLength + 1, i = 0; i < s->chunk_cnt; i++) {
|
||||||
|
s->offsets[i] = offset;
|
||||||
|
switch (header_ver) {
|
||||||
|
case 1:
|
||||||
|
offset += le16_to_cpu(s->chunks[i]);
|
||||||
|
break;
|
||||||
|
case 99:
|
||||||
|
offset += le32_to_cpu(s->chunks32[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dprintf("chunk %#x - %#x = offset %#x -> %#x\n", i * s->chunk_len, (i+1) * s->chunk_len, s->offsets[i], offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
fprintf(stderr, "DictZip: Error opening file: %s\n", err);
|
||||||
|
bdrv_delete(s->hd);
|
||||||
|
if (s->chunks)
|
||||||
|
g_free(s->chunks);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This callback gets invoked when we have the result in cache already */
|
||||||
|
static void dictzip_cache_cb(void *opaque)
|
||||||
|
{
|
||||||
|
DictZipAIOCB *acb = (DictZipAIOCB *)opaque;
|
||||||
|
|
||||||
|
qemu_iovec_from_buf(acb->qiov, 0, acb->buf, acb->len);
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
|
qemu_bh_delete(acb->bh);
|
||||||
|
qemu_aio_release(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This callback gets invoked by the underlying block reader when we have
|
||||||
|
* all compressed data. We uncompress in here. */
|
||||||
|
static void dictzip_read_cb(void *opaque, int ret)
|
||||||
|
{
|
||||||
|
DictZipAIOCB *acb = (DictZipAIOCB *)opaque;
|
||||||
|
struct BDRVDictZipState *s = acb->s;
|
||||||
|
uint8_t *buf;
|
||||||
|
DictCache *cache;
|
||||||
|
int r, i;
|
||||||
|
|
||||||
|
buf = g_malloc(acb->chunks_len);
|
||||||
|
|
||||||
|
/* try to find zlib stream for decoding */
|
||||||
|
do {
|
||||||
|
for (i = 0; i < Z_STREAM_COUNT; i++) {
|
||||||
|
if (!(s->stream_in_use & (1 << i))) {
|
||||||
|
s->stream_in_use |= (1 << i);
|
||||||
|
acb->zStream_id = i;
|
||||||
|
acb->zStream = &s->zStream[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(!acb->zStream);
|
||||||
|
|
||||||
|
/* sure, we could handle more streams, but this callback should be single
|
||||||
|
threaded and when it's not, we really want to know! */
|
||||||
|
assert(i == 0);
|
||||||
|
|
||||||
|
/* uncompress the chunk */
|
||||||
|
acb->zStream->next_in = acb->gzipped;
|
||||||
|
acb->zStream->avail_in = acb->gz_len;
|
||||||
|
acb->zStream->next_out = buf;
|
||||||
|
acb->zStream->avail_out = acb->chunks_len;
|
||||||
|
|
||||||
|
r = inflate( acb->zStream, Z_PARTIAL_FLUSH );
|
||||||
|
if ( (r != Z_OK) && (r != Z_STREAM_END) )
|
||||||
|
fprintf(stderr, "Error inflating: [%d] %s\n", r, acb->zStream->msg);
|
||||||
|
|
||||||
|
if ( r == Z_STREAM_END )
|
||||||
|
inflateReset(acb->zStream);
|
||||||
|
|
||||||
|
dprintf("inflating [%d] left: %d | %d bytes\n", r, acb->zStream->avail_in, acb->zStream->avail_out);
|
||||||
|
s->stream_in_use &= ~(1 << acb->zStream_id);
|
||||||
|
|
||||||
|
/* nofity the caller */
|
||||||
|
qemu_iovec_from_buf(acb->qiov, 0, buf + acb->offset, acb->len);
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
|
|
||||||
|
/* fill the cache */
|
||||||
|
cache = &s->cache[s->cache_index];
|
||||||
|
s->cache_index++;
|
||||||
|
if (s->cache_index == CACHE_COUNT)
|
||||||
|
s->cache_index = 0;
|
||||||
|
|
||||||
|
cache->len = 0;
|
||||||
|
if (cache->buf)
|
||||||
|
g_free(cache->buf);
|
||||||
|
cache->start = acb->gz_start;
|
||||||
|
cache->buf = buf;
|
||||||
|
cache->len = acb->chunks_len;
|
||||||
|
|
||||||
|
/* free occupied ressources */
|
||||||
|
g_free(acb->qiov_gz);
|
||||||
|
qemu_aio_release(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dictzip_aio_cancel(BlockDriverAIOCB *blockacb)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static const AIOCBInfo dictzip_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(DictZipAIOCB),
|
||||||
|
.cancel = dictzip_aio_cancel,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* This is where we get a request from a caller to read something */
|
||||||
|
static BlockDriverAIOCB *dictzip_aio_readv(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockDriverCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
DictZipAIOCB *acb;
|
||||||
|
QEMUIOVector *qiov_gz;
|
||||||
|
struct iovec *iov;
|
||||||
|
uint8_t *buf;
|
||||||
|
size_t start = sector_num * SECTOR_SIZE;
|
||||||
|
size_t len = nb_sectors * SECTOR_SIZE;
|
||||||
|
size_t end = start + len;
|
||||||
|
size_t gz_start;
|
||||||
|
size_t gz_len;
|
||||||
|
int64_t gz_sector_num;
|
||||||
|
int gz_nb_sectors;
|
||||||
|
int first_chunk, last_chunk;
|
||||||
|
int first_offset;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
acb = qemu_aio_get(&dictzip_aiocb_info, bs, cb, opaque);
|
||||||
|
if (!acb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Search Cache */
|
||||||
|
for (i = 0; i < CACHE_COUNT; i++) {
|
||||||
|
if (!s->cache[i].len)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((start >= s->cache[i].start) &&
|
||||||
|
(end <= (s->cache[i].start + s->cache[i].len))) {
|
||||||
|
acb->buf = s->cache[i].buf + (start - s->cache[i].start);
|
||||||
|
acb->len = len;
|
||||||
|
acb->qiov = qiov;
|
||||||
|
acb->bh = qemu_bh_new(dictzip_cache_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
|
|
||||||
|
return &acb->common;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No cache, so let's decode */
|
||||||
|
/* We need to read these chunks */
|
||||||
|
first_chunk = start / s->chunk_len;
|
||||||
|
first_offset = start - first_chunk * s->chunk_len;
|
||||||
|
last_chunk = end / s->chunk_len;
|
||||||
|
|
||||||
|
gz_start = s->offsets[first_chunk];
|
||||||
|
gz_len = 0;
|
||||||
|
for (i = first_chunk; i <= last_chunk; i++) {
|
||||||
|
if (s->chunks32)
|
||||||
|
gz_len += le32_to_cpu(s->chunks32[i]);
|
||||||
|
else
|
||||||
|
gz_len += le16_to_cpu(s->chunks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gz_sector_num = gz_start / SECTOR_SIZE;
|
||||||
|
gz_nb_sectors = (gz_len / SECTOR_SIZE);
|
||||||
|
|
||||||
|
/* account for tail and heads */
|
||||||
|
while ((gz_start + gz_len) > ((gz_sector_num + gz_nb_sectors) * SECTOR_SIZE))
|
||||||
|
gz_nb_sectors++;
|
||||||
|
|
||||||
|
/* Allocate qiov, iov and buf in one chunk so we only need to free qiov */
|
||||||
|
qiov_gz = g_malloc0(sizeof(QEMUIOVector) + sizeof(struct iovec) +
|
||||||
|
(gz_nb_sectors * SECTOR_SIZE));
|
||||||
|
iov = (struct iovec *)(((char *)qiov_gz) + sizeof(QEMUIOVector));
|
||||||
|
buf = ((uint8_t *)iov) + sizeof(struct iovec *);
|
||||||
|
|
||||||
|
/* Kick off the read by the backing file, so we can start decompressing */
|
||||||
|
iov->iov_base = (void *)buf;
|
||||||
|
iov->iov_len = gz_nb_sectors * 512;
|
||||||
|
qemu_iovec_init_external(qiov_gz, iov, 1);
|
||||||
|
|
||||||
|
dprintf("read %d - %d => %d - %d\n", start, end, gz_start, gz_start + gz_len);
|
||||||
|
|
||||||
|
acb->s = s;
|
||||||
|
acb->qiov = qiov;
|
||||||
|
acb->qiov_gz = qiov_gz;
|
||||||
|
acb->start = start;
|
||||||
|
acb->len = len;
|
||||||
|
acb->gzipped = buf + (gz_start % SECTOR_SIZE);
|
||||||
|
acb->gz_len = gz_len;
|
||||||
|
acb->gz_start = first_chunk * s->chunk_len;
|
||||||
|
acb->offset = first_offset;
|
||||||
|
acb->chunks_len = (last_chunk - first_chunk + 1) * s->chunk_len;
|
||||||
|
|
||||||
|
return bdrv_aio_readv(s->hd, gz_sector_num, qiov_gz, gz_nb_sectors,
|
||||||
|
dictzip_read_cb, acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dictzip_close(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < CACHE_COUNT; i++) {
|
||||||
|
if (!s->cache[i].len)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
g_free(s->cache[i].buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < Z_STREAM_COUNT; i++) {
|
||||||
|
inflateEnd(&s->zStream[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->chunks)
|
||||||
|
g_free(s->chunks);
|
||||||
|
|
||||||
|
if (s->offsets)
|
||||||
|
g_free(s->offsets);
|
||||||
|
|
||||||
|
dprintf("Close\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t dictzip_getlength(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
dprintf("getlength -> %ld\n", s->file_len);
|
||||||
|
return s->file_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriver bdrv_dictzip = {
|
||||||
|
.format_name = "dzip",
|
||||||
|
.protocol_name = "dzip",
|
||||||
|
|
||||||
|
.instance_size = sizeof(BDRVDictZipState),
|
||||||
|
.bdrv_file_open = dictzip_open,
|
||||||
|
.bdrv_close = dictzip_close,
|
||||||
|
.bdrv_getlength = dictzip_getlength,
|
||||||
|
.bdrv_probe = dictzip_probe,
|
||||||
|
|
||||||
|
.bdrv_aio_readv = dictzip_aio_readv,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void dictzip_block_init(void)
|
||||||
|
{
|
||||||
|
bdrv_register(&bdrv_dictzip);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_init(dictzip_block_init);
|
||||||
14
block/dmg.c
14
block/dmg.c
@@ -59,16 +59,9 @@ typedef struct BDRVDMGState {
|
|||||||
|
|
||||||
static int dmg_probe(const uint8_t *buf, int buf_size, const char *filename)
|
static int dmg_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||||
{
|
{
|
||||||
int len;
|
int len=strlen(filename);
|
||||||
|
if(len>4 && !strcmp(filename+len-4,".dmg"))
|
||||||
if (!filename) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = strlen(filename);
|
|
||||||
if (len > 4 && !strcmp(filename + len - 4, ".dmg")) {
|
|
||||||
return 2;
|
return 2;
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +124,7 @@ static void update_max_chunk_size(BDRVDMGState *s, uint32_t chunk,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
|
static int dmg_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVDMGState *s = bs->opaque;
|
BDRVDMGState *s = bs->opaque;
|
||||||
uint64_t info_begin, info_end, last_in_offset, last_out_offset;
|
uint64_t info_begin, info_end, last_in_offset, last_out_offset;
|
||||||
|
|||||||
589
block/gluster.c
589
block/gluster.c
@@ -3,26 +3,43 @@
|
|||||||
*
|
*
|
||||||
* Copyright (C) 2012 Bharata B Rao <bharata@linux.vnet.ibm.com>
|
* Copyright (C) 2012 Bharata B Rao <bharata@linux.vnet.ibm.com>
|
||||||
*
|
*
|
||||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
* Pipe handling mechanism in AIO implementation is derived from
|
||||||
* See the COPYING file in the top-level directory.
|
* block/rbd.c. Hence,
|
||||||
*
|
*
|
||||||
|
* Copyright (C) 2010-2011 Christian Brunner <chb@muc.de>,
|
||||||
|
* Josh Durgin <josh.durgin@dreamhost.com>
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||||
|
* the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||||
|
* GNU GPL, version 2 or (at your option) any later version.
|
||||||
*/
|
*/
|
||||||
#include <glusterfs/api/glfs.h>
|
#include <glusterfs/api/glfs.h>
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
|
#include "qemu/sockets.h"
|
||||||
#include "qemu/uri.h"
|
#include "qemu/uri.h"
|
||||||
|
|
||||||
typedef struct GlusterAIOCB {
|
typedef struct GlusterAIOCB {
|
||||||
|
BlockDriverAIOCB common;
|
||||||
int64_t size;
|
int64_t size;
|
||||||
int ret;
|
int ret;
|
||||||
|
bool *finished;
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
Coroutine *coroutine;
|
|
||||||
} GlusterAIOCB;
|
} GlusterAIOCB;
|
||||||
|
|
||||||
typedef struct BDRVGlusterState {
|
typedef struct BDRVGlusterState {
|
||||||
struct glfs *glfs;
|
struct glfs *glfs;
|
||||||
|
int fds[2];
|
||||||
struct glfs_fd *fd;
|
struct glfs_fd *fd;
|
||||||
|
int qemu_aio_count;
|
||||||
|
int event_reader_pos;
|
||||||
|
GlusterAIOCB *event_acb;
|
||||||
} BDRVGlusterState;
|
} BDRVGlusterState;
|
||||||
|
|
||||||
|
#define GLUSTER_FD_READ 0
|
||||||
|
#define GLUSTER_FD_WRITE 1
|
||||||
|
|
||||||
typedef struct GlusterConf {
|
typedef struct GlusterConf {
|
||||||
char *server;
|
char *server;
|
||||||
int port;
|
int port;
|
||||||
@@ -33,14 +50,12 @@ typedef struct GlusterConf {
|
|||||||
|
|
||||||
static void qemu_gluster_gconf_free(GlusterConf *gconf)
|
static void qemu_gluster_gconf_free(GlusterConf *gconf)
|
||||||
{
|
{
|
||||||
if (gconf) {
|
|
||||||
g_free(gconf->server);
|
g_free(gconf->server);
|
||||||
g_free(gconf->volname);
|
g_free(gconf->volname);
|
||||||
g_free(gconf->image);
|
g_free(gconf->image);
|
||||||
g_free(gconf->transport);
|
g_free(gconf->transport);
|
||||||
g_free(gconf);
|
g_free(gconf);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_volume_options(GlusterConf *gconf, char *path)
|
static int parse_volume_options(GlusterConf *gconf, char *path)
|
||||||
{
|
{
|
||||||
@@ -80,7 +95,7 @@ static int parse_volume_options(GlusterConf *gconf, char *path)
|
|||||||
* 'server' specifies the server where the volume file specification for
|
* 'server' specifies the server where the volume file specification for
|
||||||
* the given volume resides. This can be either hostname, ipv4 address
|
* the given volume resides. This can be either hostname, ipv4 address
|
||||||
* or ipv6 address. ipv6 address needs to be within square brackets [ ].
|
* or ipv6 address. ipv6 address needs to be within square brackets [ ].
|
||||||
* If transport type is 'unix', then 'server' field should not be specified.
|
* If transport type is 'unix', then 'server' field should not be specifed.
|
||||||
* The 'socket' field needs to be populated with the path to unix domain
|
* The 'socket' field needs to be populated with the path to unix domain
|
||||||
* socket.
|
* socket.
|
||||||
*
|
*
|
||||||
@@ -117,7 +132,7 @@ static int qemu_gluster_parseuri(GlusterConf *gconf, const char *filename)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* transport */
|
/* transport */
|
||||||
if (!uri->scheme || !strcmp(uri->scheme, "gluster")) {
|
if (!strcmp(uri->scheme, "gluster")) {
|
||||||
gconf->transport = g_strdup("tcp");
|
gconf->transport = g_strdup("tcp");
|
||||||
} else if (!strcmp(uri->scheme, "gluster+tcp")) {
|
} else if (!strcmp(uri->scheme, "gluster+tcp")) {
|
||||||
gconf->transport = g_strdup("tcp");
|
gconf->transport = g_strdup("tcp");
|
||||||
@@ -153,7 +168,7 @@ static int qemu_gluster_parseuri(GlusterConf *gconf, const char *filename)
|
|||||||
}
|
}
|
||||||
gconf->server = g_strdup(qp->p[0].value);
|
gconf->server = g_strdup(qp->p[0].value);
|
||||||
} else {
|
} else {
|
||||||
gconf->server = g_strdup(uri->server ? uri->server : "localhost");
|
gconf->server = g_strdup(uri->server);
|
||||||
gconf->port = uri->port;
|
gconf->port = uri->port;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,8 +180,7 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct glfs *qemu_gluster_init(GlusterConf *gconf, const char *filename,
|
static struct glfs *qemu_gluster_init(GlusterConf *gconf, const char *filename)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
struct glfs *glfs = NULL;
|
struct glfs *glfs = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
@@ -174,7 +188,7 @@ static struct glfs *qemu_gluster_init(GlusterConf *gconf, const char *filename,
|
|||||||
|
|
||||||
ret = qemu_gluster_parseuri(gconf, filename);
|
ret = qemu_gluster_parseuri(gconf, filename);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg(errp, "Usage: file=gluster[+transport]://[server[:port]]/"
|
error_report("Usage: file=gluster[+transport]://[server[:port]]/"
|
||||||
"volname/image[?socket=...]");
|
"volname/image[?socket=...]");
|
||||||
errno = -ret;
|
errno = -ret;
|
||||||
goto out;
|
goto out;
|
||||||
@@ -202,11 +216,9 @@ static struct glfs *qemu_gluster_init(GlusterConf *gconf, const char *filename,
|
|||||||
|
|
||||||
ret = glfs_init(glfs);
|
ret = glfs_init(glfs);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
error_setg_errno(errp, errno,
|
error_report("Gluster connection failed for server=%s port=%d "
|
||||||
"Gluster connection failed for server=%s port=%d "
|
"volume=%s image=%s transport=%s", gconf->server, gconf->port,
|
||||||
"volume=%s image=%s transport=%s", gconf->server,
|
gconf->volname, gconf->image, gconf->transport);
|
||||||
gconf->port, gconf->volname, gconf->image,
|
|
||||||
gconf->transport);
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
return glfs;
|
return glfs;
|
||||||
@@ -220,101 +232,96 @@ out:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qemu_gluster_complete_aio(void *opaque)
|
static void qemu_gluster_complete_aio(GlusterAIOCB *acb, BDRVGlusterState *s)
|
||||||
{
|
{
|
||||||
GlusterAIOCB *acb = (GlusterAIOCB *)opaque;
|
int ret;
|
||||||
|
bool *finished = acb->finished;
|
||||||
|
BlockDriverCompletionFunc *cb = acb->common.cb;
|
||||||
|
void *opaque = acb->common.opaque;
|
||||||
|
|
||||||
qemu_bh_delete(acb->bh);
|
if (!acb->ret || acb->ret == acb->size) {
|
||||||
acb->bh = NULL;
|
ret = 0; /* Success */
|
||||||
qemu_coroutine_enter(acb->coroutine, NULL);
|
} else if (acb->ret < 0) {
|
||||||
}
|
ret = acb->ret; /* Read/Write failed */
|
||||||
|
|
||||||
/*
|
|
||||||
* AIO callback routine called from GlusterFS thread.
|
|
||||||
*/
|
|
||||||
static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg)
|
|
||||||
{
|
|
||||||
GlusterAIOCB *acb = (GlusterAIOCB *)arg;
|
|
||||||
|
|
||||||
if (!ret || ret == acb->size) {
|
|
||||||
acb->ret = 0; /* Success */
|
|
||||||
} else if (ret < 0) {
|
|
||||||
acb->ret = ret; /* Read/Write failed */
|
|
||||||
} else {
|
} else {
|
||||||
acb->ret = -EIO; /* Partial read/write - fail it */
|
ret = -EIO; /* Partial read/write - fail it */
|
||||||
}
|
}
|
||||||
|
|
||||||
acb->bh = qemu_bh_new(qemu_gluster_complete_aio, acb);
|
s->qemu_aio_count--;
|
||||||
qemu_bh_schedule(acb->bh);
|
qemu_aio_release(acb);
|
||||||
|
cb(opaque, ret);
|
||||||
|
if (finished) {
|
||||||
|
*finished = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Convert to fine grained options */
|
static void qemu_gluster_aio_event_reader(void *opaque)
|
||||||
static QemuOptsList runtime_opts = {
|
|
||||||
.name = "gluster",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
{
|
||||||
.name = "filename",
|
BDRVGlusterState *s = opaque;
|
||||||
.type = QEMU_OPT_STRING,
|
ssize_t ret;
|
||||||
.help = "URL to the gluster image",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static void qemu_gluster_parse_flags(int bdrv_flags, int *open_flags)
|
do {
|
||||||
|
char *p = (char *)&s->event_acb;
|
||||||
|
|
||||||
|
ret = read(s->fds[GLUSTER_FD_READ], p + s->event_reader_pos,
|
||||||
|
sizeof(s->event_acb) - s->event_reader_pos);
|
||||||
|
if (ret > 0) {
|
||||||
|
s->event_reader_pos += ret;
|
||||||
|
if (s->event_reader_pos == sizeof(s->event_acb)) {
|
||||||
|
s->event_reader_pos = 0;
|
||||||
|
qemu_gluster_complete_aio(s->event_acb, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (ret < 0 && errno == EINTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qemu_gluster_aio_flush_cb(void *opaque)
|
||||||
{
|
{
|
||||||
assert(open_flags != NULL);
|
BDRVGlusterState *s = opaque;
|
||||||
|
|
||||||
*open_flags |= O_BINARY;
|
return (s->qemu_aio_count > 0);
|
||||||
|
|
||||||
if (bdrv_flags & BDRV_O_RDWR) {
|
|
||||||
*open_flags |= O_RDWR;
|
|
||||||
} else {
|
|
||||||
*open_flags |= O_RDONLY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((bdrv_flags & BDRV_O_NOCACHE)) {
|
static int qemu_gluster_open(BlockDriverState *bs, const char *filename,
|
||||||
*open_flags |= O_DIRECT;
|
int bdrv_flags)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int qemu_gluster_open(BlockDriverState *bs, QDict *options,
|
|
||||||
int bdrv_flags, Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVGlusterState *s = bs->opaque;
|
BDRVGlusterState *s = bs->opaque;
|
||||||
int open_flags = 0;
|
int open_flags = O_BINARY;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
GlusterConf *gconf = g_malloc0(sizeof(GlusterConf));
|
GlusterConf *gconf = g_malloc0(sizeof(GlusterConf));
|
||||||
QemuOpts *opts;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
const char *filename;
|
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
s->glfs = qemu_gluster_init(gconf, filename);
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = qemu_opt_get(opts, "filename");
|
|
||||||
|
|
||||||
s->glfs = qemu_gluster_init(gconf, filename, errp);
|
|
||||||
if (!s->glfs) {
|
if (!s->glfs) {
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_gluster_parse_flags(bdrv_flags, &open_flags);
|
if (bdrv_flags & BDRV_O_RDWR) {
|
||||||
|
open_flags |= O_RDWR;
|
||||||
|
} else {
|
||||||
|
open_flags |= O_RDONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((bdrv_flags & BDRV_O_NOCACHE)) {
|
||||||
|
open_flags |= O_DIRECT;
|
||||||
|
}
|
||||||
|
|
||||||
s->fd = glfs_open(s->glfs, gconf->image, open_flags);
|
s->fd = glfs_open(s->glfs, gconf->image, open_flags);
|
||||||
if (!s->fd) {
|
if (!s->fd) {
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = qemu_pipe(s->fds);
|
||||||
|
if (ret < 0) {
|
||||||
|
ret = -errno;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
fcntl(s->fds[GLUSTER_FD_READ], F_SETFL, O_NONBLOCK);
|
||||||
|
qemu_aio_set_fd_handler(s->fds[GLUSTER_FD_READ],
|
||||||
|
qemu_gluster_aio_event_reader, NULL, qemu_gluster_aio_flush_cb, s);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
qemu_opts_del(opts);
|
|
||||||
qemu_gluster_gconf_free(gconf);
|
qemu_gluster_gconf_free(gconf);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return ret;
|
return ret;
|
||||||
@@ -328,180 +335,24 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct BDRVGlusterReopenState {
|
|
||||||
struct glfs *glfs;
|
|
||||||
struct glfs_fd *fd;
|
|
||||||
} BDRVGlusterReopenState;
|
|
||||||
|
|
||||||
|
|
||||||
static int qemu_gluster_reopen_prepare(BDRVReopenState *state,
|
|
||||||
BlockReopenQueue *queue, Error **errp)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
BDRVGlusterReopenState *reop_s;
|
|
||||||
GlusterConf *gconf = NULL;
|
|
||||||
int open_flags = 0;
|
|
||||||
|
|
||||||
assert(state != NULL);
|
|
||||||
assert(state->bs != NULL);
|
|
||||||
|
|
||||||
state->opaque = g_malloc0(sizeof(BDRVGlusterReopenState));
|
|
||||||
reop_s = state->opaque;
|
|
||||||
|
|
||||||
qemu_gluster_parse_flags(state->flags, &open_flags);
|
|
||||||
|
|
||||||
gconf = g_malloc0(sizeof(GlusterConf));
|
|
||||||
|
|
||||||
reop_s->glfs = qemu_gluster_init(gconf, state->bs->filename, errp);
|
|
||||||
if (reop_s->glfs == NULL) {
|
|
||||||
ret = -errno;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
reop_s->fd = glfs_open(reop_s->glfs, gconf->image, open_flags);
|
|
||||||
if (reop_s->fd == NULL) {
|
|
||||||
/* reops->glfs will be cleaned up in _abort */
|
|
||||||
ret = -errno;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
exit:
|
|
||||||
/* state->opaque will be freed in either the _abort or _commit */
|
|
||||||
qemu_gluster_gconf_free(gconf);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void qemu_gluster_reopen_commit(BDRVReopenState *state)
|
|
||||||
{
|
|
||||||
BDRVGlusterReopenState *reop_s = state->opaque;
|
|
||||||
BDRVGlusterState *s = state->bs->opaque;
|
|
||||||
|
|
||||||
|
|
||||||
/* close the old */
|
|
||||||
if (s->fd) {
|
|
||||||
glfs_close(s->fd);
|
|
||||||
}
|
|
||||||
if (s->glfs) {
|
|
||||||
glfs_fini(s->glfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* use the newly opened image / connection */
|
|
||||||
s->fd = reop_s->fd;
|
|
||||||
s->glfs = reop_s->glfs;
|
|
||||||
|
|
||||||
g_free(state->opaque);
|
|
||||||
state->opaque = NULL;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void qemu_gluster_reopen_abort(BDRVReopenState *state)
|
|
||||||
{
|
|
||||||
BDRVGlusterReopenState *reop_s = state->opaque;
|
|
||||||
|
|
||||||
if (reop_s == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reop_s->fd) {
|
|
||||||
glfs_close(reop_s->fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reop_s->glfs) {
|
|
||||||
glfs_fini(reop_s->glfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(state->opaque);
|
|
||||||
state->opaque = NULL;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_GLUSTERFS_ZEROFILL
|
|
||||||
static coroutine_fn int qemu_gluster_co_write_zeroes(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors, BdrvRequestFlags flags)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
|
|
||||||
BDRVGlusterState *s = bs->opaque;
|
|
||||||
off_t size = nb_sectors * BDRV_SECTOR_SIZE;
|
|
||||||
off_t offset = sector_num * BDRV_SECTOR_SIZE;
|
|
||||||
|
|
||||||
acb->size = size;
|
|
||||||
acb->ret = 0;
|
|
||||||
acb->coroutine = qemu_coroutine_self();
|
|
||||||
|
|
||||||
ret = glfs_zerofill_async(s->fd, offset, size, &gluster_finish_aiocb, acb);
|
|
||||||
if (ret < 0) {
|
|
||||||
ret = -errno;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
ret = acb->ret;
|
|
||||||
|
|
||||||
out:
|
|
||||||
g_slice_free(GlusterAIOCB, acb);
|
|
||||||
return 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,
|
static int qemu_gluster_create(const char *filename,
|
||||||
QEMUOptionParameter *options, Error **errp)
|
QEMUOptionParameter *options)
|
||||||
{
|
{
|
||||||
struct glfs *glfs;
|
struct glfs *glfs;
|
||||||
struct glfs_fd *fd;
|
struct glfs_fd *fd;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
int prealloc = 0;
|
|
||||||
int64_t total_size = 0;
|
int64_t total_size = 0;
|
||||||
GlusterConf *gconf = g_malloc0(sizeof(GlusterConf));
|
GlusterConf *gconf = g_malloc0(sizeof(GlusterConf));
|
||||||
|
|
||||||
glfs = qemu_gluster_init(gconf, filename, errp);
|
glfs = qemu_gluster_init(gconf, filename);
|
||||||
if (!glfs) {
|
if (!glfs) {
|
||||||
ret = -EINVAL;
|
ret = -errno;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (options && options->name) {
|
while (options && options->name) {
|
||||||
if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
|
if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
|
||||||
total_size = options->value.n / BDRV_SECTOR_SIZE;
|
total_size = options->value.n / BDRV_SECTOR_SIZE;
|
||||||
} else if (!strcmp(options->name, BLOCK_OPT_PREALLOC)) {
|
|
||||||
if (!options->value.s || !strcmp(options->value.s, "off")) {
|
|
||||||
prealloc = 0;
|
|
||||||
} else if (!strcmp(options->value.s, "full") &&
|
|
||||||
gluster_supports_zerofill()) {
|
|
||||||
prealloc = 1;
|
|
||||||
} else {
|
|
||||||
error_setg(errp, "Invalid preallocation mode: '%s'"
|
|
||||||
" or GlusterFS doesn't support zerofill API",
|
|
||||||
options->value.s);
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
options++;
|
options++;
|
||||||
}
|
}
|
||||||
@@ -511,15 +362,9 @@ static int qemu_gluster_create(const char *filename,
|
|||||||
if (!fd) {
|
if (!fd) {
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
} else {
|
} else {
|
||||||
if (!glfs_ftruncate(fd, total_size * BDRV_SECTOR_SIZE)) {
|
if (glfs_ftruncate(fd, total_size * BDRV_SECTOR_SIZE) != 0) {
|
||||||
if (prealloc && qemu_gluster_zerofill(fd, 0,
|
|
||||||
total_size * BDRV_SECTOR_SIZE)) {
|
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ret = -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glfs_close(fd) != 0) {
|
if (glfs_close(fd) != 0) {
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
}
|
}
|
||||||
@@ -532,18 +377,72 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
|
static void qemu_gluster_aio_cancel(BlockDriverAIOCB *blockacb)
|
||||||
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov, int write)
|
{
|
||||||
|
GlusterAIOCB *acb = (GlusterAIOCB *)blockacb;
|
||||||
|
bool finished = false;
|
||||||
|
|
||||||
|
acb->finished = &finished;
|
||||||
|
while (!finished) {
|
||||||
|
qemu_aio_wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const AIOCBInfo gluster_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(GlusterAIOCB),
|
||||||
|
.cancel = qemu_gluster_aio_cancel,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg)
|
||||||
|
{
|
||||||
|
GlusterAIOCB *acb = (GlusterAIOCB *)arg;
|
||||||
|
BlockDriverState *bs = acb->common.bs;
|
||||||
|
BDRVGlusterState *s = bs->opaque;
|
||||||
|
int retval;
|
||||||
|
|
||||||
|
acb->ret = ret;
|
||||||
|
retval = qemu_write_full(s->fds[GLUSTER_FD_WRITE], &acb, sizeof(acb));
|
||||||
|
if (retval != sizeof(acb)) {
|
||||||
|
/*
|
||||||
|
* Gluster AIO callback thread failed to notify the waiting
|
||||||
|
* QEMU thread about IO completion.
|
||||||
|
*
|
||||||
|
* Complete this IO request and make the disk inaccessible for
|
||||||
|
* subsequent reads and writes.
|
||||||
|
*/
|
||||||
|
error_report("Gluster failed to notify QEMU about IO completion");
|
||||||
|
|
||||||
|
qemu_mutex_lock_iothread(); /* We are in gluster thread context */
|
||||||
|
acb->common.cb(acb->common.opaque, -EIO);
|
||||||
|
qemu_aio_release(acb);
|
||||||
|
s->qemu_aio_count--;
|
||||||
|
close(s->fds[GLUSTER_FD_READ]);
|
||||||
|
close(s->fds[GLUSTER_FD_WRITE]);
|
||||||
|
qemu_aio_set_fd_handler(s->fds[GLUSTER_FD_READ], NULL, NULL, NULL,
|
||||||
|
NULL);
|
||||||
|
bs->drv = NULL; /* Make the disk inaccessible */
|
||||||
|
qemu_mutex_unlock_iothread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriverAIOCB *qemu_gluster_aio_rw(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockDriverCompletionFunc *cb, void *opaque, int write)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
|
GlusterAIOCB *acb;
|
||||||
BDRVGlusterState *s = bs->opaque;
|
BDRVGlusterState *s = bs->opaque;
|
||||||
size_t size = nb_sectors * BDRV_SECTOR_SIZE;
|
size_t size;
|
||||||
off_t offset = sector_num * BDRV_SECTOR_SIZE;
|
off_t offset;
|
||||||
|
|
||||||
|
offset = sector_num * BDRV_SECTOR_SIZE;
|
||||||
|
size = nb_sectors * BDRV_SECTOR_SIZE;
|
||||||
|
s->qemu_aio_count++;
|
||||||
|
|
||||||
|
acb = qemu_aio_get(&gluster_aiocb_info, bs, cb, opaque);
|
||||||
acb->size = size;
|
acb->size = size;
|
||||||
acb->ret = 0;
|
acb->ret = 0;
|
||||||
acb->coroutine = qemu_coroutine_self();
|
acb->finished = NULL;
|
||||||
|
|
||||||
if (write) {
|
if (write) {
|
||||||
ret = glfs_pwritev_async(s->fd, qiov->iov, qiov->niov, offset, 0,
|
ret = glfs_pwritev_async(s->fd, qiov->iov, qiov->niov, offset, 0,
|
||||||
@@ -554,96 +453,55 @@ static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ret = -errno;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
return &acb->common;
|
||||||
qemu_coroutine_yield();
|
|
||||||
ret = acb->ret;
|
|
||||||
|
|
||||||
out:
|
out:
|
||||||
g_slice_free(GlusterAIOCB, acb);
|
s->qemu_aio_count--;
|
||||||
return ret;
|
qemu_aio_release(acb);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qemu_gluster_truncate(BlockDriverState *bs, int64_t offset)
|
static BlockDriverAIOCB *qemu_gluster_aio_readv(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockDriverCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
return qemu_gluster_aio_rw(bs, sector_num, qiov, nb_sectors, cb, opaque, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriverAIOCB *qemu_gluster_aio_writev(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockDriverCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
return qemu_gluster_aio_rw(bs, sector_num, qiov, nb_sectors, cb, opaque, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriverAIOCB *qemu_gluster_aio_flush(BlockDriverState *bs,
|
||||||
|
BlockDriverCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
GlusterAIOCB *acb;
|
||||||
BDRVGlusterState *s = bs->opaque;
|
BDRVGlusterState *s = bs->opaque;
|
||||||
|
|
||||||
ret = glfs_ftruncate(s->fd, offset);
|
acb = qemu_aio_get(&gluster_aiocb_info, bs, cb, opaque);
|
||||||
if (ret < 0) {
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static coroutine_fn int qemu_gluster_co_readv(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
return qemu_gluster_co_rw(bs, sector_num, nb_sectors, qiov, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static coroutine_fn int qemu_gluster_co_writev(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
return qemu_gluster_co_rw(bs, sector_num, nb_sectors, qiov, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static coroutine_fn int qemu_gluster_co_flush_to_disk(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
|
|
||||||
BDRVGlusterState *s = bs->opaque;
|
|
||||||
|
|
||||||
acb->size = 0;
|
acb->size = 0;
|
||||||
acb->ret = 0;
|
acb->ret = 0;
|
||||||
acb->coroutine = qemu_coroutine_self();
|
acb->finished = NULL;
|
||||||
|
s->qemu_aio_count++;
|
||||||
|
|
||||||
ret = glfs_fsync_async(s->fd, &gluster_finish_aiocb, acb);
|
ret = glfs_fsync_async(s->fd, &gluster_finish_aiocb, acb);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ret = -errno;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
return &acb->common;
|
||||||
qemu_coroutine_yield();
|
|
||||||
ret = acb->ret;
|
|
||||||
|
|
||||||
out:
|
out:
|
||||||
g_slice_free(GlusterAIOCB, acb);
|
s->qemu_aio_count--;
|
||||||
return ret;
|
qemu_aio_release(acb);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_GLUSTERFS_DISCARD
|
|
||||||
static coroutine_fn int qemu_gluster_co_discard(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
|
|
||||||
BDRVGlusterState *s = bs->opaque;
|
|
||||||
size_t size = nb_sectors * BDRV_SECTOR_SIZE;
|
|
||||||
off_t offset = sector_num * BDRV_SECTOR_SIZE;
|
|
||||||
|
|
||||||
acb->size = 0;
|
|
||||||
acb->ret = 0;
|
|
||||||
acb->coroutine = qemu_coroutine_self();
|
|
||||||
|
|
||||||
ret = glfs_discard_async(s->fd, offset, size, &gluster_finish_aiocb, acb);
|
|
||||||
if (ret < 0) {
|
|
||||||
ret = -errno;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
ret = acb->ret;
|
|
||||||
|
|
||||||
out:
|
|
||||||
g_slice_free(GlusterAIOCB, acb);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int64_t qemu_gluster_getlength(BlockDriverState *bs)
|
static int64_t qemu_gluster_getlength(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVGlusterState *s = bs->opaque;
|
BDRVGlusterState *s = bs->opaque;
|
||||||
@@ -675,6 +533,10 @@ static void qemu_gluster_close(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
BDRVGlusterState *s = bs->opaque;
|
BDRVGlusterState *s = bs->opaque;
|
||||||
|
|
||||||
|
close(s->fds[GLUSTER_FD_READ]);
|
||||||
|
close(s->fds[GLUSTER_FD_WRITE]);
|
||||||
|
qemu_aio_set_fd_handler(s->fds[GLUSTER_FD_READ], NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
if (s->fd) {
|
if (s->fd) {
|
||||||
glfs_close(s->fd);
|
glfs_close(s->fd);
|
||||||
s->fd = NULL;
|
s->fd = NULL;
|
||||||
@@ -682,23 +544,12 @@ static void qemu_gluster_close(BlockDriverState *bs)
|
|||||||
glfs_fini(s->glfs);
|
glfs_fini(s->glfs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qemu_gluster_has_zero_init(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
/* GlusterFS volume could be backed by a block device */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QEMUOptionParameter qemu_gluster_create_options[] = {
|
static QEMUOptionParameter qemu_gluster_create_options[] = {
|
||||||
{
|
{
|
||||||
.name = BLOCK_OPT_SIZE,
|
.name = BLOCK_OPT_SIZE,
|
||||||
.type = OPT_SIZE,
|
.type = OPT_SIZE,
|
||||||
.help = "Virtual disk size"
|
.help = "Virtual disk size"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.name = BLOCK_OPT_PREALLOC,
|
|
||||||
.type = OPT_STRING,
|
|
||||||
.help = "Preallocation mode (allowed values: off, full)"
|
|
||||||
},
|
|
||||||
{ NULL }
|
{ NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -706,26 +557,14 @@ static BlockDriver bdrv_gluster = {
|
|||||||
.format_name = "gluster",
|
.format_name = "gluster",
|
||||||
.protocol_name = "gluster",
|
.protocol_name = "gluster",
|
||||||
.instance_size = sizeof(BDRVGlusterState),
|
.instance_size = sizeof(BDRVGlusterState),
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_file_open = qemu_gluster_open,
|
.bdrv_file_open = qemu_gluster_open,
|
||||||
.bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
|
|
||||||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
|
||||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
|
||||||
.bdrv_close = qemu_gluster_close,
|
.bdrv_close = qemu_gluster_close,
|
||||||
.bdrv_create = qemu_gluster_create,
|
.bdrv_create = qemu_gluster_create,
|
||||||
.bdrv_getlength = qemu_gluster_getlength,
|
.bdrv_getlength = qemu_gluster_getlength,
|
||||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||||
.bdrv_truncate = qemu_gluster_truncate,
|
.bdrv_aio_readv = qemu_gluster_aio_readv,
|
||||||
.bdrv_co_readv = qemu_gluster_co_readv,
|
.bdrv_aio_writev = qemu_gluster_aio_writev,
|
||||||
.bdrv_co_writev = qemu_gluster_co_writev,
|
.bdrv_aio_flush = qemu_gluster_aio_flush,
|
||||||
.bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
|
|
||||||
.bdrv_has_zero_init = qemu_gluster_has_zero_init,
|
|
||||||
#ifdef CONFIG_GLUSTERFS_DISCARD
|
|
||||||
.bdrv_co_discard = qemu_gluster_co_discard,
|
|
||||||
#endif
|
|
||||||
#ifdef CONFIG_GLUSTERFS_ZEROFILL
|
|
||||||
.bdrv_co_write_zeroes = qemu_gluster_co_write_zeroes,
|
|
||||||
#endif
|
|
||||||
.create_options = qemu_gluster_create_options,
|
.create_options = qemu_gluster_create_options,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -733,26 +572,14 @@ static BlockDriver bdrv_gluster_tcp = {
|
|||||||
.format_name = "gluster",
|
.format_name = "gluster",
|
||||||
.protocol_name = "gluster+tcp",
|
.protocol_name = "gluster+tcp",
|
||||||
.instance_size = sizeof(BDRVGlusterState),
|
.instance_size = sizeof(BDRVGlusterState),
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_file_open = qemu_gluster_open,
|
.bdrv_file_open = qemu_gluster_open,
|
||||||
.bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
|
|
||||||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
|
||||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
|
||||||
.bdrv_close = qemu_gluster_close,
|
.bdrv_close = qemu_gluster_close,
|
||||||
.bdrv_create = qemu_gluster_create,
|
.bdrv_create = qemu_gluster_create,
|
||||||
.bdrv_getlength = qemu_gluster_getlength,
|
.bdrv_getlength = qemu_gluster_getlength,
|
||||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||||
.bdrv_truncate = qemu_gluster_truncate,
|
.bdrv_aio_readv = qemu_gluster_aio_readv,
|
||||||
.bdrv_co_readv = qemu_gluster_co_readv,
|
.bdrv_aio_writev = qemu_gluster_aio_writev,
|
||||||
.bdrv_co_writev = qemu_gluster_co_writev,
|
.bdrv_aio_flush = qemu_gluster_aio_flush,
|
||||||
.bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
|
|
||||||
.bdrv_has_zero_init = qemu_gluster_has_zero_init,
|
|
||||||
#ifdef CONFIG_GLUSTERFS_DISCARD
|
|
||||||
.bdrv_co_discard = qemu_gluster_co_discard,
|
|
||||||
#endif
|
|
||||||
#ifdef CONFIG_GLUSTERFS_ZEROFILL
|
|
||||||
.bdrv_co_write_zeroes = qemu_gluster_co_write_zeroes,
|
|
||||||
#endif
|
|
||||||
.create_options = qemu_gluster_create_options,
|
.create_options = qemu_gluster_create_options,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -760,26 +587,14 @@ static BlockDriver bdrv_gluster_unix = {
|
|||||||
.format_name = "gluster",
|
.format_name = "gluster",
|
||||||
.protocol_name = "gluster+unix",
|
.protocol_name = "gluster+unix",
|
||||||
.instance_size = sizeof(BDRVGlusterState),
|
.instance_size = sizeof(BDRVGlusterState),
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_file_open = qemu_gluster_open,
|
.bdrv_file_open = qemu_gluster_open,
|
||||||
.bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
|
|
||||||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
|
||||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
|
||||||
.bdrv_close = qemu_gluster_close,
|
.bdrv_close = qemu_gluster_close,
|
||||||
.bdrv_create = qemu_gluster_create,
|
.bdrv_create = qemu_gluster_create,
|
||||||
.bdrv_getlength = qemu_gluster_getlength,
|
.bdrv_getlength = qemu_gluster_getlength,
|
||||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||||
.bdrv_truncate = qemu_gluster_truncate,
|
.bdrv_aio_readv = qemu_gluster_aio_readv,
|
||||||
.bdrv_co_readv = qemu_gluster_co_readv,
|
.bdrv_aio_writev = qemu_gluster_aio_writev,
|
||||||
.bdrv_co_writev = qemu_gluster_co_writev,
|
.bdrv_aio_flush = qemu_gluster_aio_flush,
|
||||||
.bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
|
|
||||||
.bdrv_has_zero_init = qemu_gluster_has_zero_init,
|
|
||||||
#ifdef CONFIG_GLUSTERFS_DISCARD
|
|
||||||
.bdrv_co_discard = qemu_gluster_co_discard,
|
|
||||||
#endif
|
|
||||||
#ifdef CONFIG_GLUSTERFS_ZEROFILL
|
|
||||||
.bdrv_co_write_zeroes = qemu_gluster_co_write_zeroes,
|
|
||||||
#endif
|
|
||||||
.create_options = qemu_gluster_create_options,
|
.create_options = qemu_gluster_create_options,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -787,26 +602,14 @@ static BlockDriver bdrv_gluster_rdma = {
|
|||||||
.format_name = "gluster",
|
.format_name = "gluster",
|
||||||
.protocol_name = "gluster+rdma",
|
.protocol_name = "gluster+rdma",
|
||||||
.instance_size = sizeof(BDRVGlusterState),
|
.instance_size = sizeof(BDRVGlusterState),
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_file_open = qemu_gluster_open,
|
.bdrv_file_open = qemu_gluster_open,
|
||||||
.bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
|
|
||||||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
|
||||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
|
||||||
.bdrv_close = qemu_gluster_close,
|
.bdrv_close = qemu_gluster_close,
|
||||||
.bdrv_create = qemu_gluster_create,
|
.bdrv_create = qemu_gluster_create,
|
||||||
.bdrv_getlength = qemu_gluster_getlength,
|
.bdrv_getlength = qemu_gluster_getlength,
|
||||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||||
.bdrv_truncate = qemu_gluster_truncate,
|
.bdrv_aio_readv = qemu_gluster_aio_readv,
|
||||||
.bdrv_co_readv = qemu_gluster_co_readv,
|
.bdrv_aio_writev = qemu_gluster_aio_writev,
|
||||||
.bdrv_co_writev = qemu_gluster_co_writev,
|
.bdrv_aio_flush = qemu_gluster_aio_flush,
|
||||||
.bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
|
|
||||||
.bdrv_has_zero_init = qemu_gluster_has_zero_init,
|
|
||||||
#ifdef CONFIG_GLUSTERFS_DISCARD
|
|
||||||
.bdrv_co_discard = qemu_gluster_co_discard,
|
|
||||||
#endif
|
|
||||||
#ifdef CONFIG_GLUSTERFS_ZEROFILL
|
|
||||||
.bdrv_co_write_zeroes = qemu_gluster_co_write_zeroes,
|
|
||||||
#endif
|
|
||||||
.create_options = qemu_gluster_create_options,
|
.create_options = qemu_gluster_create_options,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
1200
block/iscsi.c
1200
block/iscsi.c
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,7 @@ struct qemu_laiocb {
|
|||||||
struct qemu_laio_state {
|
struct qemu_laio_state {
|
||||||
io_context_t ctx;
|
io_context_t ctx;
|
||||||
EventNotifier e;
|
EventNotifier e;
|
||||||
|
int count;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline ssize_t io_event_ret(struct io_event *ev)
|
static inline ssize_t io_event_ret(struct io_event *ev)
|
||||||
@@ -54,6 +55,8 @@ static void qemu_laio_process_completion(struct qemu_laio_state *s,
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
s->count--;
|
||||||
|
|
||||||
ret = laiocb->ret;
|
ret = laiocb->ret;
|
||||||
if (ret != -ECANCELED) {
|
if (ret != -ECANCELED) {
|
||||||
if (ret == laiocb->nbytes) {
|
if (ret == laiocb->nbytes) {
|
||||||
@@ -98,6 +101,13 @@ static void qemu_laio_completion_cb(EventNotifier *e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int qemu_laio_flush_cb(EventNotifier *e)
|
||||||
|
{
|
||||||
|
struct qemu_laio_state *s = container_of(e, struct qemu_laio_state, e);
|
||||||
|
|
||||||
|
return (s->count > 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void laio_cancel(BlockDriverAIOCB *blockacb)
|
static void laio_cancel(BlockDriverAIOCB *blockacb)
|
||||||
{
|
{
|
||||||
struct qemu_laiocb *laiocb = (struct qemu_laiocb *)blockacb;
|
struct qemu_laiocb *laiocb = (struct qemu_laiocb *)blockacb;
|
||||||
@@ -167,11 +177,14 @@ BlockDriverAIOCB *laio_submit(BlockDriverState *bs, void *aio_ctx, int fd,
|
|||||||
goto out_free_aiocb;
|
goto out_free_aiocb;
|
||||||
}
|
}
|
||||||
io_set_eventfd(&laiocb->iocb, event_notifier_get_fd(&s->e));
|
io_set_eventfd(&laiocb->iocb, event_notifier_get_fd(&s->e));
|
||||||
|
s->count++;
|
||||||
|
|
||||||
if (io_submit(s->ctx, 1, &iocbs) < 0)
|
if (io_submit(s->ctx, 1, &iocbs) < 0)
|
||||||
goto out_free_aiocb;
|
goto out_dec_count;
|
||||||
return &laiocb->common;
|
return &laiocb->common;
|
||||||
|
|
||||||
|
out_dec_count:
|
||||||
|
s->count--;
|
||||||
out_free_aiocb:
|
out_free_aiocb:
|
||||||
qemu_aio_release(laiocb);
|
qemu_aio_release(laiocb);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -190,7 +203,8 @@ void *laio_init(void)
|
|||||||
goto out_close_efd;
|
goto out_close_efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_aio_set_event_notifier(&s->e, qemu_laio_completion_cb);
|
qemu_aio_set_event_notifier(&s->e, qemu_laio_completion_cb,
|
||||||
|
qemu_laio_flush_cb);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
|
|
||||||
|
|||||||
202
block/mirror.c
202
block/mirror.c
@@ -31,8 +31,7 @@ typedef struct MirrorBlockJob {
|
|||||||
BlockJob common;
|
BlockJob common;
|
||||||
RateLimit limit;
|
RateLimit limit;
|
||||||
BlockDriverState *target;
|
BlockDriverState *target;
|
||||||
BlockDriverState *base;
|
MirrorSyncMode mode;
|
||||||
bool is_none_mode;
|
|
||||||
BlockdevOnError on_source_error, on_target_error;
|
BlockdevOnError on_source_error, on_target_error;
|
||||||
bool synced;
|
bool synced;
|
||||||
bool should_complete;
|
bool should_complete;
|
||||||
@@ -40,7 +39,6 @@ typedef struct MirrorBlockJob {
|
|||||||
int64_t granularity;
|
int64_t granularity;
|
||||||
size_t buf_size;
|
size_t buf_size;
|
||||||
unsigned long *cow_bitmap;
|
unsigned long *cow_bitmap;
|
||||||
BdrvDirtyBitmap *dirty_bitmap;
|
|
||||||
HBitmapIter hbi;
|
HBitmapIter hbi;
|
||||||
uint8_t *buf;
|
uint8_t *buf;
|
||||||
QSIMPLEQ_HEAD(, MirrorBuffer) buf_free;
|
QSIMPLEQ_HEAD(, MirrorBuffer) buf_free;
|
||||||
@@ -96,17 +94,9 @@ static void mirror_iteration_done(MirrorOp *op, int ret)
|
|||||||
bitmap_set(s->cow_bitmap, chunk_num, nb_chunks);
|
bitmap_set(s->cow_bitmap, chunk_num, nb_chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_iovec_destroy(&op->qiov);
|
|
||||||
g_slice_free(MirrorOp, op);
|
g_slice_free(MirrorOp, op);
|
||||||
|
|
||||||
/* Enter coroutine when it is not sleeping. The coroutine sleeps to
|
|
||||||
* rate-limit itself. The coroutine will eventually resume since there is
|
|
||||||
* a sleep timeout so don't wake it early.
|
|
||||||
*/
|
|
||||||
if (s->common.busy) {
|
|
||||||
qemu_coroutine_enter(s->common.co, NULL);
|
qemu_coroutine_enter(s->common.co, NULL);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void mirror_write_complete(void *opaque, int ret)
|
static void mirror_write_complete(void *opaque, int ret)
|
||||||
{
|
{
|
||||||
@@ -146,20 +136,18 @@ static void mirror_read_complete(void *opaque, int ret)
|
|||||||
mirror_write_complete, op);
|
mirror_write_complete, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
static void coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
||||||
{
|
{
|
||||||
BlockDriverState *source = s->common.bs;
|
BlockDriverState *source = s->common.bs;
|
||||||
int nb_sectors, sectors_per_chunk, nb_chunks;
|
int nb_sectors, sectors_per_chunk, nb_chunks;
|
||||||
int64_t end, sector_num, next_chunk, next_sector, hbitmap_next_sector;
|
int64_t end, sector_num, next_chunk, next_sector, hbitmap_next_sector;
|
||||||
uint64_t delay_ns;
|
|
||||||
MirrorOp *op;
|
MirrorOp *op;
|
||||||
|
|
||||||
s->sector_num = hbitmap_iter_next(&s->hbi);
|
s->sector_num = hbitmap_iter_next(&s->hbi);
|
||||||
if (s->sector_num < 0) {
|
if (s->sector_num < 0) {
|
||||||
bdrv_dirty_iter_init(source, s->dirty_bitmap, &s->hbi);
|
bdrv_dirty_iter_init(source, &s->hbi);
|
||||||
s->sector_num = hbitmap_iter_next(&s->hbi);
|
s->sector_num = hbitmap_iter_next(&s->hbi);
|
||||||
trace_mirror_restart_iter(s,
|
trace_mirror_restart_iter(s, bdrv_get_dirty_count(source));
|
||||||
bdrv_get_dirty_count(source, s->dirty_bitmap));
|
|
||||||
assert(s->sector_num >= 0);
|
assert(s->sector_num >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +183,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
|||||||
do {
|
do {
|
||||||
int added_sectors, added_chunks;
|
int added_sectors, added_chunks;
|
||||||
|
|
||||||
if (!bdrv_get_dirty(source, s->dirty_bitmap, next_sector) ||
|
if (!bdrv_get_dirty(source, next_sector) ||
|
||||||
test_bit(next_chunk, s->in_flight_bitmap)) {
|
test_bit(next_chunk, s->in_flight_bitmap)) {
|
||||||
assert(nb_sectors > 0);
|
assert(nb_sectors > 0);
|
||||||
break;
|
break;
|
||||||
@@ -239,12 +227,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
|||||||
nb_chunks += added_chunks;
|
nb_chunks += added_chunks;
|
||||||
next_sector += added_sectors;
|
next_sector += added_sectors;
|
||||||
next_chunk += added_chunks;
|
next_chunk += added_chunks;
|
||||||
if (!s->synced && s->common.speed) {
|
} while (next_sector < end);
|
||||||
delay_ns = ratelimit_calculate_delay(&s->limit, added_sectors);
|
|
||||||
} else {
|
|
||||||
delay_ns = 0;
|
|
||||||
}
|
|
||||||
} while (delay_ns == 0 && next_sector < end);
|
|
||||||
|
|
||||||
/* Allocate a MirrorOp that is used as an AIO callback. */
|
/* Allocate a MirrorOp that is used as an AIO callback. */
|
||||||
op = g_slice_new(MirrorOp);
|
op = g_slice_new(MirrorOp);
|
||||||
@@ -266,8 +249,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
|||||||
/* Advance the HBitmapIter in parallel, so that we do not examine
|
/* Advance the HBitmapIter in parallel, so that we do not examine
|
||||||
* the same sector twice.
|
* the same sector twice.
|
||||||
*/
|
*/
|
||||||
if (next_sector > hbitmap_next_sector
|
if (next_sector > hbitmap_next_sector && bdrv_get_dirty(source, next_sector)) {
|
||||||
&& bdrv_get_dirty(source, s->dirty_bitmap, next_sector)) {
|
|
||||||
hbitmap_next_sector = hbitmap_iter_next(&s->hbi);
|
hbitmap_next_sector = hbitmap_iter_next(&s->hbi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +263,6 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
|||||||
trace_mirror_one_iteration(s, sector_num, nb_sectors);
|
trace_mirror_one_iteration(s, sector_num, nb_sectors);
|
||||||
bdrv_aio_readv(source, sector_num, &op->qiov, nb_sectors,
|
bdrv_aio_readv(source, sector_num, &op->qiov, nb_sectors,
|
||||||
mirror_read_complete, op);
|
mirror_read_complete, op);
|
||||||
return delay_ns;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mirror_free_init(MirrorBlockJob *s)
|
static void mirror_free_init(MirrorBlockJob *s)
|
||||||
@@ -351,12 +332,13 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
sectors_per_chunk = s->granularity >> BDRV_SECTOR_BITS;
|
sectors_per_chunk = s->granularity >> BDRV_SECTOR_BITS;
|
||||||
mirror_free_init(s);
|
mirror_free_init(s);
|
||||||
|
|
||||||
if (!s->is_none_mode) {
|
if (s->mode != MIRROR_SYNC_MODE_NONE) {
|
||||||
/* First part, loop on the sectors and initialize the dirty bitmap. */
|
/* First part, loop on the sectors and initialize the dirty bitmap. */
|
||||||
BlockDriverState *base = s->base;
|
BlockDriverState *base;
|
||||||
|
base = s->mode == MIRROR_SYNC_MODE_FULL ? NULL : bs->backing_hd;
|
||||||
for (sector_num = 0; sector_num < end; ) {
|
for (sector_num = 0; sector_num < end; ) {
|
||||||
int64_t next = (sector_num | (sectors_per_chunk - 1)) + 1;
|
int64_t next = (sector_num | (sectors_per_chunk - 1)) + 1;
|
||||||
ret = bdrv_is_allocated_above(bs, base,
|
ret = bdrv_co_is_allocated_above(bs, base,
|
||||||
sector_num, next - sector_num, &n);
|
sector_num, next - sector_num, &n);
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -373,10 +355,10 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_dirty_iter_init(bs, s->dirty_bitmap, &s->hbi);
|
bdrv_dirty_iter_init(bs, &s->hbi);
|
||||||
last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
last_pause_ns = qemu_get_clock_ns(rt_clock);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint64_t delay_ns = 0;
|
uint64_t delay_ns;
|
||||||
int64_t cnt;
|
int64_t cnt;
|
||||||
bool should_complete;
|
bool should_complete;
|
||||||
|
|
||||||
@@ -385,14 +367,14 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
goto immediate_exit;
|
goto immediate_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
|
cnt = bdrv_get_dirty_count(bs);
|
||||||
|
|
||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* periodically with no pending I/O so that qemu_aio_flush() returns.
|
* periodically with no pending I/O so that qemu_aio_flush() returns.
|
||||||
* We do so every SLICE_TIME nanoseconds, or when there is an error,
|
* We do so every SLICE_TIME nanoseconds, or when there is an error,
|
||||||
* or when the source is clean, whichever comes first.
|
* or when the source is clean, whichever comes first.
|
||||||
*/
|
*/
|
||||||
if (qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - last_pause_ns < SLICE_TIME &&
|
if (qemu_get_clock_ns(rt_clock) - last_pause_ns < SLICE_TIME &&
|
||||||
s->common.iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
|
s->common.iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
|
||||||
if (s->in_flight == MAX_IN_FLIGHT || s->buf_free_count == 0 ||
|
if (s->in_flight == MAX_IN_FLIGHT || s->buf_free_count == 0 ||
|
||||||
(cnt == 0 && s->in_flight > 0)) {
|
(cnt == 0 && s->in_flight > 0)) {
|
||||||
@@ -400,12 +382,10 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
continue;
|
continue;
|
||||||
} else if (cnt != 0) {
|
} else if (cnt != 0) {
|
||||||
delay_ns = mirror_iteration(s);
|
mirror_iteration(s);
|
||||||
if (delay_ns == 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
should_complete = false;
|
should_complete = false;
|
||||||
if (s->in_flight == 0 && cnt == 0) {
|
if (s->in_flight == 0 && cnt == 0) {
|
||||||
@@ -429,7 +409,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
|
|
||||||
should_complete = s->should_complete ||
|
should_complete = s->should_complete ||
|
||||||
block_job_is_cancelled(&s->common);
|
block_job_is_cancelled(&s->common);
|
||||||
cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
|
cnt = bdrv_get_dirty_count(bs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,21 +424,28 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
*/
|
*/
|
||||||
trace_mirror_before_drain(s, cnt);
|
trace_mirror_before_drain(s, cnt);
|
||||||
bdrv_drain_all();
|
bdrv_drain_all();
|
||||||
cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
|
cnt = bdrv_get_dirty_count(bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
|
trace_mirror_before_sleep(s, cnt, s->synced);
|
||||||
if (!s->synced) {
|
if (!s->synced) {
|
||||||
/* Publish progress */
|
/* Publish progress */
|
||||||
s->common.offset = (end - cnt) * BDRV_SECTOR_SIZE;
|
s->common.offset = (end - cnt) * BDRV_SECTOR_SIZE;
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
|
||||||
|
if (s->common.speed) {
|
||||||
|
delay_ns = ratelimit_calculate_delay(&s->limit, sectors_per_chunk);
|
||||||
|
} else {
|
||||||
|
delay_ns = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
block_job_sleep_ns(&s->common, rt_clock, delay_ns);
|
||||||
if (block_job_is_cancelled(&s->common)) {
|
if (block_job_is_cancelled(&s->common)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (!should_complete) {
|
} else if (!should_complete) {
|
||||||
delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0);
|
delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0);
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&s->common, rt_clock, delay_ns);
|
||||||
} else if (cnt == 0) {
|
} else if (cnt == 0) {
|
||||||
/* The two disks are in sync. Exit and report successful
|
/* The two disks are in sync. Exit and report successful
|
||||||
* completion.
|
* completion.
|
||||||
@@ -467,7 +454,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
s->common.cancelled = false;
|
s->common.cancelled = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
last_pause_ns = qemu_get_clock_ns(rt_clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
immediate_exit:
|
immediate_exit:
|
||||||
@@ -484,22 +471,16 @@ immediate_exit:
|
|||||||
qemu_vfree(s->buf);
|
qemu_vfree(s->buf);
|
||||||
g_free(s->cow_bitmap);
|
g_free(s->cow_bitmap);
|
||||||
g_free(s->in_flight_bitmap);
|
g_free(s->in_flight_bitmap);
|
||||||
bdrv_release_dirty_bitmap(bs, s->dirty_bitmap);
|
bdrv_set_dirty_tracking(bs, 0);
|
||||||
bdrv_iostatus_disable(s->target);
|
bdrv_iostatus_disable(s->target);
|
||||||
if (s->should_complete && ret == 0) {
|
if (s->should_complete && ret == 0) {
|
||||||
if (bdrv_get_flags(s->target) != bdrv_get_flags(s->common.bs)) {
|
if (bdrv_get_flags(s->target) != bdrv_get_flags(s->common.bs)) {
|
||||||
bdrv_reopen(s->target, bdrv_get_flags(s->common.bs), NULL);
|
bdrv_reopen(s->target, bdrv_get_flags(s->common.bs), NULL);
|
||||||
}
|
}
|
||||||
bdrv_swap(s->target, s->common.bs);
|
bdrv_swap(s->target, s->common.bs);
|
||||||
if (s->common.driver->job_type == BLOCK_JOB_TYPE_COMMIT) {
|
|
||||||
/* drop the bs loop chain formed by the swap: break the loop then
|
|
||||||
* trigger the unref from the top one */
|
|
||||||
BlockDriverState *p = s->base->backing_hd;
|
|
||||||
s->base->backing_hd = NULL;
|
|
||||||
bdrv_unref(p);
|
|
||||||
}
|
}
|
||||||
}
|
bdrv_close(s->target);
|
||||||
bdrv_unref(s->target);
|
bdrv_delete(s->target);
|
||||||
block_job_completed(&s->common, ret);
|
block_job_completed(&s->common, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,12 +505,14 @@ static void mirror_iostatus_reset(BlockJob *job)
|
|||||||
static void mirror_complete(BlockJob *job, Error **errp)
|
static void mirror_complete(BlockJob *job, Error **errp)
|
||||||
{
|
{
|
||||||
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = bdrv_open_backing_file(s->target, NULL, &local_err);
|
ret = bdrv_open_backing_file(s->target);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
char backing_filename[PATH_MAX];
|
||||||
|
bdrv_get_full_backing_filename(s->target, backing_filename,
|
||||||
|
sizeof(backing_filename));
|
||||||
|
error_set(errp, QERR_OPEN_FILE_FAILED, backing_filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!s->synced) {
|
if (!s->synced) {
|
||||||
@@ -541,32 +524,20 @@ static void mirror_complete(BlockJob *job, Error **errp)
|
|||||||
block_job_resume(job);
|
block_job_resume(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const BlockJobDriver mirror_job_driver = {
|
static BlockJobType mirror_job_type = {
|
||||||
.instance_size = sizeof(MirrorBlockJob),
|
.instance_size = sizeof(MirrorBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_MIRROR,
|
.job_type = "mirror",
|
||||||
.set_speed = mirror_set_speed,
|
.set_speed = mirror_set_speed,
|
||||||
.iostatus_reset= mirror_iostatus_reset,
|
.iostatus_reset= mirror_iostatus_reset,
|
||||||
.complete = mirror_complete,
|
.complete = mirror_complete,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const BlockJobDriver commit_active_job_driver = {
|
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
.instance_size = sizeof(MirrorBlockJob),
|
int64_t speed, int64_t granularity, int64_t buf_size,
|
||||||
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
MirrorSyncMode mode, BlockdevOnError on_source_error,
|
||||||
.set_speed = mirror_set_speed,
|
|
||||||
.iostatus_reset
|
|
||||||
= mirror_iostatus_reset,
|
|
||||||
.complete = mirror_complete,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
|
||||||
int64_t speed, int64_t granularity,
|
|
||||||
int64_t buf_size,
|
|
||||||
BlockdevOnError on_source_error,
|
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
BlockDriverCompletionFunc *cb,
|
BlockDriverCompletionFunc *cb,
|
||||||
void *opaque, Error **errp,
|
void *opaque, Error **errp)
|
||||||
const BlockJobDriver *driver,
|
|
||||||
bool is_none_mode, BlockDriverState *base)
|
|
||||||
{
|
{
|
||||||
MirrorBlockJob *s;
|
MirrorBlockJob *s;
|
||||||
|
|
||||||
@@ -591,8 +562,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s = block_job_create(&mirror_job_type, bs, speed, cb, opaque, errp);
|
||||||
s = block_job_create(driver, bs, speed, cb, opaque, errp);
|
|
||||||
if (!s) {
|
if (!s) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -600,12 +570,11 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
|||||||
s->on_source_error = on_source_error;
|
s->on_source_error = on_source_error;
|
||||||
s->on_target_error = on_target_error;
|
s->on_target_error = on_target_error;
|
||||||
s->target = target;
|
s->target = target;
|
||||||
s->is_none_mode = is_none_mode;
|
s->mode = mode;
|
||||||
s->base = base;
|
|
||||||
s->granularity = granularity;
|
s->granularity = granularity;
|
||||||
s->buf_size = MAX(buf_size, granularity);
|
s->buf_size = MAX(buf_size, granularity);
|
||||||
|
|
||||||
s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity);
|
bdrv_set_dirty_tracking(bs, granularity);
|
||||||
bdrv_set_enable_write_cache(s->target, true);
|
bdrv_set_enable_write_cache(s->target, true);
|
||||||
bdrv_set_on_error(s->target, on_target_error, on_target_error);
|
bdrv_set_on_error(s->target, on_target_error, on_target_error);
|
||||||
bdrv_iostatus_enable(s->target);
|
bdrv_iostatus_enable(s->target);
|
||||||
@@ -613,80 +582,3 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
|||||||
trace_mirror_start(bs, s, s->common.co, opaque);
|
trace_mirror_start(bs, s, s->common.co, opaque);
|
||||||
qemu_coroutine_enter(s->common.co, s);
|
qemu_coroutine_enter(s->common.co, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
|
||||||
int64_t speed, int64_t granularity, int64_t buf_size,
|
|
||||||
MirrorSyncMode mode, BlockdevOnError on_source_error,
|
|
||||||
BlockdevOnError on_target_error,
|
|
||||||
BlockDriverCompletionFunc *cb,
|
|
||||||
void *opaque, Error **errp)
|
|
||||||
{
|
|
||||||
bool is_none_mode;
|
|
||||||
BlockDriverState *base;
|
|
||||||
|
|
||||||
is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
|
|
||||||
base = mode == MIRROR_SYNC_MODE_TOP ? bs->backing_hd : NULL;
|
|
||||||
mirror_start_job(bs, target, speed, granularity, buf_size,
|
|
||||||
on_source_error, on_target_error, cb, opaque, errp,
|
|
||||||
&mirror_job_driver, is_none_mode, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
void commit_active_start(BlockDriverState *bs, BlockDriverState *base,
|
|
||||||
int64_t speed,
|
|
||||||
BlockdevOnError on_error,
|
|
||||||
BlockDriverCompletionFunc *cb,
|
|
||||||
void *opaque, Error **errp)
|
|
||||||
{
|
|
||||||
int64_t length, base_length;
|
|
||||||
int orig_base_flags;
|
|
||||||
int ret;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
orig_base_flags = bdrv_get_flags(base);
|
|
||||||
|
|
||||||
if (bdrv_reopen(base, bs->open_flags, errp)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
length = bdrv_getlength(bs);
|
|
||||||
if (length < 0) {
|
|
||||||
error_setg_errno(errp, -length,
|
|
||||||
"Unable to determine length of %s", bs->filename);
|
|
||||||
goto error_restore_flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
base_length = bdrv_getlength(base);
|
|
||||||
if (base_length < 0) {
|
|
||||||
error_setg_errno(errp, -base_length,
|
|
||||||
"Unable to determine length of %s", base->filename);
|
|
||||||
goto error_restore_flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length > base_length) {
|
|
||||||
ret = bdrv_truncate(base, length);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret,
|
|
||||||
"Top image %s is larger than base image %s, and "
|
|
||||||
"resize of base image failed",
|
|
||||||
bs->filename, base->filename);
|
|
||||||
goto error_restore_flags;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bdrv_ref(base);
|
|
||||||
mirror_start_job(bs, base, speed, 0, 0,
|
|
||||||
on_error, on_error, cb, opaque, &local_err,
|
|
||||||
&commit_active_job_driver, false, base);
|
|
||||||
if (error_is_set(&local_err)) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto error_restore_flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
error_restore_flags:
|
|
||||||
/* ignore error and errp for bdrv_reopen, because we want to propagate
|
|
||||||
* the original error */
|
|
||||||
bdrv_reopen(base, orig_base_flags, NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,388 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU Block driver for NBD
|
|
||||||
*
|
|
||||||
* Copyright (C) 2008 Bull S.A.S.
|
|
||||||
* Author: Laurent Vivier <Laurent.Vivier@bull.net>
|
|
||||||
*
|
|
||||||
* Some parts:
|
|
||||||
* Copyright (C) 2007 Anthony Liguori <anthony@codemonkey.ws>
|
|
||||||
*
|
|
||||||
* 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 "nbd-client.h"
|
|
||||||
#include "qemu/sockets.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)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
|
||||||
if (s->recv_coroutine[i]) {
|
|
||||||
qemu_coroutine_enter(s->recv_coroutine[i], NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nbd_teardown_connection(NbdClientSession *client)
|
|
||||||
{
|
|
||||||
/* finish any pending coroutines */
|
|
||||||
shutdown(client->sock, 2);
|
|
||||||
nbd_recv_coroutines_enter_all(client);
|
|
||||||
|
|
||||||
qemu_aio_set_fd_handler(client->sock, NULL, NULL, NULL);
|
|
||||||
closesocket(client->sock);
|
|
||||||
client->sock = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nbd_reply_ready(void *opaque)
|
|
||||||
{
|
|
||||||
NbdClientSession *s = opaque;
|
|
||||||
uint64_t i;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
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->sock, &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], NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fail:
|
|
||||||
nbd_teardown_connection(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nbd_restart_write(void *opaque)
|
|
||||||
{
|
|
||||||
NbdClientSession *s = opaque;
|
|
||||||
|
|
||||||
qemu_coroutine_enter(s->send_coroutine, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nbd_co_send_request(NbdClientSession *s,
|
|
||||||
struct nbd_request *request,
|
|
||||||
QEMUIOVector *qiov, int offset)
|
|
||||||
{
|
|
||||||
int rc, ret;
|
|
||||||
|
|
||||||
qemu_co_mutex_lock(&s->send_mutex);
|
|
||||||
s->send_coroutine = qemu_coroutine_self();
|
|
||||||
qemu_aio_set_fd_handler(s->sock, nbd_reply_ready, nbd_restart_write, s);
|
|
||||||
if (qiov) {
|
|
||||||
if (!s->is_unix) {
|
|
||||||
socket_set_cork(s->sock, 1);
|
|
||||||
}
|
|
||||||
rc = nbd_send_request(s->sock, request);
|
|
||||||
if (rc >= 0) {
|
|
||||||
ret = qemu_co_sendv(s->sock, qiov->iov, qiov->niov,
|
|
||||||
offset, request->len);
|
|
||||||
if (ret != request->len) {
|
|
||||||
rc = -EIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!s->is_unix) {
|
|
||||||
socket_set_cork(s->sock, 0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rc = nbd_send_request(s->sock, request);
|
|
||||||
}
|
|
||||||
qemu_aio_set_fd_handler(s->sock, nbd_reply_ready, NULL, s);
|
|
||||||
s->send_coroutine = NULL;
|
|
||||||
qemu_co_mutex_unlock(&s->send_mutex);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nbd_co_receive_reply(NbdClientSession *s,
|
|
||||||
struct nbd_request *request, struct nbd_reply *reply,
|
|
||||||
QEMUIOVector *qiov, int offset)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* 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();
|
|
||||||
*reply = s->reply;
|
|
||||||
if (reply->handle != request->handle) {
|
|
||||||
reply->error = EIO;
|
|
||||||
} else {
|
|
||||||
if (qiov && reply->error == 0) {
|
|
||||||
ret = qemu_co_recvv(s->sock, qiov->iov, qiov->niov,
|
|
||||||
offset, request->len);
|
|
||||||
if (ret != request->len) {
|
|
||||||
reply->error = EIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tell the read handler to read another header. */
|
|
||||||
s->reply.handle = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nbd_coroutine_start(NbdClientSession *s,
|
|
||||||
struct nbd_request *request)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* 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++;
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
|
||||||
if (s->recv_coroutine[i] == NULL) {
|
|
||||||
s->recv_coroutine[i] = qemu_coroutine_self();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(i < MAX_NBD_REQUESTS);
|
|
||||||
request->handle = INDEX_TO_HANDLE(s, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nbd_co_readv_1(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov,
|
|
||||||
int offset)
|
|
||||||
{
|
|
||||||
struct nbd_request request = { .type = NBD_CMD_READ };
|
|
||||||
struct nbd_reply reply;
|
|
||||||
ssize_t ret;
|
|
||||||
|
|
||||||
request.from = sector_num * 512;
|
|
||||||
request.len = nb_sectors * 512;
|
|
||||||
|
|
||||||
nbd_coroutine_start(client, &request);
|
|
||||||
ret = nbd_co_send_request(client, &request, NULL, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
reply.error = -ret;
|
|
||||||
} else {
|
|
||||||
nbd_co_receive_reply(client, &request, &reply, qiov, offset);
|
|
||||||
}
|
|
||||||
nbd_coroutine_end(client, &request);
|
|
||||||
return -reply.error;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nbd_co_writev_1(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov,
|
|
||||||
int offset)
|
|
||||||
{
|
|
||||||
struct nbd_request request = { .type = NBD_CMD_WRITE };
|
|
||||||
struct nbd_reply reply;
|
|
||||||
ssize_t ret;
|
|
||||||
|
|
||||||
if (!bdrv_enable_write_cache(client->bs) &&
|
|
||||||
(client->nbdflags & NBD_FLAG_SEND_FUA)) {
|
|
||||||
request.type |= NBD_CMD_FLAG_FUA;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.from = sector_num * 512;
|
|
||||||
request.len = nb_sectors * 512;
|
|
||||||
|
|
||||||
nbd_coroutine_start(client, &request);
|
|
||||||
ret = nbd_co_send_request(client, &request, qiov, offset);
|
|
||||||
if (ret < 0) {
|
|
||||||
reply.error = -ret;
|
|
||||||
} else {
|
|
||||||
nbd_co_receive_reply(client, &request, &reply, NULL, 0);
|
|
||||||
}
|
|
||||||
nbd_coroutine_end(client, &request);
|
|
||||||
return -reply.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* qemu-nbd has a limit of slightly less than 1M per request. Try to
|
|
||||||
* remain aligned to 4K. */
|
|
||||||
#define NBD_MAX_SECTORS 2040
|
|
||||||
|
|
||||||
int nbd_client_session_co_readv(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
int offset = 0;
|
|
||||||
int ret;
|
|
||||||
while (nb_sectors > NBD_MAX_SECTORS) {
|
|
||||||
ret = nbd_co_readv_1(client, sector_num,
|
|
||||||
NBD_MAX_SECTORS, qiov, offset);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
offset += NBD_MAX_SECTORS * 512;
|
|
||||||
sector_num += NBD_MAX_SECTORS;
|
|
||||||
nb_sectors -= NBD_MAX_SECTORS;
|
|
||||||
}
|
|
||||||
return nbd_co_readv_1(client, sector_num, nb_sectors, qiov, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
int nbd_client_session_co_writev(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
int offset = 0;
|
|
||||||
int ret;
|
|
||||||
while (nb_sectors > NBD_MAX_SECTORS) {
|
|
||||||
ret = nbd_co_writev_1(client, sector_num,
|
|
||||||
NBD_MAX_SECTORS, qiov, offset);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
offset += NBD_MAX_SECTORS * 512;
|
|
||||||
sector_num += NBD_MAX_SECTORS;
|
|
||||||
nb_sectors -= NBD_MAX_SECTORS;
|
|
||||||
}
|
|
||||||
return nbd_co_writev_1(client, sector_num, nb_sectors, qiov, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
int nbd_client_session_co_flush(NbdClientSession *client)
|
|
||||||
{
|
|
||||||
struct nbd_request request = { .type = NBD_CMD_FLUSH };
|
|
||||||
struct nbd_reply reply;
|
|
||||||
ssize_t ret;
|
|
||||||
|
|
||||||
if (!(client->nbdflags & NBD_FLAG_SEND_FLUSH)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->nbdflags & NBD_FLAG_SEND_FUA) {
|
|
||||||
request.type |= NBD_CMD_FLAG_FUA;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.from = 0;
|
|
||||||
request.len = 0;
|
|
||||||
|
|
||||||
nbd_coroutine_start(client, &request);
|
|
||||||
ret = nbd_co_send_request(client, &request, NULL, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
reply.error = -ret;
|
|
||||||
} else {
|
|
||||||
nbd_co_receive_reply(client, &request, &reply, NULL, 0);
|
|
||||||
}
|
|
||||||
nbd_coroutine_end(client, &request);
|
|
||||||
return -reply.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
int nbd_client_session_co_discard(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors)
|
|
||||||
{
|
|
||||||
struct nbd_request request = { .type = NBD_CMD_TRIM };
|
|
||||||
struct nbd_reply reply;
|
|
||||||
ssize_t ret;
|
|
||||||
|
|
||||||
if (!(client->nbdflags & NBD_FLAG_SEND_TRIM)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
request.from = sector_num * 512;
|
|
||||||
request.len = nb_sectors * 512;
|
|
||||||
|
|
||||||
nbd_coroutine_start(client, &request);
|
|
||||||
ret = nbd_co_send_request(client, &request, NULL, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
reply.error = -ret;
|
|
||||||
} else {
|
|
||||||
nbd_co_receive_reply(client, &request, &reply, NULL, 0);
|
|
||||||
}
|
|
||||||
nbd_coroutine_end(client, &request);
|
|
||||||
return -reply.error;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void nbd_client_session_close(NbdClientSession *client)
|
|
||||||
{
|
|
||||||
struct nbd_request request = {
|
|
||||||
.type = NBD_CMD_DISC,
|
|
||||||
.from = 0,
|
|
||||||
.len = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!client->bs) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (client->sock == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nbd_send_request(client->sock, &request);
|
|
||||||
|
|
||||||
nbd_teardown_connection(client);
|
|
||||||
client->bs = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int nbd_client_session_init(NbdClientSession *client, BlockDriverState *bs,
|
|
||||||
int sock, const char *export)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* NBD handshake */
|
|
||||||
logout("session init %s\n", export);
|
|
||||||
qemu_set_block(sock);
|
|
||||||
ret = nbd_receive_negotiate(sock, export,
|
|
||||||
&client->nbdflags, &client->size,
|
|
||||||
&client->blocksize);
|
|
||||||
if (ret < 0) {
|
|
||||||
logout("Failed to negotiate with the NBD server\n");
|
|
||||||
closesocket(sock);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_co_mutex_init(&client->send_mutex);
|
|
||||||
qemu_co_mutex_init(&client->free_sema);
|
|
||||||
client->bs = bs;
|
|
||||||
client->sock = sock;
|
|
||||||
|
|
||||||
/* Now that we're connected, set the socket to be non-blocking and
|
|
||||||
* kick the reply mechanism. */
|
|
||||||
qemu_set_nonblock(sock);
|
|
||||||
qemu_aio_set_fd_handler(sock, nbd_reply_ready, NULL, client);
|
|
||||||
|
|
||||||
logout("Established connection with NBD server\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#ifndef NBD_CLIENT_H
|
|
||||||
#define NBD_CLIENT_H
|
|
||||||
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "block/nbd.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
|
|
||||||
/* #define DEBUG_NBD */
|
|
||||||
|
|
||||||
#if defined(DEBUG_NBD)
|
|
||||||
#define logout(fmt, ...) \
|
|
||||||
fprintf(stderr, "nbd\t%-24s" fmt, __func__, ##__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define logout(fmt, ...) ((void)0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MAX_NBD_REQUESTS 16
|
|
||||||
|
|
||||||
typedef struct NbdClientSession {
|
|
||||||
int sock;
|
|
||||||
uint32_t nbdflags;
|
|
||||||
off_t size;
|
|
||||||
size_t blocksize;
|
|
||||||
|
|
||||||
CoMutex send_mutex;
|
|
||||||
CoMutex free_sema;
|
|
||||||
Coroutine *send_coroutine;
|
|
||||||
int in_flight;
|
|
||||||
|
|
||||||
Coroutine *recv_coroutine[MAX_NBD_REQUESTS];
|
|
||||||
struct nbd_reply reply;
|
|
||||||
|
|
||||||
bool is_unix;
|
|
||||||
|
|
||||||
BlockDriverState *bs;
|
|
||||||
} NbdClientSession;
|
|
||||||
|
|
||||||
int nbd_client_session_init(NbdClientSession *client, BlockDriverState *bs,
|
|
||||||
int sock, const char *export_name);
|
|
||||||
void nbd_client_session_close(NbdClientSession *client);
|
|
||||||
|
|
||||||
int nbd_client_session_co_discard(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors);
|
|
||||||
int nbd_client_session_co_flush(NbdClientSession *client);
|
|
||||||
int nbd_client_session_co_writev(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov);
|
|
||||||
int nbd_client_session_co_readv(NbdClientSession *client, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov);
|
|
||||||
|
|
||||||
#endif /* NBD_CLIENT_H */
|
|
||||||
499
block/nbd.c
499
block/nbd.c
@@ -26,31 +26,56 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "block/nbd-client.h"
|
#include "qemu-common.h"
|
||||||
|
#include "block/nbd.h"
|
||||||
#include "qemu/uri.h"
|
#include "qemu/uri.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
#include "qapi/qmp/qjson.h"
|
|
||||||
#include "qapi/qmp/qint.h"
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define EN_OPTSTR ":exportname="
|
#define EN_OPTSTR ":exportname="
|
||||||
|
|
||||||
|
/* #define DEBUG_NBD */
|
||||||
|
|
||||||
|
#if defined(DEBUG_NBD)
|
||||||
|
#define logout(fmt, ...) \
|
||||||
|
fprintf(stderr, "nbd\t%-24s" fmt, __func__, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define logout(fmt, ...) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_NBD_REQUESTS 16
|
||||||
|
#define HANDLE_TO_INDEX(bs, handle) ((handle) ^ ((uint64_t)(intptr_t)bs))
|
||||||
|
#define INDEX_TO_HANDLE(bs, index) ((index) ^ ((uint64_t)(intptr_t)bs))
|
||||||
|
|
||||||
typedef struct BDRVNBDState {
|
typedef struct BDRVNBDState {
|
||||||
NbdClientSession client;
|
int sock;
|
||||||
QemuOpts *socket_opts;
|
uint32_t nbdflags;
|
||||||
|
off_t size;
|
||||||
|
size_t blocksize;
|
||||||
|
|
||||||
|
CoMutex send_mutex;
|
||||||
|
CoMutex free_sema;
|
||||||
|
Coroutine *send_coroutine;
|
||||||
|
int in_flight;
|
||||||
|
|
||||||
|
Coroutine *recv_coroutine[MAX_NBD_REQUESTS];
|
||||||
|
struct nbd_reply reply;
|
||||||
|
|
||||||
|
int is_unix;
|
||||||
|
char *host_spec;
|
||||||
|
char *export_name; /* An NBD server may export several devices */
|
||||||
} BDRVNBDState;
|
} BDRVNBDState;
|
||||||
|
|
||||||
static int nbd_parse_uri(const char *filename, QDict *options)
|
static int nbd_parse_uri(BDRVNBDState *s, const char *filename)
|
||||||
{
|
{
|
||||||
URI *uri;
|
URI *uri;
|
||||||
const char *p;
|
const char *p;
|
||||||
QueryParams *qp = NULL;
|
QueryParams *qp = NULL;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
bool is_unix;
|
|
||||||
|
|
||||||
uri = uri_parse(filename);
|
uri = uri_parse(filename);
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
@@ -59,11 +84,11 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
|||||||
|
|
||||||
/* transport */
|
/* transport */
|
||||||
if (!strcmp(uri->scheme, "nbd")) {
|
if (!strcmp(uri->scheme, "nbd")) {
|
||||||
is_unix = false;
|
s->is_unix = false;
|
||||||
} else if (!strcmp(uri->scheme, "nbd+tcp")) {
|
} else if (!strcmp(uri->scheme, "nbd+tcp")) {
|
||||||
is_unix = false;
|
s->is_unix = false;
|
||||||
} else if (!strcmp(uri->scheme, "nbd+unix")) {
|
} else if (!strcmp(uri->scheme, "nbd+unix")) {
|
||||||
is_unix = true;
|
s->is_unix = true;
|
||||||
} else {
|
} else {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
@@ -72,44 +97,32 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
|||||||
p = uri->path ? uri->path : "/";
|
p = uri->path ? uri->path : "/";
|
||||||
p += strspn(p, "/");
|
p += strspn(p, "/");
|
||||||
if (p[0]) {
|
if (p[0]) {
|
||||||
qdict_put(options, "export", qstring_from_str(p));
|
s->export_name = g_strdup(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
qp = query_params_parse(uri->query);
|
qp = query_params_parse(uri->query);
|
||||||
if (qp->n > 1 || (is_unix && !qp->n) || (!is_unix && qp->n)) {
|
if (qp->n > 1 || (s->is_unix && !qp->n) || (!s->is_unix && qp->n)) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_unix) {
|
if (s->is_unix) {
|
||||||
/* nbd+unix:///export?socket=path */
|
/* nbd+unix:///export?socket=path */
|
||||||
if (uri->server || uri->port || strcmp(qp->p[0].name, "socket")) {
|
if (uri->server || uri->port || strcmp(qp->p[0].name, "socket")) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
qdict_put(options, "path", qstring_from_str(qp->p[0].value));
|
s->host_spec = g_strdup(qp->p[0].value);
|
||||||
} else {
|
} else {
|
||||||
QString *host;
|
/* nbd[+tcp]://host:port/export */
|
||||||
/* nbd[+tcp]://host[:port]/export */
|
|
||||||
if (!uri->server) {
|
if (!uri->server) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
if (!uri->port) {
|
||||||
/* strip braces from literal IPv6 address */
|
uri->port = NBD_DEFAULT_PORT;
|
||||||
if (uri->server[0] == '[') {
|
|
||||||
host = qstring_from_substr(uri->server, 1,
|
|
||||||
strlen(uri->server) - 2);
|
|
||||||
} else {
|
|
||||||
host = qstring_from_str(uri->server);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
s->host_spec = g_strdup_printf("%s:%d", uri->server, uri->port);
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@@ -120,29 +133,16 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_parse_filename(const char *filename, QDict *options,
|
static int nbd_config(BDRVNBDState *s, const char *filename)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
char *file;
|
char *file;
|
||||||
char *export_name;
|
char *export_name;
|
||||||
const char *host_spec;
|
const char *host_spec;
|
||||||
const char *unixpath;
|
const char *unixpath;
|
||||||
|
int err = -EINVAL;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strstr(filename, "://")) {
|
if (strstr(filename, "://")) {
|
||||||
int ret = nbd_parse_uri(filename, options);
|
return nbd_parse_uri(s, filename);
|
||||||
if (ret < 0) {
|
|
||||||
error_setg(errp, "No valid URL specified");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file = g_strdup(filename);
|
file = g_strdup(filename);
|
||||||
@@ -154,86 +154,183 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
|||||||
}
|
}
|
||||||
export_name[0] = 0; /* truncate 'file' */
|
export_name[0] = 0; /* truncate 'file' */
|
||||||
export_name += strlen(EN_OPTSTR);
|
export_name += strlen(EN_OPTSTR);
|
||||||
|
s->export_name = g_strdup(export_name);
|
||||||
qdict_put(options, "export", qstring_from_str(export_name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* extract the host_spec - fail if it's not nbd:... */
|
/* extract the host_spec - fail if it's not nbd:... */
|
||||||
if (!strstart(file, "nbd:", &host_spec)) {
|
if (!strstart(file, "nbd:", &host_spec)) {
|
||||||
error_setg(errp, "File name string for NBD must start with 'nbd:'");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!*host_spec) {
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* are we a UNIX or TCP socket? */
|
/* are we a UNIX or TCP socket? */
|
||||||
if (strstart(host_spec, "unix:", &unixpath)) {
|
if (strstart(host_spec, "unix:", &unixpath)) {
|
||||||
qdict_put(options, "path", qstring_from_str(unixpath));
|
s->is_unix = true;
|
||||||
|
s->host_spec = g_strdup(unixpath);
|
||||||
} else {
|
} else {
|
||||||
InetSocketAddress *addr = NULL;
|
s->is_unix = false;
|
||||||
|
s->host_spec = g_strdup(host_spec);
|
||||||
addr = inet_parse(host_spec, errp);
|
|
||||||
if (error_is_set(errp)) {
|
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qdict_put(options, "host", qstring_from_str(addr->host));
|
err = 0;
|
||||||
qdict_put(options, "port", qstring_from_str(addr->port));
|
|
||||||
qapi_free_InetSocketAddress(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
out:
|
||||||
g_free(file);
|
g_free(file);
|
||||||
|
if (err != 0) {
|
||||||
|
g_free(s->export_name);
|
||||||
|
g_free(s->host_spec);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_config(BDRVNBDState *s, QDict *options, char **export,
|
static void nbd_coroutine_start(BDRVNBDState *s, struct nbd_request *request)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
int i;
|
||||||
|
|
||||||
if (qdict_haskey(options, "path") == qdict_haskey(options, "host")) {
|
/* Poor man semaphore. The free_sema is locked when no other request
|
||||||
if (qdict_haskey(options, "path")) {
|
* can be accepted, and unlocked after receiving one reply. */
|
||||||
error_setg(errp, "path and host may not be used at the same time.");
|
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++;
|
||||||
|
|
||||||
|
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
||||||
|
if (s->recv_coroutine[i] == NULL) {
|
||||||
|
s->recv_coroutine[i] = qemu_coroutine_self();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(i < MAX_NBD_REQUESTS);
|
||||||
|
request->handle = INDEX_TO_HANDLE(s, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nbd_have_request(void *opaque)
|
||||||
|
{
|
||||||
|
BDRVNBDState *s = opaque;
|
||||||
|
|
||||||
|
return s->in_flight > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nbd_reply_ready(void *opaque)
|
||||||
|
{
|
||||||
|
BDRVNBDState *s = opaque;
|
||||||
|
uint64_t i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
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->sock, &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], NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:
|
||||||
|
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
||||||
|
if (s->recv_coroutine[i]) {
|
||||||
|
qemu_coroutine_enter(s->recv_coroutine[i], NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nbd_restart_write(void *opaque)
|
||||||
|
{
|
||||||
|
BDRVNBDState *s = opaque;
|
||||||
|
qemu_coroutine_enter(s->send_coroutine, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nbd_co_send_request(BDRVNBDState *s, struct nbd_request *request,
|
||||||
|
QEMUIOVector *qiov, int offset)
|
||||||
|
{
|
||||||
|
int rc, ret;
|
||||||
|
|
||||||
|
qemu_co_mutex_lock(&s->send_mutex);
|
||||||
|
s->send_coroutine = qemu_coroutine_self();
|
||||||
|
qemu_aio_set_fd_handler(s->sock, nbd_reply_ready, nbd_restart_write,
|
||||||
|
nbd_have_request, s);
|
||||||
|
rc = nbd_send_request(s->sock, request);
|
||||||
|
if (rc >= 0 && qiov) {
|
||||||
|
ret = qemu_co_sendv(s->sock, qiov->iov, qiov->niov,
|
||||||
|
offset, request->len);
|
||||||
|
if (ret != request->len) {
|
||||||
|
rc = -EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qemu_aio_set_fd_handler(s->sock, nbd_reply_ready, NULL,
|
||||||
|
nbd_have_request, s);
|
||||||
|
s->send_coroutine = NULL;
|
||||||
|
qemu_co_mutex_unlock(&s->send_mutex);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nbd_co_receive_reply(BDRVNBDState *s, struct nbd_request *request,
|
||||||
|
struct nbd_reply *reply,
|
||||||
|
QEMUIOVector *qiov, int offset)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* 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();
|
||||||
|
*reply = s->reply;
|
||||||
|
if (reply->handle != request->handle) {
|
||||||
|
reply->error = EIO;
|
||||||
} else {
|
} else {
|
||||||
error_setg(errp, "one of path and host must be specified.");
|
if (qiov && reply->error == 0) {
|
||||||
}
|
ret = qemu_co_recvv(s->sock, qiov->iov, qiov->niov,
|
||||||
return;
|
offset, request->len);
|
||||||
}
|
if (ret != request->len) {
|
||||||
|
reply->error = EIO;
|
||||||
s->client.is_unix = qdict_haskey(options, "path");
|
|
||||||
s->socket_opts = qemu_opts_create(&socket_optslist, NULL, 0,
|
|
||||||
&error_abort);
|
|
||||||
|
|
||||||
qemu_opts_absorb_qdict(s->socket_opts, options, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!qemu_opt_get(s->socket_opts, "port")) {
|
|
||||||
qemu_opt_set_number(s->socket_opts, "port", NBD_DEFAULT_PORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
*export = g_strdup(qdict_get_try_str(options, "export"));
|
|
||||||
if (*export) {
|
|
||||||
qdict_del(options, "export");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nbd_establish_connection(BlockDriverState *bs, Error **errp)
|
/* Tell the read handler to read another header. */
|
||||||
|
s->reply.handle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nbd_coroutine_end(BDRVNBDState *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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nbd_establish_connection(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
int sock;
|
int sock;
|
||||||
|
int ret;
|
||||||
|
off_t size;
|
||||||
|
size_t blocksize;
|
||||||
|
|
||||||
if (s->client.is_unix) {
|
if (s->is_unix) {
|
||||||
sock = unix_connect_opts(s->socket_opts, errp, NULL, NULL);
|
sock = unix_socket_outgoing(s->host_spec);
|
||||||
} else {
|
} else {
|
||||||
sock = inet_connect_opts(s->socket_opts, errp, NULL, NULL);
|
sock = tcp_socket_outgoing_spec(s->host_spec);
|
||||||
if (sock >= 0) {
|
|
||||||
socket_set_nodelay(sock);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Failed to establish connection */
|
/* Failed to establish connection */
|
||||||
@@ -242,92 +339,232 @@ static int nbd_establish_connection(BlockDriverState *bs, Error **errp)
|
|||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sock;
|
/* NBD handshake */
|
||||||
|
ret = nbd_receive_negotiate(sock, s->export_name, &s->nbdflags, &size,
|
||||||
|
&blocksize);
|
||||||
|
if (ret < 0) {
|
||||||
|
logout("Failed to negotiate with the NBD server\n");
|
||||||
|
closesocket(sock);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
/* Now that we're connected, set the socket to be non-blocking and
|
||||||
Error **errp)
|
* kick the reply mechanism. */
|
||||||
|
qemu_set_nonblock(sock);
|
||||||
|
qemu_aio_set_fd_handler(sock, nbd_reply_ready, NULL,
|
||||||
|
nbd_have_request, s);
|
||||||
|
|
||||||
|
s->sock = sock;
|
||||||
|
s->size = size;
|
||||||
|
s->blocksize = blocksize;
|
||||||
|
|
||||||
|
logout("Established connection with NBD server\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nbd_teardown_connection(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
char *export = NULL;
|
struct nbd_request request;
|
||||||
int result, sock;
|
|
||||||
Error *local_err = NULL;
|
request.type = NBD_CMD_DISC;
|
||||||
|
request.from = 0;
|
||||||
|
request.len = 0;
|
||||||
|
nbd_send_request(s->sock, &request);
|
||||||
|
|
||||||
|
qemu_aio_set_fd_handler(s->sock, NULL, NULL, NULL, NULL);
|
||||||
|
closesocket(s->sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nbd_open(BlockDriverState *bs, const char* filename, int flags)
|
||||||
|
{
|
||||||
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
qemu_co_mutex_init(&s->send_mutex);
|
||||||
|
qemu_co_mutex_init(&s->free_sema);
|
||||||
|
|
||||||
/* Pop the config into our state object. Exit if invalid. */
|
/* Pop the config into our state object. Exit if invalid. */
|
||||||
nbd_config(s, options, &export, &local_err);
|
result = nbd_config(s, filename);
|
||||||
if (local_err) {
|
if (result != 0) {
|
||||||
error_propagate(errp, local_err);
|
return result;
|
||||||
return -EINVAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* establish TCP connection, return error if it fails
|
/* establish TCP connection, return error if it fails
|
||||||
* TODO: Configurable retry-until-timeout behaviour.
|
* TODO: Configurable retry-until-timeout behaviour.
|
||||||
*/
|
*/
|
||||||
sock = nbd_establish_connection(bs, errp);
|
result = nbd_establish_connection(bs);
|
||||||
if (sock < 0) {
|
|
||||||
return sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NBD handshake */
|
|
||||||
result = nbd_client_session_init(&s->client, bs, sock, export);
|
|
||||||
g_free(export);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int nbd_co_readv_1(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int offset)
|
||||||
|
{
|
||||||
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
struct nbd_request request;
|
||||||
|
struct nbd_reply reply;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
request.type = NBD_CMD_READ;
|
||||||
|
request.from = sector_num * 512;
|
||||||
|
request.len = nb_sectors * 512;
|
||||||
|
|
||||||
|
nbd_coroutine_start(s, &request);
|
||||||
|
ret = nbd_co_send_request(s, &request, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
reply.error = -ret;
|
||||||
|
} else {
|
||||||
|
nbd_co_receive_reply(s, &request, &reply, qiov, offset);
|
||||||
|
}
|
||||||
|
nbd_coroutine_end(s, &request);
|
||||||
|
return -reply.error;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nbd_co_writev_1(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int offset)
|
||||||
|
{
|
||||||
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
struct nbd_request request;
|
||||||
|
struct nbd_reply reply;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
request.type = NBD_CMD_WRITE;
|
||||||
|
if (!bdrv_enable_write_cache(bs) && (s->nbdflags & NBD_FLAG_SEND_FUA)) {
|
||||||
|
request.type |= NBD_CMD_FLAG_FUA;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.from = sector_num * 512;
|
||||||
|
request.len = nb_sectors * 512;
|
||||||
|
|
||||||
|
nbd_coroutine_start(s, &request);
|
||||||
|
ret = nbd_co_send_request(s, &request, qiov, offset);
|
||||||
|
if (ret < 0) {
|
||||||
|
reply.error = -ret;
|
||||||
|
} else {
|
||||||
|
nbd_co_receive_reply(s, &request, &reply, NULL, 0);
|
||||||
|
}
|
||||||
|
nbd_coroutine_end(s, &request);
|
||||||
|
return -reply.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* qemu-nbd has a limit of slightly less than 1M per request. Try to
|
||||||
|
* remain aligned to 4K. */
|
||||||
|
#define NBD_MAX_SECTORS 2040
|
||||||
|
|
||||||
static int nbd_co_readv(BlockDriverState *bs, int64_t sector_num,
|
static int nbd_co_readv(BlockDriverState *bs, int64_t sector_num,
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
int offset = 0;
|
||||||
|
int ret;
|
||||||
return nbd_client_session_co_readv(&s->client, sector_num,
|
while (nb_sectors > NBD_MAX_SECTORS) {
|
||||||
nb_sectors, qiov);
|
ret = nbd_co_readv_1(bs, sector_num, NBD_MAX_SECTORS, qiov, offset);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
offset += NBD_MAX_SECTORS * 512;
|
||||||
|
sector_num += NBD_MAX_SECTORS;
|
||||||
|
nb_sectors -= NBD_MAX_SECTORS;
|
||||||
|
}
|
||||||
|
return nbd_co_readv_1(bs, sector_num, nb_sectors, qiov, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nbd_co_writev(BlockDriverState *bs, int64_t sector_num,
|
static int nbd_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
int offset = 0;
|
||||||
|
int ret;
|
||||||
return nbd_client_session_co_writev(&s->client, sector_num,
|
while (nb_sectors > NBD_MAX_SECTORS) {
|
||||||
nb_sectors, qiov);
|
ret = nbd_co_writev_1(bs, sector_num, NBD_MAX_SECTORS, qiov, offset);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
offset += NBD_MAX_SECTORS * 512;
|
||||||
|
sector_num += NBD_MAX_SECTORS;
|
||||||
|
nb_sectors -= NBD_MAX_SECTORS;
|
||||||
|
}
|
||||||
|
return nbd_co_writev_1(bs, sector_num, nb_sectors, qiov, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nbd_co_flush(BlockDriverState *bs)
|
static int nbd_co_flush(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
struct nbd_request request;
|
||||||
|
struct nbd_reply reply;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
return nbd_client_session_co_flush(&s->client);
|
if (!(s->nbdflags & NBD_FLAG_SEND_FLUSH)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.type = NBD_CMD_FLUSH;
|
||||||
|
if (s->nbdflags & NBD_FLAG_SEND_FUA) {
|
||||||
|
request.type |= NBD_CMD_FLAG_FUA;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.from = 0;
|
||||||
|
request.len = 0;
|
||||||
|
|
||||||
|
nbd_coroutine_start(s, &request);
|
||||||
|
ret = nbd_co_send_request(s, &request, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
reply.error = -ret;
|
||||||
|
} else {
|
||||||
|
nbd_co_receive_reply(s, &request, &reply, NULL, 0);
|
||||||
|
}
|
||||||
|
nbd_coroutine_end(s, &request);
|
||||||
|
return -reply.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nbd_co_discard(BlockDriverState *bs, int64_t sector_num,
|
static int nbd_co_discard(BlockDriverState *bs, int64_t sector_num,
|
||||||
int nb_sectors)
|
int nb_sectors)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
struct nbd_request request;
|
||||||
|
struct nbd_reply reply;
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
return nbd_client_session_co_discard(&s->client, sector_num,
|
if (!(s->nbdflags & NBD_FLAG_SEND_TRIM)) {
|
||||||
nb_sectors);
|
return 0;
|
||||||
|
}
|
||||||
|
request.type = NBD_CMD_TRIM;
|
||||||
|
request.from = sector_num * 512;;
|
||||||
|
request.len = nb_sectors * 512;
|
||||||
|
|
||||||
|
nbd_coroutine_start(s, &request);
|
||||||
|
ret = nbd_co_send_request(s, &request, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
reply.error = -ret;
|
||||||
|
} else {
|
||||||
|
nbd_co_receive_reply(s, &request, &reply, NULL, 0);
|
||||||
|
}
|
||||||
|
nbd_coroutine_end(s, &request);
|
||||||
|
return -reply.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_close(BlockDriverState *bs)
|
static void nbd_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
g_free(s->export_name);
|
||||||
|
g_free(s->host_spec);
|
||||||
|
|
||||||
qemu_opts_del(s->socket_opts);
|
nbd_teardown_connection(bs);
|
||||||
nbd_client_session_close(&s->client);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t nbd_getlength(BlockDriverState *bs)
|
static int64_t nbd_getlength(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
|
||||||
return s->client.size;
|
return s->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockDriver bdrv_nbd = {
|
static BlockDriver bdrv_nbd = {
|
||||||
.format_name = "nbd",
|
.format_name = "nbd",
|
||||||
.protocol_name = "nbd",
|
.protocol_name = "nbd",
|
||||||
.instance_size = sizeof(BDRVNBDState),
|
.instance_size = sizeof(BDRVNBDState),
|
||||||
.bdrv_parse_filename = nbd_parse_filename,
|
|
||||||
.bdrv_file_open = nbd_open,
|
.bdrv_file_open = nbd_open,
|
||||||
.bdrv_co_readv = nbd_co_readv,
|
.bdrv_co_readv = nbd_co_readv,
|
||||||
.bdrv_co_writev = nbd_co_writev,
|
.bdrv_co_writev = nbd_co_writev,
|
||||||
@@ -341,7 +578,6 @@ static BlockDriver bdrv_nbd_tcp = {
|
|||||||
.format_name = "nbd",
|
.format_name = "nbd",
|
||||||
.protocol_name = "nbd+tcp",
|
.protocol_name = "nbd+tcp",
|
||||||
.instance_size = sizeof(BDRVNBDState),
|
.instance_size = sizeof(BDRVNBDState),
|
||||||
.bdrv_parse_filename = nbd_parse_filename,
|
|
||||||
.bdrv_file_open = nbd_open,
|
.bdrv_file_open = nbd_open,
|
||||||
.bdrv_co_readv = nbd_co_readv,
|
.bdrv_co_readv = nbd_co_readv,
|
||||||
.bdrv_co_writev = nbd_co_writev,
|
.bdrv_co_writev = nbd_co_writev,
|
||||||
@@ -355,7 +591,6 @@ static BlockDriver bdrv_nbd_unix = {
|
|||||||
.format_name = "nbd",
|
.format_name = "nbd",
|
||||||
.protocol_name = "nbd+unix",
|
.protocol_name = "nbd+unix",
|
||||||
.instance_size = sizeof(BDRVNBDState),
|
.instance_size = sizeof(BDRVNBDState),
|
||||||
.bdrv_parse_filename = nbd_parse_filename,
|
|
||||||
.bdrv_file_open = nbd_open,
|
.bdrv_file_open = nbd_open,
|
||||||
.bdrv_co_readv = nbd_co_readv,
|
.bdrv_co_readv = nbd_co_readv,
|
||||||
.bdrv_co_writev = nbd_co_writev,
|
.bdrv_co_writev = nbd_co_writev,
|
||||||
|
|||||||
442
block/nfs.c
442
block/nfs.c
@@ -1,442 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU Block driver for native access to files on NFS shares
|
|
||||||
*
|
|
||||||
* Copyright (c) 2014 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 "config-host.h"
|
|
||||||
|
|
||||||
#include <poll.h>
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "qemu/config-file.h"
|
|
||||||
#include "qemu/error-report.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "trace.h"
|
|
||||||
#include "qemu/iov.h"
|
|
||||||
#include "qemu/uri.h"
|
|
||||||
#include "sysemu/sysemu.h"
|
|
||||||
#include <nfsc/libnfs.h>
|
|
||||||
|
|
||||||
typedef struct NFSClient {
|
|
||||||
struct nfs_context *context;
|
|
||||||
struct nfsfh *fh;
|
|
||||||
int events;
|
|
||||||
bool has_zero_init;
|
|
||||||
} NFSClient;
|
|
||||||
|
|
||||||
typedef struct NFSRPC {
|
|
||||||
int ret;
|
|
||||||
int complete;
|
|
||||||
QEMUIOVector *iov;
|
|
||||||
struct stat *st;
|
|
||||||
Coroutine *co;
|
|
||||||
QEMUBH *bh;
|
|
||||||
} NFSRPC;
|
|
||||||
|
|
||||||
static void nfs_process_read(void *arg);
|
|
||||||
static void nfs_process_write(void *arg);
|
|
||||||
|
|
||||||
static void nfs_set_events(NFSClient *client)
|
|
||||||
{
|
|
||||||
int ev = nfs_which_events(client->context);
|
|
||||||
if (ev != client->events) {
|
|
||||||
qemu_aio_set_fd_handler(nfs_get_fd(client->context),
|
|
||||||
(ev & POLLIN) ? nfs_process_read : NULL,
|
|
||||||
(ev & POLLOUT) ? nfs_process_write : NULL,
|
|
||||||
client);
|
|
||||||
|
|
||||||
}
|
|
||||||
client->events = ev;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_process_read(void *arg)
|
|
||||||
{
|
|
||||||
NFSClient *client = arg;
|
|
||||||
nfs_service(client->context, POLLIN);
|
|
||||||
nfs_set_events(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_process_write(void *arg)
|
|
||||||
{
|
|
||||||
NFSClient *client = arg;
|
|
||||||
nfs_service(client->context, POLLOUT);
|
|
||||||
nfs_set_events(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_co_init_task(NFSClient *client, NFSRPC *task)
|
|
||||||
{
|
|
||||||
*task = (NFSRPC) {
|
|
||||||
.co = qemu_coroutine_self(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_co_generic_bh_cb(void *opaque)
|
|
||||||
{
|
|
||||||
NFSRPC *task = opaque;
|
|
||||||
qemu_bh_delete(task->bh);
|
|
||||||
qemu_coroutine_enter(task->co, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
|
|
||||||
void *private_data)
|
|
||||||
{
|
|
||||||
NFSRPC *task = private_data;
|
|
||||||
task->complete = 1;
|
|
||||||
task->ret = ret;
|
|
||||||
if (task->ret > 0 && task->iov) {
|
|
||||||
if (task->ret <= task->iov->size) {
|
|
||||||
qemu_iovec_from_buf(task->iov, 0, data, task->ret);
|
|
||||||
} else {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
if (task->co) {
|
|
||||||
task->bh = qemu_bh_new(nfs_co_generic_bh_cb, task);
|
|
||||||
qemu_bh_schedule(task->bh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(client, &task);
|
|
||||||
task.iov = iov;
|
|
||||||
|
|
||||||
if (nfs_pread_async(client->context, client->fh,
|
|
||||||
sector_num * BDRV_SECTOR_SIZE,
|
|
||||||
nb_sectors * BDRV_SECTOR_SIZE,
|
|
||||||
nfs_co_generic_cb, &task) != 0) {
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!task.complete) {
|
|
||||||
nfs_set_events(client);
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task.ret < 0) {
|
|
||||||
return task.ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* zero pad short reads */
|
|
||||||
if (task.ret < iov->size) {
|
|
||||||
qemu_iovec_memset(iov, task.ret, 0, iov->size - task.ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
nfs_co_init_task(client, &task);
|
|
||||||
|
|
||||||
buf = g_malloc(nb_sectors * BDRV_SECTOR_SIZE);
|
|
||||||
qemu_iovec_to_buf(iov, 0, buf, nb_sectors * BDRV_SECTOR_SIZE);
|
|
||||||
|
|
||||||
if (nfs_pwrite_async(client->context, client->fh,
|
|
||||||
sector_num * BDRV_SECTOR_SIZE,
|
|
||||||
nb_sectors * BDRV_SECTOR_SIZE,
|
|
||||||
buf, nfs_co_generic_cb, &task) != 0) {
|
|
||||||
g_free(buf);
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!task.complete) {
|
|
||||||
nfs_set_events(client);
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(buf);
|
|
||||||
|
|
||||||
if (task.ret != nb_sectors * BDRV_SECTOR_SIZE) {
|
|
||||||
return task.ret < 0 ? task.ret : -EIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn nfs_co_flush(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
NFSClient *client = bs->opaque;
|
|
||||||
NFSRPC task;
|
|
||||||
|
|
||||||
nfs_co_init_task(client, &task);
|
|
||||||
|
|
||||||
if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb,
|
|
||||||
&task) != 0) {
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = "filename",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "URL to the NFS file",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static void nfs_client_close(NFSClient *client)
|
|
||||||
{
|
|
||||||
if (client->context) {
|
|
||||||
if (client->fh) {
|
|
||||||
nfs_close(client->context, client->fh);
|
|
||||||
}
|
|
||||||
qemu_aio_set_fd_handler(nfs_get_fd(client->context), NULL, NULL, NULL);
|
|
||||||
nfs_destroy_context(client->context);
|
|
||||||
}
|
|
||||||
memset(client, 0, sizeof(NFSClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_file_close(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
NFSClient *client = bs->opaque;
|
|
||||||
nfs_client_close(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t nfs_client_open(NFSClient *client, const char *filename,
|
|
||||||
int flags, Error **errp)
|
|
||||||
{
|
|
||||||
int ret = -EINVAL, i;
|
|
||||||
struct stat st;
|
|
||||||
URI *uri;
|
|
||||||
QueryParams *qp = NULL;
|
|
||||||
char *file = NULL, *strp = NULL;
|
|
||||||
|
|
||||||
uri = uri_parse(filename);
|
|
||||||
if (!uri) {
|
|
||||||
error_setg(errp, "Invalid URL specified");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
strp = strrchr(uri->path, '/');
|
|
||||||
if (strp == NULL) {
|
|
||||||
error_setg(errp, "Invalid URL specified");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
file = g_strdup(strp);
|
|
||||||
*strp = 0;
|
|
||||||
|
|
||||||
client->context = nfs_init_context();
|
|
||||||
if (client->context == NULL) {
|
|
||||||
error_setg(errp, "Failed to init NFS context");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
qp = query_params_parse(uri->query);
|
|
||||||
for (i = 0; i < qp->n; i++) {
|
|
||||||
if (!qp->p[i].value) {
|
|
||||||
error_setg(errp, "Value for NFS parameter expected: %s",
|
|
||||||
qp->p[i].name);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
if (!strncmp(qp->p[i].name, "uid", 3)) {
|
|
||||||
nfs_set_uid(client->context, atoi(qp->p[i].value));
|
|
||||||
} else if (!strncmp(qp->p[i].name, "gid", 3)) {
|
|
||||||
nfs_set_gid(client->context, atoi(qp->p[i].value));
|
|
||||||
} else if (!strncmp(qp->p[i].name, "tcp-syncnt", 10)) {
|
|
||||||
nfs_set_tcp_syncnt(client->context, atoi(qp->p[i].value));
|
|
||||||
} else {
|
|
||||||
error_setg(errp, "Unknown NFS parameter name: %s",
|
|
||||||
qp->p[i].name);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & O_CREAT) {
|
|
||||||
ret = nfs_creat(client->context, file, 0600, &client->fh);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg(errp, "Failed to create file: %s",
|
|
||||||
nfs_get_error(client->context));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret = nfs_open(client->context, file, flags, &client->fh);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg(errp, "Failed to open file : %s",
|
|
||||||
nfs_get_error(client->context));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = nfs_fstat(client->context, client->fh, &st);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg(errp, "Failed to fstat file: %s",
|
|
||||||
nfs_get_error(client->context));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
|
|
||||||
client->has_zero_init = S_ISREG(st.st_mode);
|
|
||||||
goto out;
|
|
||||||
fail:
|
|
||||||
nfs_client_close(client);
|
|
||||||
out:
|
|
||||||
if (qp) {
|
|
||||||
query_params_free(qp);
|
|
||||||
}
|
|
||||||
uri_free(uri);
|
|
||||||
g_free(file);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
||||||
if (error_is_set(&local_err)) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
ret = nfs_client_open(client, qemu_opt_get(opts, "filename"),
|
|
||||||
(flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
|
|
||||||
errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
bs->total_sectors = ret;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nfs_file_create(const char *url, QEMUOptionParameter *options,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
int64_t total_size = 0;
|
|
||||||
NFSClient *client = g_malloc0(sizeof(NFSClient));
|
|
||||||
|
|
||||||
/* Read out options */
|
|
||||||
while (options && options->name) {
|
|
||||||
if (!strcmp(options->name, "size")) {
|
|
||||||
total_size = options->value.n;
|
|
||||||
}
|
|
||||||
options++;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = nfs_client_open(client, url, O_CREAT, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
ret = nfs_ftruncate(client->context, client->fh, total_size);
|
|
||||||
nfs_client_close(client);
|
|
||||||
out:
|
|
||||||
g_free(client);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nfs_has_zero_init(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
NFSClient *client = bs->opaque;
|
|
||||||
return client->has_zero_init;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
NFSClient *client = bs->opaque;
|
|
||||||
NFSRPC task = {0};
|
|
||||||
struct stat st;
|
|
||||||
|
|
||||||
task.st = &st;
|
|
||||||
if (nfs_fstat_async(client->context, client->fh, nfs_co_generic_cb,
|
|
||||||
&task) != 0) {
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!task.complete) {
|
|
||||||
nfs_set_events(client);
|
|
||||||
qemu_aio_wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (task.ret < 0 ? task.ret : st.st_blocks * st.st_blksize);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nfs_file_truncate(BlockDriverState *bs, int64_t offset)
|
|
||||||
{
|
|
||||||
NFSClient *client = bs->opaque;
|
|
||||||
return nfs_ftruncate(client->context, client->fh, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriver bdrv_nfs = {
|
|
||||||
.format_name = "nfs",
|
|
||||||
.protocol_name = "nfs",
|
|
||||||
|
|
||||||
.instance_size = sizeof(NFSClient),
|
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_has_zero_init = nfs_has_zero_init,
|
|
||||||
.bdrv_get_allocated_file_size = nfs_get_allocated_file_size,
|
|
||||||
.bdrv_truncate = nfs_file_truncate,
|
|
||||||
|
|
||||||
.bdrv_file_open = nfs_file_open,
|
|
||||||
.bdrv_close = nfs_file_close,
|
|
||||||
.bdrv_create = nfs_file_create,
|
|
||||||
|
|
||||||
.bdrv_co_readv = nfs_co_readv,
|
|
||||||
.bdrv_co_writev = nfs_co_writev,
|
|
||||||
.bdrv_co_flush_to_disk = nfs_co_flush,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void nfs_block_init(void)
|
|
||||||
{
|
|
||||||
bdrv_register(&bdrv_nfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
block_init(nfs_block_init);
|
|
||||||
@@ -68,8 +68,7 @@ static int parallels_probe(const uint8_t *buf, int buf_size, const char *filenam
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
|
static int parallels_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVParallelsState *s = bs->opaque;
|
BDRVParallelsState *s = bs->opaque;
|
||||||
int i;
|
int i;
|
||||||
@@ -85,8 +84,7 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
|
|
||||||
if (memcmp(ph.magic, HEADER_MAGIC, 16) ||
|
if (memcmp(ph.magic, HEADER_MAGIC, 16) ||
|
||||||
(le32_to_cpu(ph.version) != HEADER_VERSION)) {
|
(le32_to_cpu(ph.version) != HEADER_VERSION)) {
|
||||||
error_setg(errp, "Image not in Parallels format");
|
ret = -EMEDIUMTYPE;
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,14 +92,15 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
|
|
||||||
s->tracks = le32_to_cpu(ph.tracks);
|
s->tracks = le32_to_cpu(ph.tracks);
|
||||||
if (s->tracks == 0) {
|
if (s->tracks == 0) {
|
||||||
error_setg(errp, "Invalid image: Zero sectors per track");
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Invalid image: Zero sectors per track");
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->catalog_size = le32_to_cpu(ph.catalog_entries);
|
s->catalog_size = le32_to_cpu(ph.catalog_entries);
|
||||||
if (s->catalog_size > INT_MAX / 4) {
|
if (s->catalog_size > INT_MAX / 4) {
|
||||||
error_setg(errp, "Catalog too large");
|
qerror_report(ERROR_CLASS_GENERIC_ERROR, "Catalog too large");
|
||||||
ret = -EFBIG;
|
ret = -EFBIG;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|||||||
623
block/qapi.c
623
block/qapi.c
@@ -1,623 +0,0 @@
|
|||||||
/*
|
|
||||||
* Block layer qmp and info dump related functions
|
|
||||||
*
|
|
||||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
|
||||||
*
|
|
||||||
* 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 "block/qapi.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "qmp-commands.h"
|
|
||||||
#include "qapi-visit.h"
|
|
||||||
#include "qapi/qmp-output-visitor.h"
|
|
||||||
#include "qapi/qmp/types.h"
|
|
||||||
|
|
||||||
BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockDeviceInfo *info = g_malloc0(sizeof(*info));
|
|
||||||
|
|
||||||
info->file = g_strdup(bs->filename);
|
|
||||||
info->ro = bs->read_only;
|
|
||||||
info->drv = g_strdup(bs->drv->format_name);
|
|
||||||
info->encrypted = bs->encrypted;
|
|
||||||
info->encryption_key_missing = bdrv_key_required(bs);
|
|
||||||
|
|
||||||
if (bs->node_name[0]) {
|
|
||||||
info->has_node_name = true;
|
|
||||||
info->node_name = g_strdup(bs->node_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bs->backing_file[0]) {
|
|
||||||
info->has_backing_file = true;
|
|
||||||
info->backing_file = g_strdup(bs->backing_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
info->backing_file_depth = bdrv_get_backing_file_depth(bs);
|
|
||||||
|
|
||||||
if (bs->io_limits_enabled) {
|
|
||||||
ThrottleConfig cfg;
|
|
||||||
throttle_get_config(&bs->throttle_state, &cfg);
|
|
||||||
info->bps = cfg.buckets[THROTTLE_BPS_TOTAL].avg;
|
|
||||||
info->bps_rd = cfg.buckets[THROTTLE_BPS_READ].avg;
|
|
||||||
info->bps_wr = cfg.buckets[THROTTLE_BPS_WRITE].avg;
|
|
||||||
|
|
||||||
info->iops = cfg.buckets[THROTTLE_OPS_TOTAL].avg;
|
|
||||||
info->iops_rd = cfg.buckets[THROTTLE_OPS_READ].avg;
|
|
||||||
info->iops_wr = cfg.buckets[THROTTLE_OPS_WRITE].avg;
|
|
||||||
|
|
||||||
info->has_bps_max = cfg.buckets[THROTTLE_BPS_TOTAL].max;
|
|
||||||
info->bps_max = cfg.buckets[THROTTLE_BPS_TOTAL].max;
|
|
||||||
info->has_bps_rd_max = cfg.buckets[THROTTLE_BPS_READ].max;
|
|
||||||
info->bps_rd_max = cfg.buckets[THROTTLE_BPS_READ].max;
|
|
||||||
info->has_bps_wr_max = cfg.buckets[THROTTLE_BPS_WRITE].max;
|
|
||||||
info->bps_wr_max = cfg.buckets[THROTTLE_BPS_WRITE].max;
|
|
||||||
|
|
||||||
info->has_iops_max = cfg.buckets[THROTTLE_OPS_TOTAL].max;
|
|
||||||
info->iops_max = cfg.buckets[THROTTLE_OPS_TOTAL].max;
|
|
||||||
info->has_iops_rd_max = cfg.buckets[THROTTLE_OPS_READ].max;
|
|
||||||
info->iops_rd_max = cfg.buckets[THROTTLE_OPS_READ].max;
|
|
||||||
info->has_iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max;
|
|
||||||
info->iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max;
|
|
||||||
|
|
||||||
info->has_iops_size = cfg.op_size;
|
|
||||||
info->iops_size = cfg.op_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns 0 on success, with *p_list either set to describe snapshot
|
|
||||||
* information, or NULL because there are no snapshots. Returns -errno on
|
|
||||||
* error, with *p_list untouched.
|
|
||||||
*/
|
|
||||||
int bdrv_query_snapshot_info_list(BlockDriverState *bs,
|
|
||||||
SnapshotInfoList **p_list,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int i, sn_count;
|
|
||||||
QEMUSnapshotInfo *sn_tab = NULL;
|
|
||||||
SnapshotInfoList *info_list, *cur_item = NULL, *head = NULL;
|
|
||||||
SnapshotInfo *info;
|
|
||||||
|
|
||||||
sn_count = bdrv_snapshot_list(bs, &sn_tab);
|
|
||||||
if (sn_count < 0) {
|
|
||||||
const char *dev = bdrv_get_device_name(bs);
|
|
||||||
switch (sn_count) {
|
|
||||||
case -ENOMEDIUM:
|
|
||||||
error_setg(errp, "Device '%s' is not inserted", dev);
|
|
||||||
break;
|
|
||||||
case -ENOTSUP:
|
|
||||||
error_setg(errp,
|
|
||||||
"Device '%s' does not support internal snapshots",
|
|
||||||
dev);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error_setg_errno(errp, -sn_count,
|
|
||||||
"Can't list snapshots of device '%s'", dev);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return sn_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < sn_count; i++) {
|
|
||||||
info = g_new0(SnapshotInfo, 1);
|
|
||||||
info->id = g_strdup(sn_tab[i].id_str);
|
|
||||||
info->name = g_strdup(sn_tab[i].name);
|
|
||||||
info->vm_state_size = sn_tab[i].vm_state_size;
|
|
||||||
info->date_sec = sn_tab[i].date_sec;
|
|
||||||
info->date_nsec = sn_tab[i].date_nsec;
|
|
||||||
info->vm_clock_sec = sn_tab[i].vm_clock_nsec / 1000000000;
|
|
||||||
info->vm_clock_nsec = sn_tab[i].vm_clock_nsec % 1000000000;
|
|
||||||
|
|
||||||
info_list = g_new0(SnapshotInfoList, 1);
|
|
||||||
info_list->value = info;
|
|
||||||
|
|
||||||
/* XXX: waiting for the qapi to support qemu-queue.h types */
|
|
||||||
if (!cur_item) {
|
|
||||||
head = cur_item = info_list;
|
|
||||||
} else {
|
|
||||||
cur_item->next = info_list;
|
|
||||||
cur_item = info_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(sn_tab);
|
|
||||||
*p_list = head;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bdrv_query_image_info:
|
|
||||||
* @bs: block device to examine
|
|
||||||
* @p_info: location to store image information
|
|
||||||
* @errp: location to store error information
|
|
||||||
*
|
|
||||||
* Store "flat" image information in @p_info.
|
|
||||||
*
|
|
||||||
* "Flat" means it does *not* query backing image information,
|
|
||||||
* i.e. (*pinfo)->has_backing_image will be set to false and
|
|
||||||
* (*pinfo)->backing_image to NULL even when the image does in fact have
|
|
||||||
* a backing image.
|
|
||||||
*
|
|
||||||
* @p_info will be set only on success. On error, store error in @errp.
|
|
||||||
*/
|
|
||||||
void bdrv_query_image_info(BlockDriverState *bs,
|
|
||||||
ImageInfo **p_info,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
uint64_t total_sectors;
|
|
||||||
const char *backing_filename;
|
|
||||||
char backing_filename2[1024];
|
|
||||||
BlockDriverInfo bdi;
|
|
||||||
int ret;
|
|
||||||
Error *err = NULL;
|
|
||||||
ImageInfo *info = g_new0(ImageInfo, 1);
|
|
||||||
|
|
||||||
bdrv_get_geometry(bs, &total_sectors);
|
|
||||||
|
|
||||||
info->filename = g_strdup(bs->filename);
|
|
||||||
info->format = g_strdup(bdrv_get_format_name(bs));
|
|
||||||
info->virtual_size = total_sectors * 512;
|
|
||||||
info->actual_size = bdrv_get_allocated_file_size(bs);
|
|
||||||
info->has_actual_size = info->actual_size >= 0;
|
|
||||||
if (bdrv_is_encrypted(bs)) {
|
|
||||||
info->encrypted = true;
|
|
||||||
info->has_encrypted = true;
|
|
||||||
}
|
|
||||||
if (bdrv_get_info(bs, &bdi) >= 0) {
|
|
||||||
if (bdi.cluster_size != 0) {
|
|
||||||
info->cluster_size = bdi.cluster_size;
|
|
||||||
info->has_cluster_size = true;
|
|
||||||
}
|
|
||||||
info->dirty_flag = bdi.is_dirty;
|
|
||||||
info->has_dirty_flag = true;
|
|
||||||
}
|
|
||||||
info->format_specific = bdrv_get_specific_info(bs);
|
|
||||||
info->has_format_specific = info->format_specific != NULL;
|
|
||||||
|
|
||||||
backing_filename = bs->backing_file;
|
|
||||||
if (backing_filename[0] != '\0') {
|
|
||||||
info->backing_filename = g_strdup(backing_filename);
|
|
||||||
info->has_backing_filename = true;
|
|
||||||
bdrv_get_full_backing_filename(bs, backing_filename2,
|
|
||||||
sizeof(backing_filename2));
|
|
||||||
|
|
||||||
if (strcmp(backing_filename, backing_filename2) != 0) {
|
|
||||||
info->full_backing_filename =
|
|
||||||
g_strdup(backing_filename2);
|
|
||||||
info->has_full_backing_filename = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bs->backing_format[0]) {
|
|
||||||
info->backing_filename_format = g_strdup(bs->backing_format);
|
|
||||||
info->has_backing_filename_format = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_query_snapshot_info_list(bs, &info->snapshots, &err);
|
|
||||||
switch (ret) {
|
|
||||||
case 0:
|
|
||||||
if (info->snapshots) {
|
|
||||||
info->has_snapshots = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
/* recoverable error */
|
|
||||||
case -ENOMEDIUM:
|
|
||||||
case -ENOTSUP:
|
|
||||||
error_free(err);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error_propagate(errp, err);
|
|
||||||
qapi_free_ImageInfo(info);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
*p_info = info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @p_info will be set only on success. */
|
|
||||||
void bdrv_query_info(BlockDriverState *bs,
|
|
||||||
BlockInfo **p_info,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BlockInfo *info = g_malloc0(sizeof(*info));
|
|
||||||
BlockDriverState *bs0;
|
|
||||||
ImageInfo **p_image_info;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
info->device = g_strdup(bs->device_name);
|
|
||||||
info->type = g_strdup("unknown");
|
|
||||||
info->locked = bdrv_dev_is_medium_locked(bs);
|
|
||||||
info->removable = bdrv_dev_has_removable_media(bs);
|
|
||||||
|
|
||||||
if (bdrv_dev_has_removable_media(bs)) {
|
|
||||||
info->has_tray_open = true;
|
|
||||||
info->tray_open = bdrv_dev_is_tray_open(bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bdrv_iostatus_is_enabled(bs)) {
|
|
||||||
info->has_io_status = true;
|
|
||||||
info->io_status = bs->iostatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!QLIST_EMPTY(&bs->dirty_bitmaps)) {
|
|
||||||
info->has_dirty_bitmaps = true;
|
|
||||||
info->dirty_bitmaps = bdrv_query_dirty_bitmaps(bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bs->drv) {
|
|
||||||
info->has_inserted = true;
|
|
||||||
info->inserted = bdrv_block_device_info(bs);
|
|
||||||
|
|
||||||
bs0 = bs;
|
|
||||||
p_image_info = &info->inserted->image;
|
|
||||||
while (1) {
|
|
||||||
bdrv_query_image_info(bs0, p_image_info, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
if (bs0->drv && bs0->backing_hd) {
|
|
||||||
bs0 = bs0->backing_hd;
|
|
||||||
(*p_image_info)->has_backing_image = true;
|
|
||||||
p_image_info = &((*p_image_info)->backing_image);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*p_info = info;
|
|
||||||
return;
|
|
||||||
|
|
||||||
err:
|
|
||||||
qapi_free_BlockInfo(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockStats *bdrv_query_stats(const BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockStats *s;
|
|
||||||
|
|
||||||
s = g_malloc0(sizeof(*s));
|
|
||||||
|
|
||||||
if (bs->device_name[0]) {
|
|
||||||
s->has_device = true;
|
|
||||||
s->device = g_strdup(bs->device_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
s->stats = g_malloc0(sizeof(*s->stats));
|
|
||||||
s->stats->rd_bytes = bs->nr_bytes[BDRV_ACCT_READ];
|
|
||||||
s->stats->wr_bytes = bs->nr_bytes[BDRV_ACCT_WRITE];
|
|
||||||
s->stats->rd_operations = bs->nr_ops[BDRV_ACCT_READ];
|
|
||||||
s->stats->wr_operations = bs->nr_ops[BDRV_ACCT_WRITE];
|
|
||||||
s->stats->wr_highest_offset = bs->wr_highest_sector * BDRV_SECTOR_SIZE;
|
|
||||||
s->stats->flush_operations = bs->nr_ops[BDRV_ACCT_FLUSH];
|
|
||||||
s->stats->wr_total_time_ns = bs->total_time_ns[BDRV_ACCT_WRITE];
|
|
||||||
s->stats->rd_total_time_ns = bs->total_time_ns[BDRV_ACCT_READ];
|
|
||||||
s->stats->flush_total_time_ns = bs->total_time_ns[BDRV_ACCT_FLUSH];
|
|
||||||
|
|
||||||
if (bs->file) {
|
|
||||||
s->has_parent = true;
|
|
||||||
s->parent = bdrv_query_stats(bs->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bs->backing_hd) {
|
|
||||||
s->has_backing = true;
|
|
||||||
s->backing = bdrv_query_stats(bs->backing_hd);
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockInfoList *qmp_query_block(Error **errp)
|
|
||||||
{
|
|
||||||
BlockInfoList *head = NULL, **p_next = &head;
|
|
||||||
BlockDriverState *bs = NULL;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
while ((bs = bdrv_next(bs))) {
|
|
||||||
BlockInfoList *info = g_malloc0(sizeof(*info));
|
|
||||||
bdrv_query_info(bs, &info->value, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
*p_next = info;
|
|
||||||
p_next = &info->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return head;
|
|
||||||
|
|
||||||
err:
|
|
||||||
qapi_free_BlockInfoList(head);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockStatsList *qmp_query_blockstats(Error **errp)
|
|
||||||
{
|
|
||||||
BlockStatsList *head = NULL, **p_next = &head;
|
|
||||||
BlockDriverState *bs = NULL;
|
|
||||||
|
|
||||||
while ((bs = bdrv_next(bs))) {
|
|
||||||
BlockStatsList *info = g_malloc0(sizeof(*info));
|
|
||||||
info->value = bdrv_query_stats(bs);
|
|
||||||
|
|
||||||
*p_next = info;
|
|
||||||
p_next = &info->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define NB_SUFFIXES 4
|
|
||||||
|
|
||||||
static char *get_human_readable_size(char *buf, int buf_size, int64_t size)
|
|
||||||
{
|
|
||||||
static const char suffixes[NB_SUFFIXES] = "KMGT";
|
|
||||||
int64_t base;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (size <= 999) {
|
|
||||||
snprintf(buf, buf_size, "%" PRId64, size);
|
|
||||||
} else {
|
|
||||||
base = 1024;
|
|
||||||
for (i = 0; i < NB_SUFFIXES; i++) {
|
|
||||||
if (size < (10 * base)) {
|
|
||||||
snprintf(buf, buf_size, "%0.1f%c",
|
|
||||||
(double)size / base,
|
|
||||||
suffixes[i]);
|
|
||||||
break;
|
|
||||||
} else if (size < (1000 * base) || i == (NB_SUFFIXES - 1)) {
|
|
||||||
snprintf(buf, buf_size, "%" PRId64 "%c",
|
|
||||||
((size + (base >> 1)) / base),
|
|
||||||
suffixes[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
base = base * 1024;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_snapshot_dump(fprintf_function func_fprintf, void *f,
|
|
||||||
QEMUSnapshotInfo *sn)
|
|
||||||
{
|
|
||||||
char buf1[128], date_buf[128], clock_buf[128];
|
|
||||||
struct tm tm;
|
|
||||||
time_t ti;
|
|
||||||
int64_t secs;
|
|
||||||
|
|
||||||
if (!sn) {
|
|
||||||
func_fprintf(f,
|
|
||||||
"%-10s%-20s%7s%20s%15s",
|
|
||||||
"ID", "TAG", "VM SIZE", "DATE", "VM CLOCK");
|
|
||||||
} else {
|
|
||||||
ti = sn->date_sec;
|
|
||||||
localtime_r(&ti, &tm);
|
|
||||||
strftime(date_buf, sizeof(date_buf),
|
|
||||||
"%Y-%m-%d %H:%M:%S", &tm);
|
|
||||||
secs = sn->vm_clock_nsec / 1000000000;
|
|
||||||
snprintf(clock_buf, sizeof(clock_buf),
|
|
||||||
"%02d:%02d:%02d.%03d",
|
|
||||||
(int)(secs / 3600),
|
|
||||||
(int)((secs / 60) % 60),
|
|
||||||
(int)(secs % 60),
|
|
||||||
(int)((sn->vm_clock_nsec / 1000000) % 1000));
|
|
||||||
func_fprintf(f,
|
|
||||||
"%-10s%-20s%7s%20s%15s",
|
|
||||||
sn->id_str, sn->name,
|
|
||||||
get_human_readable_size(buf1, sizeof(buf1),
|
|
||||||
sn->vm_state_size),
|
|
||||||
date_buf,
|
|
||||||
clock_buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump_qdict(fprintf_function func_fprintf, void *f, int indentation,
|
|
||||||
QDict *dict);
|
|
||||||
static void dump_qlist(fprintf_function func_fprintf, void *f, int indentation,
|
|
||||||
QList *list);
|
|
||||||
|
|
||||||
static void dump_qobject(fprintf_function func_fprintf, void *f,
|
|
||||||
int comp_indent, QObject *obj)
|
|
||||||
{
|
|
||||||
switch (qobject_type(obj)) {
|
|
||||||
case QTYPE_QINT: {
|
|
||||||
QInt *value = qobject_to_qint(obj);
|
|
||||||
func_fprintf(f, "%" PRId64, qint_get_int(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QTYPE_QSTRING: {
|
|
||||||
QString *value = qobject_to_qstring(obj);
|
|
||||||
func_fprintf(f, "%s", qstring_get_str(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QTYPE_QDICT: {
|
|
||||||
QDict *value = qobject_to_qdict(obj);
|
|
||||||
dump_qdict(func_fprintf, f, comp_indent, value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QTYPE_QLIST: {
|
|
||||||
QList *value = qobject_to_qlist(obj);
|
|
||||||
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_int(value) ? "true" : "false");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QTYPE_QERROR: {
|
|
||||||
QString *value = qerror_human((QError *)obj);
|
|
||||||
func_fprintf(f, "%s", qstring_get_str(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QTYPE_NONE:
|
|
||||||
break;
|
|
||||||
case QTYPE_MAX:
|
|
||||||
default:
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump_qlist(fprintf_function func_fprintf, void *f, int indentation,
|
|
||||||
QList *list)
|
|
||||||
{
|
|
||||||
const QListEntry *entry;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (entry = qlist_first(list); entry; entry = qlist_next(entry), i++) {
|
|
||||||
qtype_code type = qobject_type(entry->value);
|
|
||||||
bool composite = (type == QTYPE_QDICT || type == QTYPE_QLIST);
|
|
||||||
const char *format = composite ? "%*s[%i]:\n" : "%*s[%i]: ";
|
|
||||||
|
|
||||||
func_fprintf(f, format, indentation * 4, "", i);
|
|
||||||
dump_qobject(func_fprintf, f, indentation + 1, entry->value);
|
|
||||||
if (!composite) {
|
|
||||||
func_fprintf(f, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump_qdict(fprintf_function func_fprintf, void *f, int indentation,
|
|
||||||
QDict *dict)
|
|
||||||
{
|
|
||||||
const QDictEntry *entry;
|
|
||||||
|
|
||||||
for (entry = qdict_first(dict); entry; entry = qdict_next(dict, entry)) {
|
|
||||||
qtype_code type = qobject_type(entry->value);
|
|
||||||
bool composite = (type == QTYPE_QDICT || type == QTYPE_QLIST);
|
|
||||||
const char *format = composite ? "%*s%s:\n" : "%*s%s: ";
|
|
||||||
char key[strlen(entry->key) + 1];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* replace dashes with spaces in key (variable) names */
|
|
||||||
for (i = 0; entry->key[i]; i++) {
|
|
||||||
key[i] = entry->key[i] == '-' ? ' ' : entry->key[i];
|
|
||||||
}
|
|
||||||
key[i] = 0;
|
|
||||||
|
|
||||||
func_fprintf(f, format, indentation * 4, "", key);
|
|
||||||
dump_qobject(func_fprintf, f, indentation + 1, entry->value);
|
|
||||||
if (!composite) {
|
|
||||||
func_fprintf(f, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_image_info_specific_dump(fprintf_function func_fprintf, void *f,
|
|
||||||
ImageInfoSpecific *info_spec)
|
|
||||||
{
|
|
||||||
Error *local_err = NULL;
|
|
||||||
QmpOutputVisitor *ov = qmp_output_visitor_new();
|
|
||||||
QObject *obj, *data;
|
|
||||||
|
|
||||||
visit_type_ImageInfoSpecific(qmp_output_get_visitor(ov), &info_spec, NULL,
|
|
||||||
&local_err);
|
|
||||||
obj = qmp_output_get_qobject(ov);
|
|
||||||
assert(qobject_type(obj) == QTYPE_QDICT);
|
|
||||||
data = qdict_get(qobject_to_qdict(obj), "data");
|
|
||||||
dump_qobject(func_fprintf, f, 1, data);
|
|
||||||
qmp_output_visitor_cleanup(ov);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_image_info_dump(fprintf_function func_fprintf, void *f,
|
|
||||||
ImageInfo *info)
|
|
||||||
{
|
|
||||||
char size_buf[128], dsize_buf[128];
|
|
||||||
if (!info->has_actual_size) {
|
|
||||||
snprintf(dsize_buf, sizeof(dsize_buf), "unavailable");
|
|
||||||
} else {
|
|
||||||
get_human_readable_size(dsize_buf, sizeof(dsize_buf),
|
|
||||||
info->actual_size);
|
|
||||||
}
|
|
||||||
get_human_readable_size(size_buf, sizeof(size_buf), info->virtual_size);
|
|
||||||
func_fprintf(f,
|
|
||||||
"image: %s\n"
|
|
||||||
"file format: %s\n"
|
|
||||||
"virtual size: %s (%" PRId64 " bytes)\n"
|
|
||||||
"disk size: %s\n",
|
|
||||||
info->filename, info->format, size_buf,
|
|
||||||
info->virtual_size,
|
|
||||||
dsize_buf);
|
|
||||||
|
|
||||||
if (info->has_encrypted && info->encrypted) {
|
|
||||||
func_fprintf(f, "encrypted: yes\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info->has_cluster_size) {
|
|
||||||
func_fprintf(f, "cluster_size: %" PRId64 "\n",
|
|
||||||
info->cluster_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info->has_dirty_flag && info->dirty_flag) {
|
|
||||||
func_fprintf(f, "cleanly shut down: no\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info->has_backing_filename) {
|
|
||||||
func_fprintf(f, "backing file: %s", info->backing_filename);
|
|
||||||
if (info->has_full_backing_filename) {
|
|
||||||
func_fprintf(f, " (actual path: %s)", info->full_backing_filename);
|
|
||||||
}
|
|
||||||
func_fprintf(f, "\n");
|
|
||||||
if (info->has_backing_filename_format) {
|
|
||||||
func_fprintf(f, "backing file format: %s\n",
|
|
||||||
info->backing_filename_format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info->has_snapshots) {
|
|
||||||
SnapshotInfoList *elem;
|
|
||||||
|
|
||||||
func_fprintf(f, "Snapshot list:\n");
|
|
||||||
bdrv_snapshot_dump(func_fprintf, f, NULL);
|
|
||||||
func_fprintf(f, "\n");
|
|
||||||
|
|
||||||
/* Ideally bdrv_snapshot_dump() would operate on SnapshotInfoList but
|
|
||||||
* we convert to the block layer's native QEMUSnapshotInfo for now.
|
|
||||||
*/
|
|
||||||
for (elem = info->snapshots; elem; elem = elem->next) {
|
|
||||||
QEMUSnapshotInfo sn = {
|
|
||||||
.vm_state_size = elem->value->vm_state_size,
|
|
||||||
.date_sec = elem->value->date_sec,
|
|
||||||
.date_nsec = elem->value->date_nsec,
|
|
||||||
.vm_clock_nsec = elem->value->vm_clock_sec * 1000000000ULL +
|
|
||||||
elem->value->vm_clock_nsec,
|
|
||||||
};
|
|
||||||
|
|
||||||
pstrcpy(sn.id_str, sizeof(sn.id_str), elem->value->id);
|
|
||||||
pstrcpy(sn.name, sizeof(sn.name), elem->value->name);
|
|
||||||
bdrv_snapshot_dump(func_fprintf, f, &sn);
|
|
||||||
func_fprintf(f, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info->has_format_specific) {
|
|
||||||
func_fprintf(f, "Format specific information:\n");
|
|
||||||
bdrv_image_info_specific_dump(func_fprintf, f, info->format_specific);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
95
block/qcow.c
95
block/qcow.c
@@ -25,7 +25,7 @@
|
|||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#include "qemu/aes.h"
|
#include "block/aes.h"
|
||||||
#include "migration/migration.h"
|
#include "migration/migration.h"
|
||||||
|
|
||||||
/**************************************************************/
|
/**************************************************************/
|
||||||
@@ -60,7 +60,7 @@ typedef struct BDRVQcowState {
|
|||||||
int cluster_sectors;
|
int cluster_sectors;
|
||||||
int l2_bits;
|
int l2_bits;
|
||||||
int l2_size;
|
int l2_size;
|
||||||
int l1_size;
|
unsigned int l1_size;
|
||||||
uint64_t cluster_offset_mask;
|
uint64_t cluster_offset_mask;
|
||||||
uint64_t l1_table_offset;
|
uint64_t l1_table_offset;
|
||||||
uint64_t *l1_table;
|
uint64_t *l1_table;
|
||||||
@@ -92,8 +92,7 @@ static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
static int qcow_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
int len, i, shift, ret;
|
int len, i, shift, ret;
|
||||||
@@ -113,26 +112,41 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
be64_to_cpus(&header.l1_table_offset);
|
be64_to_cpus(&header.l1_table_offset);
|
||||||
|
|
||||||
if (header.magic != QCOW_MAGIC) {
|
if (header.magic != QCOW_MAGIC) {
|
||||||
error_setg(errp, "Image not in qcow format");
|
ret = -EMEDIUMTYPE;
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
if (header.version != QCOW_VERSION) {
|
if (header.version != QCOW_VERSION) {
|
||||||
char version[64];
|
char version[64];
|
||||||
snprintf(version, sizeof(version), "QCOW version %d", header.version);
|
snprintf(version, sizeof(version), "QCOW version %d", header.version);
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
bs->device_name, "qcow", version);
|
bs->device_name, "qcow", version);
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.size <= 1 || header.cluster_bits < 9) {
|
if (header.size <= 1) {
|
||||||
error_setg(errp, "invalid value in qcow header");
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Image size is too small (must be at least 2 bytes)");
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
if (header.cluster_bits < 9 || header.cluster_bits > 16) {
|
||||||
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Cluster size must be between 512 and 64k");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* l2_bits specifies number of entries; storing a uint64_t in each entry,
|
||||||
|
* so bytes = num_entries << 3. */
|
||||||
|
if (header.l2_bits < 9 - 3 || header.l2_bits > 16 - 3) {
|
||||||
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"L2 table size must be between 512 and 64k");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if (header.crypt_method > QCOW_CRYPT_AES) {
|
if (header.crypt_method > QCOW_CRYPT_AES) {
|
||||||
error_setg(errp, "invalid encryption method in qcow header");
|
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
@@ -150,7 +164,19 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
|
|
||||||
/* read the level 1 table */
|
/* read the level 1 table */
|
||||||
shift = s->cluster_bits + s->l2_bits;
|
shift = s->cluster_bits + s->l2_bits;
|
||||||
s->l1_size = (header.size + (1LL << shift) - 1) >> shift;
|
if (header.size > UINT64_MAX - (1LL << shift)) {
|
||||||
|
qerror_report(ERROR_CLASS_GENERIC_ERROR, "Image too large");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
} else {
|
||||||
|
uint64_t l1_size = (header.size + (1LL << shift) - 1) >> shift;
|
||||||
|
if (l1_size > INT_MAX / sizeof(uint64_t)) {
|
||||||
|
qerror_report(ERROR_CLASS_GENERIC_ERROR, "Image too large");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
s->l1_size = l1_size;
|
||||||
|
}
|
||||||
|
|
||||||
s->l1_table_offset = header.l1_table_offset;
|
s->l1_table_offset = header.l1_table_offset;
|
||||||
s->l1_table = g_malloc(s->l1_size * sizeof(uint64_t));
|
s->l1_table = g_malloc(s->l1_size * sizeof(uint64_t));
|
||||||
@@ -399,7 +425,7 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
|
|||||||
return cluster_offset;
|
return cluster_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t coroutine_fn qcow_co_get_block_status(BlockDriverState *bs,
|
static int coroutine_fn qcow_co_is_allocated(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors, int *pnum)
|
int64_t sector_num, int nb_sectors, int *pnum)
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
@@ -414,14 +440,7 @@ static int64_t coroutine_fn qcow_co_get_block_status(BlockDriverState *bs,
|
|||||||
if (n > nb_sectors)
|
if (n > nb_sectors)
|
||||||
n = nb_sectors;
|
n = nb_sectors;
|
||||||
*pnum = n;
|
*pnum = n;
|
||||||
if (!cluster_offset) {
|
return (cluster_offset != 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if ((cluster_offset & QCOW_OFLAG_COMPRESSED) || s->crypt_method) {
|
|
||||||
return BDRV_BLOCK_DATA;
|
|
||||||
}
|
|
||||||
cluster_offset |= (index_in_cluster << BDRV_SECTOR_BITS);
|
|
||||||
return BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | cluster_offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int decompress_buffer(uint8_t *out_buf, int out_buf_size,
|
static int decompress_buffer(uint8_t *out_buf, int out_buf_size,
|
||||||
@@ -662,8 +681,7 @@ static void qcow_close(BlockDriverState *bs)
|
|||||||
error_free(s->migration_blocker);
|
error_free(s->migration_blocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qcow_create(const char *filename, QEMUOptionParameter *options,
|
static int qcow_create(const char *filename, QEMUOptionParameter *options)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
int header_size, backing_filename_len, l1_size, shift, i;
|
int header_size, backing_filename_len, l1_size, shift, i;
|
||||||
QCowHeader header;
|
QCowHeader header;
|
||||||
@@ -671,7 +689,6 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
int64_t total_size = 0;
|
int64_t total_size = 0;
|
||||||
const char *backing_file = NULL;
|
const char *backing_file = NULL;
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
BlockDriverState *qcow_bs;
|
BlockDriverState *qcow_bs;
|
||||||
|
|
||||||
@@ -687,17 +704,13 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
options++;
|
options++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = bdrv_create_file(filename, options, &local_err);
|
ret = bdrv_create_file(filename, options);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow_bs = NULL;
|
ret = bdrv_file_open(&qcow_bs, filename, BDRV_O_RDWR);
|
||||||
ret = bdrv_open(&qcow_bs, filename, NULL, NULL,
|
|
||||||
BDRV_O_RDWR | BDRV_O_PROTOCOL, NULL, &local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,7 +736,7 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
backing_file = NULL;
|
backing_file = NULL;
|
||||||
}
|
}
|
||||||
header.cluster_bits = 9; /* 512 byte cluster to avoid copying
|
header.cluster_bits = 9; /* 512 byte cluster to avoid copying
|
||||||
unmodified sectors */
|
unmodifyed sectors */
|
||||||
header.l2_bits = 12; /* 32 KB L2 tables */
|
header.l2_bits = 12; /* 32 KB L2 tables */
|
||||||
} else {
|
} else {
|
||||||
header.cluster_bits = 12; /* 4 KB clusters */
|
header.cluster_bits = 12; /* 4 KB clusters */
|
||||||
@@ -768,7 +781,7 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
g_free(tmp);
|
g_free(tmp);
|
||||||
ret = 0;
|
ret = 0;
|
||||||
exit:
|
exit:
|
||||||
bdrv_unref(qcow_bs);
|
bdrv_delete(qcow_bs);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -804,21 +817,8 @@ static int qcow_write_compressed(BlockDriverState *bs, int64_t sector_num,
|
|||||||
uint8_t *out_buf;
|
uint8_t *out_buf;
|
||||||
uint64_t cluster_offset;
|
uint64_t cluster_offset;
|
||||||
|
|
||||||
if (nb_sectors != s->cluster_sectors) {
|
if (nb_sectors != s->cluster_sectors)
|
||||||
ret = -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
/* Zero-pad last write if image size is not cluster aligned */
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_buf = g_malloc(s->cluster_size + (s->cluster_size / 1000) + 128);
|
out_buf = g_malloc(s->cluster_size + (s->cluster_size / 1000) + 128);
|
||||||
|
|
||||||
@@ -909,11 +909,10 @@ static BlockDriver bdrv_qcow = {
|
|||||||
.bdrv_close = qcow_close,
|
.bdrv_close = qcow_close,
|
||||||
.bdrv_reopen_prepare = qcow_reopen_prepare,
|
.bdrv_reopen_prepare = qcow_reopen_prepare,
|
||||||
.bdrv_create = qcow_create,
|
.bdrv_create = qcow_create,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
|
||||||
|
|
||||||
.bdrv_co_readv = qcow_co_readv,
|
.bdrv_co_readv = qcow_co_readv,
|
||||||
.bdrv_co_writev = qcow_co_writev,
|
.bdrv_co_writev = qcow_co_writev,
|
||||||
.bdrv_co_get_block_status = qcow_co_get_block_status,
|
.bdrv_co_is_allocated = qcow_co_is_allocated,
|
||||||
|
|
||||||
.bdrv_set_key = qcow_set_key,
|
.bdrv_set_key = qcow_set_key,
|
||||||
.bdrv_make_empty = qcow_make_empty,
|
.bdrv_make_empty = qcow_make_empty,
|
||||||
|
|||||||
@@ -114,21 +114,6 @@ static int qcow2_cache_entry_flush(BlockDriverState *bs, Qcow2Cache *c, int i)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c == s->refcount_block_cache) {
|
|
||||||
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_REFCOUNT_BLOCK,
|
|
||||||
c->entries[i].offset, s->cluster_size);
|
|
||||||
} else if (c == s->l2_table_cache) {
|
|
||||||
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_ACTIVE_L2,
|
|
||||||
c->entries[i].offset, s->cluster_size);
|
|
||||||
} else {
|
|
||||||
ret = qcow2_pre_write_overlap_check(bs, 0,
|
|
||||||
c->entries[i].offset, s->cluster_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == s->refcount_block_cache) {
|
if (c == s->refcount_block_cache) {
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_UPDATE_PART);
|
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_UPDATE_PART);
|
||||||
} else if (c == s->l2_table_cache) {
|
} else if (c == s->l2_table_cache) {
|
||||||
@@ -200,24 +185,6 @@ void qcow2_cache_depends_on_flush(Qcow2Cache *c)
|
|||||||
c->depends_on_flush = true;
|
c->depends_on_flush = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int qcow2_cache_empty(BlockDriverState *bs, Qcow2Cache *c)
|
|
||||||
{
|
|
||||||
int ret, i;
|
|
||||||
|
|
||||||
ret = qcow2_cache_flush(bs, c);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < c->size; i++) {
|
|
||||||
assert(c->entries[i].ref == 0);
|
|
||||||
c->entries[i].offset = 0;
|
|
||||||
c->entries[i].cache_hits = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int qcow2_cache_find_entry_to_replace(Qcow2Cache *c)
|
static int qcow2_cache_find_entry_to_replace(Qcow2Cache *c)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -170,22 +170,13 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
|
|||||||
snapshots_offset = qcow2_alloc_clusters(bs, snapshots_size);
|
snapshots_offset = qcow2_alloc_clusters(bs, snapshots_size);
|
||||||
offset = snapshots_offset;
|
offset = snapshots_offset;
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
ret = offset;
|
return offset;
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
ret = bdrv_flush(bs);
|
ret = bdrv_flush(bs);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The snapshot list position has not yet been updated, so these clusters
|
|
||||||
* must indeed be completely free */
|
|
||||||
ret = qcow2_pre_write_overlap_check(bs, 0, offset, snapshots_size);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Write all snapshots to the new list */
|
/* Write all snapshots to the new list */
|
||||||
for(i = 0; i < s->nb_snapshots; i++) {
|
for(i = 0; i < s->nb_snapshots; i++) {
|
||||||
sn = s->snapshots + i;
|
sn = s->snapshots + i;
|
||||||
@@ -208,7 +199,6 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
|
|||||||
|
|
||||||
id_str_size = strlen(sn->id_str);
|
id_str_size = strlen(sn->id_str);
|
||||||
name_size = strlen(sn->name);
|
name_size = strlen(sn->name);
|
||||||
assert(id_str_size <= UINT16_MAX && name_size <= UINT16_MAX);
|
|
||||||
h.id_str_size = cpu_to_be16(id_str_size);
|
h.id_str_size = cpu_to_be16(id_str_size);
|
||||||
h.name_size = cpu_to_be16(name_size);
|
h.name_size = cpu_to_be16(name_size);
|
||||||
offset = align_offset(offset, 8);
|
offset = align_offset(offset, 8);
|
||||||
@@ -260,17 +250,12 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* free the old snapshot table */
|
/* free the old snapshot table */
|
||||||
qcow2_free_clusters(bs, s->snapshots_offset, s->snapshots_size,
|
qcow2_free_clusters(bs, s->snapshots_offset, s->snapshots_size);
|
||||||
QCOW2_DISCARD_SNAPSHOT);
|
|
||||||
s->snapshots_offset = snapshots_offset;
|
s->snapshots_offset = snapshots_offset;
|
||||||
s->snapshots_size = snapshots_size;
|
s->snapshots_size = snapshots_size;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
if (snapshots_offset > 0) {
|
|
||||||
qcow2_free_clusters(bs, snapshots_offset, snapshots_size,
|
|
||||||
QCOW2_DISCARD_ALWAYS);
|
|
||||||
}
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,8 +264,7 @@ static void find_new_snapshot_id(BlockDriverState *bs,
|
|||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
QCowSnapshot *sn;
|
QCowSnapshot *sn;
|
||||||
int i;
|
int i, id, id_max = 0;
|
||||||
unsigned long id, id_max = 0;
|
|
||||||
|
|
||||||
for(i = 0; i < s->nb_snapshots; i++) {
|
for(i = 0; i < s->nb_snapshots; i++) {
|
||||||
sn = s->snapshots + i;
|
sn = s->snapshots + i;
|
||||||
@@ -288,50 +272,34 @@ static void find_new_snapshot_id(BlockDriverState *bs,
|
|||||||
if (id > id_max)
|
if (id > id_max)
|
||||||
id_max = id;
|
id_max = id;
|
||||||
}
|
}
|
||||||
snprintf(id_str, id_str_size, "%lu", id_max + 1);
|
snprintf(id_str, id_str_size, "%d", id_max + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int find_snapshot_by_id_and_name(BlockDriverState *bs,
|
static int find_snapshot_by_id(BlockDriverState *bs, const char *id_str)
|
||||||
const char *id,
|
|
||||||
const char *name)
|
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (id && name) {
|
|
||||||
for(i = 0; i < s->nb_snapshots; i++) {
|
for(i = 0; i < s->nb_snapshots; i++) {
|
||||||
if (!strcmp(s->snapshots[i].id_str, id) &&
|
if (!strcmp(s->snapshots[i].id_str, id_str))
|
||||||
!strcmp(s->snapshots[i].name, name)) {
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (id) {
|
|
||||||
for (i = 0; i < s->nb_snapshots; i++) {
|
|
||||||
if (!strcmp(s->snapshots[i].id_str, id)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (name) {
|
|
||||||
for (i = 0; i < s->nb_snapshots; i++) {
|
|
||||||
if (!strcmp(s->snapshots[i].name, name)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int find_snapshot_by_id_or_name(BlockDriverState *bs,
|
static int find_snapshot_by_id_or_name(BlockDriverState *bs, const char *name)
|
||||||
const char *id_or_name)
|
|
||||||
{
|
{
|
||||||
int ret;
|
BDRVQcowState *s = bs->opaque;
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
ret = find_snapshot_by_id_and_name(bs, id_or_name, NULL);
|
ret = find_snapshot_by_id(bs, name);
|
||||||
if (ret >= 0) {
|
if (ret >= 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
for(i = 0; i < s->nb_snapshots; i++) {
|
||||||
|
if (!strcmp(s->snapshots[i].name, name))
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
return find_snapshot_by_id_and_name(bs, NULL, id_or_name);
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if no id is provided, a new one is constructed */
|
/* if no id is provided, a new one is constructed */
|
||||||
@@ -357,7 +325,7 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check that the ID is unique */
|
/* Check that the ID is unique */
|
||||||
if (find_snapshot_by_id_and_name(bs, sn_info->id_str, NULL) >= 0) {
|
if (find_snapshot_by_id(bs, sn_info->id_str) >= 0) {
|
||||||
return -EEXIST;
|
return -EEXIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,12 +354,6 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
|
|||||||
l1_table[i] = cpu_to_be64(s->l1_table[i]);
|
l1_table[i] = cpu_to_be64(s->l1_table[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = qcow2_pre_write_overlap_check(bs, 0, sn->l1_table_offset,
|
|
||||||
s->l1_size * sizeof(uint64_t));
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_pwrite(bs->file, sn->l1_table_offset, l1_table,
|
ret = bdrv_pwrite(bs->file, sn->l1_table_offset, l1_table,
|
||||||
s->l1_size * sizeof(uint64_t));
|
s->l1_size * sizeof(uint64_t));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -411,6 +373,11 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = bdrv_flush(bs);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
/* Append the new snapshot to the snapshot list */
|
/* Append the new snapshot to the snapshot list */
|
||||||
new_snapshot_list = g_malloc((s->nb_snapshots + 1) * sizeof(QCowSnapshot));
|
new_snapshot_list = g_malloc((s->nb_snapshots + 1) * sizeof(QCowSnapshot));
|
||||||
if (s->snapshots) {
|
if (s->snapshots) {
|
||||||
@@ -425,19 +392,11 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
|
|||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
g_free(s->snapshots);
|
g_free(s->snapshots);
|
||||||
s->snapshots = old_snapshot_list;
|
s->snapshots = old_snapshot_list;
|
||||||
s->nb_snapshots--;
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(old_snapshot_list);
|
g_free(old_snapshot_list);
|
||||||
|
|
||||||
/* 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_discard_clusters(bs, qcow2_vm_state_offset(s),
|
|
||||||
align_offset(sn->vm_state_size, s->cluster_size)
|
|
||||||
>> BDRV_SECTOR_BITS,
|
|
||||||
QCOW2_DISCARD_NEVER);
|
|
||||||
|
|
||||||
#ifdef DEBUG_ALLOC
|
#ifdef DEBUG_ALLOC
|
||||||
{
|
{
|
||||||
BdrvCheckResult result = {0};
|
BdrvCheckResult result = {0};
|
||||||
@@ -512,12 +471,6 @@ int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id)
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_ACTIVE_L1,
|
|
||||||
s->l1_table_offset, cur_l1_bytes);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_pwrite_sync(bs->file, s->l1_table_offset, sn_l1_table,
|
ret = bdrv_pwrite_sync(bs->file, s->l1_table_offset, sn_l1_table,
|
||||||
cur_l1_bytes);
|
cur_l1_bytes);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -574,19 +527,15 @@ fail:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int qcow2_snapshot_delete(BlockDriverState *bs,
|
int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id)
|
||||||
const char *snapshot_id,
|
|
||||||
const char *name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
QCowSnapshot sn;
|
QCowSnapshot sn;
|
||||||
int snapshot_index, ret;
|
int snapshot_index, ret;
|
||||||
|
|
||||||
/* Search the snapshot */
|
/* Search the snapshot */
|
||||||
snapshot_index = find_snapshot_by_id_and_name(bs, snapshot_id, name);
|
snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id);
|
||||||
if (snapshot_index < 0) {
|
if (snapshot_index < 0) {
|
||||||
error_setg(errp, "Can't find the snapshot");
|
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
sn = s->snapshots[snapshot_index];
|
sn = s->snapshots[snapshot_index];
|
||||||
@@ -598,8 +547,6 @@ int qcow2_snapshot_delete(BlockDriverState *bs,
|
|||||||
s->nb_snapshots--;
|
s->nb_snapshots--;
|
||||||
ret = qcow2_write_snapshots(bs);
|
ret = qcow2_write_snapshots(bs);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg_errno(errp, -ret,
|
|
||||||
"Failed to remove snapshot from snapshot list");
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,17 +564,13 @@ int qcow2_snapshot_delete(BlockDriverState *bs,
|
|||||||
ret = qcow2_update_snapshot_refcount(bs, sn.l1_table_offset,
|
ret = qcow2_update_snapshot_refcount(bs, sn.l1_table_offset,
|
||||||
sn.l1_size, -1);
|
sn.l1_size, -1);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg_errno(errp, -ret, "Failed to free the cluster and L1 table");
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
qcow2_free_clusters(bs, sn.l1_table_offset, sn.l1_size * sizeof(uint64_t),
|
qcow2_free_clusters(bs, sn.l1_table_offset, sn.l1_size * sizeof(uint64_t));
|
||||||
QCOW2_DISCARD_SNAPSHOT);
|
|
||||||
|
|
||||||
/* must update the copied flag on the current cluster offsets */
|
/* must update the copied flag on the current cluster offsets */
|
||||||
ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0);
|
ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg_errno(errp, -ret,
|
|
||||||
"Failed to update snapshot status in disk");
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,10 +612,7 @@ int qcow2_snapshot_list(BlockDriverState *bs, QEMUSnapshotInfo **psn_tab)
|
|||||||
return s->nb_snapshots;
|
return s->nb_snapshots;
|
||||||
}
|
}
|
||||||
|
|
||||||
int qcow2_snapshot_load_tmp(BlockDriverState *bs,
|
int qcow2_snapshot_load_tmp(BlockDriverState *bs, const char *snapshot_name)
|
||||||
const char *snapshot_id,
|
|
||||||
const char *name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
int i, snapshot_index;
|
int i, snapshot_index;
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
@@ -684,17 +624,15 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs,
|
|||||||
assert(bs->read_only);
|
assert(bs->read_only);
|
||||||
|
|
||||||
/* Search the snapshot */
|
/* Search the snapshot */
|
||||||
snapshot_index = find_snapshot_by_id_and_name(bs, snapshot_id, name);
|
snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_name);
|
||||||
if (snapshot_index < 0) {
|
if (snapshot_index < 0) {
|
||||||
error_setg(errp,
|
|
||||||
"Can't find snapshot");
|
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
sn = &s->snapshots[snapshot_index];
|
sn = &s->snapshots[snapshot_index];
|
||||||
|
|
||||||
/* Allocate and read in the snapshot's L1 table */
|
/* Allocate and read in the snapshot's L1 table */
|
||||||
if (sn->l1_size > QCOW_MAX_L1_SIZE) {
|
if (sn->l1_size > QCOW_MAX_L1_SIZE) {
|
||||||
error_setg(errp, "Snapshot L1 table too large");
|
error_report("Snapshot L1 table too large");
|
||||||
return -EFBIG;
|
return -EFBIG;
|
||||||
}
|
}
|
||||||
new_l1_bytes = sn->l1_size * sizeof(uint64_t);
|
new_l1_bytes = sn->l1_size * sizeof(uint64_t);
|
||||||
@@ -702,7 +640,6 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs,
|
|||||||
|
|
||||||
ret = bdrv_pread(bs->file, sn->l1_table_offset, new_l1_table, new_l1_bytes);
|
ret = bdrv_pread(bs->file, sn->l1_table_offset, new_l1_table, new_l1_bytes);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg(errp, "Failed to read l1 table for snapshot");
|
|
||||||
g_free(new_l1_table);
|
g_free(new_l1_table);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
853
block/qcow2.c
853
block/qcow2.c
File diff suppressed because it is too large
Load Diff
171
block/qcow2.h
171
block/qcow2.h
@@ -25,7 +25,7 @@
|
|||||||
#ifndef BLOCK_QCOW2_H
|
#ifndef BLOCK_QCOW2_H
|
||||||
#define BLOCK_QCOW2_H
|
#define BLOCK_QCOW2_H
|
||||||
|
|
||||||
#include "qemu/aes.h"
|
#include "block/aes.h"
|
||||||
#include "block/coroutine.h"
|
#include "block/coroutine.h"
|
||||||
|
|
||||||
//#define DEBUG_ALLOC
|
//#define DEBUG_ALLOC
|
||||||
@@ -53,11 +53,11 @@
|
|||||||
#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS)
|
#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS)
|
||||||
|
|
||||||
/* indicate that the refcount of the referenced cluster is exactly one. */
|
/* indicate that the refcount of the referenced cluster is exactly one. */
|
||||||
#define QCOW_OFLAG_COPIED (1ULL << 63)
|
#define QCOW_OFLAG_COPIED (1LL << 63)
|
||||||
/* indicate that the cluster is compressed (they never have the copied flag) */
|
/* indicate that the cluster is compressed (they never have the copied flag) */
|
||||||
#define QCOW_OFLAG_COMPRESSED (1ULL << 62)
|
#define QCOW_OFLAG_COMPRESSED (1LL << 62)
|
||||||
/* The cluster reads as all zeros */
|
/* The cluster reads as all zeros */
|
||||||
#define QCOW_OFLAG_ZERO (1ULL << 0)
|
#define QCOW_OFLAG_ZERO (1LL << 0)
|
||||||
|
|
||||||
#define REFCOUNT_SHIFT 1 /* refcount size is 2 bytes */
|
#define REFCOUNT_SHIFT 1 /* refcount size is 2 bytes */
|
||||||
|
|
||||||
@@ -71,21 +71,6 @@
|
|||||||
|
|
||||||
#define DEFAULT_CLUSTER_SIZE 65536
|
#define DEFAULT_CLUSTER_SIZE 65536
|
||||||
|
|
||||||
|
|
||||||
#define QCOW2_OPT_LAZY_REFCOUNTS "lazy-refcounts"
|
|
||||||
#define QCOW2_OPT_DISCARD_REQUEST "pass-discard-request"
|
|
||||||
#define QCOW2_OPT_DISCARD_SNAPSHOT "pass-discard-snapshot"
|
|
||||||
#define QCOW2_OPT_DISCARD_OTHER "pass-discard-other"
|
|
||||||
#define QCOW2_OPT_OVERLAP "overlap-check"
|
|
||||||
#define QCOW2_OPT_OVERLAP_MAIN_HEADER "overlap-check.main-header"
|
|
||||||
#define QCOW2_OPT_OVERLAP_ACTIVE_L1 "overlap-check.active-l1"
|
|
||||||
#define QCOW2_OPT_OVERLAP_ACTIVE_L2 "overlap-check.active-l2"
|
|
||||||
#define QCOW2_OPT_OVERLAP_REFCOUNT_TABLE "overlap-check.refcount-table"
|
|
||||||
#define QCOW2_OPT_OVERLAP_REFCOUNT_BLOCK "overlap-check.refcount-block"
|
|
||||||
#define QCOW2_OPT_OVERLAP_SNAPSHOT_TABLE "overlap-check.snapshot-table"
|
|
||||||
#define QCOW2_OPT_OVERLAP_INACTIVE_L1 "overlap-check.inactive-l1"
|
|
||||||
#define QCOW2_OPT_OVERLAP_INACTIVE_L2 "overlap-check.inactive-l2"
|
|
||||||
|
|
||||||
typedef struct QCowHeader {
|
typedef struct QCowHeader {
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
@@ -108,7 +93,7 @@ typedef struct QCowHeader {
|
|||||||
|
|
||||||
uint32_t refcount_order;
|
uint32_t refcount_order;
|
||||||
uint32_t header_length;
|
uint32_t header_length;
|
||||||
} QEMU_PACKED QCowHeader;
|
} QCowHeader;
|
||||||
|
|
||||||
typedef struct QEMU_PACKED QCowSnapshotHeader {
|
typedef struct QEMU_PACKED QCowSnapshotHeader {
|
||||||
/* header is 8 byte aligned */
|
/* header is 8 byte aligned */
|
||||||
@@ -167,12 +152,9 @@ enum {
|
|||||||
/* Incompatible feature bits */
|
/* Incompatible feature bits */
|
||||||
enum {
|
enum {
|
||||||
QCOW2_INCOMPAT_DIRTY_BITNR = 0,
|
QCOW2_INCOMPAT_DIRTY_BITNR = 0,
|
||||||
QCOW2_INCOMPAT_CORRUPT_BITNR = 1,
|
|
||||||
QCOW2_INCOMPAT_DIRTY = 1 << QCOW2_INCOMPAT_DIRTY_BITNR,
|
QCOW2_INCOMPAT_DIRTY = 1 << QCOW2_INCOMPAT_DIRTY_BITNR,
|
||||||
QCOW2_INCOMPAT_CORRUPT = 1 << QCOW2_INCOMPAT_CORRUPT_BITNR,
|
|
||||||
|
|
||||||
QCOW2_INCOMPAT_MASK = QCOW2_INCOMPAT_DIRTY
|
QCOW2_INCOMPAT_MASK = QCOW2_INCOMPAT_DIRTY,
|
||||||
| QCOW2_INCOMPAT_CORRUPT,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Compatible feature bits */
|
/* Compatible feature bits */
|
||||||
@@ -183,28 +165,12 @@ enum {
|
|||||||
QCOW2_COMPAT_FEAT_MASK = QCOW2_COMPAT_LAZY_REFCOUNTS,
|
QCOW2_COMPAT_FEAT_MASK = QCOW2_COMPAT_LAZY_REFCOUNTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum qcow2_discard_type {
|
|
||||||
QCOW2_DISCARD_NEVER = 0,
|
|
||||||
QCOW2_DISCARD_ALWAYS,
|
|
||||||
QCOW2_DISCARD_REQUEST,
|
|
||||||
QCOW2_DISCARD_SNAPSHOT,
|
|
||||||
QCOW2_DISCARD_OTHER,
|
|
||||||
QCOW2_DISCARD_MAX
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Qcow2Feature {
|
typedef struct Qcow2Feature {
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
uint8_t bit;
|
uint8_t bit;
|
||||||
char name[46];
|
char name[46];
|
||||||
} QEMU_PACKED Qcow2Feature;
|
} QEMU_PACKED Qcow2Feature;
|
||||||
|
|
||||||
typedef struct Qcow2DiscardRegion {
|
|
||||||
BlockDriverState *bs;
|
|
||||||
uint64_t offset;
|
|
||||||
uint64_t bytes;
|
|
||||||
QTAILQ_ENTRY(Qcow2DiscardRegion) next;
|
|
||||||
} Qcow2DiscardRegion;
|
|
||||||
|
|
||||||
typedef struct BDRVQcowState {
|
typedef struct BDRVQcowState {
|
||||||
int cluster_bits;
|
int cluster_bits;
|
||||||
int cluster_size;
|
int cluster_size;
|
||||||
@@ -246,12 +212,6 @@ typedef struct BDRVQcowState {
|
|||||||
|
|
||||||
int flags;
|
int flags;
|
||||||
int qcow_version;
|
int qcow_version;
|
||||||
bool use_lazy_refcounts;
|
|
||||||
int refcount_order;
|
|
||||||
|
|
||||||
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
|
||||||
|
|
||||||
int overlap_check; /* bitmask of Qcow2MetadataOverlap values */
|
|
||||||
|
|
||||||
uint64_t incompatible_features;
|
uint64_t incompatible_features;
|
||||||
uint64_t compatible_features;
|
uint64_t compatible_features;
|
||||||
@@ -260,8 +220,6 @@ typedef struct BDRVQcowState {
|
|||||||
size_t unknown_header_fields_size;
|
size_t unknown_header_fields_size;
|
||||||
void* unknown_header_fields;
|
void* unknown_header_fields;
|
||||||
QLIST_HEAD(, Qcow2UnknownHeaderExtension) unknown_header_ext;
|
QLIST_HEAD(, Qcow2UnknownHeaderExtension) unknown_header_ext;
|
||||||
QTAILQ_HEAD (, Qcow2DiscardRegion) discards;
|
|
||||||
bool cache_discards;
|
|
||||||
} BDRVQcowState;
|
} BDRVQcowState;
|
||||||
|
|
||||||
/* XXX: use std qcow open function ? */
|
/* XXX: use std qcow open function ? */
|
||||||
@@ -327,9 +285,6 @@ typedef struct QCowL2Meta
|
|||||||
*/
|
*/
|
||||||
Qcow2COWRegion cow_end;
|
Qcow2COWRegion cow_end;
|
||||||
|
|
||||||
/** Pointer to next L2Meta of the same write request */
|
|
||||||
struct QCowL2Meta *next;
|
|
||||||
|
|
||||||
QLIST_ENTRY(QCowL2Meta) next_in_flight;
|
QLIST_ENTRY(QCowL2Meta) next_in_flight;
|
||||||
} QCowL2Meta;
|
} QCowL2Meta;
|
||||||
|
|
||||||
@@ -340,60 +295,11 @@ enum {
|
|||||||
QCOW2_CLUSTER_ZERO
|
QCOW2_CLUSTER_ZERO
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum QCow2MetadataOverlap {
|
#define L1E_OFFSET_MASK 0x00ffffffffffff00ULL
|
||||||
QCOW2_OL_MAIN_HEADER_BITNR = 0,
|
#define L2E_OFFSET_MASK 0x00ffffffffffff00ULL
|
||||||
QCOW2_OL_ACTIVE_L1_BITNR = 1,
|
|
||||||
QCOW2_OL_ACTIVE_L2_BITNR = 2,
|
|
||||||
QCOW2_OL_REFCOUNT_TABLE_BITNR = 3,
|
|
||||||
QCOW2_OL_REFCOUNT_BLOCK_BITNR = 4,
|
|
||||||
QCOW2_OL_SNAPSHOT_TABLE_BITNR = 5,
|
|
||||||
QCOW2_OL_INACTIVE_L1_BITNR = 6,
|
|
||||||
QCOW2_OL_INACTIVE_L2_BITNR = 7,
|
|
||||||
|
|
||||||
QCOW2_OL_MAX_BITNR = 8,
|
|
||||||
|
|
||||||
QCOW2_OL_NONE = 0,
|
|
||||||
QCOW2_OL_MAIN_HEADER = (1 << QCOW2_OL_MAIN_HEADER_BITNR),
|
|
||||||
QCOW2_OL_ACTIVE_L1 = (1 << QCOW2_OL_ACTIVE_L1_BITNR),
|
|
||||||
QCOW2_OL_ACTIVE_L2 = (1 << QCOW2_OL_ACTIVE_L2_BITNR),
|
|
||||||
QCOW2_OL_REFCOUNT_TABLE = (1 << QCOW2_OL_REFCOUNT_TABLE_BITNR),
|
|
||||||
QCOW2_OL_REFCOUNT_BLOCK = (1 << QCOW2_OL_REFCOUNT_BLOCK_BITNR),
|
|
||||||
QCOW2_OL_SNAPSHOT_TABLE = (1 << QCOW2_OL_SNAPSHOT_TABLE_BITNR),
|
|
||||||
QCOW2_OL_INACTIVE_L1 = (1 << QCOW2_OL_INACTIVE_L1_BITNR),
|
|
||||||
/* NOTE: Checking overlaps with inactive L2 tables will result in bdrv
|
|
||||||
* reads. */
|
|
||||||
QCOW2_OL_INACTIVE_L2 = (1 << QCOW2_OL_INACTIVE_L2_BITNR),
|
|
||||||
} QCow2MetadataOverlap;
|
|
||||||
|
|
||||||
/* Perform all overlap checks which can be done in constant time */
|
|
||||||
#define QCOW2_OL_CONSTANT \
|
|
||||||
(QCOW2_OL_MAIN_HEADER | QCOW2_OL_ACTIVE_L1 | QCOW2_OL_REFCOUNT_TABLE | \
|
|
||||||
QCOW2_OL_SNAPSHOT_TABLE)
|
|
||||||
|
|
||||||
/* Perform all overlap checks which don't require disk access */
|
|
||||||
#define QCOW2_OL_CACHED \
|
|
||||||
(QCOW2_OL_CONSTANT | QCOW2_OL_ACTIVE_L2 | QCOW2_OL_REFCOUNT_BLOCK | \
|
|
||||||
QCOW2_OL_INACTIVE_L1)
|
|
||||||
|
|
||||||
/* Perform all overlap checks */
|
|
||||||
#define QCOW2_OL_ALL \
|
|
||||||
(QCOW2_OL_CACHED | QCOW2_OL_INACTIVE_L2)
|
|
||||||
|
|
||||||
#define L1E_OFFSET_MASK 0x00fffffffffffe00ULL
|
|
||||||
#define L2E_OFFSET_MASK 0x00fffffffffffe00ULL
|
|
||||||
#define L2E_COMPRESSED_OFFSET_SIZE_MASK 0x3fffffffffffffffULL
|
#define L2E_COMPRESSED_OFFSET_SIZE_MASK 0x3fffffffffffffffULL
|
||||||
|
|
||||||
#define REFT_OFFSET_MASK 0xfffffffffffffe00ULL
|
#define REFT_OFFSET_MASK 0xffffffffffffff00ULL
|
||||||
|
|
||||||
static inline int64_t start_of_cluster(BDRVQcowState *s, int64_t offset)
|
|
||||||
{
|
|
||||||
return offset & ~(s->cluster_size - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int64_t offset_into_cluster(BDRVQcowState *s, int64_t offset)
|
|
||||||
{
|
|
||||||
return offset & (s->cluster_size - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int size_to_clusters(BDRVQcowState *s, int64_t size)
|
static inline int size_to_clusters(BDRVQcowState *s, int64_t size)
|
||||||
{
|
{
|
||||||
@@ -406,22 +312,12 @@ static inline int64_t size_to_l1(BDRVQcowState *s, int64_t size)
|
|||||||
return (size + (1ULL << shift) - 1) >> shift;
|
return (size + (1ULL << shift) - 1) >> shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int offset_to_l2_index(BDRVQcowState *s, int64_t offset)
|
|
||||||
{
|
|
||||||
return (offset >> s->cluster_bits) & (s->l2_size - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int64_t align_offset(int64_t offset, int n)
|
static inline int64_t align_offset(int64_t offset, int n)
|
||||||
{
|
{
|
||||||
offset = (offset + n - 1) & ~(n - 1);
|
offset = (offset + n - 1) & ~(n - 1);
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int64_t qcow2_vm_state_offset(BDRVQcowState *s)
|
|
||||||
{
|
|
||||||
return (int64_t)s->l1_vm_state_index << (s->cluster_bits + s->l2_bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint64_t qcow2_max_refcount_clusters(BDRVQcowState *s)
|
static inline uint64_t qcow2_max_refcount_clusters(BDRVQcowState *s)
|
||||||
{
|
{
|
||||||
return QCOW_MAX_REFTABLE_SIZE >> s->cluster_bits;
|
return QCOW_MAX_REFTABLE_SIZE >> s->cluster_bits;
|
||||||
@@ -446,17 +342,6 @@ static inline bool qcow2_need_accurate_refcounts(BDRVQcowState *s)
|
|||||||
return !(s->incompatible_features & QCOW2_INCOMPAT_DIRTY);
|
return !(s->incompatible_features & QCOW2_INCOMPAT_DIRTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint64_t l2meta_cow_start(QCowL2Meta *m)
|
|
||||||
{
|
|
||||||
return m->offset + m->cow_start.offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint64_t l2meta_cow_end(QCowL2Meta *m)
|
|
||||||
{
|
|
||||||
return m->offset + m->cow_end.offset
|
|
||||||
+ (m->cow_end.nb_sectors << BDRV_SECTOR_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME Need qcow2_ prefix to global functions
|
// FIXME Need qcow2_ prefix to global functions
|
||||||
|
|
||||||
/* qcow2.c functions */
|
/* qcow2.c functions */
|
||||||
@@ -464,26 +349,20 @@ int qcow2_backing_read1(BlockDriverState *bs, QEMUIOVector *qiov,
|
|||||||
int64_t sector_num, int nb_sectors);
|
int64_t sector_num, int nb_sectors);
|
||||||
|
|
||||||
int qcow2_mark_dirty(BlockDriverState *bs);
|
int qcow2_mark_dirty(BlockDriverState *bs);
|
||||||
int qcow2_mark_corrupt(BlockDriverState *bs);
|
|
||||||
int qcow2_mark_consistent(BlockDriverState *bs);
|
|
||||||
int qcow2_update_header(BlockDriverState *bs);
|
int qcow2_update_header(BlockDriverState *bs);
|
||||||
|
|
||||||
/* qcow2-refcount.c functions */
|
/* qcow2-refcount.c functions */
|
||||||
int qcow2_refcount_init(BlockDriverState *bs);
|
int qcow2_refcount_init(BlockDriverState *bs);
|
||||||
void qcow2_refcount_close(BlockDriverState *bs);
|
void qcow2_refcount_close(BlockDriverState *bs);
|
||||||
|
|
||||||
int qcow2_update_cluster_refcount(BlockDriverState *bs, int64_t cluster_index,
|
|
||||||
int addend, enum qcow2_discard_type type);
|
|
||||||
|
|
||||||
int64_t qcow2_alloc_clusters(BlockDriverState *bs, uint64_t size);
|
int64_t qcow2_alloc_clusters(BlockDriverState *bs, uint64_t size);
|
||||||
int qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
|
int qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
|
||||||
int nb_clusters);
|
int nb_clusters);
|
||||||
int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size);
|
int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size);
|
||||||
void qcow2_free_clusters(BlockDriverState *bs,
|
void qcow2_free_clusters(BlockDriverState *bs,
|
||||||
int64_t offset, int64_t size,
|
int64_t offset, int64_t size);
|
||||||
enum qcow2_discard_type type);
|
void qcow2_free_any_clusters(BlockDriverState *bs,
|
||||||
void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
uint64_t cluster_offset, int nb_clusters);
|
||||||
int nb_clusters, enum qcow2_discard_type type);
|
|
||||||
|
|
||||||
int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
int64_t l1_table_offset, int l1_size, int addend);
|
int64_t l1_table_offset, int l1_size, int addend);
|
||||||
@@ -491,17 +370,9 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||||||
int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||||
BdrvCheckMode fix);
|
BdrvCheckMode fix);
|
||||||
|
|
||||||
void qcow2_process_discards(BlockDriverState *bs, int ret);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
/* qcow2-cluster.c functions */
|
/* qcow2-cluster.c functions */
|
||||||
int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
||||||
bool exact_size);
|
bool exact_size);
|
||||||
int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index);
|
|
||||||
void qcow2_l2_cache_reset(BlockDriverState *bs);
|
void qcow2_l2_cache_reset(BlockDriverState *bs);
|
||||||
int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset);
|
int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset);
|
||||||
void qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
|
void qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
|
||||||
@@ -512,30 +383,22 @@ void qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
|
|||||||
int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
int *num, uint64_t *cluster_offset);
|
int *num, uint64_t *cluster_offset);
|
||||||
int qcow2_alloc_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
int qcow2_alloc_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
int *num, uint64_t *host_offset, QCowL2Meta **m);
|
int n_start, int n_end, int *num, uint64_t *host_offset, QCowL2Meta **m);
|
||||||
uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
||||||
uint64_t offset,
|
uint64_t offset,
|
||||||
int compressed_size);
|
int compressed_size);
|
||||||
|
|
||||||
int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m);
|
int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m);
|
||||||
int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset,
|
int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset,
|
||||||
int nb_sectors, enum qcow2_discard_type type);
|
int nb_sectors);
|
||||||
int qcow2_zero_clusters(BlockDriverState *bs, uint64_t offset, int nb_sectors);
|
int qcow2_zero_clusters(BlockDriverState *bs, uint64_t offset, int nb_sectors);
|
||||||
|
|
||||||
int qcow2_expand_zero_clusters(BlockDriverState *bs);
|
|
||||||
|
|
||||||
/* qcow2-snapshot.c functions */
|
/* qcow2-snapshot.c functions */
|
||||||
int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info);
|
int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info);
|
||||||
int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id);
|
int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id);
|
||||||
int qcow2_snapshot_delete(BlockDriverState *bs,
|
int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id);
|
||||||
const char *snapshot_id,
|
|
||||||
const char *name,
|
|
||||||
Error **errp);
|
|
||||||
int qcow2_snapshot_list(BlockDriverState *bs, QEMUSnapshotInfo **psn_tab);
|
int qcow2_snapshot_list(BlockDriverState *bs, QEMUSnapshotInfo **psn_tab);
|
||||||
int qcow2_snapshot_load_tmp(BlockDriverState *bs,
|
int qcow2_snapshot_load_tmp(BlockDriverState *bs, const char *snapshot_name);
|
||||||
const char *snapshot_id,
|
|
||||||
const char *name,
|
|
||||||
Error **errp);
|
|
||||||
|
|
||||||
void qcow2_free_snapshots(BlockDriverState *bs);
|
void qcow2_free_snapshots(BlockDriverState *bs);
|
||||||
int qcow2_read_snapshots(BlockDriverState *bs);
|
int qcow2_read_snapshots(BlockDriverState *bs);
|
||||||
@@ -550,8 +413,6 @@ int qcow2_cache_set_dependency(BlockDriverState *bs, Qcow2Cache *c,
|
|||||||
Qcow2Cache *dependency);
|
Qcow2Cache *dependency);
|
||||||
void qcow2_cache_depends_on_flush(Qcow2Cache *c);
|
void qcow2_cache_depends_on_flush(Qcow2Cache *c);
|
||||||
|
|
||||||
int qcow2_cache_empty(BlockDriverState *bs, Qcow2Cache *c);
|
|
||||||
|
|
||||||
int qcow2_cache_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
int qcow2_cache_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||||
void **table);
|
void **table);
|
||||||
int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||||
|
|||||||
123
block/qed.c
123
block/qed.c
@@ -353,10 +353,10 @@ static void qed_start_need_check_timer(BDRVQEDState *s)
|
|||||||
{
|
{
|
||||||
trace_qed_start_need_check_timer(s);
|
trace_qed_start_need_check_timer(s);
|
||||||
|
|
||||||
/* Use QEMU_CLOCK_VIRTUAL so we don't alter the image file while suspended for
|
/* Use vm_clock so we don't alter the image file while suspended for
|
||||||
* migration.
|
* migration.
|
||||||
*/
|
*/
|
||||||
timer_mod(s->need_check_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
|
qemu_mod_timer(s->need_check_timer, qemu_get_clock_ns(vm_clock) +
|
||||||
get_ticks_per_sec() * QED_NEED_CHECK_TIMEOUT);
|
get_ticks_per_sec() * QED_NEED_CHECK_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +364,7 @@ static void qed_start_need_check_timer(BDRVQEDState *s)
|
|||||||
static void qed_cancel_need_check_timer(BDRVQEDState *s)
|
static void qed_cancel_need_check_timer(BDRVQEDState *s)
|
||||||
{
|
{
|
||||||
trace_qed_cancel_need_check_timer(s);
|
trace_qed_cancel_need_check_timer(s);
|
||||||
timer_del(s->need_check_timer);
|
qemu_del_timer(s->need_check_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bdrv_qed_rebind(BlockDriverState *bs)
|
static void bdrv_qed_rebind(BlockDriverState *bs)
|
||||||
@@ -373,8 +373,7 @@ static void bdrv_qed_rebind(BlockDriverState *bs)
|
|||||||
s->bs = bs;
|
s->bs = bs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
|
static int bdrv_qed_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVQEDState *s = bs->opaque;
|
BDRVQEDState *s = bs->opaque;
|
||||||
QEDHeader le_header;
|
QEDHeader le_header;
|
||||||
@@ -391,15 +390,14 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
qed_header_le_to_cpu(&le_header, &s->header);
|
qed_header_le_to_cpu(&le_header, &s->header);
|
||||||
|
|
||||||
if (s->header.magic != QED_MAGIC) {
|
if (s->header.magic != QED_MAGIC) {
|
||||||
error_setg(errp, "Image not in QED format");
|
return -EMEDIUMTYPE;
|
||||||
return -EINVAL;
|
|
||||||
}
|
}
|
||||||
if (s->header.features & ~QED_FEATURE_MASK) {
|
if (s->header.features & ~QED_FEATURE_MASK) {
|
||||||
/* image uses unsupported feature bits */
|
/* image uses unsupported feature bits */
|
||||||
char buf[64];
|
char buf[64];
|
||||||
snprintf(buf, sizeof(buf), "%" PRIx64,
|
snprintf(buf, sizeof(buf), "%" PRIx64,
|
||||||
s->header.features & ~QED_FEATURE_MASK);
|
s->header.features & ~QED_FEATURE_MASK);
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
bs->device_name, "QED", buf);
|
bs->device_name, "QED", buf);
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
@@ -496,7 +494,7 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s->need_check_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
s->need_check_timer = qemu_new_timer_ns(vm_clock,
|
||||||
qed_need_check_timer_cb, s);
|
qed_need_check_timer_cb, s);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@@ -507,15 +505,6 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bdrv_qed_refresh_limits(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BDRVQEDState *s = bs->opaque;
|
|
||||||
|
|
||||||
bs->bl.write_zeroes_alignment = s->header.cluster_size >> BDRV_SECTOR_BITS;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We have nothing to do for QED reopen, stubs just return
|
/* We have nothing to do for QED reopen, stubs just return
|
||||||
* success */
|
* success */
|
||||||
static int bdrv_qed_reopen_prepare(BDRVReopenState *state,
|
static int bdrv_qed_reopen_prepare(BDRVReopenState *state,
|
||||||
@@ -529,7 +518,7 @@ static void bdrv_qed_close(BlockDriverState *bs)
|
|||||||
BDRVQEDState *s = bs->opaque;
|
BDRVQEDState *s = bs->opaque;
|
||||||
|
|
||||||
qed_cancel_need_check_timer(s);
|
qed_cancel_need_check_timer(s);
|
||||||
timer_free(s->need_check_timer);
|
qemu_free_timer(s->need_check_timer);
|
||||||
|
|
||||||
/* Ensure writes reach stable storage */
|
/* Ensure writes reach stable storage */
|
||||||
bdrv_flush(bs->file);
|
bdrv_flush(bs->file);
|
||||||
@@ -546,8 +535,7 @@ static void bdrv_qed_close(BlockDriverState *bs)
|
|||||||
|
|
||||||
static int qed_create(const char *filename, uint32_t cluster_size,
|
static int qed_create(const char *filename, uint32_t cluster_size,
|
||||||
uint64_t image_size, uint32_t table_size,
|
uint64_t image_size, uint32_t table_size,
|
||||||
const char *backing_file, const char *backing_fmt,
|
const char *backing_file, const char *backing_fmt)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
QEDHeader header = {
|
QEDHeader header = {
|
||||||
.magic = QED_MAGIC,
|
.magic = QED_MAGIC,
|
||||||
@@ -562,22 +550,16 @@ static int qed_create(const char *filename, uint32_t cluster_size,
|
|||||||
QEDHeader le_header;
|
QEDHeader le_header;
|
||||||
uint8_t *l1_table = NULL;
|
uint8_t *l1_table = NULL;
|
||||||
size_t l1_size = header.cluster_size * header.table_size;
|
size_t l1_size = header.cluster_size * header.table_size;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs = NULL;
|
||||||
|
|
||||||
ret = bdrv_create_file(filename, NULL, &local_err);
|
ret = bdrv_create_file(filename, NULL);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bs = NULL;
|
ret = bdrv_file_open(&bs, filename, BDRV_O_RDWR | BDRV_O_CACHE_WB);
|
||||||
ret = bdrv_open(&bs, filename, NULL, NULL,
|
|
||||||
BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_PROTOCOL, NULL,
|
|
||||||
&local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,12 +599,11 @@ static int qed_create(const char *filename, uint32_t cluster_size,
|
|||||||
ret = 0; /* success */
|
ret = 0; /* success */
|
||||||
out:
|
out:
|
||||||
g_free(l1_table);
|
g_free(l1_table);
|
||||||
bdrv_unref(bs);
|
bdrv_delete(bs);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bdrv_qed_create(const char *filename, QEMUOptionParameter *options,
|
static int bdrv_qed_create(const char *filename, QEMUOptionParameter *options)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
uint64_t image_size = 0;
|
uint64_t image_size = 0;
|
||||||
uint32_t cluster_size = QED_DEFAULT_CLUSTER_SIZE;
|
uint32_t cluster_size = QED_DEFAULT_CLUSTER_SIZE;
|
||||||
@@ -667,70 +648,54 @@ static int bdrv_qed_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return qed_create(filename, cluster_size, image_size, table_size,
|
return qed_create(filename, cluster_size, image_size, table_size,
|
||||||
backing_file, backing_fmt, errp);
|
backing_file, backing_fmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BlockDriverState *bs;
|
|
||||||
Coroutine *co;
|
Coroutine *co;
|
||||||
uint64_t pos;
|
int is_allocated;
|
||||||
int64_t status;
|
|
||||||
int *pnum;
|
int *pnum;
|
||||||
} QEDIsAllocatedCB;
|
} QEDIsAllocatedCB;
|
||||||
|
|
||||||
static void qed_is_allocated_cb(void *opaque, int ret, uint64_t offset, size_t len)
|
static void qed_is_allocated_cb(void *opaque, int ret, uint64_t offset, size_t len)
|
||||||
{
|
{
|
||||||
QEDIsAllocatedCB *cb = opaque;
|
QEDIsAllocatedCB *cb = opaque;
|
||||||
BDRVQEDState *s = cb->bs->opaque;
|
|
||||||
*cb->pnum = len / BDRV_SECTOR_SIZE;
|
*cb->pnum = len / BDRV_SECTOR_SIZE;
|
||||||
switch (ret) {
|
cb->is_allocated = (ret == QED_CLUSTER_FOUND || ret == QED_CLUSTER_ZERO);
|
||||||
case QED_CLUSTER_FOUND:
|
|
||||||
offset |= qed_offset_into_cluster(s, cb->pos);
|
|
||||||
cb->status = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | offset;
|
|
||||||
break;
|
|
||||||
case QED_CLUSTER_ZERO:
|
|
||||||
cb->status = BDRV_BLOCK_ZERO;
|
|
||||||
break;
|
|
||||||
case QED_CLUSTER_L2:
|
|
||||||
case QED_CLUSTER_L1:
|
|
||||||
cb->status = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(ret < 0);
|
|
||||||
cb->status = ret;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cb->co) {
|
if (cb->co) {
|
||||||
qemu_coroutine_enter(cb->co, NULL);
|
qemu_coroutine_enter(cb->co, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t coroutine_fn bdrv_qed_co_get_block_status(BlockDriverState *bs,
|
static int coroutine_fn bdrv_qed_co_is_allocated(BlockDriverState *bs,
|
||||||
int64_t sector_num,
|
int64_t sector_num,
|
||||||
int nb_sectors, int *pnum)
|
int nb_sectors, int *pnum)
|
||||||
{
|
{
|
||||||
BDRVQEDState *s = bs->opaque;
|
BDRVQEDState *s = bs->opaque;
|
||||||
|
uint64_t pos = (uint64_t)sector_num * BDRV_SECTOR_SIZE;
|
||||||
size_t len = (size_t)nb_sectors * BDRV_SECTOR_SIZE;
|
size_t len = (size_t)nb_sectors * BDRV_SECTOR_SIZE;
|
||||||
QEDIsAllocatedCB cb = {
|
QEDIsAllocatedCB cb = {
|
||||||
.bs = bs,
|
.is_allocated = -1,
|
||||||
.pos = (uint64_t)sector_num * BDRV_SECTOR_SIZE,
|
|
||||||
.status = BDRV_BLOCK_OFFSET_MASK,
|
|
||||||
.pnum = pnum,
|
.pnum = pnum,
|
||||||
};
|
};
|
||||||
QEDRequest request = { .l2_table = NULL };
|
QEDRequest request = { .l2_table = NULL };
|
||||||
|
|
||||||
qed_find_cluster(s, &request, cb.pos, len, qed_is_allocated_cb, &cb);
|
qed_find_cluster(s, &request, pos, len, qed_is_allocated_cb, &cb);
|
||||||
|
|
||||||
/* Now sleep if the callback wasn't invoked immediately */
|
/* Now sleep if the callback wasn't invoked immediately */
|
||||||
while (cb.status == BDRV_BLOCK_OFFSET_MASK) {
|
while (cb.is_allocated == -1) {
|
||||||
cb.co = qemu_coroutine_self();
|
cb.co = qemu_coroutine_self();
|
||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
qed_unref_l2_cache_entry(request.l2_table);
|
qed_unref_l2_cache_entry(request.l2_table);
|
||||||
|
|
||||||
return cb.status;
|
return cb.is_allocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bdrv_qed_make_empty(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BDRVQEDState *acb_to_s(QEDAIOCB *acb)
|
static BDRVQEDState *acb_to_s(QEDAIOCB *acb)
|
||||||
@@ -1403,8 +1368,7 @@ static void coroutine_fn qed_co_write_zeroes_cb(void *opaque, int ret)
|
|||||||
|
|
||||||
static int coroutine_fn bdrv_qed_co_write_zeroes(BlockDriverState *bs,
|
static int coroutine_fn bdrv_qed_co_write_zeroes(BlockDriverState *bs,
|
||||||
int64_t sector_num,
|
int64_t sector_num,
|
||||||
int nb_sectors,
|
int nb_sectors)
|
||||||
BdrvRequestFlags flags)
|
|
||||||
{
|
{
|
||||||
BlockDriverAIOCB *blockacb;
|
BlockDriverAIOCB *blockacb;
|
||||||
BDRVQEDState *s = bs->opaque;
|
BDRVQEDState *s = bs->opaque;
|
||||||
@@ -1481,8 +1445,6 @@ static int bdrv_qed_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
|||||||
memset(bdi, 0, sizeof(*bdi));
|
memset(bdi, 0, sizeof(*bdi));
|
||||||
bdi->cluster_size = s->header.cluster_size;
|
bdi->cluster_size = s->header.cluster_size;
|
||||||
bdi->is_dirty = s->header.features & QED_F_NEED_CHECK;
|
bdi->is_dirty = s->header.features & QED_F_NEED_CHECK;
|
||||||
bdi->unallocated_blocks_are_zero = true;
|
|
||||||
bdi->can_write_zeroes_with_unmap = true;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1558,31 +1520,13 @@ static int bdrv_qed_change_backing_file(BlockDriverState *bs,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bdrv_qed_invalidate_cache(BlockDriverState *bs, Error **errp)
|
static void bdrv_qed_invalidate_cache(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVQEDState *s = bs->opaque;
|
BDRVQEDState *s = bs->opaque;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
bdrv_qed_close(bs);
|
bdrv_qed_close(bs);
|
||||||
|
|
||||||
bdrv_invalidate_cache(bs->file, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(s, 0, sizeof(BDRVQEDState));
|
memset(s, 0, sizeof(BDRVQEDState));
|
||||||
ret = bdrv_qed_open(bs, NULL, bs->open_flags, &local_err);
|
bdrv_qed_open(bs, bs->open_flags);
|
||||||
if (local_err) {
|
|
||||||
error_setg(errp, "Could not reopen qed layer: %s",
|
|
||||||
error_get_pretty(local_err));
|
|
||||||
error_free(local_err);
|
|
||||||
return;
|
|
||||||
} else if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret, "Could not reopen qed layer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bdrv_qed_check(BlockDriverState *bs, BdrvCheckResult *result,
|
static int bdrv_qed_check(BlockDriverState *bs, BdrvCheckResult *result,
|
||||||
@@ -1630,15 +1574,14 @@ static BlockDriver bdrv_qed = {
|
|||||||
.bdrv_close = bdrv_qed_close,
|
.bdrv_close = bdrv_qed_close,
|
||||||
.bdrv_reopen_prepare = bdrv_qed_reopen_prepare,
|
.bdrv_reopen_prepare = bdrv_qed_reopen_prepare,
|
||||||
.bdrv_create = bdrv_qed_create,
|
.bdrv_create = bdrv_qed_create,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
.bdrv_co_is_allocated = bdrv_qed_co_is_allocated,
|
||||||
.bdrv_co_get_block_status = bdrv_qed_co_get_block_status,
|
.bdrv_make_empty = bdrv_qed_make_empty,
|
||||||
.bdrv_aio_readv = bdrv_qed_aio_readv,
|
.bdrv_aio_readv = bdrv_qed_aio_readv,
|
||||||
.bdrv_aio_writev = bdrv_qed_aio_writev,
|
.bdrv_aio_writev = bdrv_qed_aio_writev,
|
||||||
.bdrv_co_write_zeroes = bdrv_qed_co_write_zeroes,
|
.bdrv_co_write_zeroes = bdrv_qed_co_write_zeroes,
|
||||||
.bdrv_truncate = bdrv_qed_truncate,
|
.bdrv_truncate = bdrv_qed_truncate,
|
||||||
.bdrv_getlength = bdrv_qed_getlength,
|
.bdrv_getlength = bdrv_qed_getlength,
|
||||||
.bdrv_get_info = bdrv_qed_get_info,
|
.bdrv_get_info = bdrv_qed_get_info,
|
||||||
.bdrv_refresh_limits = bdrv_qed_refresh_limits,
|
|
||||||
.bdrv_change_backing_file = bdrv_qed_change_backing_file,
|
.bdrv_change_backing_file = bdrv_qed_change_backing_file,
|
||||||
.bdrv_invalidate_cache = bdrv_qed_invalidate_cache,
|
.bdrv_invalidate_cache = bdrv_qed_invalidate_cache,
|
||||||
.bdrv_check = bdrv_qed_check,
|
.bdrv_check = bdrv_qed_check,
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ typedef struct {
|
|||||||
/* if (features & QED_F_BACKING_FILE) */
|
/* if (features & QED_F_BACKING_FILE) */
|
||||||
uint32_t backing_filename_offset; /* in bytes from start of header */
|
uint32_t backing_filename_offset; /* in bytes from start of header */
|
||||||
uint32_t backing_filename_size; /* in bytes */
|
uint32_t backing_filename_size; /* in bytes */
|
||||||
} QEMU_PACKED QEDHeader;
|
} QEDHeader;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint64_t offsets[0]; /* in bytes */
|
uint64_t offsets[0]; /* in bytes */
|
||||||
|
|||||||
877
block/quorum.c
877
block/quorum.c
@@ -1,877 +0,0 @@
|
|||||||
/*
|
|
||||||
* Quorum Block filter
|
|
||||||
*
|
|
||||||
* Copyright (C) 2012-2014 Nodalink, EURL.
|
|
||||||
*
|
|
||||||
* Author:
|
|
||||||
* Benoît Canet <benoit.canet@irqsave.net>
|
|
||||||
*
|
|
||||||
* Based on the design and code of blkverify.c (Copyright (C) 2010 IBM, Corp)
|
|
||||||
* and blkmirror.c (Copyright (C) 2011 Red Hat, Inc).
|
|
||||||
*
|
|
||||||
* 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 <gnutls/gnutls.h>
|
|
||||||
#include <gnutls/crypto.h>
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "qapi/qmp/qjson.h"
|
|
||||||
|
|
||||||
#define HASH_LENGTH 32
|
|
||||||
|
|
||||||
#define QUORUM_OPT_VOTE_THRESHOLD "vote-threshold"
|
|
||||||
#define QUORUM_OPT_BLKVERIFY "blkverify"
|
|
||||||
|
|
||||||
/* This union holds a vote hash value */
|
|
||||||
typedef union QuorumVoteValue {
|
|
||||||
char h[HASH_LENGTH]; /* SHA-256 hash */
|
|
||||||
int64_t l; /* simpler 64 bits hash */
|
|
||||||
} QuorumVoteValue;
|
|
||||||
|
|
||||||
/* A vote item */
|
|
||||||
typedef struct QuorumVoteItem {
|
|
||||||
int index;
|
|
||||||
QLIST_ENTRY(QuorumVoteItem) next;
|
|
||||||
} QuorumVoteItem;
|
|
||||||
|
|
||||||
/* this structure is a vote version. A version is the set of votes sharing the
|
|
||||||
* same vote value.
|
|
||||||
* The set of votes will be tracked with the items field and its cardinality is
|
|
||||||
* vote_count.
|
|
||||||
*/
|
|
||||||
typedef struct QuorumVoteVersion {
|
|
||||||
QuorumVoteValue value;
|
|
||||||
int index;
|
|
||||||
int vote_count;
|
|
||||||
QLIST_HEAD(, QuorumVoteItem) items;
|
|
||||||
QLIST_ENTRY(QuorumVoteVersion) next;
|
|
||||||
} QuorumVoteVersion;
|
|
||||||
|
|
||||||
/* this structure holds a group of vote versions together */
|
|
||||||
typedef struct QuorumVotes {
|
|
||||||
QLIST_HEAD(, QuorumVoteVersion) vote_list;
|
|
||||||
bool (*compare)(QuorumVoteValue *a, QuorumVoteValue *b);
|
|
||||||
} QuorumVotes;
|
|
||||||
|
|
||||||
/* the following structure holds the state of one quorum instance */
|
|
||||||
typedef struct BDRVQuorumState {
|
|
||||||
BlockDriverState **bs; /* children BlockDriverStates */
|
|
||||||
int num_children; /* children count */
|
|
||||||
int threshold; /* if less than threshold children reads gave the
|
|
||||||
* same result a quorum error occurs.
|
|
||||||
*/
|
|
||||||
bool is_blkverify; /* true if the driver is in blkverify mode
|
|
||||||
* Writes are mirrored on two children devices.
|
|
||||||
* On reads the two children devices' contents are
|
|
||||||
* compared and if a difference is spotted its
|
|
||||||
* location is printed and the code aborts.
|
|
||||||
* It is useful to debug other block drivers by
|
|
||||||
* comparing them with a reference one.
|
|
||||||
*/
|
|
||||||
} BDRVQuorumState;
|
|
||||||
|
|
||||||
typedef struct QuorumAIOCB QuorumAIOCB;
|
|
||||||
|
|
||||||
/* Quorum will create one instance of the following structure per operation it
|
|
||||||
* performs on its children.
|
|
||||||
* So for each read/write operation coming from the upper layer there will be
|
|
||||||
* $children_count QuorumChildRequest.
|
|
||||||
*/
|
|
||||||
typedef struct QuorumChildRequest {
|
|
||||||
BlockDriverAIOCB *aiocb;
|
|
||||||
QEMUIOVector qiov;
|
|
||||||
uint8_t *buf;
|
|
||||||
int ret;
|
|
||||||
QuorumAIOCB *parent;
|
|
||||||
} QuorumChildRequest;
|
|
||||||
|
|
||||||
/* Quorum will use the following structure to track progress of each read/write
|
|
||||||
* operation received by the upper layer.
|
|
||||||
* This structure hold pointers to the QuorumChildRequest structures instances
|
|
||||||
* used to do operations on each children and track overall progress.
|
|
||||||
*/
|
|
||||||
struct QuorumAIOCB {
|
|
||||||
BlockDriverAIOCB common;
|
|
||||||
|
|
||||||
/* Request metadata */
|
|
||||||
uint64_t sector_num;
|
|
||||||
int nb_sectors;
|
|
||||||
|
|
||||||
QEMUIOVector *qiov; /* calling IOV */
|
|
||||||
|
|
||||||
QuorumChildRequest *qcrs; /* individual child requests */
|
|
||||||
int count; /* number of completed AIOCB */
|
|
||||||
int success_count; /* number of successfully completed AIOCB */
|
|
||||||
|
|
||||||
QuorumVotes votes;
|
|
||||||
|
|
||||||
bool is_read;
|
|
||||||
int vote_ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void quorum_vote(QuorumAIOCB *acb);
|
|
||||||
|
|
||||||
static void quorum_aio_cancel(BlockDriverAIOCB *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++) {
|
|
||||||
bdrv_aio_cancel(acb->qcrs[i].aiocb);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(acb->qcrs);
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AIOCBInfo quorum_aiocb_info = {
|
|
||||||
.aiocb_size = sizeof(QuorumAIOCB),
|
|
||||||
.cancel = quorum_aio_cancel,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void quorum_aio_finalize(QuorumAIOCB *acb)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
|
||||||
int i, ret = 0;
|
|
||||||
|
|
||||||
if (acb->vote_ret) {
|
|
||||||
ret = acb->vote_ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
acb->common.cb(acb->common.opaque, ret);
|
|
||||||
|
|
||||||
if (acb->is_read) {
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
qemu_vfree(acb->qcrs[i].buf);
|
|
||||||
qemu_iovec_destroy(&acb->qcrs[i].qiov);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(acb->qcrs);
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool quorum_sha256_compare(QuorumVoteValue *a, QuorumVoteValue *b)
|
|
||||||
{
|
|
||||||
return !memcmp(a->h, b->h, HASH_LENGTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool quorum_64bits_compare(QuorumVoteValue *a, QuorumVoteValue *b)
|
|
||||||
{
|
|
||||||
return a->l == b->l;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QuorumAIOCB *quorum_aio_get(BDRVQuorumState *s,
|
|
||||||
BlockDriverState *bs,
|
|
||||||
QEMUIOVector *qiov,
|
|
||||||
uint64_t sector_num,
|
|
||||||
int nb_sectors,
|
|
||||||
BlockDriverCompletionFunc *cb,
|
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
QuorumAIOCB *acb = qemu_aio_get(&quorum_aiocb_info, bs, cb, opaque);
|
|
||||||
int i;
|
|
||||||
|
|
||||||
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->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;
|
|
||||||
acb->qcrs[i].parent = acb;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acb;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_report_bad(QuorumAIOCB *acb, char *node_name, int ret)
|
|
||||||
{
|
|
||||||
QObject *data;
|
|
||||||
assert(node_name);
|
|
||||||
data = qobject_from_jsonf("{ 'node-name': %s"
|
|
||||||
", 'sector-num': %" PRId64
|
|
||||||
", 'sectors-count': %d }",
|
|
||||||
node_name, acb->sector_num, acb->nb_sectors);
|
|
||||||
if (ret < 0) {
|
|
||||||
QDict *dict = qobject_to_qdict(data);
|
|
||||||
qdict_put(dict, "error", qstring_from_str(strerror(-ret)));
|
|
||||||
}
|
|
||||||
monitor_protocol_event(QEVENT_QUORUM_REPORT_BAD, data);
|
|
||||||
qobject_decref(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_report_failure(QuorumAIOCB *acb)
|
|
||||||
{
|
|
||||||
QObject *data;
|
|
||||||
const char *reference = acb->common.bs->device_name[0] ?
|
|
||||||
acb->common.bs->device_name :
|
|
||||||
acb->common.bs->node_name;
|
|
||||||
data = qobject_from_jsonf("{ 'reference': %s"
|
|
||||||
", 'sector-num': %" PRId64
|
|
||||||
", 'sectors-count': %d }",
|
|
||||||
reference, acb->sector_num, acb->nb_sectors);
|
|
||||||
monitor_protocol_event(QEVENT_QUORUM_FAILURE, data);
|
|
||||||
qobject_decref(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int quorum_vote_error(QuorumAIOCB *acb);
|
|
||||||
|
|
||||||
static bool quorum_has_too_much_io_failed(QuorumAIOCB *acb)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
|
||||||
|
|
||||||
if (acb->success_count < s->threshold) {
|
|
||||||
acb->vote_ret = quorum_vote_error(acb);
|
|
||||||
quorum_report_failure(acb);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_aio_cb(void *opaque, int ret)
|
|
||||||
{
|
|
||||||
QuorumChildRequest *sacb = opaque;
|
|
||||||
QuorumAIOCB *acb = sacb->parent;
|
|
||||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
|
||||||
|
|
||||||
sacb->ret = ret;
|
|
||||||
acb->count++;
|
|
||||||
if (ret == 0) {
|
|
||||||
acb->success_count++;
|
|
||||||
} else {
|
|
||||||
quorum_report_bad(acb, sacb->aiocb->bs->node_name, ret);
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
quorum_vote(acb);
|
|
||||||
} else {
|
|
||||||
quorum_has_too_much_io_failed(acb);
|
|
||||||
}
|
|
||||||
|
|
||||||
quorum_aio_finalize(acb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_report_bad_versions(BDRVQuorumState *s,
|
|
||||||
QuorumAIOCB *acb,
|
|
||||||
QuorumVoteValue *value)
|
|
||||||
{
|
|
||||||
QuorumVoteVersion *version;
|
|
||||||
QuorumVoteItem *item;
|
|
||||||
|
|
||||||
QLIST_FOREACH(version, &acb->votes.vote_list, next) {
|
|
||||||
if (acb->votes.compare(&version->value, value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
QLIST_FOREACH(item, &version->items, next) {
|
|
||||||
quorum_report_bad(acb, s->bs[item->index]->node_name, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_copy_qiov(QEMUIOVector *dest, QEMUIOVector *source)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
assert(dest->niov == source->niov);
|
|
||||||
assert(dest->size == source->size);
|
|
||||||
for (i = 0; i < source->niov; i++) {
|
|
||||||
assert(dest->iov[i].iov_len == source->iov[i].iov_len);
|
|
||||||
memcpy(dest->iov[i].iov_base,
|
|
||||||
source->iov[i].iov_base,
|
|
||||||
source->iov[i].iov_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_count_vote(QuorumVotes *votes,
|
|
||||||
QuorumVoteValue *value,
|
|
||||||
int index)
|
|
||||||
{
|
|
||||||
QuorumVoteVersion *v = NULL, *version = NULL;
|
|
||||||
QuorumVoteItem *item;
|
|
||||||
|
|
||||||
/* look if we have something with this hash */
|
|
||||||
QLIST_FOREACH(v, &votes->vote_list, next) {
|
|
||||||
if (votes->compare(&v->value, value)) {
|
|
||||||
version = v;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* It's a version not yet in the list add it */
|
|
||||||
if (!version) {
|
|
||||||
version = g_new0(QuorumVoteVersion, 1);
|
|
||||||
QLIST_INIT(&version->items);
|
|
||||||
memcpy(&version->value, value, sizeof(version->value));
|
|
||||||
version->index = index;
|
|
||||||
version->vote_count = 0;
|
|
||||||
QLIST_INSERT_HEAD(&votes->vote_list, version, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
version->vote_count++;
|
|
||||||
|
|
||||||
item = g_new0(QuorumVoteItem, 1);
|
|
||||||
item->index = index;
|
|
||||||
QLIST_INSERT_HEAD(&version->items, item, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_free_vote_list(QuorumVotes *votes)
|
|
||||||
{
|
|
||||||
QuorumVoteVersion *version, *next_version;
|
|
||||||
QuorumVoteItem *item, *next_item;
|
|
||||||
|
|
||||||
QLIST_FOREACH_SAFE(version, &votes->vote_list, next, next_version) {
|
|
||||||
QLIST_REMOVE(version, next);
|
|
||||||
QLIST_FOREACH_SAFE(item, &version->items, next, next_item) {
|
|
||||||
QLIST_REMOVE(item, next);
|
|
||||||
g_free(item);
|
|
||||||
}
|
|
||||||
g_free(version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int quorum_compute_hash(QuorumAIOCB *acb, int i, QuorumVoteValue *hash)
|
|
||||||
{
|
|
||||||
int j, ret;
|
|
||||||
gnutls_hash_hd_t dig;
|
|
||||||
QEMUIOVector *qiov = &acb->qcrs[i].qiov;
|
|
||||||
|
|
||||||
ret = gnutls_hash_init(&dig, GNUTLS_DIG_SHA256);
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < qiov->niov; j++) {
|
|
||||||
ret = gnutls_hash(dig, qiov->iov[j].iov_base, qiov->iov[j].iov_len);
|
|
||||||
if (ret < 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gnutls_hash_deinit(dig, (void *) hash);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QuorumVoteVersion *quorum_get_vote_winner(QuorumVotes *votes)
|
|
||||||
{
|
|
||||||
int max = 0;
|
|
||||||
QuorumVoteVersion *candidate, *winner = NULL;
|
|
||||||
|
|
||||||
QLIST_FOREACH(candidate, &votes->vote_list, next) {
|
|
||||||
if (candidate->vote_count > max) {
|
|
||||||
max = candidate->vote_count;
|
|
||||||
winner = candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return winner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* qemu_iovec_compare is handy for blkverify mode because it returns the first
|
|
||||||
* differing byte location. Yet it is handcoded to compare vectors one byte
|
|
||||||
* after another so it does not benefit from the libc SIMD optimizations.
|
|
||||||
* quorum_iovec_compare is written for speed and should be used in the non
|
|
||||||
* blkverify mode of quorum.
|
|
||||||
*/
|
|
||||||
static bool quorum_iovec_compare(QEMUIOVector *a, QEMUIOVector *b)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
int result;
|
|
||||||
|
|
||||||
assert(a->niov == b->niov);
|
|
||||||
for (i = 0; i < a->niov; i++) {
|
|
||||||
assert(a->iov[i].iov_len == b->iov[i].iov_len);
|
|
||||||
result = memcmp(a->iov[i].iov_base,
|
|
||||||
b->iov[i].iov_base,
|
|
||||||
a->iov[i].iov_len);
|
|
||||||
if (result) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GCC_FMT_ATTR(2, 3) quorum_err(QuorumAIOCB *acb,
|
|
||||||
const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
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);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool quorum_compare(QuorumAIOCB *acb,
|
|
||||||
QEMUIOVector *a,
|
|
||||||
QEMUIOVector *b)
|
|
||||||
{
|
|
||||||
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 in sector %" PRId64,
|
|
||||||
acb->sector_num +
|
|
||||||
(uint64_t)(offset / BDRV_SECTOR_SIZE));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return quorum_iovec_compare(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Do a vote to get the error code */
|
|
||||||
static int quorum_vote_error(QuorumAIOCB *acb)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
|
||||||
QuorumVoteVersion *winner = NULL;
|
|
||||||
QuorumVotes error_votes;
|
|
||||||
QuorumVoteValue result_value;
|
|
||||||
int i, ret = 0;
|
|
||||||
bool error = false;
|
|
||||||
|
|
||||||
QLIST_INIT(&error_votes.vote_list);
|
|
||||||
error_votes.compare = quorum_64bits_compare;
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
ret = acb->qcrs[i].ret;
|
|
||||||
if (ret) {
|
|
||||||
error = true;
|
|
||||||
result_value.l = ret;
|
|
||||||
quorum_count_vote(&error_votes, &result_value, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
winner = quorum_get_vote_winner(&error_votes);
|
|
||||||
ret = winner->value.l;
|
|
||||||
}
|
|
||||||
|
|
||||||
quorum_free_vote_list(&error_votes);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_vote(QuorumAIOCB *acb)
|
|
||||||
{
|
|
||||||
bool quorum = true;
|
|
||||||
int i, j, ret;
|
|
||||||
QuorumVoteValue hash;
|
|
||||||
BDRVQuorumState *s = acb->common.bs->opaque;
|
|
||||||
QuorumVoteVersion *winner;
|
|
||||||
|
|
||||||
if (quorum_has_too_much_io_failed(acb)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* get the index of the first successful read */
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
if (!acb->qcrs[i].ret) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(i < s->num_children);
|
|
||||||
|
|
||||||
/* compare this read with all other successful reads stopping at quorum
|
|
||||||
* failure
|
|
||||||
*/
|
|
||||||
for (j = i + 1; j < s->num_children; j++) {
|
|
||||||
if (acb->qcrs[j].ret) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
quorum = quorum_compare(acb, &acb->qcrs[i].qiov, &acb->qcrs[j].qiov);
|
|
||||||
if (!quorum) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Every successful read agrees */
|
|
||||||
if (quorum) {
|
|
||||||
quorum_copy_qiov(acb->qiov, &acb->qcrs[i].qiov);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* compute hashes for each successful read, also store indexes */
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
if (acb->qcrs[i].ret) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ret = quorum_compute_hash(acb, i, &hash);
|
|
||||||
/* if ever the hash computation failed */
|
|
||||||
if (ret < 0) {
|
|
||||||
acb->vote_ret = ret;
|
|
||||||
goto free_exit;
|
|
||||||
}
|
|
||||||
quorum_count_vote(&acb->votes, &hash, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vote to select the most represented version */
|
|
||||||
winner = quorum_get_vote_winner(&acb->votes);
|
|
||||||
|
|
||||||
/* if the winner count is smaller than threshold the read fails */
|
|
||||||
if (winner->vote_count < s->threshold) {
|
|
||||||
quorum_report_failure(acb);
|
|
||||||
acb->vote_ret = -EIO;
|
|
||||||
goto free_exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* we have a winner: copy it */
|
|
||||||
quorum_copy_qiov(acb->qiov, &acb->qcrs[winner->index].qiov);
|
|
||||||
|
|
||||||
/* some versions are bad print them */
|
|
||||||
quorum_report_bad_versions(s, acb, &winner->value);
|
|
||||||
|
|
||||||
free_exit:
|
|
||||||
/* free lists */
|
|
||||||
quorum_free_vote_list(&acb->votes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriverAIOCB *quorum_aio_readv(BlockDriverState *bs,
|
|
||||||
int64_t sector_num,
|
|
||||||
QEMUIOVector *qiov,
|
|
||||||
int nb_sectors,
|
|
||||||
BlockDriverCompletionFunc *cb,
|
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
QuorumAIOCB *acb = quorum_aio_get(s, bs, qiov, sector_num,
|
|
||||||
nb_sectors, cb, opaque);
|
|
||||||
int i;
|
|
||||||
|
|
||||||
acb->is_read = true;
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
acb->qcrs[i].buf = qemu_blockalign(s->bs[i], qiov->size);
|
|
||||||
qemu_iovec_init(&acb->qcrs[i].qiov, qiov->niov);
|
|
||||||
qemu_iovec_clone(&acb->qcrs[i].qiov, qiov, acb->qcrs[i].buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
bdrv_aio_readv(s->bs[i], sector_num, &acb->qcrs[i].qiov, nb_sectors,
|
|
||||||
quorum_aio_cb, &acb->qcrs[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return &acb->common;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriverAIOCB *quorum_aio_writev(BlockDriverState *bs,
|
|
||||||
int64_t sector_num,
|
|
||||||
QEMUIOVector *qiov,
|
|
||||||
int nb_sectors,
|
|
||||||
BlockDriverCompletionFunc *cb,
|
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
QuorumAIOCB *acb = quorum_aio_get(s, bs, qiov, sector_num, nb_sectors,
|
|
||||||
cb, opaque);
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
acb->qcrs[i].aiocb = bdrv_aio_writev(s->bs[i], sector_num, qiov,
|
|
||||||
nb_sectors, &quorum_aio_cb,
|
|
||||||
&acb->qcrs[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return &acb->common;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t quorum_getlength(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
int64_t result;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* check that all file have the same length */
|
|
||||||
result = bdrv_getlength(s->bs[0]);
|
|
||||||
if (result < 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
for (i = 1; i < s->num_children; i++) {
|
|
||||||
int64_t value = bdrv_getlength(s->bs[i]);
|
|
||||||
if (value < 0) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (value != result) {
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_invalidate_cache(BlockDriverState *bs, Error **errp)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
bdrv_invalidate_cache(s->bs[i], &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static coroutine_fn int quorum_co_flush(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
QuorumVoteVersion *winner = NULL;
|
|
||||||
QuorumVotes error_votes;
|
|
||||||
QuorumVoteValue result_value;
|
|
||||||
int i;
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
QLIST_INIT(&error_votes.vote_list);
|
|
||||||
error_votes.compare = quorum_64bits_compare;
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
result = bdrv_co_flush(s->bs[i]);
|
|
||||||
result_value.l = result;
|
|
||||||
quorum_count_vote(&error_votes, &result_value, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
winner = quorum_get_vote_winner(&error_votes);
|
|
||||||
result = winner->value.l;
|
|
||||||
|
|
||||||
quorum_free_vote_list(&error_votes);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool quorum_recurse_is_first_non_filter(BlockDriverState *bs,
|
|
||||||
BlockDriverState *candidate)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
bool perm = bdrv_recurse_is_first_non_filter(s->bs[i],
|
|
||||||
candidate);
|
|
||||||
if (perm) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int quorum_valid_threshold(int threshold, int num_children, Error **errp)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (threshold < 1) {
|
|
||||||
error_set(errp, QERR_INVALID_PARAMETER_VALUE,
|
|
||||||
"vote-threshold", "value >= 1");
|
|
||||||
return -ERANGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (threshold > num_children) {
|
|
||||||
error_setg(errp, "threshold may not exceed children count");
|
|
||||||
return -ERANGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QemuOptsList quorum_runtime_opts = {
|
|
||||||
.name = "quorum",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(quorum_runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = QUORUM_OPT_VOTE_THRESHOLD,
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "The number of vote needed for reaching quorum",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = QUORUM_OPT_BLKVERIFY,
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "Trigger block verify mode if set",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
QemuOpts *opts;
|
|
||||||
bool *opened;
|
|
||||||
QDict *sub = NULL;
|
|
||||||
QList *list = NULL;
|
|
||||||
const QListEntry *lentry;
|
|
||||||
int i;
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
qdict_flatten(options);
|
|
||||||
qdict_extract_subqdict(options, &sub, "children.");
|
|
||||||
qdict_array_split(sub, &list);
|
|
||||||
|
|
||||||
if (qdict_size(sub)) {
|
|
||||||
error_setg(&local_err, "Invalid option children.%s",
|
|
||||||
qdict_first(sub)->key);
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* count how many different children are present */
|
|
||||||
s->num_children = qlist_size(list);
|
|
||||||
if (s->num_children < 2) {
|
|
||||||
error_setg(&local_err,
|
|
||||||
"Number of provided children must be greater than 1");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = qemu_opts_create(&quorum_runtime_opts, NULL, 0, &error_abort);
|
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
||||||
if (error_is_set(&local_err)) {
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->threshold = qemu_opt_get_number(opts, QUORUM_OPT_VOTE_THRESHOLD, 0);
|
|
||||||
|
|
||||||
/* and validate it against s->num_children */
|
|
||||||
ret = quorum_valid_threshold(s->threshold, s->num_children, &local_err);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* is the driver in blkverify mode */
|
|
||||||
if (qemu_opt_get_bool(opts, QUORUM_OPT_BLKVERIFY, false) &&
|
|
||||||
s->num_children == 2 && s->threshold == 2) {
|
|
||||||
s->is_blkverify = true;
|
|
||||||
} else if (qemu_opt_get_bool(opts, QUORUM_OPT_BLKVERIFY, false)) {
|
|
||||||
fprintf(stderr, "blkverify mode is set by setting blkverify=on "
|
|
||||||
"and using two files with vote_threshold=2\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* allocate the children BlockDriverState array */
|
|
||||||
s->bs = g_new0(BlockDriverState *, s->num_children);
|
|
||||||
opened = g_new0(bool, s->num_children);
|
|
||||||
|
|
||||||
for (i = 0, lentry = qlist_first(list); lentry;
|
|
||||||
lentry = qlist_next(lentry), i++) {
|
|
||||||
QDict *d;
|
|
||||||
QString *string;
|
|
||||||
|
|
||||||
switch (qobject_type(lentry->value))
|
|
||||||
{
|
|
||||||
/* List of options */
|
|
||||||
case QTYPE_QDICT:
|
|
||||||
d = qobject_to_qdict(lentry->value);
|
|
||||||
QINCREF(d);
|
|
||||||
ret = bdrv_open(&s->bs[i], NULL, NULL, d, flags, NULL,
|
|
||||||
&local_err);
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* QMP reference */
|
|
||||||
case QTYPE_QSTRING:
|
|
||||||
string = qobject_to_qstring(lentry->value);
|
|
||||||
ret = bdrv_open(&s->bs[i], NULL, qstring_get_str(string), NULL,
|
|
||||||
flags, NULL, &local_err);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
error_setg(&local_err, "Specification of child block device %i "
|
|
||||||
"is invalid", i);
|
|
||||||
ret = -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
goto close_exit;
|
|
||||||
}
|
|
||||||
opened[i] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(opened);
|
|
||||||
goto exit;
|
|
||||||
|
|
||||||
close_exit:
|
|
||||||
/* cleanup on error */
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
if (!opened[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bdrv_unref(s->bs[i]);
|
|
||||||
}
|
|
||||||
g_free(s->bs);
|
|
||||||
g_free(opened);
|
|
||||||
exit:
|
|
||||||
/* propagate error */
|
|
||||||
if (error_is_set(&local_err)) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
}
|
|
||||||
QDECREF(list);
|
|
||||||
QDECREF(sub);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quorum_close(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BDRVQuorumState *s = bs->opaque;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
|
||||||
bdrv_unref(s->bs[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(s->bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriver bdrv_quorum = {
|
|
||||||
.format_name = "quorum",
|
|
||||||
.protocol_name = "quorum",
|
|
||||||
|
|
||||||
.instance_size = sizeof(BDRVQuorumState),
|
|
||||||
|
|
||||||
.bdrv_file_open = quorum_open,
|
|
||||||
.bdrv_close = quorum_close,
|
|
||||||
|
|
||||||
.bdrv_co_flush_to_disk = quorum_co_flush,
|
|
||||||
|
|
||||||
.bdrv_getlength = quorum_getlength,
|
|
||||||
|
|
||||||
.bdrv_aio_readv = quorum_aio_readv,
|
|
||||||
.bdrv_aio_writev = quorum_aio_writev,
|
|
||||||
.bdrv_invalidate_cache = quorum_invalidate_cache,
|
|
||||||
|
|
||||||
.is_filter = true,
|
|
||||||
.bdrv_recurse_is_first_non_filter = quorum_recurse_is_first_non_filter,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void bdrv_quorum_init(void)
|
|
||||||
{
|
|
||||||
bdrv_register(&bdrv_quorum);
|
|
||||||
}
|
|
||||||
|
|
||||||
block_init(bdrv_quorum_init);
|
|
||||||
@@ -21,10 +21,9 @@
|
|||||||
#define QEMU_AIO_IOCTL 0x0004
|
#define QEMU_AIO_IOCTL 0x0004
|
||||||
#define QEMU_AIO_FLUSH 0x0008
|
#define QEMU_AIO_FLUSH 0x0008
|
||||||
#define QEMU_AIO_DISCARD 0x0010
|
#define QEMU_AIO_DISCARD 0x0010
|
||||||
#define QEMU_AIO_WRITE_ZEROES 0x0020
|
|
||||||
#define QEMU_AIO_TYPE_MASK \
|
#define QEMU_AIO_TYPE_MASK \
|
||||||
(QEMU_AIO_READ|QEMU_AIO_WRITE|QEMU_AIO_IOCTL|QEMU_AIO_FLUSH| \
|
(QEMU_AIO_READ|QEMU_AIO_WRITE|QEMU_AIO_IOCTL|QEMU_AIO_FLUSH| \
|
||||||
QEMU_AIO_DISCARD|QEMU_AIO_WRITE_ZEROES)
|
QEMU_AIO_DISCARD)
|
||||||
|
|
||||||
/* AIO flags */
|
/* AIO flags */
|
||||||
#define QEMU_AIO_MISALIGNED 0x1000
|
#define QEMU_AIO_MISALIGNED 0x1000
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -85,7 +85,6 @@ static size_t handle_aiocb_rw(RawWin32AIOData *aiocb)
|
|||||||
ret_count = 0;
|
ret_count = 0;
|
||||||
}
|
}
|
||||||
if (ret_count != len) {
|
if (ret_count != len) {
|
||||||
offset += ret_count;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
offset += len;
|
offset += len;
|
||||||
@@ -145,7 +144,6 @@ static BlockDriverAIOCB *paio_submit(BlockDriverState *bs, HANDLE hfile,
|
|||||||
BlockDriverCompletionFunc *cb, void *opaque, int type)
|
BlockDriverCompletionFunc *cb, void *opaque, int type)
|
||||||
{
|
{
|
||||||
RawWin32AIOData *acb = g_slice_new(RawWin32AIOData);
|
RawWin32AIOData *acb = g_slice_new(RawWin32AIOData);
|
||||||
ThreadPool *pool;
|
|
||||||
|
|
||||||
acb->bs = bs;
|
acb->bs = bs;
|
||||||
acb->hfile = hfile;
|
acb->hfile = hfile;
|
||||||
@@ -159,8 +157,7 @@ static BlockDriverAIOCB *paio_submit(BlockDriverState *bs, HANDLE hfile,
|
|||||||
acb->aio_offset = sector_num * 512;
|
acb->aio_offset = sector_num * 512;
|
||||||
|
|
||||||
trace_paio_submit(acb, opaque, sector_num, nb_sectors, type);
|
trace_paio_submit(acb, opaque, sector_num, nb_sectors, type);
|
||||||
pool = aio_get_thread_pool(bdrv_get_aio_context(bs));
|
return thread_pool_submit_aio(aio_worker, acb, cb, opaque);
|
||||||
return thread_pool_submit_aio(pool, aio_worker, acb, cb, opaque);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int qemu_ftruncate64(int fd, int64_t length)
|
int qemu_ftruncate64(int fd, int64_t length)
|
||||||
@@ -202,35 +199,6 @@ static int set_sparse(int fd)
|
|||||||
NULL, 0, NULL, 0, &returned, NULL);
|
NULL, 0, NULL, 0, &returned, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void raw_probe_alignment(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BDRVRawState *s = bs->opaque;
|
|
||||||
DWORD sectorsPerCluster, freeClusters, totalClusters, count;
|
|
||||||
DISK_GEOMETRY_EX dg;
|
|
||||||
BOOL status;
|
|
||||||
|
|
||||||
if (s->type == FTYPE_CD) {
|
|
||||||
bs->request_alignment = 2048;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (s->type == FTYPE_HARDDISK) {
|
|
||||||
status = DeviceIoControl(s->hfile, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
|
|
||||||
NULL, 0, &dg, sizeof(dg), &count, NULL);
|
|
||||||
if (status != 0) {
|
|
||||||
bs->request_alignment = dg.Geometry.BytesPerSector;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* try GetDiskFreeSpace too */
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->drive_path[0]) {
|
|
||||||
GetDiskFreeSpace(s->drive_path, §orsPerCluster,
|
|
||||||
&dg.Geometry.BytesPerSector,
|
|
||||||
&freeClusters, &totalClusters);
|
|
||||||
bs->request_alignment = dg.Geometry.BytesPerSector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void raw_parse_flags(int flags, int *access_flags, DWORD *overlapped)
|
static void raw_parse_flags(int flags, int *access_flags, DWORD *overlapped)
|
||||||
{
|
{
|
||||||
assert(access_flags != NULL);
|
assert(access_flags != NULL);
|
||||||
@@ -251,104 +219,43 @@ static void raw_parse_flags(int flags, int *access_flags, DWORD *overlapped)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void raw_parse_filename(const char *filename, QDict *options,
|
static int raw_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
/* 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 = {
|
|
||||||
.name = "raw",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(raw_runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = "filename",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "File name of the image",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVRawState *s = bs->opaque;
|
BDRVRawState *s = bs->opaque;
|
||||||
int access_flags;
|
int access_flags;
|
||||||
DWORD overlapped;
|
DWORD overlapped;
|
||||||
QemuOpts *opts;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
const char *filename;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
s->type = FTYPE_FILE;
|
s->type = FTYPE_FILE;
|
||||||
|
|
||||||
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 fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = qemu_opt_get(opts, "filename");
|
|
||||||
|
|
||||||
raw_parse_flags(flags, &access_flags, &overlapped);
|
raw_parse_flags(flags, &access_flags, &overlapped);
|
||||||
|
|
||||||
if ((flags & BDRV_O_NATIVE_AIO) && aio == NULL) {
|
if ((flags & BDRV_O_NATIVE_AIO) && aio == NULL) {
|
||||||
aio = win32_aio_init();
|
aio = win32_aio_init();
|
||||||
if (aio == NULL) {
|
if (aio == NULL) {
|
||||||
error_setg(errp, "Could not initialize AIO");
|
return -EINVAL;
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filename[0] && filename[1] == ':') {
|
|
||||||
snprintf(s->drive_path, sizeof(s->drive_path), "%c:\\", filename[0]);
|
|
||||||
} else if (filename[0] == '\\' && filename[1] == '\\') {
|
|
||||||
s->drive_path[0] = 0;
|
|
||||||
} else {
|
|
||||||
/* Relative path. */
|
|
||||||
char buf[MAX_PATH];
|
|
||||||
GetCurrentDirectory(MAX_PATH, buf);
|
|
||||||
snprintf(s->drive_path, sizeof(s->drive_path), "%c:\\", buf[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
s->hfile = CreateFile(filename, access_flags,
|
s->hfile = CreateFile(filename, access_flags,
|
||||||
FILE_SHARE_READ, NULL,
|
FILE_SHARE_READ, NULL,
|
||||||
OPEN_EXISTING, overlapped, NULL);
|
OPEN_EXISTING, overlapped, NULL);
|
||||||
if (s->hfile == INVALID_HANDLE_VALUE) {
|
if (s->hfile == INVALID_HANDLE_VALUE) {
|
||||||
int err = GetLastError();
|
int err = GetLastError();
|
||||||
|
|
||||||
if (err == ERROR_ACCESS_DENIED) {
|
if (err == ERROR_ACCESS_DENIED)
|
||||||
ret = -EACCES;
|
return -EACCES;
|
||||||
} else {
|
return -EINVAL;
|
||||||
ret = -EINVAL;
|
|
||||||
}
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & BDRV_O_NATIVE_AIO) {
|
if (flags & BDRV_O_NATIVE_AIO) {
|
||||||
ret = win32_aio_attach(aio, s->hfile);
|
int ret = win32_aio_attach(aio, s->hfile);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
CloseHandle(s->hfile);
|
CloseHandle(s->hfile);
|
||||||
error_setg_errno(errp, -ret, "Could not enable AIO");
|
return ret;
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
s->aio = aio;
|
s->aio = aio;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
raw_probe_alignment(bs);
|
|
||||||
ret = 0;
|
|
||||||
fail:
|
|
||||||
qemu_opts_del(opts);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockDriverAIOCB *raw_aio_readv(BlockDriverState *bs,
|
static BlockDriverAIOCB *raw_aio_readv(BlockDriverState *bs,
|
||||||
@@ -475,14 +382,11 @@ static int64_t raw_get_allocated_file_size(BlockDriverState *bs)
|
|||||||
return st.st_size;
|
return st.st_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int raw_create(const char *filename, QEMUOptionParameter *options,
|
static int raw_create(const char *filename, QEMUOptionParameter *options)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
int64_t total_size = 0;
|
int64_t total_size = 0;
|
||||||
|
|
||||||
strstart(filename, "file:", &filename);
|
|
||||||
|
|
||||||
/* Read out options */
|
/* Read out options */
|
||||||
while (options && options->name) {
|
while (options && options->name) {
|
||||||
if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
|
if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
|
||||||
@@ -493,10 +397,8 @@ static int raw_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
|
|
||||||
fd = qemu_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
|
fd = qemu_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
|
||||||
0644);
|
0644);
|
||||||
if (fd < 0) {
|
if (fd < 0)
|
||||||
error_setg_errno(errp, errno, "Could not create file");
|
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
|
||||||
set_sparse(fd);
|
set_sparse(fd);
|
||||||
ftruncate(fd, total_size * 512);
|
ftruncate(fd, total_size * 512);
|
||||||
qemu_close(fd);
|
qemu_close(fd);
|
||||||
@@ -516,12 +418,9 @@ static BlockDriver bdrv_file = {
|
|||||||
.format_name = "file",
|
.format_name = "file",
|
||||||
.protocol_name = "file",
|
.protocol_name = "file",
|
||||||
.instance_size = sizeof(BDRVRawState),
|
.instance_size = sizeof(BDRVRawState),
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_parse_filename = raw_parse_filename,
|
|
||||||
.bdrv_file_open = raw_open,
|
.bdrv_file_open = raw_open,
|
||||||
.bdrv_close = raw_close,
|
.bdrv_close = raw_close,
|
||||||
.bdrv_create = raw_create,
|
.bdrv_create = raw_create,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
|
||||||
|
|
||||||
.bdrv_aio_readv = raw_aio_readv,
|
.bdrv_aio_readv = raw_aio_readv,
|
||||||
.bdrv_aio_writev = raw_aio_writev,
|
.bdrv_aio_writev = raw_aio_writev,
|
||||||
@@ -593,44 +492,16 @@ static int hdev_probe_device(const char *filename)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hdev_parse_filename(const char *filename, QDict *options,
|
static int hdev_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
/* 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,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVRawState *s = bs->opaque;
|
BDRVRawState *s = bs->opaque;
|
||||||
int access_flags, create_flags;
|
int access_flags, create_flags;
|
||||||
int ret = 0;
|
|
||||||
DWORD overlapped;
|
DWORD overlapped;
|
||||||
char device_name[64];
|
char device_name[64];
|
||||||
|
|
||||||
Error *local_err = NULL;
|
|
||||||
const char *filename;
|
|
||||||
|
|
||||||
QemuOpts *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 done;
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = qemu_opt_get(opts, "filename");
|
|
||||||
|
|
||||||
if (strstart(filename, "/dev/cdrom", NULL)) {
|
if (strstart(filename, "/dev/cdrom", NULL)) {
|
||||||
if (find_cdrom(device_name, sizeof(device_name)) < 0) {
|
if (find_cdrom(device_name, sizeof(device_name)) < 0)
|
||||||
error_setg(errp, "Could not open CD-ROM drive");
|
return -ENOENT;
|
||||||
ret = -ENOENT;
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
filename = device_name;
|
filename = device_name;
|
||||||
} else {
|
} else {
|
||||||
/* transform drive letters into device name */
|
/* transform drive letters into device name */
|
||||||
@@ -653,37 +524,32 @@ static int hdev_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
if (s->hfile == INVALID_HANDLE_VALUE) {
|
if (s->hfile == INVALID_HANDLE_VALUE) {
|
||||||
int err = GetLastError();
|
int err = GetLastError();
|
||||||
|
|
||||||
if (err == ERROR_ACCESS_DENIED) {
|
if (err == ERROR_ACCESS_DENIED)
|
||||||
ret = -EACCES;
|
return -EACCES;
|
||||||
} else {
|
return -1;
|
||||||
ret = -EINVAL;
|
|
||||||
}
|
}
|
||||||
error_setg_errno(errp, -ret, "Could not open device");
|
return 0;
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
static int hdev_has_zero_init(BlockDriverState *bs)
|
||||||
qemu_opts_del(opts);
|
{
|
||||||
return ret;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockDriver bdrv_host_device = {
|
static BlockDriver bdrv_host_device = {
|
||||||
.format_name = "host_device",
|
.format_name = "host_device",
|
||||||
.protocol_name = "host_device",
|
.protocol_name = "host_device",
|
||||||
.instance_size = sizeof(BDRVRawState),
|
.instance_size = sizeof(BDRVRawState),
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_parse_filename = hdev_parse_filename,
|
|
||||||
.bdrv_probe_device = hdev_probe_device,
|
.bdrv_probe_device = hdev_probe_device,
|
||||||
.bdrv_file_open = hdev_open,
|
.bdrv_file_open = hdev_open,
|
||||||
.bdrv_close = raw_close,
|
.bdrv_close = raw_close,
|
||||||
|
.bdrv_has_zero_init = hdev_has_zero_init,
|
||||||
|
|
||||||
.bdrv_aio_readv = raw_aio_readv,
|
.bdrv_aio_readv = raw_aio_readv,
|
||||||
.bdrv_aio_writev = raw_aio_writev,
|
.bdrv_aio_writev = raw_aio_writev,
|
||||||
.bdrv_aio_flush = raw_aio_flush,
|
.bdrv_aio_flush = raw_aio_flush,
|
||||||
|
|
||||||
.bdrv_getlength = raw_getlength,
|
.bdrv_getlength = raw_getlength,
|
||||||
.has_variable_length = true,
|
|
||||||
|
|
||||||
.bdrv_get_allocated_file_size
|
.bdrv_get_allocated_file_size
|
||||||
= raw_get_allocated_file_size,
|
= raw_get_allocated_file_size,
|
||||||
};
|
};
|
||||||
|
|||||||
155
block/raw.c
Normal file
155
block/raw.c
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "block/block_int.h"
|
||||||
|
#include "qemu/module.h"
|
||||||
|
|
||||||
|
static int raw_open(BlockDriverState *bs, int flags)
|
||||||
|
{
|
||||||
|
bs->sg = bs->file->sg;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We have nothing to do for raw reopen, stubs just return
|
||||||
|
* success */
|
||||||
|
static int raw_reopen_prepare(BDRVReopenState *state,
|
||||||
|
BlockReopenQueue *queue, Error **errp)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coroutine_fn raw_co_readv(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
|
{
|
||||||
|
BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO);
|
||||||
|
return bdrv_co_readv(bs->file, sector_num, nb_sectors, qiov);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coroutine_fn raw_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
|
{
|
||||||
|
BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO);
|
||||||
|
return bdrv_co_writev(bs->file, sector_num, nb_sectors, qiov);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void raw_close(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coroutine_fn raw_co_is_allocated(BlockDriverState *bs,
|
||||||
|
int64_t sector_num,
|
||||||
|
int nb_sectors, int *pnum)
|
||||||
|
{
|
||||||
|
return bdrv_co_is_allocated(bs->file, sector_num, nb_sectors, pnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t raw_getlength(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return bdrv_getlength(bs->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int raw_truncate(BlockDriverState *bs, int64_t offset)
|
||||||
|
{
|
||||||
|
return bdrv_truncate(bs->file, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int raw_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||||
|
{
|
||||||
|
return 1; /* everything can be opened as raw image */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coroutine_fn raw_co_discard(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, int nb_sectors)
|
||||||
|
{
|
||||||
|
return bdrv_co_discard(bs->file, sector_num, nb_sectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int raw_is_inserted(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return bdrv_is_inserted(bs->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int raw_media_changed(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return bdrv_media_changed(bs->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void raw_eject(BlockDriverState *bs, bool eject_flag)
|
||||||
|
{
|
||||||
|
bdrv_eject(bs->file, eject_flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void raw_lock_medium(BlockDriverState *bs, bool locked)
|
||||||
|
{
|
||||||
|
bdrv_lock_medium(bs->file, locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int raw_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
|
||||||
|
{
|
||||||
|
return bdrv_ioctl(bs->file, req, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriverAIOCB *raw_aio_ioctl(BlockDriverState *bs,
|
||||||
|
unsigned long int req, void *buf,
|
||||||
|
BlockDriverCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
return bdrv_aio_ioctl(bs->file, req, buf, cb, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int raw_create(const char *filename, QEMUOptionParameter *options)
|
||||||
|
{
|
||||||
|
return bdrv_create_file(filename, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QEMUOptionParameter raw_create_options[] = {
|
||||||
|
{
|
||||||
|
.name = BLOCK_OPT_SIZE,
|
||||||
|
.type = OPT_SIZE,
|
||||||
|
.help = "Virtual disk size"
|
||||||
|
},
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static int raw_has_zero_init(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return bdrv_has_zero_init(bs->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriver bdrv_raw = {
|
||||||
|
.format_name = "raw",
|
||||||
|
|
||||||
|
/* It's really 0, but we need to make g_malloc() happy */
|
||||||
|
.instance_size = 1,
|
||||||
|
|
||||||
|
.bdrv_open = raw_open,
|
||||||
|
.bdrv_close = raw_close,
|
||||||
|
|
||||||
|
.bdrv_reopen_prepare = raw_reopen_prepare,
|
||||||
|
|
||||||
|
.bdrv_co_readv = raw_co_readv,
|
||||||
|
.bdrv_co_writev = raw_co_writev,
|
||||||
|
.bdrv_co_is_allocated = raw_co_is_allocated,
|
||||||
|
.bdrv_co_discard = raw_co_discard,
|
||||||
|
|
||||||
|
.bdrv_probe = raw_probe,
|
||||||
|
.bdrv_getlength = raw_getlength,
|
||||||
|
.bdrv_truncate = raw_truncate,
|
||||||
|
|
||||||
|
.bdrv_is_inserted = raw_is_inserted,
|
||||||
|
.bdrv_media_changed = raw_media_changed,
|
||||||
|
.bdrv_eject = raw_eject,
|
||||||
|
.bdrv_lock_medium = raw_lock_medium,
|
||||||
|
|
||||||
|
.bdrv_ioctl = raw_ioctl,
|
||||||
|
.bdrv_aio_ioctl = raw_aio_ioctl,
|
||||||
|
|
||||||
|
.bdrv_create = raw_create,
|
||||||
|
.create_options = raw_create_options,
|
||||||
|
.bdrv_has_zero_init = raw_has_zero_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void bdrv_raw_init(void)
|
||||||
|
{
|
||||||
|
bdrv_register(&bdrv_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_init(bdrv_raw_init);
|
||||||
206
block/raw_bsd.c
206
block/raw_bsd.c
@@ -1,206 +0,0 @@
|
|||||||
/* BlockDriver implementation for "raw"
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010, 2013, Red Hat, Inc.
|
|
||||||
* Copyright (C) 2010, Blue Swirl <blauwirbel@gmail.com>
|
|
||||||
* Copyright (C) 2009, Anthony Liguori <aliguori@us.ibm.com>
|
|
||||||
*
|
|
||||||
* Author:
|
|
||||||
* Laszlo Ersek <lersek@redhat.com>
|
|
||||||
*
|
|
||||||
* 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 "block/block_int.h"
|
|
||||||
#include "qemu/option.h"
|
|
||||||
|
|
||||||
static QEMUOptionParameter raw_create_options[] = {
|
|
||||||
{
|
|
||||||
.name = BLOCK_OPT_SIZE,
|
|
||||||
.type = OPT_SIZE,
|
|
||||||
.help = "Virtual disk size"
|
|
||||||
},
|
|
||||||
{ 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
static int raw_reopen_prepare(BDRVReopenState *reopen_state,
|
|
||||||
BlockReopenQueue *queue, Error **errp)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn raw_co_readv(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO);
|
|
||||||
return bdrv_co_readv(bs->file, sector_num, nb_sectors, qiov);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn raw_co_writev(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO);
|
|
||||||
return bdrv_co_writev(bs->file, sector_num, nb_sectors, qiov);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
|
|
||||||
int64_t sector_num,
|
|
||||||
int nb_sectors, int *pnum)
|
|
||||||
{
|
|
||||||
*pnum = nb_sectors;
|
|
||||||
return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID | BDRV_BLOCK_DATA |
|
|
||||||
(sector_num << BDRV_SECTOR_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn raw_co_write_zeroes(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors,
|
|
||||||
BdrvRequestFlags flags)
|
|
||||||
{
|
|
||||||
return bdrv_co_write_zeroes(bs->file, sector_num, nb_sectors, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn raw_co_discard(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors)
|
|
||||||
{
|
|
||||||
return bdrv_co_discard(bs->file, sector_num, nb_sectors);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t raw_getlength(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
return bdrv_getlength(bs->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
|
||||||
{
|
|
||||||
return bdrv_get_info(bs->file, bdi);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_refresh_limits(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
bs->bl = bs->file->bl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_truncate(BlockDriverState *bs, int64_t offset)
|
|
||||||
{
|
|
||||||
return bdrv_truncate(bs->file, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_is_inserted(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
return bdrv_is_inserted(bs->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_media_changed(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
return bdrv_media_changed(bs->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void raw_eject(BlockDriverState *bs, bool eject_flag)
|
|
||||||
{
|
|
||||||
bdrv_eject(bs->file, eject_flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void raw_lock_medium(BlockDriverState *bs, bool locked)
|
|
||||||
{
|
|
||||||
bdrv_lock_medium(bs->file, locked);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
|
|
||||||
{
|
|
||||||
return bdrv_ioctl(bs->file, req, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriverAIOCB *raw_aio_ioctl(BlockDriverState *bs,
|
|
||||||
unsigned long int req, void *buf,
|
|
||||||
BlockDriverCompletionFunc *cb,
|
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
return bdrv_aio_ioctl(bs->file, req, buf, cb, opaque);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_has_zero_init(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
return bdrv_has_zero_init(bs->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_create(const char *filename, QEMUOptionParameter *options,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = bdrv_create_file(filename, options, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
bs->sg = bs->file->sg;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void raw_close(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static int raw_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|
||||||
{
|
|
||||||
/* smallest possible positive score so that raw is used if and only if no
|
|
||||||
* other block driver works
|
|
||||||
*/
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriver bdrv_raw = {
|
|
||||||
.format_name = "raw",
|
|
||||||
.bdrv_probe = &raw_probe,
|
|
||||||
.bdrv_reopen_prepare = &raw_reopen_prepare,
|
|
||||||
.bdrv_open = &raw_open,
|
|
||||||
.bdrv_close = &raw_close,
|
|
||||||
.bdrv_create = &raw_create,
|
|
||||||
.bdrv_co_readv = &raw_co_readv,
|
|
||||||
.bdrv_co_writev = &raw_co_writev,
|
|
||||||
.bdrv_co_write_zeroes = &raw_co_write_zeroes,
|
|
||||||
.bdrv_co_discard = &raw_co_discard,
|
|
||||||
.bdrv_co_get_block_status = &raw_co_get_block_status,
|
|
||||||
.bdrv_truncate = &raw_truncate,
|
|
||||||
.bdrv_getlength = &raw_getlength,
|
|
||||||
.has_variable_length = true,
|
|
||||||
.bdrv_get_info = &raw_get_info,
|
|
||||||
.bdrv_refresh_limits = &raw_refresh_limits,
|
|
||||||
.bdrv_is_inserted = &raw_is_inserted,
|
|
||||||
.bdrv_media_changed = &raw_media_changed,
|
|
||||||
.bdrv_eject = &raw_eject,
|
|
||||||
.bdrv_lock_medium = &raw_lock_medium,
|
|
||||||
.bdrv_ioctl = &raw_ioctl,
|
|
||||||
.bdrv_aio_ioctl = &raw_aio_ioctl,
|
|
||||||
.create_options = &raw_create_options[0],
|
|
||||||
.bdrv_has_zero_init = &raw_has_zero_init
|
|
||||||
};
|
|
||||||
|
|
||||||
static void bdrv_raw_init(void)
|
|
||||||
{
|
|
||||||
bdrv_register(&bdrv_raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
block_init(bdrv_raw_init);
|
|
||||||
210
block/rbd.c
210
block/rbd.c
@@ -95,13 +95,19 @@ typedef struct RADOSCB {
|
|||||||
#define RBD_FD_WRITE 1
|
#define RBD_FD_WRITE 1
|
||||||
|
|
||||||
typedef struct BDRVRBDState {
|
typedef struct BDRVRBDState {
|
||||||
|
int fds[2];
|
||||||
rados_t cluster;
|
rados_t cluster;
|
||||||
rados_ioctx_t io_ctx;
|
rados_ioctx_t io_ctx;
|
||||||
rbd_image_t image;
|
rbd_image_t image;
|
||||||
char name[RBD_MAX_IMAGE_NAME_SIZE];
|
char name[RBD_MAX_IMAGE_NAME_SIZE];
|
||||||
|
int qemu_aio_count;
|
||||||
char *snap;
|
char *snap;
|
||||||
|
int event_reader_pos;
|
||||||
|
RADOSCB *event_rcb;
|
||||||
} BDRVRBDState;
|
} BDRVRBDState;
|
||||||
|
|
||||||
|
static void rbd_aio_bh_cb(void *opaque);
|
||||||
|
|
||||||
static int qemu_rbd_next_tok(char *dst, int dst_len,
|
static int qemu_rbd_next_tok(char *dst, int dst_len,
|
||||||
char *src, char delim,
|
char *src, char delim,
|
||||||
const char *name,
|
const char *name,
|
||||||
@@ -282,8 +288,7 @@ static int qemu_rbd_set_conf(rados_t cluster, const char *conf)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qemu_rbd_create(const char *filename, QEMUOptionParameter *options,
|
static int qemu_rbd_create(const char *filename, QEMUOptionParameter *options)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
int64_t bytes = 0;
|
int64_t bytes = 0;
|
||||||
int64_t objsize;
|
int64_t objsize;
|
||||||
@@ -364,8 +369,9 @@ static int qemu_rbd_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This aio completion is being called from rbd_finish_bh() and runs in qemu
|
* This aio completion is being called from qemu_rbd_aio_event_reader()
|
||||||
* BH context.
|
* and runs in qemu context. It schedules a bh, but just in case the aio
|
||||||
|
* was not cancelled before.
|
||||||
*/
|
*/
|
||||||
static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
||||||
{
|
{
|
||||||
@@ -395,37 +401,47 @@ static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
|||||||
acb->ret = r;
|
acb->ret = r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Note that acb->bh can be NULL in case where the aio was cancelled */
|
||||||
|
acb->bh = qemu_bh_new(rbd_aio_bh_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
g_free(rcb);
|
g_free(rcb);
|
||||||
|
|
||||||
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));
|
|
||||||
acb->status = 0;
|
|
||||||
|
|
||||||
if (!acb->cancelled) {
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Convert to fine grained options */
|
/*
|
||||||
static QemuOptsList runtime_opts = {
|
* aio fd read handler. It runs in the qemu context and calls the
|
||||||
.name = "rbd",
|
* completion handling of completed rados aio operations.
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
*/
|
||||||
.desc = {
|
static void qemu_rbd_aio_event_reader(void *opaque)
|
||||||
{
|
{
|
||||||
.name = "filename",
|
BDRVRBDState *s = opaque;
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Specification of the rbd image",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
ssize_t ret;
|
||||||
Error **errp)
|
|
||||||
|
do {
|
||||||
|
char *p = (char *)&s->event_rcb;
|
||||||
|
|
||||||
|
/* now read the rcb pointer that was sent from a non qemu thread */
|
||||||
|
ret = read(s->fds[RBD_FD_READ], p + s->event_reader_pos,
|
||||||
|
sizeof(s->event_rcb) - s->event_reader_pos);
|
||||||
|
if (ret > 0) {
|
||||||
|
s->event_reader_pos += ret;
|
||||||
|
if (s->event_reader_pos == sizeof(s->event_rcb)) {
|
||||||
|
s->event_reader_pos = 0;
|
||||||
|
qemu_rbd_complete_aio(s->event_rcb);
|
||||||
|
s->qemu_aio_count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (ret < 0 && errno == EINTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qemu_rbd_aio_flush_cb(void *opaque)
|
||||||
|
{
|
||||||
|
BDRVRBDState *s = opaque;
|
||||||
|
|
||||||
|
return (s->qemu_aio_count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qemu_rbd_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
{
|
{
|
||||||
BDRVRBDState *s = bs->opaque;
|
BDRVRBDState *s = bs->opaque;
|
||||||
char pool[RBD_MAX_POOL_NAME_SIZE];
|
char pool[RBD_MAX_POOL_NAME_SIZE];
|
||||||
@@ -433,35 +449,20 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
char conf[RBD_MAX_CONF_SIZE];
|
char conf[RBD_MAX_CONF_SIZE];
|
||||||
char clientname_buf[RBD_MAX_CONF_SIZE];
|
char clientname_buf[RBD_MAX_CONF_SIZE];
|
||||||
char *clientname;
|
char *clientname;
|
||||||
QemuOpts *opts;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
const char *filename;
|
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
qerror_report_err(local_err);
|
|
||||||
error_free(local_err);
|
|
||||||
qemu_opts_del(opts);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = qemu_opt_get(opts, "filename");
|
|
||||||
|
|
||||||
if (qemu_rbd_parsename(filename, pool, sizeof(pool),
|
if (qemu_rbd_parsename(filename, pool, sizeof(pool),
|
||||||
snap_buf, sizeof(snap_buf),
|
snap_buf, sizeof(snap_buf),
|
||||||
s->name, sizeof(s->name),
|
s->name, sizeof(s->name),
|
||||||
conf, sizeof(conf)) < 0) {
|
conf, sizeof(conf)) < 0) {
|
||||||
r = -EINVAL;
|
return -EINVAL;
|
||||||
goto failed_opts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clientname = qemu_rbd_parse_clientname(conf, clientname_buf);
|
clientname = qemu_rbd_parse_clientname(conf, clientname_buf);
|
||||||
r = rados_create(&s->cluster, clientname);
|
r = rados_create(&s->cluster, clientname);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
error_report("error initializing");
|
error_report("error initializing");
|
||||||
goto failed_opts;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->snap = NULL;
|
s->snap = NULL;
|
||||||
@@ -515,16 +516,27 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
|
|
||||||
bs->read_only = (s->snap != NULL);
|
bs->read_only = (s->snap != NULL);
|
||||||
|
|
||||||
qemu_opts_del(opts);
|
s->event_reader_pos = 0;
|
||||||
|
r = qemu_pipe(s->fds);
|
||||||
|
if (r < 0) {
|
||||||
|
error_report("error opening eventfd");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
fcntl(s->fds[0], F_SETFL, O_NONBLOCK);
|
||||||
|
fcntl(s->fds[1], F_SETFL, O_NONBLOCK);
|
||||||
|
qemu_aio_set_fd_handler(s->fds[RBD_FD_READ], qemu_rbd_aio_event_reader,
|
||||||
|
NULL, qemu_rbd_aio_flush_cb, s);
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
rbd_close(s->image);
|
||||||
failed_open:
|
failed_open:
|
||||||
rados_ioctx_destroy(s->io_ctx);
|
rados_ioctx_destroy(s->io_ctx);
|
||||||
failed_shutdown:
|
failed_shutdown:
|
||||||
rados_shutdown(s->cluster);
|
rados_shutdown(s->cluster);
|
||||||
g_free(s->snap);
|
g_free(s->snap);
|
||||||
failed_opts:
|
|
||||||
qemu_opts_del(opts);
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +544,10 @@ static void qemu_rbd_close(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
BDRVRBDState *s = bs->opaque;
|
BDRVRBDState *s = bs->opaque;
|
||||||
|
|
||||||
|
close(s->fds[0]);
|
||||||
|
close(s->fds[1]);
|
||||||
|
qemu_aio_set_fd_handler(s->fds[RBD_FD_READ], NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
rbd_close(s->image);
|
rbd_close(s->image);
|
||||||
rados_ioctx_destroy(s->io_ctx);
|
rados_ioctx_destroy(s->io_ctx);
|
||||||
g_free(s->snap);
|
g_free(s->snap);
|
||||||
@@ -559,11 +575,34 @@ static const AIOCBInfo rbd_aiocb_info = {
|
|||||||
.cancel = qemu_rbd_aio_cancel,
|
.cancel = qemu_rbd_aio_cancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void rbd_finish_bh(void *opaque)
|
static int qemu_rbd_send_pipe(BDRVRBDState *s, RADOSCB *rcb)
|
||||||
{
|
{
|
||||||
RADOSCB *rcb = opaque;
|
int ret = 0;
|
||||||
qemu_bh_delete(rcb->acb->bh);
|
while (1) {
|
||||||
qemu_rbd_complete_aio(rcb);
|
fd_set wfd;
|
||||||
|
int fd = s->fds[RBD_FD_WRITE];
|
||||||
|
|
||||||
|
/* send the op pointer to the qemu thread that is responsible
|
||||||
|
for the aio/op completion. Must do it in a qemu thread context */
|
||||||
|
ret = write(fd, (void *)&rcb, sizeof(rcb));
|
||||||
|
if (ret >= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (errno != EAGAIN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FD_ZERO(&wfd);
|
||||||
|
FD_SET(fd, &wfd);
|
||||||
|
do {
|
||||||
|
ret = select(fd + 1, NULL, &wfd, NULL, NULL);
|
||||||
|
} while (ret < 0 && errno == EINTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -571,18 +610,40 @@ static void rbd_finish_bh(void *opaque)
|
|||||||
*
|
*
|
||||||
* Note: this function is being called from a non qemu thread so
|
* Note: this function is being called from a non qemu thread so
|
||||||
* we need to be careful about what we do here. Generally we only
|
* we need to be careful about what we do here. Generally we only
|
||||||
* schedule a BH, and do the rest of the io completion handling
|
* write to the block notification pipe, and do the rest of the
|
||||||
* from rbd_finish_bh() which runs in a qemu context.
|
* io completion handling from qemu_rbd_aio_event_reader() which
|
||||||
|
* runs in a qemu context.
|
||||||
*/
|
*/
|
||||||
static void rbd_finish_aiocb(rbd_completion_t c, RADOSCB *rcb)
|
static void rbd_finish_aiocb(rbd_completion_t c, RADOSCB *rcb)
|
||||||
{
|
{
|
||||||
RBDAIOCB *acb = rcb->acb;
|
int ret;
|
||||||
|
|
||||||
rcb->ret = rbd_aio_get_return_value(c);
|
rcb->ret = rbd_aio_get_return_value(c);
|
||||||
rbd_aio_release(c);
|
rbd_aio_release(c);
|
||||||
|
ret = qemu_rbd_send_pipe(rcb->s, rcb);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_report("failed writing to acb->s->fds");
|
||||||
|
g_free(rcb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
acb->bh = qemu_bh_new(rbd_finish_bh, rcb);
|
/* Callback when all queued rbd_aio requests are complete */
|
||||||
qemu_bh_schedule(acb->bh);
|
|
||||||
|
static void rbd_aio_bh_cb(void *opaque)
|
||||||
|
{
|
||||||
|
RBDAIOCB *acb = opaque;
|
||||||
|
|
||||||
|
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_bh_delete(acb->bh);
|
||||||
|
acb->bh = NULL;
|
||||||
|
acb->status = 0;
|
||||||
|
|
||||||
|
if (!acb->cancelled) {
|
||||||
|
qemu_aio_release(acb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rbd_aio_discard_wrapper(rbd_image_t image,
|
static int rbd_aio_discard_wrapper(rbd_image_t image,
|
||||||
@@ -648,6 +709,8 @@ static BlockDriverAIOCB *rbd_start_aio(BlockDriverState *bs,
|
|||||||
off = sector_num * BDRV_SECTOR_SIZE;
|
off = sector_num * BDRV_SECTOR_SIZE;
|
||||||
size = nb_sectors * BDRV_SECTOR_SIZE;
|
size = nb_sectors * BDRV_SECTOR_SIZE;
|
||||||
|
|
||||||
|
s->qemu_aio_count++; /* All the RADOSCB */
|
||||||
|
|
||||||
rcb = g_malloc(sizeof(RADOSCB));
|
rcb = g_malloc(sizeof(RADOSCB));
|
||||||
rcb->done = 0;
|
rcb->done = 0;
|
||||||
rcb->acb = acb;
|
rcb->acb = acb;
|
||||||
@@ -684,6 +747,7 @@ static BlockDriverAIOCB *rbd_start_aio(BlockDriverState *bs,
|
|||||||
|
|
||||||
failed:
|
failed:
|
||||||
g_free(rcb);
|
g_free(rcb);
|
||||||
|
s->qemu_aio_count--;
|
||||||
qemu_aio_release(acb);
|
qemu_aio_release(acb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -807,31 +871,12 @@ static int qemu_rbd_snap_create(BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int qemu_rbd_snap_remove(BlockDriverState *bs,
|
static int qemu_rbd_snap_remove(BlockDriverState *bs,
|
||||||
const char *snapshot_id,
|
const char *snapshot_name)
|
||||||
const char *snapshot_name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVRBDState *s = bs->opaque;
|
BDRVRBDState *s = bs->opaque;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
if (!snapshot_name) {
|
|
||||||
error_setg(errp, "rbd need a valid snapshot name");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If snapshot_id is specified, it must be equal to name, see
|
|
||||||
qemu_rbd_snap_list() */
|
|
||||||
if (snapshot_id && strcmp(snapshot_id, snapshot_name)) {
|
|
||||||
error_setg(errp,
|
|
||||||
"rbd do not support snapshot id, it should be NULL or "
|
|
||||||
"equal to snapshot name");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rbd_snap_remove(s->image, snapshot_name);
|
r = rbd_snap_remove(s->image, snapshot_name);
|
||||||
if (r < 0) {
|
|
||||||
error_setg_errno(errp, -r, "Failed to remove the snapshot");
|
|
||||||
}
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,7 +902,7 @@ static int qemu_rbd_snap_list(BlockDriverState *bs,
|
|||||||
do {
|
do {
|
||||||
snaps = g_malloc(sizeof(*snaps) * max_snaps);
|
snaps = g_malloc(sizeof(*snaps) * max_snaps);
|
||||||
snap_count = rbd_snap_list(s->image, snaps, &max_snaps);
|
snap_count = rbd_snap_list(s->image, snaps, &max_snaps);
|
||||||
if (snap_count <= 0) {
|
if (snap_count < 0) {
|
||||||
g_free(snaps);
|
g_free(snaps);
|
||||||
}
|
}
|
||||||
} while (snap_count == -ERANGE);
|
} while (snap_count == -ERANGE);
|
||||||
@@ -881,7 +926,6 @@ static int qemu_rbd_snap_list(BlockDriverState *bs,
|
|||||||
sn_info->vm_clock_nsec = 0;
|
sn_info->vm_clock_nsec = 0;
|
||||||
}
|
}
|
||||||
rbd_snap_list_end(snaps);
|
rbd_snap_list_end(snaps);
|
||||||
g_free(snaps);
|
|
||||||
|
|
||||||
done:
|
done:
|
||||||
*psn_tab = sn_tab;
|
*psn_tab = sn_tab;
|
||||||
@@ -917,11 +961,9 @@ static QEMUOptionParameter qemu_rbd_create_options[] = {
|
|||||||
static BlockDriver bdrv_rbd = {
|
static BlockDriver bdrv_rbd = {
|
||||||
.format_name = "rbd",
|
.format_name = "rbd",
|
||||||
.instance_size = sizeof(BDRVRBDState),
|
.instance_size = sizeof(BDRVRBDState),
|
||||||
.bdrv_needs_filename = true,
|
|
||||||
.bdrv_file_open = qemu_rbd_open,
|
.bdrv_file_open = qemu_rbd_open,
|
||||||
.bdrv_close = qemu_rbd_close,
|
.bdrv_close = qemu_rbd_close,
|
||||||
.bdrv_create = qemu_rbd_create,
|
.bdrv_create = qemu_rbd_create,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
|
||||||
.bdrv_get_info = qemu_rbd_getinfo,
|
.bdrv_get_info = qemu_rbd_getinfo,
|
||||||
.create_options = qemu_rbd_create_options,
|
.create_options = qemu_rbd_create_options,
|
||||||
.bdrv_getlength = qemu_rbd_getlength,
|
.bdrv_getlength = qemu_rbd_getlength,
|
||||||
|
|||||||
1192
block/sheepdog.c
1192
block/sheepdog.c
File diff suppressed because it is too large
Load Diff
353
block/snapshot.c
353
block/snapshot.c
@@ -1,353 +0,0 @@
|
|||||||
/*
|
|
||||||
* Block layer snapshot related functions
|
|
||||||
*
|
|
||||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
|
||||||
*
|
|
||||||
* 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 "block/snapshot.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
|
|
||||||
QemuOptsList internal_snapshot_opts = {
|
|
||||||
.name = "snapshot",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(internal_snapshot_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = SNAPSHOT_OPT_ID,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "snapshot id"
|
|
||||||
},{
|
|
||||||
.name = SNAPSHOT_OPT_NAME,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "snapshot name"
|
|
||||||
},{
|
|
||||||
/* end of list */
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
int bdrv_snapshot_find(BlockDriverState *bs, QEMUSnapshotInfo *sn_info,
|
|
||||||
const char *name)
|
|
||||||
{
|
|
||||||
QEMUSnapshotInfo *sn_tab, *sn;
|
|
||||||
int nb_sns, i, ret;
|
|
||||||
|
|
||||||
ret = -ENOENT;
|
|
||||||
nb_sns = bdrv_snapshot_list(bs, &sn_tab);
|
|
||||||
if (nb_sns < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
for (i = 0; i < nb_sns; i++) {
|
|
||||||
sn = &sn_tab[i];
|
|
||||||
if (!strcmp(sn->id_str, name) || !strcmp(sn->name, name)) {
|
|
||||||
*sn_info = *sn;
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_free(sn_tab);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up an internal snapshot by @id and @name.
|
|
||||||
* @bs: block device to search
|
|
||||||
* @id: unique snapshot ID, or NULL
|
|
||||||
* @name: snapshot name, or NULL
|
|
||||||
* @sn_info: location to store information on the snapshot found
|
|
||||||
* @errp: location to store error, will be set only for exception
|
|
||||||
*
|
|
||||||
* This function will traverse snapshot list in @bs to search the matching
|
|
||||||
* one, @id and @name are the matching condition:
|
|
||||||
* If both @id and @name are specified, find the first one with id @id and
|
|
||||||
* name @name.
|
|
||||||
* If only @id is specified, find the first one with id @id.
|
|
||||||
* If only @name is specified, find the first one with name @name.
|
|
||||||
* if none is specified, abort().
|
|
||||||
*
|
|
||||||
* Returns: true when a snapshot is found and @sn_info will be filled, false
|
|
||||||
* when error or not found. If all operation succeed but no matching one is
|
|
||||||
* found, @errp will NOT be set.
|
|
||||||
*/
|
|
||||||
bool bdrv_snapshot_find_by_id_and_name(BlockDriverState *bs,
|
|
||||||
const char *id,
|
|
||||||
const char *name,
|
|
||||||
QEMUSnapshotInfo *sn_info,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
QEMUSnapshotInfo *sn_tab, *sn;
|
|
||||||
int nb_sns, i;
|
|
||||||
bool ret = false;
|
|
||||||
|
|
||||||
assert(id || name);
|
|
||||||
|
|
||||||
nb_sns = bdrv_snapshot_list(bs, &sn_tab);
|
|
||||||
if (nb_sns < 0) {
|
|
||||||
error_setg_errno(errp, -nb_sns, "Failed to get a snapshot list");
|
|
||||||
return false;
|
|
||||||
} else if (nb_sns == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id && name) {
|
|
||||||
for (i = 0; i < nb_sns; i++) {
|
|
||||||
sn = &sn_tab[i];
|
|
||||||
if (!strcmp(sn->id_str, id) && !strcmp(sn->name, name)) {
|
|
||||||
*sn_info = *sn;
|
|
||||||
ret = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (id) {
|
|
||||||
for (i = 0; i < nb_sns; i++) {
|
|
||||||
sn = &sn_tab[i];
|
|
||||||
if (!strcmp(sn->id_str, id)) {
|
|
||||||
*sn_info = *sn;
|
|
||||||
ret = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (name) {
|
|
||||||
for (i = 0; i < nb_sns; i++) {
|
|
||||||
sn = &sn_tab[i];
|
|
||||||
if (!strcmp(sn->name, name)) {
|
|
||||||
*sn_info = *sn;
|
|
||||||
ret = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(sn_tab);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bdrv_can_snapshot(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockDriver *drv = bs->drv;
|
|
||||||
if (!drv || !bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!drv->bdrv_snapshot_create) {
|
|
||||||
if (bs->file != NULL) {
|
|
||||||
return bdrv_can_snapshot(bs->file);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bdrv_snapshot_create(BlockDriverState *bs,
|
|
||||||
QEMUSnapshotInfo *sn_info)
|
|
||||||
{
|
|
||||||
BlockDriver *drv = bs->drv;
|
|
||||||
if (!drv) {
|
|
||||||
return -ENOMEDIUM;
|
|
||||||
}
|
|
||||||
if (drv->bdrv_snapshot_create) {
|
|
||||||
return drv->bdrv_snapshot_create(bs, sn_info);
|
|
||||||
}
|
|
||||||
if (bs->file) {
|
|
||||||
return bdrv_snapshot_create(bs->file, sn_info);
|
|
||||||
}
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bdrv_snapshot_goto(BlockDriverState *bs,
|
|
||||||
const char *snapshot_id)
|
|
||||||
{
|
|
||||||
BlockDriver *drv = bs->drv;
|
|
||||||
int ret, open_ret;
|
|
||||||
|
|
||||||
if (!drv) {
|
|
||||||
return -ENOMEDIUM;
|
|
||||||
}
|
|
||||||
if (drv->bdrv_snapshot_goto) {
|
|
||||||
return drv->bdrv_snapshot_goto(bs, snapshot_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bs->file) {
|
|
||||||
drv->bdrv_close(bs);
|
|
||||||
ret = bdrv_snapshot_goto(bs->file, snapshot_id);
|
|
||||||
open_ret = drv->bdrv_open(bs, NULL, bs->open_flags, NULL);
|
|
||||||
if (open_ret < 0) {
|
|
||||||
bdrv_unref(bs->file);
|
|
||||||
bs->drv = NULL;
|
|
||||||
return open_ret;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an internal snapshot by @snapshot_id and @name.
|
|
||||||
* @bs: block device used in the operation
|
|
||||||
* @snapshot_id: unique snapshot ID, or NULL
|
|
||||||
* @name: snapshot name, or NULL
|
|
||||||
* @errp: location to store error
|
|
||||||
*
|
|
||||||
* If both @snapshot_id and @name are specified, delete the first one with
|
|
||||||
* id @snapshot_id and name @name.
|
|
||||||
* If only @snapshot_id is specified, delete the first one with id
|
|
||||||
* @snapshot_id.
|
|
||||||
* If only @name is specified, delete the first one with name @name.
|
|
||||||
* if none is specified, return -EINVAL.
|
|
||||||
*
|
|
||||||
* Returns: 0 on success, -errno on failure. If @bs is not inserted, return
|
|
||||||
* -ENOMEDIUM. If @snapshot_id and @name are both NULL, return -EINVAL. If @bs
|
|
||||||
* does not support internal snapshot deletion, return -ENOTSUP. If @bs does
|
|
||||||
* not support parameter @snapshot_id or @name, or one of them is not correctly
|
|
||||||
* specified, return -EINVAL. If @bs can't find one matching @id and @name,
|
|
||||||
* return -ENOENT. If @errp != NULL, it will always be filled with error
|
|
||||||
* message on failure.
|
|
||||||
*/
|
|
||||||
int bdrv_snapshot_delete(BlockDriverState *bs,
|
|
||||||
const char *snapshot_id,
|
|
||||||
const char *name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BlockDriver *drv = bs->drv;
|
|
||||||
if (!drv) {
|
|
||||||
error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, bdrv_get_device_name(bs));
|
|
||||||
return -ENOMEDIUM;
|
|
||||||
}
|
|
||||||
if (!snapshot_id && !name) {
|
|
||||||
error_setg(errp, "snapshot_id and name are both NULL");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
if (drv->bdrv_snapshot_delete) {
|
|
||||||
return drv->bdrv_snapshot_delete(bs, snapshot_id, name, errp);
|
|
||||||
}
|
|
||||||
if (bs->file) {
|
|
||||||
return bdrv_snapshot_delete(bs->file, snapshot_id, name, errp);
|
|
||||||
}
|
|
||||||
error_set(errp, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
|
||||||
drv->format_name, bdrv_get_device_name(bs),
|
|
||||||
"internal snapshot deletion");
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_snapshot_delete_by_id_or_name(BlockDriverState *bs,
|
|
||||||
const char *id_or_name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
ret = bdrv_snapshot_delete(bs, id_or_name, NULL, &local_err);
|
|
||||||
if (ret == -ENOENT || ret == -EINVAL) {
|
|
||||||
error_free(local_err);
|
|
||||||
local_err = NULL;
|
|
||||||
ret = bdrv_snapshot_delete(bs, NULL, id_or_name, &local_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int bdrv_snapshot_list(BlockDriverState *bs,
|
|
||||||
QEMUSnapshotInfo **psn_info)
|
|
||||||
{
|
|
||||||
BlockDriver *drv = bs->drv;
|
|
||||||
if (!drv) {
|
|
||||||
return -ENOMEDIUM;
|
|
||||||
}
|
|
||||||
if (drv->bdrv_snapshot_list) {
|
|
||||||
return drv->bdrv_snapshot_list(bs, psn_info);
|
|
||||||
}
|
|
||||||
if (bs->file) {
|
|
||||||
return bdrv_snapshot_list(bs->file, psn_info);
|
|
||||||
}
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporarily load an internal snapshot by @snapshot_id and @name.
|
|
||||||
* @bs: block device used in the operation
|
|
||||||
* @snapshot_id: unique snapshot ID, or NULL
|
|
||||||
* @name: snapshot name, or NULL
|
|
||||||
* @errp: location to store error
|
|
||||||
*
|
|
||||||
* If both @snapshot_id and @name are specified, load the first one with
|
|
||||||
* id @snapshot_id and name @name.
|
|
||||||
* If only @snapshot_id is specified, load the first one with id
|
|
||||||
* @snapshot_id.
|
|
||||||
* If only @name is specified, load the first one with name @name.
|
|
||||||
* if none is specified, return -EINVAL.
|
|
||||||
*
|
|
||||||
* Returns: 0 on success, -errno on fail. If @bs is not inserted, return
|
|
||||||
* -ENOMEDIUM. If @bs is not readonly, return -EINVAL. If @bs did not support
|
|
||||||
* internal snapshot, return -ENOTSUP. If qemu can't find a matching @id and
|
|
||||||
* @name, return -ENOENT. If @errp != NULL, it will always be filled on
|
|
||||||
* failure.
|
|
||||||
*/
|
|
||||||
int bdrv_snapshot_load_tmp(BlockDriverState *bs,
|
|
||||||
const char *snapshot_id,
|
|
||||||
const char *name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BlockDriver *drv = bs->drv;
|
|
||||||
|
|
||||||
if (!drv) {
|
|
||||||
error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, bdrv_get_device_name(bs));
|
|
||||||
return -ENOMEDIUM;
|
|
||||||
}
|
|
||||||
if (!snapshot_id && !name) {
|
|
||||||
error_setg(errp, "snapshot_id and name are both NULL");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
if (!bs->read_only) {
|
|
||||||
error_setg(errp, "Device is not readonly");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
if (drv->bdrv_snapshot_load_tmp) {
|
|
||||||
return drv->bdrv_snapshot_load_tmp(bs, snapshot_id, name, errp);
|
|
||||||
}
|
|
||||||
error_set(errp, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
|
||||||
drv->format_name, bdrv_get_device_name(bs),
|
|
||||||
"temporarily load internal snapshot");
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bdrv_snapshot_load_tmp_by_id_or_name(BlockDriverState *bs,
|
|
||||||
const char *id_or_name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
ret = bdrv_snapshot_load_tmp(bs, id_or_name, NULL, &local_err);
|
|
||||||
if (ret == -ENOENT || ret == -EINVAL) {
|
|
||||||
error_free(local_err);
|
|
||||||
local_err = NULL;
|
|
||||||
ret = bdrv_snapshot_load_tmp(bs, NULL, id_or_name, &local_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
1070
block/ssh.c
1070
block/ssh.c
File diff suppressed because it is too large
Load Diff
@@ -57,11 +57,6 @@ static void close_unused_images(BlockDriverState *top, BlockDriverState *base,
|
|||||||
BlockDriverState *intermediate;
|
BlockDriverState *intermediate;
|
||||||
intermediate = top->backing_hd;
|
intermediate = top->backing_hd;
|
||||||
|
|
||||||
/* Must assign before bdrv_delete() to prevent traversing dangling pointer
|
|
||||||
* while we delete backing image instances.
|
|
||||||
*/
|
|
||||||
top->backing_hd = base;
|
|
||||||
|
|
||||||
while (intermediate) {
|
while (intermediate) {
|
||||||
BlockDriverState *unused;
|
BlockDriverState *unused;
|
||||||
|
|
||||||
@@ -73,10 +68,9 @@ static void close_unused_images(BlockDriverState *top, BlockDriverState *base,
|
|||||||
unused = intermediate;
|
unused = intermediate;
|
||||||
intermediate = intermediate->backing_hd;
|
intermediate = intermediate->backing_hd;
|
||||||
unused->backing_hd = NULL;
|
unused->backing_hd = NULL;
|
||||||
bdrv_unref(unused);
|
bdrv_delete(unused);
|
||||||
}
|
}
|
||||||
|
top->backing_hd = base;
|
||||||
bdrv_refresh_limits(top);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void coroutine_fn stream_run(void *opaque)
|
static void coroutine_fn stream_run(void *opaque)
|
||||||
@@ -90,11 +84,6 @@ static void coroutine_fn stream_run(void *opaque)
|
|||||||
int n = 0;
|
int n = 0;
|
||||||
void *buf;
|
void *buf;
|
||||||
|
|
||||||
if (!bs->backing_hd) {
|
|
||||||
block_job_completed(&s->common, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->common.len = bdrv_getlength(bs);
|
s->common.len = bdrv_getlength(bs);
|
||||||
if (s->common.len < 0) {
|
if (s->common.len < 0) {
|
||||||
block_job_completed(&s->common, s->common.len);
|
block_job_completed(&s->common, s->common.len);
|
||||||
@@ -121,21 +110,20 @@ wait:
|
|||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* with no pending I/O here so that bdrv_drain_all() returns.
|
* with no pending I/O here so that bdrv_drain_all() returns.
|
||||||
*/
|
*/
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&s->common, rt_clock, delay_ns);
|
||||||
if (block_job_is_cancelled(&s->common)) {
|
if (block_job_is_cancelled(&s->common)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy = false;
|
ret = bdrv_co_is_allocated(bs, sector_num,
|
||||||
|
|
||||||
ret = bdrv_is_allocated(bs, sector_num,
|
|
||||||
STREAM_BUFFER_SIZE / BDRV_SECTOR_SIZE, &n);
|
STREAM_BUFFER_SIZE / BDRV_SECTOR_SIZE, &n);
|
||||||
if (ret == 1) {
|
if (ret == 1) {
|
||||||
/* Allocated in the top, no need to copy. */
|
/* Allocated in the top, no need to copy. */
|
||||||
} else if (ret >= 0) {
|
copy = false;
|
||||||
|
} else {
|
||||||
/* Copy if allocated in the intermediate images. Limit to the
|
/* Copy if allocated in the intermediate images. Limit to the
|
||||||
* known-unallocated area [sector_num, sector_num+n). */
|
* known-unallocated area [sector_num, sector_num+n). */
|
||||||
ret = bdrv_is_allocated_above(bs->backing_hd, base,
|
ret = bdrv_co_is_allocated_above(bs->backing_hd, base,
|
||||||
sector_num, n, &n);
|
sector_num, n, &n);
|
||||||
|
|
||||||
/* Finish early if end of backing file has been reached */
|
/* Finish early if end of backing file has been reached */
|
||||||
@@ -146,7 +134,7 @@ wait:
|
|||||||
copy = (ret == 1);
|
copy = (ret == 1);
|
||||||
}
|
}
|
||||||
trace_stream_one_iteration(s, sector_num, n, ret);
|
trace_stream_one_iteration(s, sector_num, n, ret);
|
||||||
if (copy) {
|
if (ret >= 0 && copy) {
|
||||||
if (s->common.speed) {
|
if (s->common.speed) {
|
||||||
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
||||||
if (delay_ns > 0) {
|
if (delay_ns > 0) {
|
||||||
@@ -210,9 +198,9 @@ static void stream_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|||||||
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const BlockJobDriver stream_job_driver = {
|
static BlockJobType stream_job_type = {
|
||||||
.instance_size = sizeof(StreamBlockJob),
|
.instance_size = sizeof(StreamBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_STREAM,
|
.job_type = "stream",
|
||||||
.set_speed = stream_set_speed,
|
.set_speed = stream_set_speed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,7 +219,7 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s = block_job_create(&stream_job_driver, bs, speed, cb, opaque, errp);
|
s = block_job_create(&stream_job_type, bs, speed, cb, opaque, errp);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
365
block/tar.c
Normal file
365
block/tar.c
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
/*
|
||||||
|
* Tar block driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 Alexander Graf <agraf@suse.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-common.h"
|
||||||
|
#include "block/block_int.h"
|
||||||
|
|
||||||
|
// #define DEBUG
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define dprintf(fmt, ...) do { printf("tar: " fmt, ## __VA_ARGS__); } while (0)
|
||||||
|
#else
|
||||||
|
#define dprintf(fmt, ...) do { } while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SECTOR_SIZE 512
|
||||||
|
|
||||||
|
#define POSIX_TAR_MAGIC "ustar"
|
||||||
|
#define OFFS_LENGTH 0x7c
|
||||||
|
#define OFFS_TYPE 0x9c
|
||||||
|
#define OFFS_MAGIC 0x101
|
||||||
|
|
||||||
|
#define OFFS_S_SP 0x182
|
||||||
|
#define OFFS_S_EXT 0x1e2
|
||||||
|
#define OFFS_S_LENGTH 0x1e3
|
||||||
|
#define OFFS_SX_EXT 0x1f8
|
||||||
|
|
||||||
|
typedef struct SparseCache {
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
} SparseCache;
|
||||||
|
|
||||||
|
typedef struct BDRVTarState {
|
||||||
|
BlockDriverState *hd;
|
||||||
|
size_t file_sec;
|
||||||
|
uint64_t file_len;
|
||||||
|
SparseCache *sparse;
|
||||||
|
int sparse_num;
|
||||||
|
uint64_t last_end;
|
||||||
|
char longfile[2048];
|
||||||
|
} BDRVTarState;
|
||||||
|
|
||||||
|
static int tar_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||||
|
{
|
||||||
|
if (buf_size < OFFS_MAGIC + 5)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* we only support newer tar */
|
||||||
|
if (!strncmp((char*)buf + OFFS_MAGIC, POSIX_TAR_MAGIC, 5))
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int str_ends(char *str, const char *end)
|
||||||
|
{
|
||||||
|
int end_len = strlen(end);
|
||||||
|
int str_len = strlen(str);
|
||||||
|
|
||||||
|
if (str_len < end_len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return !strncmp(str + str_len - end_len, end, end_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_target_file(BlockDriverState *bs, char *filename,
|
||||||
|
char *header)
|
||||||
|
{
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
|
if (str_ends(filename, ".raw"))
|
||||||
|
retval = 1;
|
||||||
|
|
||||||
|
if (str_ends(filename, ".qcow"))
|
||||||
|
retval = 1;
|
||||||
|
|
||||||
|
if (str_ends(filename, ".qcow2"))
|
||||||
|
retval = 1;
|
||||||
|
|
||||||
|
if (str_ends(filename, ".vmdk"))
|
||||||
|
retval = 1;
|
||||||
|
|
||||||
|
if (retval &&
|
||||||
|
(header[OFFS_TYPE] != '0') &&
|
||||||
|
(header[OFFS_TYPE] != 'S')) {
|
||||||
|
retval = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dprintf("does filename %s match? %s\n", filename, retval ? "yes" : "no");
|
||||||
|
|
||||||
|
/* make sure we're not using this name again */
|
||||||
|
filename[0] = '\0';
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t tar2u64(char *ptr)
|
||||||
|
{
|
||||||
|
uint64_t retval;
|
||||||
|
char oldend = ptr[12];
|
||||||
|
|
||||||
|
ptr[12] = '\0';
|
||||||
|
if (*ptr & 0x80) {
|
||||||
|
/* XXX we only support files up to 64 bit length */
|
||||||
|
retval = be64_to_cpu(*(uint64_t *)(ptr+4));
|
||||||
|
dprintf("Convert %lx -> %#lx\n", *(uint64_t*)(ptr+4), retval);
|
||||||
|
} else {
|
||||||
|
retval = strtol(ptr, NULL, 8);
|
||||||
|
dprintf("Convert %s -> %#lx\n", ptr, retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr[12] = oldend;
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tar_sparse(BDRVTarState *s, uint64_t offs, uint64_t len)
|
||||||
|
{
|
||||||
|
SparseCache *sparse;
|
||||||
|
|
||||||
|
if (!len)
|
||||||
|
return;
|
||||||
|
if (!(offs - s->last_end)) {
|
||||||
|
s->last_end += len;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s->last_end > offs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dprintf("Last chunk until %lx new chunk at %lx\n", s->last_end, offs);
|
||||||
|
|
||||||
|
s->sparse = g_realloc(s->sparse, (s->sparse_num + 1) * sizeof(SparseCache));
|
||||||
|
sparse = &s->sparse[s->sparse_num];
|
||||||
|
sparse->start = s->last_end;
|
||||||
|
sparse->end = offs;
|
||||||
|
s->last_end = offs + len;
|
||||||
|
s->sparse_num++;
|
||||||
|
dprintf("Sparse at %lx end=%lx\n", sparse->start,
|
||||||
|
sparse->end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tar_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
|
{
|
||||||
|
BDRVTarState *s = bs->opaque;
|
||||||
|
char header[SECTOR_SIZE];
|
||||||
|
char *real_file = header;
|
||||||
|
char *magic;
|
||||||
|
const char *fname = filename;
|
||||||
|
size_t header_offs = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!strncmp(filename, "tar://", 6))
|
||||||
|
fname += 6;
|
||||||
|
else if (!strncmp(filename, "tar:", 4))
|
||||||
|
fname += 4;
|
||||||
|
|
||||||
|
ret = bdrv_file_open(&s->hd, fname, flags);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Search the file for an image */
|
||||||
|
|
||||||
|
do {
|
||||||
|
/* tar header */
|
||||||
|
if (bdrv_pread(s->hd, header_offs, header, SECTOR_SIZE) != SECTOR_SIZE)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if ((header_offs > 1) && !header[0]) {
|
||||||
|
fprintf(stderr, "Tar: No image file found in archive\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
magic = &header[OFFS_MAGIC];
|
||||||
|
if (strncmp(magic, POSIX_TAR_MAGIC, 5)) {
|
||||||
|
fprintf(stderr, "Tar: Invalid magic: %s\n", magic);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
dprintf("file type: %c\n", header[OFFS_TYPE]);
|
||||||
|
|
||||||
|
/* file length*/
|
||||||
|
s->file_len = (tar2u64(&header[OFFS_LENGTH]) + (SECTOR_SIZE - 1)) &
|
||||||
|
~(SECTOR_SIZE - 1);
|
||||||
|
s->file_sec = (header_offs / SECTOR_SIZE) + 1;
|
||||||
|
|
||||||
|
header_offs += s->file_len + SECTOR_SIZE;
|
||||||
|
|
||||||
|
if (header[OFFS_TYPE] == 'L') {
|
||||||
|
bdrv_pread(s->hd, header_offs - s->file_len, s->longfile,
|
||||||
|
sizeof(s->longfile));
|
||||||
|
s->longfile[sizeof(s->longfile)-1] = '\0';
|
||||||
|
real_file = header;
|
||||||
|
} else if (s->longfile[0]) {
|
||||||
|
real_file = s->longfile;
|
||||||
|
} else {
|
||||||
|
real_file = header;
|
||||||
|
}
|
||||||
|
} while(!is_target_file(bs, real_file, header));
|
||||||
|
|
||||||
|
/* We found an image! */
|
||||||
|
|
||||||
|
if (header[OFFS_TYPE] == 'S') {
|
||||||
|
uint8_t isextended;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = OFFS_S_SP; i < (OFFS_S_SP + (4 * 24)); i += 24)
|
||||||
|
tar_sparse(s, tar2u64(&header[i]), tar2u64(&header[i+12]));
|
||||||
|
|
||||||
|
s->file_len = tar2u64(&header[OFFS_S_LENGTH]);
|
||||||
|
isextended = header[OFFS_S_EXT];
|
||||||
|
|
||||||
|
while (isextended) {
|
||||||
|
if (bdrv_pread(s->hd, s->file_sec * SECTOR_SIZE, header,
|
||||||
|
SECTOR_SIZE) != SECTOR_SIZE)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
for (i = 0; i < (21 * 24); i += 24)
|
||||||
|
tar_sparse(s, tar2u64(&header[i]), tar2u64(&header[i+12]));
|
||||||
|
isextended = header[OFFS_SX_EXT];
|
||||||
|
s->file_sec++;
|
||||||
|
}
|
||||||
|
tar_sparse(s, s->file_len, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
fprintf(stderr, "Tar: Error opening file\n");
|
||||||
|
bdrv_delete(s->hd);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct TarAIOCB {
|
||||||
|
BlockDriverAIOCB common;
|
||||||
|
QEMUBH *bh;
|
||||||
|
} TarAIOCB;
|
||||||
|
|
||||||
|
/* This callback gets invoked when we have pure sparseness */
|
||||||
|
static void tar_sparse_cb(void *opaque)
|
||||||
|
{
|
||||||
|
TarAIOCB *acb = (TarAIOCB *)opaque;
|
||||||
|
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
|
qemu_bh_delete(acb->bh);
|
||||||
|
qemu_aio_release(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tar_aio_cancel(BlockDriverAIOCB *blockacb)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIOCBInfo tar_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(TarAIOCB),
|
||||||
|
.cancel = tar_aio_cancel,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* This is where we get a request from a caller to read something */
|
||||||
|
static BlockDriverAIOCB *tar_aio_readv(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockDriverCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
BDRVTarState *s = bs->opaque;
|
||||||
|
SparseCache *sparse;
|
||||||
|
int64_t sec_file = sector_num + s->file_sec;
|
||||||
|
int64_t start = sector_num * SECTOR_SIZE;
|
||||||
|
int64_t end = start + (nb_sectors * SECTOR_SIZE);
|
||||||
|
int i;
|
||||||
|
TarAIOCB *acb;
|
||||||
|
|
||||||
|
for (i = 0; i < s->sparse_num; i++) {
|
||||||
|
sparse = &s->sparse[i];
|
||||||
|
if (sparse->start > end) {
|
||||||
|
/* We expect the cache to be start increasing */
|
||||||
|
break;
|
||||||
|
} else if ((sparse->start < start) && (sparse->end <= start)) {
|
||||||
|
/* sparse before our offset */
|
||||||
|
sec_file -= (sparse->end - sparse->start) / SECTOR_SIZE;
|
||||||
|
} else if ((sparse->start <= start) && (sparse->end >= end)) {
|
||||||
|
/* all our sectors are sparse */
|
||||||
|
char *buf = g_malloc0(nb_sectors * SECTOR_SIZE);
|
||||||
|
|
||||||
|
acb = qemu_aio_get(&tar_aiocb_info, bs, cb, opaque);
|
||||||
|
qemu_iovec_from_buf(qiov, 0, buf, nb_sectors * SECTOR_SIZE);
|
||||||
|
g_free(buf);
|
||||||
|
acb->bh = qemu_bh_new(tar_sparse_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
|
|
||||||
|
return &acb->common;
|
||||||
|
} else if (((sparse->start >= start) && (sparse->start < end)) ||
|
||||||
|
((sparse->end >= start) && (sparse->end < end))) {
|
||||||
|
/* we're semi-sparse (worst case) */
|
||||||
|
/* let's go synchronous and read all sectors individually */
|
||||||
|
char *buf = g_malloc(nb_sectors * SECTOR_SIZE);
|
||||||
|
uint64_t offs;
|
||||||
|
|
||||||
|
for (offs = 0; offs < (nb_sectors * SECTOR_SIZE);
|
||||||
|
offs += SECTOR_SIZE) {
|
||||||
|
bdrv_pread(bs, (sector_num * SECTOR_SIZE) + offs,
|
||||||
|
buf + offs, SECTOR_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
qemu_iovec_from_buf(qiov, 0, buf, nb_sectors * SECTOR_SIZE);
|
||||||
|
acb = qemu_aio_get(&tar_aiocb_info, bs, cb, opaque);
|
||||||
|
acb->bh = qemu_bh_new(tar_sparse_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
|
|
||||||
|
return &acb->common;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bdrv_aio_readv(s->hd, sec_file, qiov, nb_sectors,
|
||||||
|
cb, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tar_close(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
dprintf("Close\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t tar_getlength(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVTarState *s = bs->opaque;
|
||||||
|
dprintf("getlength -> %ld\n", s->file_len);
|
||||||
|
return s->file_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriver bdrv_tar = {
|
||||||
|
.format_name = "tar",
|
||||||
|
.protocol_name = "tar",
|
||||||
|
|
||||||
|
.instance_size = sizeof(BDRVTarState),
|
||||||
|
.bdrv_file_open = tar_open,
|
||||||
|
.bdrv_close = tar_close,
|
||||||
|
.bdrv_getlength = tar_getlength,
|
||||||
|
.bdrv_probe = tar_probe,
|
||||||
|
|
||||||
|
.bdrv_aio_readv = tar_aio_readv,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void tar_block_init(void)
|
||||||
|
{
|
||||||
|
bdrv_register(&bdrv_tar);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_init(tar_block_init);
|
||||||
66
block/vdi.c
66
block/vdi.c
@@ -31,7 +31,7 @@
|
|||||||
* Allocation of blocks could be optimized (less writes to block map and
|
* Allocation of blocks could be optimized (less writes to block map and
|
||||||
* header).
|
* header).
|
||||||
*
|
*
|
||||||
* Read and write of adjacent blocks could be done in one operation
|
* Read and write of adjacents blocks could be done in one operation
|
||||||
* (current code uses one operation per block (1 MiB).
|
* (current code uses one operation per block (1 MiB).
|
||||||
*
|
*
|
||||||
* The code is not thread safe (missing locks for changes in header and
|
* The code is not thread safe (missing locks for changes in header and
|
||||||
@@ -170,7 +170,7 @@ typedef struct {
|
|||||||
uuid_t uuid_link;
|
uuid_t uuid_link;
|
||||||
uuid_t uuid_parent;
|
uuid_t uuid_parent;
|
||||||
uint64_t unused2[7];
|
uint64_t unused2[7];
|
||||||
} QEMU_PACKED VdiHeader;
|
} VdiHeader;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* The block map entries are little endian (even in memory). */
|
/* The block map entries are little endian (even in memory). */
|
||||||
@@ -336,7 +336,6 @@ static int vdi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
|||||||
logout("\n");
|
logout("\n");
|
||||||
bdi->cluster_size = s->block_size;
|
bdi->cluster_size = s->block_size;
|
||||||
bdi->vm_state_offset = 0;
|
bdi->vm_state_offset = 0;
|
||||||
bdi->unallocated_blocks_are_zero = true;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,8 +369,7 @@ static int vdi_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vdi_open(BlockDriverState *bs, QDict *options, int flags,
|
static int vdi_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVVdiState *s = bs->opaque;
|
BDRVVdiState *s = bs->opaque;
|
||||||
VdiHeader header;
|
VdiHeader header;
|
||||||
@@ -391,8 +389,8 @@ static int vdi_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (header.disk_size > VDI_DISK_SIZE_MAX) {
|
if (header.disk_size > VDI_DISK_SIZE_MAX) {
|
||||||
error_setg(errp, "Unsupported VDI image size (size is 0x%" PRIx64
|
logout("Unsupported VDI image size (size is 0x%" PRIx64
|
||||||
", max supported is 0x%" PRIx64 ")",
|
", max supported is 0x%" PRIx64 ")\n",
|
||||||
header.disk_size, VDI_DISK_SIZE_MAX);
|
header.disk_size, VDI_DISK_SIZE_MAX);
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
@@ -408,55 +406,48 @@ static int vdi_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (header.signature != VDI_SIGNATURE) {
|
if (header.signature != VDI_SIGNATURE) {
|
||||||
error_setg(errp, "Image not in VDI format (bad signature %08x)", header.signature);
|
logout("bad vdi signature %08x\n", header.signature);
|
||||||
ret = -EINVAL;
|
ret = -EMEDIUMTYPE;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (header.version != VDI_VERSION_1_1) {
|
} else if (header.version != VDI_VERSION_1_1) {
|
||||||
error_setg(errp, "unsupported VDI image (version %u.%u)",
|
logout("unsupported version %u.%u\n",
|
||||||
header.version >> 16, header.version & 0xffff);
|
header.version >> 16, header.version & 0xffff);
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (header.offset_bmap % SECTOR_SIZE != 0) {
|
} else if (header.offset_bmap % SECTOR_SIZE != 0) {
|
||||||
/* We only support block maps which start on a sector boundary. */
|
/* We only support block maps which start on a sector boundary. */
|
||||||
error_setg(errp, "unsupported VDI image (unaligned block map offset "
|
logout("unsupported block map offset 0x%x B\n", header.offset_bmap);
|
||||||
"0x%x)", header.offset_bmap);
|
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (header.offset_data % SECTOR_SIZE != 0) {
|
} else if (header.offset_data % SECTOR_SIZE != 0) {
|
||||||
/* We only support data blocks which start on a sector boundary. */
|
/* We only support data blocks which start on a sector boundary. */
|
||||||
error_setg(errp, "unsupported VDI image (unaligned data offset 0x%x)",
|
logout("unsupported data offset 0x%x B\n", header.offset_data);
|
||||||
header.offset_data);
|
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (header.sector_size != SECTOR_SIZE) {
|
} else if (header.sector_size != SECTOR_SIZE) {
|
||||||
error_setg(errp, "unsupported VDI image (sector size %u is not %u)",
|
logout("unsupported sector size %u B\n", header.sector_size);
|
||||||
header.sector_size, SECTOR_SIZE);
|
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (header.block_size != DEFAULT_CLUSTER_SIZE) {
|
} else if (header.block_size != DEFAULT_CLUSTER_SIZE) {
|
||||||
error_setg(errp, "unsupported VDI image (block size %u is not %u)",
|
logout("unsupported VDI image (block size %u is not %u)\n",
|
||||||
header.block_size, DEFAULT_CLUSTER_SIZE);
|
header.block_size, DEFAULT_CLUSTER_SIZE);
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (header.disk_size >
|
} else if (header.disk_size >
|
||||||
(uint64_t)header.blocks_in_image * header.block_size) {
|
(uint64_t)header.blocks_in_image * header.block_size) {
|
||||||
error_setg(errp, "unsupported VDI image (disk size %" PRIu64 ", "
|
logout("unsupported disk size %" PRIu64 " B\n", header.disk_size);
|
||||||
"image bitmap has room for %" PRIu64 ")",
|
|
||||||
header.disk_size,
|
|
||||||
(uint64_t)header.blocks_in_image * header.block_size);
|
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (!uuid_is_null(header.uuid_link)) {
|
} else if (!uuid_is_null(header.uuid_link)) {
|
||||||
error_setg(errp, "unsupported VDI image (non-NULL link UUID)");
|
logout("link uuid != 0, unsupported\n");
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (!uuid_is_null(header.uuid_parent)) {
|
} else if (!uuid_is_null(header.uuid_parent)) {
|
||||||
error_setg(errp, "unsupported VDI image (non-NULL parent UUID)");
|
logout("parent uuid != 0, unsupported\n");
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (header.blocks_in_image > VDI_BLOCKS_IN_IMAGE_MAX) {
|
} else if (header.blocks_in_image > VDI_BLOCKS_IN_IMAGE_MAX) {
|
||||||
error_setg(errp, "unsupported VDI image "
|
logout("unsupported VDI image (too many blocks %u, max is %u)\n",
|
||||||
"(too many blocks %u, max is %u)",
|
|
||||||
header.blocks_in_image, VDI_BLOCKS_IN_IMAGE_MAX);
|
header.blocks_in_image, VDI_BLOCKS_IN_IMAGE_MAX);
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
@@ -498,7 +489,7 @@ static int vdi_reopen_prepare(BDRVReopenState *state,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t coroutine_fn vdi_co_get_block_status(BlockDriverState *bs,
|
static int coroutine_fn vdi_co_is_allocated(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors, int *pnum)
|
int64_t sector_num, int nb_sectors, int *pnum)
|
||||||
{
|
{
|
||||||
/* TODO: Check for too large sector_num (in bdrv_is_allocated or here). */
|
/* TODO: Check for too large sector_num (in bdrv_is_allocated or here). */
|
||||||
@@ -507,23 +498,12 @@ static int64_t coroutine_fn vdi_co_get_block_status(BlockDriverState *bs,
|
|||||||
size_t sector_in_block = sector_num % s->block_sectors;
|
size_t sector_in_block = sector_num % s->block_sectors;
|
||||||
int n_sectors = s->block_sectors - sector_in_block;
|
int n_sectors = s->block_sectors - sector_in_block;
|
||||||
uint32_t bmap_entry = le32_to_cpu(s->bmap[bmap_index]);
|
uint32_t bmap_entry = le32_to_cpu(s->bmap[bmap_index]);
|
||||||
uint64_t offset;
|
|
||||||
int result;
|
|
||||||
|
|
||||||
logout("%p, %" PRId64 ", %d, %p\n", bs, sector_num, nb_sectors, pnum);
|
logout("%p, %" PRId64 ", %d, %p\n", bs, sector_num, nb_sectors, pnum);
|
||||||
if (n_sectors > nb_sectors) {
|
if (n_sectors > nb_sectors) {
|
||||||
n_sectors = nb_sectors;
|
n_sectors = nb_sectors;
|
||||||
}
|
}
|
||||||
*pnum = n_sectors;
|
*pnum = n_sectors;
|
||||||
result = VDI_IS_ALLOCATED(bmap_entry);
|
return VDI_IS_ALLOCATED(bmap_entry);
|
||||||
if (!result) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = s->header.offset_data +
|
|
||||||
(uint64_t)bmap_entry * s->block_size +
|
|
||||||
sector_in_block * SECTOR_SIZE;
|
|
||||||
return BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vdi_co_read(BlockDriverState *bs,
|
static int vdi_co_read(BlockDriverState *bs,
|
||||||
@@ -672,8 +652,7 @@ static int vdi_co_write(BlockDriverState *bs,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vdi_create(const char *filename, QEMUOptionParameter *options,
|
static int vdi_create(const char *filename, QEMUOptionParameter *options)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
@@ -710,8 +689,8 @@ static int vdi_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
|
|
||||||
if (bytes > VDI_DISK_SIZE_MAX) {
|
if (bytes > VDI_DISK_SIZE_MAX) {
|
||||||
result = -ENOTSUP;
|
result = -ENOTSUP;
|
||||||
error_setg(errp, "Unsupported VDI image size (size is 0x%" PRIx64
|
logout("Unsupported VDI image size (size is 0x%" PRIx64
|
||||||
", max supported is 0x%" PRIx64 ")",
|
", max supported is 0x%" PRIx64 ")\n",
|
||||||
bytes, VDI_DISK_SIZE_MAX);
|
bytes, VDI_DISK_SIZE_MAX);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@@ -829,8 +808,7 @@ static BlockDriver bdrv_vdi = {
|
|||||||
.bdrv_close = vdi_close,
|
.bdrv_close = vdi_close,
|
||||||
.bdrv_reopen_prepare = vdi_reopen_prepare,
|
.bdrv_reopen_prepare = vdi_reopen_prepare,
|
||||||
.bdrv_create = vdi_create,
|
.bdrv_create = vdi_create,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
.bdrv_co_is_allocated = vdi_co_is_allocated,
|
||||||
.bdrv_co_get_block_status = vdi_co_get_block_status,
|
|
||||||
.bdrv_make_empty = vdi_make_empty,
|
.bdrv_make_empty = vdi_make_empty,
|
||||||
|
|
||||||
.bdrv_read = vdi_co_read,
|
.bdrv_read = vdi_co_read,
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Block driver for Hyper-V VHDX Images
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013 Red Hat, Inc.,
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Jeff Cody <jcody@redhat.com>
|
|
||||||
*
|
|
||||||
* This is based on the "VHDX Format Specification v1.00", published 8/25/2012
|
|
||||||
* by Microsoft:
|
|
||||||
* https://www.microsoft.com/en-us/download/details.aspx?id=34750
|
|
||||||
*
|
|
||||||
* 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-common.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "block/vhdx.h"
|
|
||||||
|
|
||||||
#include <uuid/uuid.h>
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* All the VHDX formats on disk are little endian - the following
|
|
||||||
* are helper import/export functions to correctly convert
|
|
||||||
* endianness from disk read to native cpu format, and back again.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/* VHDX File Header */
|
|
||||||
|
|
||||||
|
|
||||||
void vhdx_header_le_import(VHDXHeader *h)
|
|
||||||
{
|
|
||||||
assert(h != NULL);
|
|
||||||
|
|
||||||
le32_to_cpus(&h->signature);
|
|
||||||
le32_to_cpus(&h->checksum);
|
|
||||||
le64_to_cpus(&h->sequence_number);
|
|
||||||
|
|
||||||
leguid_to_cpus(&h->file_write_guid);
|
|
||||||
leguid_to_cpus(&h->data_write_guid);
|
|
||||||
leguid_to_cpus(&h->log_guid);
|
|
||||||
|
|
||||||
le16_to_cpus(&h->log_version);
|
|
||||||
le16_to_cpus(&h->version);
|
|
||||||
le32_to_cpus(&h->log_length);
|
|
||||||
le64_to_cpus(&h->log_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_header_le_export(VHDXHeader *orig_h, VHDXHeader *new_h)
|
|
||||||
{
|
|
||||||
assert(orig_h != NULL);
|
|
||||||
assert(new_h != NULL);
|
|
||||||
|
|
||||||
new_h->signature = cpu_to_le32(orig_h->signature);
|
|
||||||
new_h->checksum = cpu_to_le32(orig_h->checksum);
|
|
||||||
new_h->sequence_number = cpu_to_le64(orig_h->sequence_number);
|
|
||||||
|
|
||||||
new_h->file_write_guid = orig_h->file_write_guid;
|
|
||||||
new_h->data_write_guid = orig_h->data_write_guid;
|
|
||||||
new_h->log_guid = orig_h->log_guid;
|
|
||||||
|
|
||||||
cpu_to_leguids(&new_h->file_write_guid);
|
|
||||||
cpu_to_leguids(&new_h->data_write_guid);
|
|
||||||
cpu_to_leguids(&new_h->log_guid);
|
|
||||||
|
|
||||||
new_h->log_version = cpu_to_le16(orig_h->log_version);
|
|
||||||
new_h->version = cpu_to_le16(orig_h->version);
|
|
||||||
new_h->log_length = cpu_to_le32(orig_h->log_length);
|
|
||||||
new_h->log_offset = cpu_to_le64(orig_h->log_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* VHDX Log Headers */
|
|
||||||
|
|
||||||
|
|
||||||
void vhdx_log_desc_le_import(VHDXLogDescriptor *d)
|
|
||||||
{
|
|
||||||
assert(d != NULL);
|
|
||||||
|
|
||||||
le32_to_cpus(&d->signature);
|
|
||||||
le32_to_cpus(&d->trailing_bytes);
|
|
||||||
le64_to_cpus(&d->leading_bytes);
|
|
||||||
le64_to_cpus(&d->file_offset);
|
|
||||||
le64_to_cpus(&d->sequence_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_log_desc_le_export(VHDXLogDescriptor *d)
|
|
||||||
{
|
|
||||||
assert(d != NULL);
|
|
||||||
|
|
||||||
cpu_to_le32s(&d->signature);
|
|
||||||
cpu_to_le32s(&d->trailing_bytes);
|
|
||||||
cpu_to_le64s(&d->leading_bytes);
|
|
||||||
cpu_to_le64s(&d->file_offset);
|
|
||||||
cpu_to_le64s(&d->sequence_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_log_data_le_export(VHDXLogDataSector *d)
|
|
||||||
{
|
|
||||||
assert(d != NULL);
|
|
||||||
|
|
||||||
cpu_to_le32s(&d->data_signature);
|
|
||||||
cpu_to_le32s(&d->sequence_high);
|
|
||||||
cpu_to_le32s(&d->sequence_low);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_log_entry_hdr_le_import(VHDXLogEntryHeader *hdr)
|
|
||||||
{
|
|
||||||
assert(hdr != NULL);
|
|
||||||
|
|
||||||
le32_to_cpus(&hdr->signature);
|
|
||||||
le32_to_cpus(&hdr->checksum);
|
|
||||||
le32_to_cpus(&hdr->entry_length);
|
|
||||||
le32_to_cpus(&hdr->tail);
|
|
||||||
le64_to_cpus(&hdr->sequence_number);
|
|
||||||
le32_to_cpus(&hdr->descriptor_count);
|
|
||||||
leguid_to_cpus(&hdr->log_guid);
|
|
||||||
le64_to_cpus(&hdr->flushed_file_offset);
|
|
||||||
le64_to_cpus(&hdr->last_file_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_log_entry_hdr_le_export(VHDXLogEntryHeader *hdr)
|
|
||||||
{
|
|
||||||
assert(hdr != NULL);
|
|
||||||
|
|
||||||
cpu_to_le32s(&hdr->signature);
|
|
||||||
cpu_to_le32s(&hdr->checksum);
|
|
||||||
cpu_to_le32s(&hdr->entry_length);
|
|
||||||
cpu_to_le32s(&hdr->tail);
|
|
||||||
cpu_to_le64s(&hdr->sequence_number);
|
|
||||||
cpu_to_le32s(&hdr->descriptor_count);
|
|
||||||
cpu_to_leguids(&hdr->log_guid);
|
|
||||||
cpu_to_le64s(&hdr->flushed_file_offset);
|
|
||||||
cpu_to_le64s(&hdr->last_file_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Region table entries */
|
|
||||||
void vhdx_region_header_le_import(VHDXRegionTableHeader *hdr)
|
|
||||||
{
|
|
||||||
assert(hdr != NULL);
|
|
||||||
|
|
||||||
le32_to_cpus(&hdr->signature);
|
|
||||||
le32_to_cpus(&hdr->checksum);
|
|
||||||
le32_to_cpus(&hdr->entry_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_region_header_le_export(VHDXRegionTableHeader *hdr)
|
|
||||||
{
|
|
||||||
assert(hdr != NULL);
|
|
||||||
|
|
||||||
cpu_to_le32s(&hdr->signature);
|
|
||||||
cpu_to_le32s(&hdr->checksum);
|
|
||||||
cpu_to_le32s(&hdr->entry_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_region_entry_le_import(VHDXRegionTableEntry *e)
|
|
||||||
{
|
|
||||||
assert(e != NULL);
|
|
||||||
|
|
||||||
leguid_to_cpus(&e->guid);
|
|
||||||
le64_to_cpus(&e->file_offset);
|
|
||||||
le32_to_cpus(&e->length);
|
|
||||||
le32_to_cpus(&e->data_bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_region_entry_le_export(VHDXRegionTableEntry *e)
|
|
||||||
{
|
|
||||||
assert(e != NULL);
|
|
||||||
|
|
||||||
cpu_to_leguids(&e->guid);
|
|
||||||
cpu_to_le64s(&e->file_offset);
|
|
||||||
cpu_to_le32s(&e->length);
|
|
||||||
cpu_to_le32s(&e->data_bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Metadata headers & table */
|
|
||||||
void vhdx_metadata_header_le_import(VHDXMetadataTableHeader *hdr)
|
|
||||||
{
|
|
||||||
assert(hdr != NULL);
|
|
||||||
|
|
||||||
le64_to_cpus(&hdr->signature);
|
|
||||||
le16_to_cpus(&hdr->entry_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_metadata_header_le_export(VHDXMetadataTableHeader *hdr)
|
|
||||||
{
|
|
||||||
assert(hdr != NULL);
|
|
||||||
|
|
||||||
cpu_to_le64s(&hdr->signature);
|
|
||||||
cpu_to_le16s(&hdr->entry_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_metadata_entry_le_import(VHDXMetadataTableEntry *e)
|
|
||||||
{
|
|
||||||
assert(e != NULL);
|
|
||||||
|
|
||||||
leguid_to_cpus(&e->item_id);
|
|
||||||
le32_to_cpus(&e->offset);
|
|
||||||
le32_to_cpus(&e->length);
|
|
||||||
le32_to_cpus(&e->data_bits);
|
|
||||||
}
|
|
||||||
void vhdx_metadata_entry_le_export(VHDXMetadataTableEntry *e)
|
|
||||||
{
|
|
||||||
assert(e != NULL);
|
|
||||||
|
|
||||||
cpu_to_leguids(&e->item_id);
|
|
||||||
cpu_to_le32s(&e->offset);
|
|
||||||
cpu_to_le32s(&e->length);
|
|
||||||
cpu_to_le32s(&e->data_bits);
|
|
||||||
}
|
|
||||||
1021
block/vhdx-log.c
1021
block/vhdx-log.c
File diff suppressed because it is too large
Load Diff
1935
block/vhdx.c
1935
block/vhdx.c
File diff suppressed because it is too large
Load Diff
450
block/vhdx.h
450
block/vhdx.h
@@ -1,450 +0,0 @@
|
|||||||
/*
|
|
||||||
* Block driver for Hyper-V VHDX Images
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013 Red Hat, Inc.,
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Jeff Cody <jcody@redhat.com>
|
|
||||||
*
|
|
||||||
* This is based on the "VHDX Format Specification v1.00", published 8/25/2012
|
|
||||||
* by Microsoft:
|
|
||||||
* https://www.microsoft.com/en-us/download/details.aspx?id=34750
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BLOCK_VHDX_H
|
|
||||||
#define BLOCK_VHDX_H
|
|
||||||
|
|
||||||
#define KiB (1 * 1024)
|
|
||||||
#define MiB (KiB * 1024)
|
|
||||||
#define GiB (MiB * 1024)
|
|
||||||
#define TiB ((uint64_t) GiB * 1024)
|
|
||||||
|
|
||||||
/* Structures and fields present in the VHDX file */
|
|
||||||
|
|
||||||
/* The header section has the following blocks,
|
|
||||||
* each block is 64KB:
|
|
||||||
*
|
|
||||||
* _____________________________________________________________________________
|
|
||||||
* | File Id. | Header 1 | Header 2 | Region Table | Reserved (768KB) |
|
|
||||||
* |----------|---------------|------------|--------------|--------------------|
|
|
||||||
* | | | | | |
|
|
||||||
* 0.........64KB...........128KB........192KB..........256KB................1MB
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define VHDX_HEADER_BLOCK_SIZE (64 * 1024)
|
|
||||||
|
|
||||||
#define VHDX_FILE_ID_OFFSET 0
|
|
||||||
#define VHDX_HEADER1_OFFSET (VHDX_HEADER_BLOCK_SIZE * 1)
|
|
||||||
#define VHDX_HEADER2_OFFSET (VHDX_HEADER_BLOCK_SIZE * 2)
|
|
||||||
#define VHDX_REGION_TABLE_OFFSET (VHDX_HEADER_BLOCK_SIZE * 3)
|
|
||||||
#define VHDX_REGION_TABLE2_OFFSET (VHDX_HEADER_BLOCK_SIZE * 4)
|
|
||||||
|
|
||||||
#define VHDX_HEADER_SECTION_END (1 * MiB)
|
|
||||||
/*
|
|
||||||
* A note on the use of MS-GUID fields. For more details on the GUID,
|
|
||||||
* please see: https://en.wikipedia.org/wiki/Globally_unique_identifier.
|
|
||||||
*
|
|
||||||
* The VHDX specification only states that these are MS GUIDs, and which
|
|
||||||
* bytes are data1-data4. It makes no mention of what algorithm should be used
|
|
||||||
* to generate the GUID, nor what standard. However, looking at the specified
|
|
||||||
* known GUID fields, it appears the GUIDs are:
|
|
||||||
* Standard/DCE GUID type (noted by 10b in the MSB of byte 0 of .data4)
|
|
||||||
* Random algorithm (noted by 0x4XXX for .data3)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ---- HEADER SECTION STRUCTURES ---- */
|
|
||||||
|
|
||||||
/* These structures are ones that are defined in the VHDX specification
|
|
||||||
* document */
|
|
||||||
|
|
||||||
#define VHDX_FILE_SIGNATURE 0x656C696678646876ULL /* "vhdxfile" in ASCII */
|
|
||||||
typedef struct VHDXFileIdentifier {
|
|
||||||
uint64_t signature; /* "vhdxfile" in ASCII */
|
|
||||||
uint16_t creator[256]; /* optional; utf-16 string to identify
|
|
||||||
the vhdx file creator. Diagnostic
|
|
||||||
only */
|
|
||||||
} VHDXFileIdentifier;
|
|
||||||
|
|
||||||
|
|
||||||
/* the guid is a 16 byte unique ID - the definition for this used by
|
|
||||||
* Microsoft is not just 16 bytes though - it is a structure that is defined,
|
|
||||||
* so we need to follow it here so that endianness does not trip us up */
|
|
||||||
|
|
||||||
typedef struct QEMU_PACKED MSGUID {
|
|
||||||
uint32_t data1;
|
|
||||||
uint16_t data2;
|
|
||||||
uint16_t data3;
|
|
||||||
uint8_t data4[8];
|
|
||||||
} MSGUID;
|
|
||||||
|
|
||||||
#define guid_eq(a, b) \
|
|
||||||
(memcmp(&(a), &(b), sizeof(MSGUID)) == 0)
|
|
||||||
|
|
||||||
#define VHDX_HEADER_SIZE (4 * 1024) /* although the vhdx_header struct in disk
|
|
||||||
is only 582 bytes, for purposes of crc
|
|
||||||
the header is the first 4KB of the 64KB
|
|
||||||
block */
|
|
||||||
|
|
||||||
/* The full header is 4KB, although the actual header data is much smaller.
|
|
||||||
* But for the checksum calculation, it is over the entire 4KB structure,
|
|
||||||
* not just the defined portion of it */
|
|
||||||
#define VHDX_HEADER_SIGNATURE 0x64616568
|
|
||||||
typedef struct QEMU_PACKED VHDXHeader {
|
|
||||||
uint32_t signature; /* "head" in ASCII */
|
|
||||||
uint32_t checksum; /* CRC-32C hash of the whole header */
|
|
||||||
uint64_t sequence_number; /* Seq number of this header. Each
|
|
||||||
VHDX file has 2 of these headers,
|
|
||||||
and only the header with the highest
|
|
||||||
sequence number is valid */
|
|
||||||
MSGUID file_write_guid; /* 128 bit unique identifier. Must be
|
|
||||||
updated to new, unique value before
|
|
||||||
the first modification is made to
|
|
||||||
file */
|
|
||||||
MSGUID data_write_guid; /* 128 bit unique identifier. Must be
|
|
||||||
updated to new, unique value before
|
|
||||||
the first modification is made to
|
|
||||||
visible data. Visbile data is
|
|
||||||
defined as:
|
|
||||||
- system & user metadata
|
|
||||||
- raw block data
|
|
||||||
- disk size
|
|
||||||
- any change that will
|
|
||||||
cause the virtual disk
|
|
||||||
sector read to differ
|
|
||||||
|
|
||||||
This does not need to change if
|
|
||||||
blocks are re-arranged */
|
|
||||||
MSGUID log_guid; /* 128 bit unique identifier. If zero,
|
|
||||||
there is no valid log. If non-zero,
|
|
||||||
log entries with this guid are
|
|
||||||
valid. */
|
|
||||||
uint16_t log_version; /* version of the log format. Must be
|
|
||||||
set to zero */
|
|
||||||
uint16_t version; /* version of the vhdx file. Currently,
|
|
||||||
only supported version is "1" */
|
|
||||||
uint32_t log_length; /* length of the log. Must be multiple
|
|
||||||
of 1MB */
|
|
||||||
uint64_t log_offset; /* byte offset in the file of the log.
|
|
||||||
Must also be a multiple of 1MB */
|
|
||||||
} VHDXHeader;
|
|
||||||
|
|
||||||
/* Header for the region table block */
|
|
||||||
#define VHDX_REGION_SIGNATURE 0x69676572 /* "regi" in ASCII */
|
|
||||||
typedef struct QEMU_PACKED VHDXRegionTableHeader {
|
|
||||||
uint32_t signature; /* "regi" in ASCII */
|
|
||||||
uint32_t checksum; /* CRC-32C hash of the 64KB table */
|
|
||||||
uint32_t entry_count; /* number of valid entries */
|
|
||||||
uint32_t reserved;
|
|
||||||
} VHDXRegionTableHeader;
|
|
||||||
|
|
||||||
/* Individual region table entry. There may be a maximum of 2047 of these
|
|
||||||
*
|
|
||||||
* There are two known region table properties. Both are required.
|
|
||||||
* BAT (block allocation table): 2DC27766F62342009D64115E9BFD4A08
|
|
||||||
* Metadata: 8B7CA20647904B9AB8FE575F050F886E
|
|
||||||
*/
|
|
||||||
#define VHDX_REGION_ENTRY_REQUIRED 0x01 /* if set, parser must understand
|
|
||||||
this entry in order to open
|
|
||||||
file */
|
|
||||||
typedef struct QEMU_PACKED VHDXRegionTableEntry {
|
|
||||||
MSGUID guid; /* 128-bit unique identifier */
|
|
||||||
uint64_t file_offset; /* offset of the object in the file.
|
|
||||||
Must be multiple of 1MB */
|
|
||||||
uint32_t length; /* length, in bytes, of the object */
|
|
||||||
uint32_t data_bits;
|
|
||||||
} VHDXRegionTableEntry;
|
|
||||||
|
|
||||||
|
|
||||||
/* ---- LOG ENTRY STRUCTURES ---- */
|
|
||||||
#define VHDX_LOG_MIN_SIZE (1024 * 1024)
|
|
||||||
#define VHDX_LOG_SECTOR_SIZE 4096
|
|
||||||
#define VHDX_LOG_HDR_SIZE 64
|
|
||||||
#define VHDX_LOG_SIGNATURE 0x65676f6c
|
|
||||||
typedef struct QEMU_PACKED VHDXLogEntryHeader {
|
|
||||||
uint32_t signature; /* "loge" in ASCII */
|
|
||||||
uint32_t checksum; /* CRC-32C hash of the 64KB table */
|
|
||||||
uint32_t entry_length; /* length in bytes, multiple of 1MB */
|
|
||||||
uint32_t tail; /* byte offset of first log entry of a
|
|
||||||
seq, where this entry is the last
|
|
||||||
entry */
|
|
||||||
uint64_t sequence_number; /* incremented with each log entry.
|
|
||||||
May not be zero. */
|
|
||||||
uint32_t descriptor_count; /* number of descriptors in this log
|
|
||||||
entry, must be >= 0 */
|
|
||||||
uint32_t reserved;
|
|
||||||
MSGUID log_guid; /* value of the log_guid from
|
|
||||||
vhdx_header. If not found in
|
|
||||||
vhdx_header, it is invalid */
|
|
||||||
uint64_t flushed_file_offset; /* see spec for full details - this
|
|
||||||
should be vhdx file size in bytes */
|
|
||||||
uint64_t last_file_offset; /* size in bytes that all allocated
|
|
||||||
file structures fit into */
|
|
||||||
} VHDXLogEntryHeader;
|
|
||||||
|
|
||||||
#define VHDX_LOG_DESC_SIZE 32
|
|
||||||
#define VHDX_LOG_DESC_SIGNATURE 0x63736564
|
|
||||||
#define VHDX_LOG_ZERO_SIGNATURE 0x6f72657a
|
|
||||||
typedef struct QEMU_PACKED VHDXLogDescriptor {
|
|
||||||
uint32_t signature; /* "zero" or "desc" in ASCII */
|
|
||||||
union {
|
|
||||||
uint32_t reserved; /* zero desc */
|
|
||||||
uint32_t trailing_bytes; /* data desc: bytes 4092-4096 of the
|
|
||||||
data sector */
|
|
||||||
};
|
|
||||||
union {
|
|
||||||
uint64_t zero_length; /* zero desc: length of the section to
|
|
||||||
zero */
|
|
||||||
uint64_t leading_bytes; /* data desc: bytes 0-7 of the data
|
|
||||||
sector */
|
|
||||||
};
|
|
||||||
uint64_t file_offset; /* file offset to write zeros - multiple
|
|
||||||
of 4kB */
|
|
||||||
uint64_t sequence_number; /* must match same field in
|
|
||||||
vhdx_log_entry_header */
|
|
||||||
} VHDXLogDescriptor;
|
|
||||||
|
|
||||||
#define VHDX_LOG_DATA_SIGNATURE 0x61746164
|
|
||||||
typedef struct QEMU_PACKED VHDXLogDataSector {
|
|
||||||
uint32_t data_signature; /* "data" in ASCII */
|
|
||||||
uint32_t sequence_high; /* 4 MSB of 8 byte sequence_number */
|
|
||||||
uint8_t data[4084]; /* raw data, bytes 8-4091 (inclusive).
|
|
||||||
see the data descriptor field for the
|
|
||||||
other mising bytes */
|
|
||||||
uint32_t sequence_low; /* 4 LSB of 8 byte sequence_number */
|
|
||||||
} VHDXLogDataSector;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* block states - different state values depending on whether it is a
|
|
||||||
* payload block, or a sector block. */
|
|
||||||
|
|
||||||
#define PAYLOAD_BLOCK_NOT_PRESENT 0
|
|
||||||
#define PAYLOAD_BLOCK_UNDEFINED 1
|
|
||||||
#define PAYLOAD_BLOCK_ZERO 2
|
|
||||||
#define PAYLOAD_BLOCK_UNMAPPED 5
|
|
||||||
#define PAYLOAD_BLOCK_FULLY_PRESENT 6
|
|
||||||
#define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7
|
|
||||||
|
|
||||||
#define SB_BLOCK_NOT_PRESENT 0
|
|
||||||
#define SB_BLOCK_PRESENT 6
|
|
||||||
|
|
||||||
/* per the spec */
|
|
||||||
#define VHDX_MAX_SECTORS_PER_BLOCK (1 << 23)
|
|
||||||
|
|
||||||
/* upper 44 bits are the file offset in 1MB units lower 3 bits are the state
|
|
||||||
other bits are reserved */
|
|
||||||
#define VHDX_BAT_STATE_BIT_MASK 0x07
|
|
||||||
#define VHDX_BAT_FILE_OFF_MASK 0xFFFFFFFFFFF00000ULL /* upper 44 bits */
|
|
||||||
typedef uint64_t VHDXBatEntry;
|
|
||||||
|
|
||||||
/* ---- METADATA REGION STRUCTURES ---- */
|
|
||||||
|
|
||||||
#define VHDX_METADATA_ENTRY_SIZE 32
|
|
||||||
#define VHDX_METADATA_MAX_ENTRIES 2047 /* not including the header */
|
|
||||||
#define VHDX_METADATA_TABLE_MAX_SIZE \
|
|
||||||
(VHDX_METADATA_ENTRY_SIZE * (VHDX_METADATA_MAX_ENTRIES+1))
|
|
||||||
#define VHDX_METADATA_SIGNATURE 0x617461646174656DULL /* "metadata" in ASCII */
|
|
||||||
typedef struct QEMU_PACKED VHDXMetadataTableHeader {
|
|
||||||
uint64_t signature; /* "metadata" in ASCII */
|
|
||||||
uint16_t reserved;
|
|
||||||
uint16_t entry_count; /* number table entries. <= 2047 */
|
|
||||||
uint32_t reserved2[5];
|
|
||||||
} VHDXMetadataTableHeader;
|
|
||||||
|
|
||||||
#define VHDX_META_FLAGS_IS_USER 0x01 /* max 1024 entries */
|
|
||||||
#define VHDX_META_FLAGS_IS_VIRTUAL_DISK 0x02 /* virtual disk metadata if set,
|
|
||||||
otherwise file metdata */
|
|
||||||
#define VHDX_META_FLAGS_IS_REQUIRED 0x04 /* parse must understand this
|
|
||||||
entry to open the file */
|
|
||||||
typedef struct QEMU_PACKED VHDXMetadataTableEntry {
|
|
||||||
MSGUID item_id; /* 128-bit identifier for metadata */
|
|
||||||
uint32_t offset; /* byte offset of the metadata. At
|
|
||||||
least 64kB. Relative to start of
|
|
||||||
metadata region */
|
|
||||||
/* note: if length = 0, so is offset */
|
|
||||||
uint32_t length; /* length of metadata. <= 1MB. */
|
|
||||||
uint32_t data_bits; /* least-significant 3 bits are flags,
|
|
||||||
the rest are reserved (see above) */
|
|
||||||
uint32_t reserved2;
|
|
||||||
} VHDXMetadataTableEntry;
|
|
||||||
|
|
||||||
#define VHDX_PARAMS_LEAVE_BLOCKS_ALLOCED 0x01 /* Do not change any blocks to
|
|
||||||
be BLOCK_NOT_PRESENT.
|
|
||||||
If set indicates a fixed
|
|
||||||
size VHDX file */
|
|
||||||
#define VHDX_PARAMS_HAS_PARENT 0x02 /* has parent / backing file */
|
|
||||||
#define VHDX_BLOCK_SIZE_MIN (1 * MiB)
|
|
||||||
#define VHDX_BLOCK_SIZE_MAX (256 * MiB)
|
|
||||||
typedef struct QEMU_PACKED VHDXFileParameters {
|
|
||||||
uint32_t block_size; /* size of each payload block, always
|
|
||||||
power of 2, <= 256MB and >= 1MB. */
|
|
||||||
uint32_t data_bits; /* least-significant 2 bits are flags,
|
|
||||||
the rest are reserved (see above) */
|
|
||||||
} VHDXFileParameters;
|
|
||||||
|
|
||||||
#define VHDX_MAX_IMAGE_SIZE ((uint64_t) 64 * TiB)
|
|
||||||
typedef struct QEMU_PACKED VHDXVirtualDiskSize {
|
|
||||||
uint64_t virtual_disk_size; /* Size of the virtual disk, in bytes.
|
|
||||||
Must be multiple of the sector size,
|
|
||||||
max of 64TB */
|
|
||||||
} VHDXVirtualDiskSize;
|
|
||||||
|
|
||||||
typedef struct QEMU_PACKED VHDXPage83Data {
|
|
||||||
MSGUID page_83_data; /* unique id for scsi devices that
|
|
||||||
support page 0x83 */
|
|
||||||
} VHDXPage83Data;
|
|
||||||
|
|
||||||
typedef struct QEMU_PACKED VHDXVirtualDiskLogicalSectorSize {
|
|
||||||
uint32_t logical_sector_size; /* virtual disk sector size (in bytes).
|
|
||||||
Can only be 512 or 4096 bytes */
|
|
||||||
} VHDXVirtualDiskLogicalSectorSize;
|
|
||||||
|
|
||||||
typedef struct QEMU_PACKED VHDXVirtualDiskPhysicalSectorSize {
|
|
||||||
uint32_t physical_sector_size; /* physical sector size (in bytes).
|
|
||||||
Can only be 512 or 4096 bytes */
|
|
||||||
} VHDXVirtualDiskPhysicalSectorSize;
|
|
||||||
|
|
||||||
typedef struct QEMU_PACKED VHDXParentLocatorHeader {
|
|
||||||
MSGUID locator_type; /* type of the parent virtual disk. */
|
|
||||||
uint16_t reserved;
|
|
||||||
uint16_t key_value_count; /* number of key/value pairs for this
|
|
||||||
locator */
|
|
||||||
} VHDXParentLocatorHeader;
|
|
||||||
|
|
||||||
/* key and value strings are UNICODE strings, UTF-16 LE encoding, no NULs */
|
|
||||||
typedef struct QEMU_PACKED VHDXParentLocatorEntry {
|
|
||||||
uint32_t key_offset; /* offset in metadata for key, > 0 */
|
|
||||||
uint32_t value_offset; /* offset in metadata for value, >0 */
|
|
||||||
uint16_t key_length; /* length of entry key, > 0 */
|
|
||||||
uint16_t value_length; /* length of entry value, > 0 */
|
|
||||||
} VHDXParentLocatorEntry;
|
|
||||||
|
|
||||||
|
|
||||||
/* ----- END VHDX SPECIFICATION STRUCTURES ---- */
|
|
||||||
|
|
||||||
typedef struct VHDXMetadataEntries {
|
|
||||||
VHDXMetadataTableEntry file_parameters_entry;
|
|
||||||
VHDXMetadataTableEntry virtual_disk_size_entry;
|
|
||||||
VHDXMetadataTableEntry page83_data_entry;
|
|
||||||
VHDXMetadataTableEntry logical_sector_size_entry;
|
|
||||||
VHDXMetadataTableEntry phys_sector_size_entry;
|
|
||||||
VHDXMetadataTableEntry parent_locator_entry;
|
|
||||||
uint16_t present;
|
|
||||||
} VHDXMetadataEntries;
|
|
||||||
|
|
||||||
typedef struct VHDXLogEntries {
|
|
||||||
uint64_t offset;
|
|
||||||
uint64_t length;
|
|
||||||
uint32_t write;
|
|
||||||
uint32_t read;
|
|
||||||
VHDXLogEntryHeader *hdr;
|
|
||||||
void *desc_buffer;
|
|
||||||
uint64_t sequence;
|
|
||||||
uint32_t tail;
|
|
||||||
} VHDXLogEntries;
|
|
||||||
|
|
||||||
typedef struct VHDXRegionEntry {
|
|
||||||
uint64_t start;
|
|
||||||
uint64_t end;
|
|
||||||
QLIST_ENTRY(VHDXRegionEntry) entries;
|
|
||||||
} VHDXRegionEntry;
|
|
||||||
|
|
||||||
typedef struct BDRVVHDXState {
|
|
||||||
CoMutex lock;
|
|
||||||
|
|
||||||
int curr_header;
|
|
||||||
VHDXHeader *headers[2];
|
|
||||||
|
|
||||||
VHDXRegionTableHeader rt;
|
|
||||||
VHDXRegionTableEntry bat_rt; /* region table for the BAT */
|
|
||||||
VHDXRegionTableEntry metadata_rt; /* region table for the metadata */
|
|
||||||
|
|
||||||
VHDXMetadataTableHeader metadata_hdr;
|
|
||||||
VHDXMetadataEntries metadata_entries;
|
|
||||||
|
|
||||||
VHDXFileParameters params;
|
|
||||||
uint32_t block_size;
|
|
||||||
uint32_t block_size_bits;
|
|
||||||
uint32_t sectors_per_block;
|
|
||||||
uint32_t sectors_per_block_bits;
|
|
||||||
|
|
||||||
uint64_t virtual_disk_size;
|
|
||||||
uint32_t logical_sector_size;
|
|
||||||
uint32_t physical_sector_size;
|
|
||||||
|
|
||||||
uint64_t chunk_ratio;
|
|
||||||
uint32_t chunk_ratio_bits;
|
|
||||||
uint32_t logical_sector_size_bits;
|
|
||||||
|
|
||||||
uint32_t bat_entries;
|
|
||||||
VHDXBatEntry *bat;
|
|
||||||
uint64_t bat_offset;
|
|
||||||
|
|
||||||
bool first_visible_write;
|
|
||||||
MSGUID session_guid;
|
|
||||||
|
|
||||||
VHDXLogEntries log;
|
|
||||||
|
|
||||||
VHDXParentLocatorHeader parent_header;
|
|
||||||
VHDXParentLocatorEntry *parent_entries;
|
|
||||||
|
|
||||||
Error *migration_blocker;
|
|
||||||
|
|
||||||
bool log_replayed_on_open;
|
|
||||||
|
|
||||||
QLIST_HEAD(VHDXRegionHead, VHDXRegionEntry) regions;
|
|
||||||
} BDRVVHDXState;
|
|
||||||
|
|
||||||
void vhdx_guid_generate(MSGUID *guid);
|
|
||||||
|
|
||||||
int vhdx_update_headers(BlockDriverState *bs, BDRVVHDXState *s, bool rw,
|
|
||||||
MSGUID *log_guid);
|
|
||||||
|
|
||||||
uint32_t vhdx_update_checksum(uint8_t *buf, size_t size, int crc_offset);
|
|
||||||
uint32_t vhdx_checksum_calc(uint32_t crc, uint8_t *buf, size_t size,
|
|
||||||
int crc_offset);
|
|
||||||
|
|
||||||
bool vhdx_checksum_is_valid(uint8_t *buf, size_t size, int crc_offset);
|
|
||||||
|
|
||||||
int vhdx_parse_log(BlockDriverState *bs, BDRVVHDXState *s, bool *flushed,
|
|
||||||
Error **errp);
|
|
||||||
|
|
||||||
int vhdx_log_write_and_flush(BlockDriverState *bs, BDRVVHDXState *s,
|
|
||||||
void *data, uint32_t length, uint64_t offset);
|
|
||||||
|
|
||||||
static inline void leguid_to_cpus(MSGUID *guid)
|
|
||||||
{
|
|
||||||
le32_to_cpus(&guid->data1);
|
|
||||||
le16_to_cpus(&guid->data2);
|
|
||||||
le16_to_cpus(&guid->data3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void cpu_to_leguids(MSGUID *guid)
|
|
||||||
{
|
|
||||||
cpu_to_le32s(&guid->data1);
|
|
||||||
cpu_to_le16s(&guid->data2);
|
|
||||||
cpu_to_le16s(&guid->data3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vhdx_header_le_import(VHDXHeader *h);
|
|
||||||
void vhdx_header_le_export(VHDXHeader *orig_h, VHDXHeader *new_h);
|
|
||||||
void vhdx_log_desc_le_import(VHDXLogDescriptor *d);
|
|
||||||
void vhdx_log_desc_le_export(VHDXLogDescriptor *d);
|
|
||||||
void vhdx_log_data_le_export(VHDXLogDataSector *d);
|
|
||||||
void vhdx_log_entry_hdr_le_import(VHDXLogEntryHeader *hdr);
|
|
||||||
void vhdx_log_entry_hdr_le_export(VHDXLogEntryHeader *hdr);
|
|
||||||
void vhdx_region_header_le_import(VHDXRegionTableHeader *hdr);
|
|
||||||
void vhdx_region_header_le_export(VHDXRegionTableHeader *hdr);
|
|
||||||
void vhdx_region_entry_le_import(VHDXRegionTableEntry *e);
|
|
||||||
void vhdx_region_entry_le_export(VHDXRegionTableEntry *e);
|
|
||||||
void vhdx_metadata_header_le_import(VHDXMetadataTableHeader *hdr);
|
|
||||||
void vhdx_metadata_header_le_export(VHDXMetadataTableHeader *hdr);
|
|
||||||
void vhdx_metadata_entry_le_import(VHDXMetadataTableEntry *e);
|
|
||||||
void vhdx_metadata_entry_le_export(VHDXMetadataTableEntry *e);
|
|
||||||
int vhdx_user_visible_write(BlockDriverState *bs, BDRVVHDXState *s);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
981
block/vmdk.c
981
block/vmdk.c
File diff suppressed because it is too large
Load Diff
82
block/vpc.c
82
block/vpc.c
@@ -48,7 +48,7 @@ enum vhd_type {
|
|||||||
#define VHD_MAX_SECTORS (65535LL * 255 * 255)
|
#define VHD_MAX_SECTORS (65535LL * 255 * 255)
|
||||||
|
|
||||||
// always big-endian
|
// always big-endian
|
||||||
typedef struct vhd_footer {
|
struct vhd_footer {
|
||||||
char creator[8]; // "conectix"
|
char creator[8]; // "conectix"
|
||||||
uint32_t features;
|
uint32_t features;
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
@@ -81,9 +81,9 @@ typedef struct vhd_footer {
|
|||||||
uint8_t uuid[16];
|
uint8_t uuid[16];
|
||||||
|
|
||||||
uint8_t in_saved_state;
|
uint8_t in_saved_state;
|
||||||
} QEMU_PACKED VHDFooter;
|
};
|
||||||
|
|
||||||
typedef struct vhd_dyndisk_header {
|
struct vhd_dyndisk_header {
|
||||||
char magic[8]; // "cxsparse"
|
char magic[8]; // "cxsparse"
|
||||||
|
|
||||||
// Offset of next header structure, 0xFFFFFFFF if none
|
// Offset of next header structure, 0xFFFFFFFF if none
|
||||||
@@ -113,7 +113,7 @@ typedef struct vhd_dyndisk_header {
|
|||||||
uint32_t reserved;
|
uint32_t reserved;
|
||||||
uint64_t data_offset;
|
uint64_t data_offset;
|
||||||
} parent_locator[8];
|
} parent_locator[8];
|
||||||
} QEMU_PACKED VHDDynDiskHeader;
|
};
|
||||||
|
|
||||||
typedef struct BDRVVPCState {
|
typedef struct BDRVVPCState {
|
||||||
CoMutex lock;
|
CoMutex lock;
|
||||||
@@ -157,13 +157,12 @@ static int vpc_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
static int vpc_open(BlockDriverState *bs, int flags)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVVPCState *s = bs->opaque;
|
BDRVVPCState *s = bs->opaque;
|
||||||
int i;
|
int i;
|
||||||
VHDFooter *footer;
|
struct vhd_footer* footer;
|
||||||
VHDDynDiskHeader *dyndisk_header;
|
struct vhd_dyndisk_header* dyndisk_header;
|
||||||
uint8_t buf[HEADER_SIZE];
|
uint8_t buf[HEADER_SIZE];
|
||||||
uint32_t checksum;
|
uint32_t checksum;
|
||||||
uint64_t computed_size;
|
uint64_t computed_size;
|
||||||
@@ -175,7 +174,7 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer = (VHDFooter *) s->footer_buf;
|
footer = (struct vhd_footer*) s->footer_buf;
|
||||||
if (strncmp(footer->creator, "conectix", 8)) {
|
if (strncmp(footer->creator, "conectix", 8)) {
|
||||||
int64_t offset = bdrv_getlength(bs->file);
|
int64_t offset = bdrv_getlength(bs->file);
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
@@ -193,8 +192,7 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
if (strncmp(footer->creator, "conectix", 8)) {
|
if (strncmp(footer->creator, "conectix", 8)) {
|
||||||
error_setg(errp, "invalid VPC image");
|
ret = -EMEDIUMTYPE;
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
disk_type = VHD_FIXED;
|
disk_type = VHD_FIXED;
|
||||||
@@ -215,15 +213,6 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
bs->total_sectors = (int64_t)
|
bs->total_sectors = (int64_t)
|
||||||
be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl;
|
be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl;
|
||||||
|
|
||||||
/* images created with disk2vhd report a far higher virtual size
|
|
||||||
* than expected with the cyls * heads * sectors_per_cyl formula.
|
|
||||||
* use the footer->size instead if the image was created with
|
|
||||||
* disk2vhd.
|
|
||||||
*/
|
|
||||||
if (!strncmp(footer->creator_app, "d2v", 4)) {
|
|
||||||
bs->total_sectors = be64_to_cpu(footer->size) / BDRV_SECTOR_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allow a maximum disk size of approximately 2 TB */
|
/* Allow a maximum disk size of approximately 2 TB */
|
||||||
if (bs->total_sectors >= VHD_MAX_SECTORS) {
|
if (bs->total_sectors >= VHD_MAX_SECTORS) {
|
||||||
ret = -EFBIG;
|
ret = -EFBIG;
|
||||||
@@ -237,7 +226,7 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
dyndisk_header = (VHDDynDiskHeader *) buf;
|
dyndisk_header = (struct vhd_dyndisk_header *) buf;
|
||||||
|
|
||||||
if (strncmp(dyndisk_header->magic, "cxsparse", 8)) {
|
if (strncmp(dyndisk_header->magic, "cxsparse", 8)) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
@@ -246,7 +235,8 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
|
|
||||||
s->block_size = be32_to_cpu(dyndisk_header->block_size);
|
s->block_size = be32_to_cpu(dyndisk_header->block_size);
|
||||||
if (!is_power_of_2(s->block_size) || s->block_size < BDRV_SECTOR_SIZE) {
|
if (!is_power_of_2(s->block_size) || s->block_size < BDRV_SECTOR_SIZE) {
|
||||||
error_setg(errp, "Invalid block size %" PRIu32, s->block_size);
|
qerror_report(ERROR_CLASS_GENERIC_ERROR,
|
||||||
|
"Invalid block size %" PRIu32, s->block_size);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
@@ -294,13 +284,6 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->free_data_block_offset > bdrv_getlength(bs->file)) {
|
|
||||||
error_setg(errp, "block-vpc: free_data_block_offset points after "
|
|
||||||
"the end of file. The image has been truncated.");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->last_bitmap_offset = (int64_t) -1;
|
s->last_bitmap_offset = (int64_t) -1;
|
||||||
|
|
||||||
#ifdef CACHE
|
#ifdef CACHE
|
||||||
@@ -480,19 +463,6 @@ fail:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vpc_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
|
||||||
{
|
|
||||||
BDRVVPCState *s = (BDRVVPCState *)bs->opaque;
|
|
||||||
VHDFooter *footer = (VHDFooter *) s->footer_buf;
|
|
||||||
|
|
||||||
if (cpu_to_be32(footer->type) != VHD_FIXED) {
|
|
||||||
bdi->cluster_size = s->block_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
bdi->unallocated_blocks_are_zero = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int vpc_read(BlockDriverState *bs, int64_t sector_num,
|
static int vpc_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
uint8_t *buf, int nb_sectors)
|
uint8_t *buf, int nb_sectors)
|
||||||
{
|
{
|
||||||
@@ -500,7 +470,7 @@ static int vpc_read(BlockDriverState *bs, int64_t sector_num,
|
|||||||
int ret;
|
int ret;
|
||||||
int64_t offset;
|
int64_t offset;
|
||||||
int64_t sectors, sectors_per_block;
|
int64_t sectors, sectors_per_block;
|
||||||
VHDFooter *footer = (VHDFooter *) s->footer_buf;
|
struct vhd_footer *footer = (struct vhd_footer *) s->footer_buf;
|
||||||
|
|
||||||
if (cpu_to_be32(footer->type) == VHD_FIXED) {
|
if (cpu_to_be32(footer->type) == VHD_FIXED) {
|
||||||
return bdrv_read(bs->file, sector_num, buf, nb_sectors);
|
return bdrv_read(bs->file, sector_num, buf, nb_sectors);
|
||||||
@@ -549,7 +519,7 @@ static int vpc_write(BlockDriverState *bs, int64_t sector_num,
|
|||||||
int64_t offset;
|
int64_t offset;
|
||||||
int64_t sectors, sectors_per_block;
|
int64_t sectors, sectors_per_block;
|
||||||
int ret;
|
int ret;
|
||||||
VHDFooter *footer = (VHDFooter *) s->footer_buf;
|
struct vhd_footer *footer = (struct vhd_footer *) s->footer_buf;
|
||||||
|
|
||||||
if (cpu_to_be32(footer->type) == VHD_FIXED) {
|
if (cpu_to_be32(footer->type) == VHD_FIXED) {
|
||||||
return bdrv_write(bs->file, sector_num, buf, nb_sectors);
|
return bdrv_write(bs->file, sector_num, buf, nb_sectors);
|
||||||
@@ -651,8 +621,8 @@ static int calculate_geometry(int64_t total_sectors, uint16_t* cyls,
|
|||||||
|
|
||||||
static int create_dynamic_disk(int fd, uint8_t *buf, int64_t total_sectors)
|
static int create_dynamic_disk(int fd, uint8_t *buf, int64_t total_sectors)
|
||||||
{
|
{
|
||||||
VHDDynDiskHeader *dyndisk_header =
|
struct vhd_dyndisk_header* dyndisk_header =
|
||||||
(VHDDynDiskHeader *) buf;
|
(struct vhd_dyndisk_header*) buf;
|
||||||
size_t block_size, num_bat_entries;
|
size_t block_size, num_bat_entries;
|
||||||
int i;
|
int i;
|
||||||
int ret = -EIO;
|
int ret = -EIO;
|
||||||
@@ -738,11 +708,10 @@ static int create_fixed_disk(int fd, uint8_t *buf, int64_t total_size)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vpc_create(const char *filename, QEMUOptionParameter *options,
|
static int vpc_create(const char *filename, QEMUOptionParameter *options)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
uint8_t buf[1024];
|
uint8_t buf[1024];
|
||||||
VHDFooter *footer = (VHDFooter *) buf;
|
struct vhd_footer *footer = (struct vhd_footer *) buf;
|
||||||
QEMUOptionParameter *disk_type_param;
|
QEMUOptionParameter *disk_type_param;
|
||||||
int fd, i;
|
int fd, i;
|
||||||
uint16_t cyls = 0;
|
uint16_t cyls = 0;
|
||||||
@@ -842,18 +811,6 @@ static int vpc_create(const char *filename, QEMUOptionParameter *options,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vpc_has_zero_init(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BDRVVPCState *s = bs->opaque;
|
|
||||||
VHDFooter *footer = (VHDFooter *) s->footer_buf;
|
|
||||||
|
|
||||||
if (cpu_to_be32(footer->type) == VHD_FIXED) {
|
|
||||||
return bdrv_has_zero_init(bs->file);
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void vpc_close(BlockDriverState *bs)
|
static void vpc_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVVPCState *s = bs->opaque;
|
BDRVVPCState *s = bs->opaque;
|
||||||
@@ -895,10 +852,7 @@ static BlockDriver bdrv_vpc = {
|
|||||||
.bdrv_read = vpc_co_read,
|
.bdrv_read = vpc_co_read,
|
||||||
.bdrv_write = vpc_co_write,
|
.bdrv_write = vpc_co_write,
|
||||||
|
|
||||||
.bdrv_get_info = vpc_get_info,
|
|
||||||
|
|
||||||
.create_options = vpc_create_options,
|
.create_options = vpc_create_options,
|
||||||
.bdrv_has_zero_init = vpc_has_zero_init,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_vpc_init(void)
|
static void bdrv_vpc_init(void)
|
||||||
|
|||||||
281
block/vvfat.c
281
block/vvfat.c
@@ -1,4 +1,4 @@
|
|||||||
/* vim:set shiftwidth=4 ts=4: */
|
/* vim:set shiftwidth=4 ts=8: */
|
||||||
/*
|
/*
|
||||||
* QEMU Block driver for virtual VFAT (shadows a local directory)
|
* QEMU Block driver for virtual VFAT (shadows a local directory)
|
||||||
*
|
*
|
||||||
@@ -28,8 +28,6 @@
|
|||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "migration/migration.h"
|
#include "migration/migration.h"
|
||||||
#include "qapi/qmp/qint.h"
|
|
||||||
#include "qapi/qmp/qbool.h"
|
|
||||||
|
|
||||||
#ifndef S_IWGRP
|
#ifndef S_IWGRP
|
||||||
#define S_IWGRP 0
|
#define S_IWGRP 0
|
||||||
@@ -266,7 +264,8 @@ typedef struct mbr_t {
|
|||||||
} QEMU_PACKED mbr_t;
|
} QEMU_PACKED mbr_t;
|
||||||
|
|
||||||
typedef struct direntry_t {
|
typedef struct direntry_t {
|
||||||
uint8_t name[8 + 3];
|
uint8_t name[8];
|
||||||
|
uint8_t extension[3];
|
||||||
uint8_t attributes;
|
uint8_t attributes;
|
||||||
uint8_t reserved[2];
|
uint8_t reserved[2];
|
||||||
uint16_t ctime;
|
uint16_t ctime;
|
||||||
@@ -517,9 +516,11 @@ static inline uint8_t fat_chksum(const direntry_t* entry)
|
|||||||
uint8_t chksum=0;
|
uint8_t chksum=0;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(entry->name); i++) {
|
for(i=0;i<11;i++) {
|
||||||
chksum = (((chksum & 0xfe) >> 1) |
|
unsigned char c;
|
||||||
((chksum & 0x01) ? 0x80 : 0)) + entry->name[i];
|
|
||||||
|
c = (i < 8) ? entry->name[i] : entry->extension[i-8];
|
||||||
|
chksum=(((chksum&0xfe)>>1)|((chksum&0x01)?0x80:0)) + c;
|
||||||
}
|
}
|
||||||
|
|
||||||
return chksum;
|
return chksum;
|
||||||
@@ -614,7 +615,7 @@ static inline direntry_t* create_short_and_long_name(BDRVVVFATState* s,
|
|||||||
|
|
||||||
if(is_dot) {
|
if(is_dot) {
|
||||||
entry=array_get_next(&(s->directory));
|
entry=array_get_next(&(s->directory));
|
||||||
memset(entry->name, 0x20, sizeof(entry->name));
|
memset(entry->name,0x20,11);
|
||||||
memcpy(entry->name,filename,strlen(filename));
|
memcpy(entry->name,filename,strlen(filename));
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -629,14 +630,12 @@ static inline direntry_t* create_short_and_long_name(BDRVVVFATState* s,
|
|||||||
i = 8;
|
i = 8;
|
||||||
|
|
||||||
entry=array_get_next(&(s->directory));
|
entry=array_get_next(&(s->directory));
|
||||||
memset(entry->name, 0x20, sizeof(entry->name));
|
memset(entry->name,0x20,11);
|
||||||
memcpy(entry->name, filename, i);
|
memcpy(entry->name, filename, i);
|
||||||
|
|
||||||
if (j > 0) {
|
if(j > 0)
|
||||||
for (i = 0; i < 3 && filename[j + 1 + i]; i++) {
|
for (i = 0; i < 3 && filename[j+1+i]; i++)
|
||||||
entry->name[8 + i] = filename[j + 1 + i];
|
entry->extension[i] = filename[j+1+i];
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* upcase & remove unwanted characters */
|
/* upcase & remove unwanted characters */
|
||||||
for(i=10;i>=0;i--) {
|
for(i=10;i>=0;i--) {
|
||||||
@@ -860,7 +859,8 @@ static int init_directories(BDRVVVFATState* s,
|
|||||||
{
|
{
|
||||||
direntry_t* entry=array_get_next(&(s->directory));
|
direntry_t* entry=array_get_next(&(s->directory));
|
||||||
entry->attributes=0x28; /* archive | volume label */
|
entry->attributes=0x28; /* archive | volume label */
|
||||||
memcpy(entry->name, "QEMU VVFAT ", sizeof(entry->name));
|
memcpy(entry->name,"QEMU VVF",8);
|
||||||
|
memcpy(entry->extension,"AT ",3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now build FAT, and write back information into directory */
|
/* Now build FAT, and write back information into directory */
|
||||||
@@ -988,91 +988,10 @@ static void vvfat_rebind(BlockDriverState *bs)
|
|||||||
s->bs = bs;
|
s->bs = bs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
static int vvfat_open(BlockDriverState *bs, const char* dirname, int flags)
|
||||||
.name = "vvfat",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = "dir",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Host directory to map to the vvfat device",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "fat-type",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "FAT type (12, 16 or 32)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "floppy",
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "Create a floppy rather than a hard disk image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "rw",
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "Make the image writable",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static void vvfat_parse_filename(const char *filename, QDict *options,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int fat_type = 0;
|
|
||||||
bool floppy = false;
|
|
||||||
bool rw = false;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!strstart(filename, "fat:", NULL)) {
|
|
||||||
error_setg(errp, "File name string must start with 'fat:'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse options */
|
|
||||||
if (strstr(filename, ":32:")) {
|
|
||||||
fat_type = 32;
|
|
||||||
} else if (strstr(filename, ":16:")) {
|
|
||||||
fat_type = 16;
|
|
||||||
} else if (strstr(filename, ":12:")) {
|
|
||||||
fat_type = 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strstr(filename, ":floppy:")) {
|
|
||||||
floppy = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strstr(filename, ":rw:")) {
|
|
||||||
rw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the directory name without options */
|
|
||||||
i = strrchr(filename, ':') - filename;
|
|
||||||
assert(i >= 3);
|
|
||||||
if (filename[i - 2] == ':' && qemu_isalpha(filename[i - 1])) {
|
|
||||||
/* workaround for DOS drive names */
|
|
||||||
filename += i - 1;
|
|
||||||
} else {
|
|
||||||
filename += i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fill in the options QDict */
|
|
||||||
qdict_put(options, "dir", qstring_from_str(filename));
|
|
||||||
qdict_put(options, "fat-type", qint_from_int(fat_type));
|
|
||||||
qdict_put(options, "floppy", qbool_from_int(floppy));
|
|
||||||
qdict_put(options, "rw", qbool_from_int(rw));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int vvfat_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BDRVVVFATState *s = bs->opaque;
|
BDRVVVFATState *s = bs->opaque;
|
||||||
int cyls, heads, secs;
|
int i, cyls, heads, secs;
|
||||||
bool floppy;
|
|
||||||
const char *dirname;
|
|
||||||
QemuOpts *opts;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
vvv = s;
|
vvv = s;
|
||||||
@@ -1083,25 +1002,34 @@ DLOG(if (stderr == NULL) {
|
|||||||
setbuf(stderr, NULL);
|
setbuf(stderr, NULL);
|
||||||
})
|
})
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
s->bs = bs;
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
||||||
if (local_err) {
|
/* LATER TODO: if FAT32, adjust */
|
||||||
error_propagate(errp, local_err);
|
s->sectors_per_cluster=0x10;
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
s->current_cluster=0xffffffff;
|
||||||
|
|
||||||
|
s->first_sectors_number=0x40;
|
||||||
|
/* read only is the default for safety */
|
||||||
|
bs->read_only = 1;
|
||||||
|
s->qcow = s->write_target = NULL;
|
||||||
|
s->qcow_filename = NULL;
|
||||||
|
s->fat2 = NULL;
|
||||||
|
s->downcase_short_names = 1;
|
||||||
|
|
||||||
|
if (!strstart(dirname, "fat:", NULL))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (strstr(dirname, ":32:")) {
|
||||||
|
fprintf(stderr, "Big fat greek warning: FAT32 has not been tested. You are welcome to do so!\n");
|
||||||
|
s->fat_type = 32;
|
||||||
|
} else if (strstr(dirname, ":16:")) {
|
||||||
|
s->fat_type = 16;
|
||||||
|
} else if (strstr(dirname, ":12:")) {
|
||||||
|
s->fat_type = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
dirname = qemu_opt_get(opts, "dir");
|
if (strstr(dirname, ":floppy:")) {
|
||||||
if (!dirname) {
|
|
||||||
error_setg(errp, "vvfat block driver requires a 'dir' option");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->fat_type = qemu_opt_get_number(opts, "fat-type", 0);
|
|
||||||
floppy = qemu_opt_get_bool(opts, "floppy", false);
|
|
||||||
|
|
||||||
if (floppy) {
|
|
||||||
/* 1.44MB or 2.88MB floppy. 2.88MB can be FAT12 (default) or FAT16. */
|
/* 1.44MB or 2.88MB floppy. 2.88MB can be FAT12 (default) or FAT16. */
|
||||||
if (!s->fat_type) {
|
if (!s->fat_type) {
|
||||||
s->fat_type = 12;
|
s->fat_type = 12;
|
||||||
@@ -1119,59 +1047,33 @@ DLOG(if (stderr == NULL) {
|
|||||||
if (!s->fat_type) {
|
if (!s->fat_type) {
|
||||||
s->fat_type = 16;
|
s->fat_type = 16;
|
||||||
}
|
}
|
||||||
s->first_sectors_number = 0x40;
|
|
||||||
cyls = s->fat_type == 12 ? 64 : 1024;
|
cyls = s->fat_type == 12 ? 64 : 1024;
|
||||||
heads = 16;
|
heads = 16;
|
||||||
secs = 63;
|
secs = 63;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (s->fat_type) {
|
|
||||||
case 32:
|
|
||||||
fprintf(stderr, "Big fat greek warning: FAT32 has not been tested. "
|
|
||||||
"You are welcome to do so!\n");
|
|
||||||
break;
|
|
||||||
case 16:
|
|
||||||
case 12:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error_setg(errp, "Valid FAT types are only 12, 16 and 32");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
s->bs = bs;
|
|
||||||
|
|
||||||
/* LATER TODO: if FAT32, adjust */
|
|
||||||
s->sectors_per_cluster=0x10;
|
|
||||||
|
|
||||||
s->current_cluster=0xffffffff;
|
|
||||||
|
|
||||||
/* read only is the default for safety */
|
|
||||||
bs->read_only = 1;
|
|
||||||
s->qcow = s->write_target = NULL;
|
|
||||||
s->qcow_filename = NULL;
|
|
||||||
s->fat2 = NULL;
|
|
||||||
s->downcase_short_names = 1;
|
|
||||||
|
|
||||||
fprintf(stderr, "vvfat %s chs %d,%d,%d\n",
|
fprintf(stderr, "vvfat %s chs %d,%d,%d\n",
|
||||||
dirname, cyls, heads, secs);
|
dirname, cyls, heads, secs);
|
||||||
|
|
||||||
s->sector_count = cyls * heads * secs - (s->first_sectors_number - 1);
|
s->sector_count = cyls * heads * secs - (s->first_sectors_number - 1);
|
||||||
|
|
||||||
if (qemu_opt_get_bool(opts, "rw", false)) {
|
if (strstr(dirname, ":rw:")) {
|
||||||
ret = enable_write_target(s);
|
if (enable_write_target(s))
|
||||||
if (ret < 0) {
|
return -1;
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
bs->read_only = 0;
|
bs->read_only = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i = strrchr(dirname, ':') - dirname;
|
||||||
|
assert(i >= 3);
|
||||||
|
if (dirname[i-2] == ':' && qemu_isalpha(dirname[i-1]))
|
||||||
|
/* workaround for DOS drive names */
|
||||||
|
dirname += i-1;
|
||||||
|
else
|
||||||
|
dirname += i+1;
|
||||||
|
|
||||||
bs->total_sectors = cyls * heads * secs;
|
bs->total_sectors = cyls * heads * secs;
|
||||||
|
|
||||||
if (init_directories(s, dirname, heads, secs)) {
|
if (init_directories(s, dirname, heads, secs)) {
|
||||||
ret = -EIO;
|
return -1;
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s->sector_count = s->faked_sectors + s->sectors_per_cluster*s->cluster_count;
|
s->sector_count = s->faked_sectors + s->sectors_per_cluster*s->cluster_count;
|
||||||
@@ -1191,10 +1093,7 @@ DLOG(if (stderr == NULL) {
|
|||||||
migrate_add_blocker(s->migration_blocker);
|
migrate_add_blocker(s->migration_blocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
return 0;
|
||||||
fail:
|
|
||||||
qemu_opts_del(opts);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void vvfat_close_current_file(BDRVVVFATState *s)
|
static inline void vvfat_close_current_file(BDRVVVFATState *s)
|
||||||
@@ -1586,20 +1485,17 @@ static int parse_short_name(BDRVVVFATState* s,
|
|||||||
lfn->name[i] = direntry->name[i];
|
lfn->name[i] = direntry->name[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j = 2; j >= 0 && direntry->name[8 + j] == ' '; j--) {
|
for (j = 2; j >= 0 && direntry->extension[j] == ' '; j--);
|
||||||
}
|
|
||||||
if (j >= 0) {
|
if (j >= 0) {
|
||||||
lfn->name[i++] = '.';
|
lfn->name[i++] = '.';
|
||||||
lfn->name[i + j + 1] = '\0';
|
lfn->name[i + j + 1] = '\0';
|
||||||
for (;j >= 0; j--) {
|
for (;j >= 0; j--) {
|
||||||
uint8_t c = direntry->name[8 + j];
|
if (direntry->extension[j] <= ' ' || direntry->extension[j] > 0x7f)
|
||||||
if (c <= ' ' || c > 0x7f) {
|
|
||||||
return -2;
|
return -2;
|
||||||
} else if (s->downcase_short_names) {
|
else if (s->downcase_short_names)
|
||||||
lfn->name[i + j] = qemu_tolower(c);
|
lfn->name[i + j] = qemu_tolower(direntry->extension[j]);
|
||||||
} else {
|
else
|
||||||
lfn->name[i + j] = c;
|
lfn->name[i + j] = direntry->extension[j];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
lfn->name[i + j + 1] = '\0';
|
lfn->name[i + j + 1] = '\0';
|
||||||
@@ -2873,17 +2769,16 @@ static coroutine_fn int vvfat_co_write(BlockDriverState *bs, int64_t sector_num,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t coroutine_fn vvfat_co_get_block_status(BlockDriverState *bs,
|
static int coroutine_fn vvfat_co_is_allocated(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors, int* n)
|
int64_t sector_num, int nb_sectors, int* n)
|
||||||
{
|
{
|
||||||
BDRVVVFATState* s = bs->opaque;
|
BDRVVVFATState* s = bs->opaque;
|
||||||
*n = s->sector_count - sector_num;
|
*n = s->sector_count - sector_num;
|
||||||
if (*n > nb_sectors) {
|
if (*n > nb_sectors)
|
||||||
*n = nb_sectors;
|
*n = nb_sectors;
|
||||||
} else if (*n < 0) {
|
else if (*n < 0)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
return 1;
|
||||||
return BDRV_BLOCK_DATA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int write_target_commit(BlockDriverState *bs, int64_t sector_num,
|
static int write_target_commit(BlockDriverState *bs, int64_t sector_num,
|
||||||
@@ -2894,7 +2789,7 @@ static int write_target_commit(BlockDriverState *bs, int64_t sector_num,
|
|||||||
|
|
||||||
static void write_target_close(BlockDriverState *bs) {
|
static void write_target_close(BlockDriverState *bs) {
|
||||||
BDRVVVFATState* s = *((BDRVVVFATState**) bs->opaque);
|
BDRVVVFATState* s = *((BDRVVVFATState**) bs->opaque);
|
||||||
bdrv_unref(s->qcow);
|
bdrv_delete(s->qcow);
|
||||||
g_free(s->qcow_filename);
|
g_free(s->qcow_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2908,7 +2803,6 @@ static int enable_write_target(BDRVVVFATState *s)
|
|||||||
{
|
{
|
||||||
BlockDriver *bdrv_qcow;
|
BlockDriver *bdrv_qcow;
|
||||||
QEMUOptionParameter *options;
|
QEMUOptionParameter *options;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
int size = sector2cluster(s, s->sector_count);
|
int size = sector2cluster(s, s->sector_count);
|
||||||
s->used_clusters = calloc(size, 1);
|
s->used_clusters = calloc(size, 1);
|
||||||
@@ -2918,7 +2812,9 @@ static int enable_write_target(BDRVVVFATState *s)
|
|||||||
s->qcow_filename = g_malloc(1024);
|
s->qcow_filename = g_malloc(1024);
|
||||||
ret = get_tmp_filename(s->qcow_filename, 1024);
|
ret = get_tmp_filename(s->qcow_filename, 1024);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto err;
|
g_free(s->qcow_filename);
|
||||||
|
s->qcow_filename = NULL;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_qcow = bdrv_find_format("qcow");
|
bdrv_qcow = bdrv_find_format("qcow");
|
||||||
@@ -2926,38 +2822,30 @@ static int enable_write_target(BDRVVVFATState *s)
|
|||||||
set_option_parameter_int(options, BLOCK_OPT_SIZE, s->sector_count * 512);
|
set_option_parameter_int(options, BLOCK_OPT_SIZE, s->sector_count * 512);
|
||||||
set_option_parameter(options, BLOCK_OPT_BACKING_FILE, "fat:");
|
set_option_parameter(options, BLOCK_OPT_BACKING_FILE, "fat:");
|
||||||
|
|
||||||
ret = bdrv_create(bdrv_qcow, s->qcow_filename, options, &local_err);
|
if (bdrv_create(bdrv_qcow, s->qcow_filename, options) < 0)
|
||||||
if (ret < 0) {
|
return -1;
|
||||||
qerror_report_err(local_err);
|
|
||||||
error_free(local_err);
|
s->qcow = bdrv_new("");
|
||||||
goto err;
|
if (s->qcow == NULL) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->qcow = NULL;
|
ret = bdrv_open(s->qcow, s->qcow_filename,
|
||||||
ret = bdrv_open(&s->qcow, s->qcow_filename, NULL, NULL,
|
BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH, bdrv_qcow);
|
||||||
BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH, bdrv_qcow,
|
|
||||||
&local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
qerror_report_err(local_err);
|
return ret;
|
||||||
error_free(local_err);
|
|
||||||
goto err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
unlink(s->qcow_filename);
|
unlink(s->qcow_filename);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
s->bs->backing_hd = bdrv_new("");
|
s->bs->backing_hd = calloc(sizeof(BlockDriverState), 1);
|
||||||
s->bs->backing_hd->drv = &vvfat_write_target;
|
s->bs->backing_hd->drv = &vvfat_write_target;
|
||||||
s->bs->backing_hd->opaque = g_malloc(sizeof(void*));
|
s->bs->backing_hd->opaque = g_malloc(sizeof(void*));
|
||||||
*(void**)s->bs->backing_hd->opaque = s;
|
*(void**)s->bs->backing_hd->opaque = s;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err:
|
|
||||||
g_free(s->qcow_filename);
|
|
||||||
s->qcow_filename = NULL;
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void vvfat_close(BlockDriverState *bs)
|
static void vvfat_close(BlockDriverState *bs)
|
||||||
@@ -2978,17 +2866,14 @@ static void vvfat_close(BlockDriverState *bs)
|
|||||||
|
|
||||||
static BlockDriver bdrv_vvfat = {
|
static BlockDriver bdrv_vvfat = {
|
||||||
.format_name = "vvfat",
|
.format_name = "vvfat",
|
||||||
.protocol_name = "fat",
|
|
||||||
.instance_size = sizeof(BDRVVVFATState),
|
.instance_size = sizeof(BDRVVVFATState),
|
||||||
|
|
||||||
.bdrv_parse_filename = vvfat_parse_filename,
|
|
||||||
.bdrv_file_open = vvfat_open,
|
.bdrv_file_open = vvfat_open,
|
||||||
.bdrv_close = vvfat_close,
|
|
||||||
.bdrv_rebind = vvfat_rebind,
|
.bdrv_rebind = vvfat_rebind,
|
||||||
|
|
||||||
.bdrv_read = vvfat_co_read,
|
.bdrv_read = vvfat_co_read,
|
||||||
.bdrv_write = vvfat_co_write,
|
.bdrv_write = vvfat_co_write,
|
||||||
.bdrv_co_get_block_status = vvfat_co_get_block_status,
|
.bdrv_close = vvfat_close,
|
||||||
|
.bdrv_co_is_allocated = vvfat_co_is_allocated,
|
||||||
|
.protocol_name = "fat",
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_vvfat_init(void)
|
static void bdrv_vvfat_init(void)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
|
#include "qemu-common.h"
|
||||||
#include "block/aio.h"
|
#include "block/aio.h"
|
||||||
#include "raw-aio.h"
|
#include "raw-aio.h"
|
||||||
#include "qemu/event_notifier.h"
|
#include "qemu/event_notifier.h"
|
||||||
@@ -105,6 +106,13 @@ static void win32_aio_completion_cb(EventNotifier *e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int win32_aio_flush_cb(EventNotifier *e)
|
||||||
|
{
|
||||||
|
QEMUWin32AIOState *s = container_of(e, QEMUWin32AIOState, e);
|
||||||
|
|
||||||
|
return (s->count > 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void win32_aio_cancel(BlockDriverAIOCB *blockacb)
|
static void win32_aio_cancel(BlockDriverAIOCB *blockacb)
|
||||||
{
|
{
|
||||||
QEMUWin32AIOCB *waiocb = (QEMUWin32AIOCB *)blockacb;
|
QEMUWin32AIOCB *waiocb = (QEMUWin32AIOCB *)blockacb;
|
||||||
@@ -194,7 +202,8 @@ QEMUWin32AIOState *win32_aio_init(void)
|
|||||||
goto out_close_efd;
|
goto out_close_efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_aio_set_event_notifier(&s->e, win32_aio_completion_cb);
|
qemu_aio_set_event_notifier(&s->e, win32_aio_completion_cb,
|
||||||
|
win32_aio_flush_cb);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sysemu/blockdev.h"
|
#include "sysemu/blockdev.h"
|
||||||
#include "hw/block/block.h"
|
#include "hw/block-common.h"
|
||||||
#include "monitor/monitor.h"
|
#include "monitor/monitor.h"
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
@@ -69,6 +69,12 @@ static void nbd_close_notifier(Notifier *n, void *data)
|
|||||||
g_free(cn);
|
g_free(cn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void nbd_server_put_ref(NBDExport *exp)
|
||||||
|
{
|
||||||
|
BlockDriverState *bs = nbd_export_get_blockdev(exp);
|
||||||
|
drive_put_ref(drive_get_by_blockdev(bs));
|
||||||
|
}
|
||||||
|
|
||||||
void qmp_nbd_server_add(const char *device, bool has_writable, bool writable,
|
void qmp_nbd_server_add(const char *device, bool has_writable, bool writable,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@@ -99,9 +105,11 @@ void qmp_nbd_server_add(const char *device, bool has_writable, bool writable,
|
|||||||
writable = false;
|
writable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
exp = nbd_export_new(bs, 0, -1, writable ? 0 : NBD_FLAG_READ_ONLY, NULL);
|
exp = nbd_export_new(bs, 0, -1, writable ? 0 : NBD_FLAG_READ_ONLY,
|
||||||
|
nbd_server_put_ref);
|
||||||
|
|
||||||
nbd_export_set_name(exp, device);
|
nbd_export_set_name(exp, device);
|
||||||
|
drive_get_ref(drive_get_by_blockdev(bs));
|
||||||
|
|
||||||
n = g_malloc0(sizeof(NBDCloseNotifier));
|
n = g_malloc0(sizeof(NBDCloseNotifier));
|
||||||
n->n.notify = nbd_close_notifier;
|
n->n.notify = nbd_close_notifier;
|
||||||
|
|||||||
1944
blockdev.c
1944
blockdev.c
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user