1
The following changes since commit 248b23735645f7cbb503d9be6f5bf825f2a603ab:
1
The following changes since commit 2387df497b4b4bcf754eb7398edca82889e2ef54:
2
2
3
Update version for v2.10.0-rc4 release (2017-08-24 17:34:26 +0100)
3
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2020-10-10' into staging (2020-10-12 11:29:42 +0100)
4
4
5
are available in the git repository at:
5
are available in the Git repository at:
6
6
7
git://github.com/stefanha/qemu.git tags/block-pull-request
7
https://gitlab.com/stefanha/qemu.git tags/block-pull-request
8
8
9
for you to fetch changes up to 3e4c705212abfe8c9882a00beb2d1466a8a53cec:
9
for you to fetch changes up to 3664ec6bbe236126b79d251d4037889e7181ab55:
10
10
11
qcow2: allocate cluster_cache/cluster_data on demand (2017-08-30 18:02:10 +0100)
11
iotests: add commit top->base cases to 274 (2020-10-12 16:47:58 +0100)
12
13
----------------------------------------------------------------
14
Pull request
15
16
v2:
17
* Rebase and resolve conflict with commit 029a88c9a7e3 ("qemu-nbd: Honor
18
SIGINT and SIGHUP") [Peter]
12
19
13
----------------------------------------------------------------
20
----------------------------------------------------------------
14
21
15
----------------------------------------------------------------
22
Coiby Xu (7):
23
libvhost-user: Allow vu_message_read to be replaced
24
libvhost-user: remove watch for kick_fd when de-initialize vu-dev
25
util/vhost-user-server: generic vhost user server
26
block: move logical block size check function to a common utility
27
function
28
block/export: vhost-user block device backend server
29
test: new qTest case to test the vhost-user-blk-server
30
MAINTAINERS: Add vhost-user block device backend server maintainer
16
31
17
Alberto Garcia (8):
32
Philippe Mathieu-Daudé (1):
18
throttle: Fix wrong variable name in the header documentation
33
block/nvme: Add driver statistics for access alignment and hw errors
19
throttle: Update the throttle_fix_bucket() documentation
20
throttle: Make throttle_is_valid() a bit less verbose
21
throttle: Remove throttle_fix_bucket() / throttle_unfix_bucket()
22
throttle: Make LeakyBucket.avg and LeakyBucket.max integer types
23
throttle: Make burst_length 64bit and add range checks
24
throttle: Test the valid range of config values
25
misc: Remove unused Error variables
26
34
27
Dan Aloni (1):
35
Stefan Hajnoczi (17):
28
nvme: Fix get/set number of queues feature, again
36
util/vhost-user-server: s/fileds/fields/ typo fix
37
util/vhost-user-server: drop unnecessary QOM cast
38
util/vhost-user-server: drop unnecessary watch deletion
39
block/export: consolidate request structs into VuBlockReq
40
util/vhost-user-server: drop unused DevicePanicNotifier
41
util/vhost-user-server: fix memory leak in vu_message_read()
42
util/vhost-user-server: check EOF when reading payload
43
util/vhost-user-server: rework vu_client_trip() coroutine lifecycle
44
block/export: report flush errors
45
block/export: convert vhost-user-blk server to block export API
46
util/vhost-user-server: move header to include/
47
util/vhost-user-server: use static library in meson.build
48
qemu-storage-daemon: avoid compiling blockdev_ss twice
49
block: move block exports to libblockdev
50
block/export: add iothread and fixed-iothread options
51
block/export: add vhost-user-blk multi-queue support
52
tests/qtest: add multi-queue test case to vhost-user-blk-test
29
53
30
Eduardo Habkost (1):
54
Vladimir Sementsov-Ogievskiy (5):
31
oslib-posix: Print errors before aborting on qemu_alloc_stack()
55
block/io: fix bdrv_co_block_status_above
56
block/io: bdrv_common_block_status_above: support include_base
57
block/io: bdrv_common_block_status_above: support bs == base
58
block/io: fix bdrv_is_allocated_above
59
iotests: add commit top->base cases to 274
32
60
33
Fred Rolland (1):
61
MAINTAINERS | 10 +
34
qemu-doc: Add UUID support in initiator name
62
qapi/block-core.json | 24 +-
35
63
qapi/block-export.json | 36 +-
36
Stefan Hajnoczi (4):
64
block/coroutines.h | 2 +
37
scripts: add argparse module for Python 2.6 compatibility
65
block/export/vhost-user-blk-server.h | 19 +
38
docker.py: Python 2.6 argparse compatibility
66
contrib/libvhost-user/libvhost-user.h | 21 +
39
tests: migration/guestperf Python 2.6 argparse compatibility
67
include/qemu/vhost-user-server.h | 65 ++
40
qcow2: allocate cluster_cache/cluster_data on demand
68
tests/qtest/libqos/libqtest.h | 17 +
41
69
tests/qtest/libqos/vhost-user-blk.h | 48 ++
42
include/qemu/throttle.h | 8 +-
70
util/block-helpers.h | 19 +
43
block/qcow.c | 12 +-
71
block/export/export.c | 37 +-
44
block/qcow2-cluster.c | 17 +
72
block/export/vhost-user-blk-server.c | 431 +++++++++++
45
block/qcow2.c | 20 +-
73
block/io.c | 132 ++--
46
dump.c | 4 +-
74
block/nvme.c | 27 +
47
hw/block/nvme.c | 4 +-
75
block/qcow2.c | 16 +-
48
tests/test-throttle.c | 80 +-
76
contrib/libvhost-user/libvhost-user-glib.c | 2 +-
49
util/oslib-posix.c | 2 +
77
contrib/libvhost-user/libvhost-user.c | 15 +-
50
util/throttle.c | 86 +-
78
hw/core/qdev-properties-system.c | 31 +-
51
COPYING.PYTHON | 270 ++++
79
nbd/server.c | 2 -
52
qemu-doc.texi | 5 +-
80
qemu-nbd.c | 25 +-
53
scripts/argparse.py | 2406 ++++++++++++++++++++++++++++++++++++
81
softmmu/vl.c | 4 +
54
tests/docker/docker.py | 4 +-
82
stubs/blk-exp-close-all.c | 7 +
55
tests/migration/guestperf/shell.py | 8 +-
83
tests/qtest/libqos/vhost-user-blk.c | 129 ++++
56
14 files changed, 2831 insertions(+), 95 deletions(-)
84
tests/qtest/libqtest.c | 36 +-
57
create mode 100644 COPYING.PYTHON
85
tests/qtest/vhost-user-blk-test.c | 822 +++++++++++++++++++++
58
create mode 100644 scripts/argparse.py
86
tests/vhost-user-bridge.c | 2 +
87
tools/virtiofsd/fuse_virtio.c | 4 +-
88
util/block-helpers.c | 46 ++
89
util/vhost-user-server.c | 446 +++++++++++
90
block/export/meson.build | 3 +-
91
contrib/libvhost-user/meson.build | 1 +
92
meson.build | 22 +-
93
nbd/meson.build | 2 +
94
storage-daemon/meson.build | 3 +-
95
stubs/meson.build | 1 +
96
tests/qemu-iotests/274 | 20 +
97
tests/qemu-iotests/274.out | 68 ++
98
tests/qtest/libqos/meson.build | 1 +
99
tests/qtest/meson.build | 4 +-
100
util/meson.build | 4 +
101
40 files changed, 2476 insertions(+), 128 deletions(-)
102
create mode 100644 block/export/vhost-user-blk-server.h
103
create mode 100644 include/qemu/vhost-user-server.h
104
create mode 100644 tests/qtest/libqos/vhost-user-blk.h
105
create mode 100644 util/block-helpers.h
106
create mode 100644 block/export/vhost-user-blk-server.c
107
create mode 100644 stubs/blk-exp-close-all.c
108
create mode 100644 tests/qtest/libqos/vhost-user-blk.c
109
create mode 100644 tests/qtest/vhost-user-blk-test.c
110
create mode 100644 util/block-helpers.c
111
create mode 100644 util/vhost-user-server.c
59
112
60
--
113
--
61
2.13.5
114
2.26.2
62
115
63
diff view generated by jsdifflib
New patch
1
From: Philippe Mathieu-Daudé <philmd@redhat.com>
1
2
3
Keep statistics of some hardware errors, and number of
4
aligned/unaligned I/O accesses.
5
6
QMP example booting a full RHEL 8.3 aarch64 guest:
7
8
{ "execute": "query-blockstats" }
9
{
10
"return": [
11
{
12
"device": "",
13
"node-name": "drive0",
14
"stats": {
15
"flush_total_time_ns": 6026948,
16
"wr_highest_offset": 3383991230464,
17
"wr_total_time_ns": 807450995,
18
"failed_wr_operations": 0,
19
"failed_rd_operations": 0,
20
"wr_merged": 3,
21
"wr_bytes": 50133504,
22
"failed_unmap_operations": 0,
23
"failed_flush_operations": 0,
24
"account_invalid": false,
25
"rd_total_time_ns": 1846979900,
26
"flush_operations": 130,
27
"wr_operations": 659,
28
"rd_merged": 1192,
29
"rd_bytes": 218244096,
30
"account_failed": false,
31
"idle_time_ns": 2678641497,
32
"rd_operations": 7406,
33
},
34
"driver-specific": {
35
"driver": "nvme",
36
"completion-errors": 0,
37
"unaligned-accesses": 2959,
38
"aligned-accesses": 4477
39
},
40
"qdev": "/machine/peripheral-anon/device[0]/virtio-backend"
41
}
42
]
43
}
44
45
Suggested-by: Stefan Hajnoczi <stefanha@gmail.com>
46
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
47
Acked-by: Markus Armbruster <armbru@redhat.com>
48
Message-id: 20201001162939.1567915-1-philmd@redhat.com
49
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
50
---
51
qapi/block-core.json | 24 +++++++++++++++++++++++-
52
block/nvme.c | 27 +++++++++++++++++++++++++++
53
2 files changed, 50 insertions(+), 1 deletion(-)
54
55
diff --git a/qapi/block-core.json b/qapi/block-core.json
56
index XXXXXXX..XXXXXXX 100644
57
--- a/qapi/block-core.json
58
+++ b/qapi/block-core.json
59
@@ -XXX,XX +XXX,XX @@
60
'discard-nb-failed': 'uint64',
61
'discard-bytes-ok': 'uint64' } }
62
63
+##
64
+# @BlockStatsSpecificNvme:
65
+#
66
+# NVMe driver statistics
67
+#
68
+# @completion-errors: The number of completion errors.
69
+#
70
+# @aligned-accesses: The number of aligned accesses performed by
71
+# the driver.
72
+#
73
+# @unaligned-accesses: The number of unaligned accesses performed by
74
+# the driver.
75
+#
76
+# Since: 5.2
77
+##
78
+{ 'struct': 'BlockStatsSpecificNvme',
79
+ 'data': {
80
+ 'completion-errors': 'uint64',
81
+ 'aligned-accesses': 'uint64',
82
+ 'unaligned-accesses': 'uint64' } }
83
+
84
##
85
# @BlockStatsSpecific:
86
#
87
@@ -XXX,XX +XXX,XX @@
88
'discriminator': 'driver',
89
'data': {
90
'file': 'BlockStatsSpecificFile',
91
- 'host_device': 'BlockStatsSpecificFile' } }
92
+ 'host_device': 'BlockStatsSpecificFile',
93
+ 'nvme': 'BlockStatsSpecificNvme' } }
94
95
##
96
# @BlockStats:
97
diff --git a/block/nvme.c b/block/nvme.c
98
index XXXXXXX..XXXXXXX 100644
99
--- a/block/nvme.c
100
+++ b/block/nvme.c
101
@@ -XXX,XX +XXX,XX @@ struct BDRVNVMeState {
102
103
/* PCI address (required for nvme_refresh_filename()) */
104
char *device;
105
+
106
+ struct {
107
+ uint64_t completion_errors;
108
+ uint64_t aligned_accesses;
109
+ uint64_t unaligned_accesses;
110
+ } stats;
111
};
112
113
#define NVME_BLOCK_OPT_DEVICE "device"
114
@@ -XXX,XX +XXX,XX @@ static bool nvme_process_completion(NVMeQueuePair *q)
115
break;
116
}
117
ret = nvme_translate_error(c);
118
+ if (ret) {
119
+ s->stats.completion_errors++;
120
+ }
121
q->cq.head = (q->cq.head + 1) % NVME_QUEUE_SIZE;
122
if (!q->cq.head) {
123
q->cq_phase = !q->cq_phase;
124
@@ -XXX,XX +XXX,XX @@ static int nvme_co_prw(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
125
assert(QEMU_IS_ALIGNED(bytes, s->page_size));
126
assert(bytes <= s->max_transfer);
127
if (nvme_qiov_aligned(bs, qiov)) {
128
+ s->stats.aligned_accesses++;
129
return nvme_co_prw_aligned(bs, offset, bytes, qiov, is_write, flags);
130
}
131
+ s->stats.unaligned_accesses++;
132
trace_nvme_prw_buffered(s, offset, bytes, qiov->niov, is_write);
133
buf = qemu_try_memalign(s->page_size, bytes);
134
135
@@ -XXX,XX +XXX,XX @@ static void nvme_unregister_buf(BlockDriverState *bs, void *host)
136
qemu_vfio_dma_unmap(s->vfio, host);
137
}
138
139
+static BlockStatsSpecific *nvme_get_specific_stats(BlockDriverState *bs)
140
+{
141
+ BlockStatsSpecific *stats = g_new(BlockStatsSpecific, 1);
142
+ BDRVNVMeState *s = bs->opaque;
143
+
144
+ stats->driver = BLOCKDEV_DRIVER_NVME;
145
+ stats->u.nvme = (BlockStatsSpecificNvme) {
146
+ .completion_errors = s->stats.completion_errors,
147
+ .aligned_accesses = s->stats.aligned_accesses,
148
+ .unaligned_accesses = s->stats.unaligned_accesses,
149
+ };
150
+
151
+ return stats;
152
+}
153
+
154
static const char *const nvme_strong_runtime_opts[] = {
155
NVME_BLOCK_OPT_DEVICE,
156
NVME_BLOCK_OPT_NAMESPACE,
157
@@ -XXX,XX +XXX,XX @@ static BlockDriver bdrv_nvme = {
158
.bdrv_refresh_filename = nvme_refresh_filename,
159
.bdrv_refresh_limits = nvme_refresh_limits,
160
.strong_runtime_opts = nvme_strong_runtime_opts,
161
+ .bdrv_get_specific_stats = nvme_get_specific_stats,
162
163
.bdrv_detach_aio_context = nvme_detach_aio_context,
164
.bdrv_attach_aio_context = nvme_attach_aio_context,
165
--
166
2.26.2
167
diff view generated by jsdifflib
New patch
1
From: Coiby Xu <coiby.xu@gmail.com>
1
2
3
Allow vu_message_read to be replaced by one which will make use of the
4
QIOChannel functions. Thus reading vhost-user message won't stall the
5
guest. For slave channel, we still use the default vu_message_read.
6
7
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
8
Signed-off-by: Coiby Xu <coiby.xu@gmail.com>
9
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
10
Message-id: 20200918080912.321299-2-coiby.xu@gmail.com
11
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
12
---
13
contrib/libvhost-user/libvhost-user.h | 21 +++++++++++++++++++++
14
contrib/libvhost-user/libvhost-user-glib.c | 2 +-
15
contrib/libvhost-user/libvhost-user.c | 14 +++++++-------
16
tests/vhost-user-bridge.c | 2 ++
17
tools/virtiofsd/fuse_virtio.c | 4 ++--
18
5 files changed, 33 insertions(+), 10 deletions(-)
19
20
diff --git a/contrib/libvhost-user/libvhost-user.h b/contrib/libvhost-user/libvhost-user.h
21
index XXXXXXX..XXXXXXX 100644
22
--- a/contrib/libvhost-user/libvhost-user.h
23
+++ b/contrib/libvhost-user/libvhost-user.h
24
@@ -XXX,XX +XXX,XX @@
25
*/
26
#define VHOST_USER_MAX_RAM_SLOTS 32
27
28
+#define VHOST_USER_HDR_SIZE offsetof(VhostUserMsg, payload.u64)
29
+
30
typedef enum VhostSetConfigType {
31
VHOST_SET_CONFIG_TYPE_MASTER = 0,
32
VHOST_SET_CONFIG_TYPE_MIGRATION = 1,
33
@@ -XXX,XX +XXX,XX @@ typedef uint64_t (*vu_get_features_cb) (VuDev *dev);
34
typedef void (*vu_set_features_cb) (VuDev *dev, uint64_t features);
35
typedef int (*vu_process_msg_cb) (VuDev *dev, VhostUserMsg *vmsg,
36
int *do_reply);
37
+typedef bool (*vu_read_msg_cb) (VuDev *dev, int sock, VhostUserMsg *vmsg);
38
typedef void (*vu_queue_set_started_cb) (VuDev *dev, int qidx, bool started);
39
typedef bool (*vu_queue_is_processed_in_order_cb) (VuDev *dev, int qidx);
40
typedef int (*vu_get_config_cb) (VuDev *dev, uint8_t *config, uint32_t len);
41
@@ -XXX,XX +XXX,XX @@ struct VuDev {
42
bool broken;
43
uint16_t max_queues;
44
45
+ /* @read_msg: custom method to read vhost-user message
46
+ *
47
+ * Read data from vhost_user socket fd and fill up
48
+ * the passed VhostUserMsg *vmsg struct.
49
+ *
50
+ * If reading fails, it should close the received set of file
51
+ * descriptors as socket message's auxiliary data.
52
+ *
53
+ * For the details, please refer to vu_message_read in libvhost-user.c
54
+ * which will be used by default if not custom method is provided when
55
+ * calling vu_init
56
+ *
57
+ * Returns: true if vhost-user message successfully received,
58
+ * otherwise return false.
59
+ *
60
+ */
61
+ vu_read_msg_cb read_msg;
62
/* @set_watch: add or update the given fd to the watch set,
63
* call cb when condition is met */
64
vu_set_watch_cb set_watch;
65
@@ -XXX,XX +XXX,XX @@ bool vu_init(VuDev *dev,
66
uint16_t max_queues,
67
int socket,
68
vu_panic_cb panic,
69
+ vu_read_msg_cb read_msg,
70
vu_set_watch_cb set_watch,
71
vu_remove_watch_cb remove_watch,
72
const VuDevIface *iface);
73
diff --git a/contrib/libvhost-user/libvhost-user-glib.c b/contrib/libvhost-user/libvhost-user-glib.c
74
index XXXXXXX..XXXXXXX 100644
75
--- a/contrib/libvhost-user/libvhost-user-glib.c
76
+++ b/contrib/libvhost-user/libvhost-user-glib.c
77
@@ -XXX,XX +XXX,XX @@ vug_init(VugDev *dev, uint16_t max_queues, int socket,
78
g_assert(dev);
79
g_assert(iface);
80
81
- if (!vu_init(&dev->parent, max_queues, socket, panic, set_watch,
82
+ if (!vu_init(&dev->parent, max_queues, socket, panic, NULL, set_watch,
83
remove_watch, iface)) {
84
return false;
85
}
86
diff --git a/contrib/libvhost-user/libvhost-user.c b/contrib/libvhost-user/libvhost-user.c
87
index XXXXXXX..XXXXXXX 100644
88
--- a/contrib/libvhost-user/libvhost-user.c
89
+++ b/contrib/libvhost-user/libvhost-user.c
90
@@ -XXX,XX +XXX,XX @@
91
/* The version of inflight buffer */
92
#define INFLIGHT_VERSION 1
93
94
-#define VHOST_USER_HDR_SIZE offsetof(VhostUserMsg, payload.u64)
95
-
96
/* The version of the protocol we support */
97
#define VHOST_USER_VERSION 1
98
#define LIBVHOST_USER_DEBUG 0
99
@@ -XXX,XX +XXX,XX @@ have_userfault(void)
100
}
101
102
static bool
103
-vu_message_read(VuDev *dev, int conn_fd, VhostUserMsg *vmsg)
104
+vu_message_read_default(VuDev *dev, int conn_fd, VhostUserMsg *vmsg)
105
{
106
char control[CMSG_SPACE(VHOST_MEMORY_BASELINE_NREGIONS * sizeof(int))] = {};
107
struct iovec iov = {
108
@@ -XXX,XX +XXX,XX @@ vu_process_message_reply(VuDev *dev, const VhostUserMsg *vmsg)
109
goto out;
110
}
111
112
- if (!vu_message_read(dev, dev->slave_fd, &msg_reply)) {
113
+ if (!vu_message_read_default(dev, dev->slave_fd, &msg_reply)) {
114
goto out;
115
}
116
117
@@ -XXX,XX +XXX,XX @@ vu_set_mem_table_exec_postcopy(VuDev *dev, VhostUserMsg *vmsg)
118
/* Wait for QEMU to confirm that it's registered the handler for the
119
* faults.
120
*/
121
- if (!vu_message_read(dev, dev->sock, vmsg) ||
122
+ if (!dev->read_msg(dev, dev->sock, vmsg) ||
123
vmsg->size != sizeof(vmsg->payload.u64) ||
124
vmsg->payload.u64 != 0) {
125
vu_panic(dev, "failed to receive valid ack for postcopy set-mem-table");
126
@@ -XXX,XX +XXX,XX @@ vu_dispatch(VuDev *dev)
127
int reply_requested;
128
bool need_reply, success = false;
129
130
- if (!vu_message_read(dev, dev->sock, &vmsg)) {
131
+ if (!dev->read_msg(dev, dev->sock, &vmsg)) {
132
goto end;
133
}
134
135
@@ -XXX,XX +XXX,XX @@ vu_init(VuDev *dev,
136
uint16_t max_queues,
137
int socket,
138
vu_panic_cb panic,
139
+ vu_read_msg_cb read_msg,
140
vu_set_watch_cb set_watch,
141
vu_remove_watch_cb remove_watch,
142
const VuDevIface *iface)
143
@@ -XXX,XX +XXX,XX @@ vu_init(VuDev *dev,
144
145
dev->sock = socket;
146
dev->panic = panic;
147
+ dev->read_msg = read_msg ? read_msg : vu_message_read_default;
148
dev->set_watch = set_watch;
149
dev->remove_watch = remove_watch;
150
dev->iface = iface;
151
@@ -XXX,XX +XXX,XX @@ static void _vu_queue_notify(VuDev *dev, VuVirtq *vq, bool sync)
152
153
vu_message_write(dev, dev->slave_fd, &vmsg);
154
if (ack) {
155
- vu_message_read(dev, dev->slave_fd, &vmsg);
156
+ vu_message_read_default(dev, dev->slave_fd, &vmsg);
157
}
158
return;
159
}
160
diff --git a/tests/vhost-user-bridge.c b/tests/vhost-user-bridge.c
161
index XXXXXXX..XXXXXXX 100644
162
--- a/tests/vhost-user-bridge.c
163
+++ b/tests/vhost-user-bridge.c
164
@@ -XXX,XX +XXX,XX @@ vubr_accept_cb(int sock, void *ctx)
165
VHOST_USER_BRIDGE_MAX_QUEUES,
166
conn_fd,
167
vubr_panic,
168
+ NULL,
169
vubr_set_watch,
170
vubr_remove_watch,
171
&vuiface)) {
172
@@ -XXX,XX +XXX,XX @@ vubr_new(const char *path, bool client)
173
VHOST_USER_BRIDGE_MAX_QUEUES,
174
dev->sock,
175
vubr_panic,
176
+ NULL,
177
vubr_set_watch,
178
vubr_remove_watch,
179
&vuiface)) {
180
diff --git a/tools/virtiofsd/fuse_virtio.c b/tools/virtiofsd/fuse_virtio.c
181
index XXXXXXX..XXXXXXX 100644
182
--- a/tools/virtiofsd/fuse_virtio.c
183
+++ b/tools/virtiofsd/fuse_virtio.c
184
@@ -XXX,XX +XXX,XX @@ int virtio_session_mount(struct fuse_session *se)
185
se->vu_socketfd = data_sock;
186
se->virtio_dev->se = se;
187
pthread_rwlock_init(&se->virtio_dev->vu_dispatch_rwlock, NULL);
188
- vu_init(&se->virtio_dev->dev, 2, se->vu_socketfd, fv_panic, fv_set_watch,
189
- fv_remove_watch, &fv_iface);
190
+ vu_init(&se->virtio_dev->dev, 2, se->vu_socketfd, fv_panic, NULL,
191
+ fv_set_watch, fv_remove_watch, &fv_iface);
192
193
return 0;
194
}
195
--
196
2.26.2
197
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
From: Coiby Xu <coiby.xu@gmail.com>
2
2
3
Use a pointer to the bucket instead of repeating cfg->buckets[i] all
3
When the client is running in gdb and quit command is run in gdb,
4
the time. This makes the code more concise and will help us expand the
4
QEMU will still dispatch the event which will cause segment fault in
5
checks later and save a few line breaks.
5
the callback function.
6
6
7
Signed-off-by: Alberto Garcia <berto@igalia.com>
7
Signed-off-by: Coiby Xu <coiby.xu@gmail.com>
8
Message-id: 763ffc40a26b17d54cf93f5a999e4656049fcf0c.1503580370.git.berto@igalia.com
8
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
9
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
10
Message-id: 20200918080912.321299-3-coiby.xu@gmail.com
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
11
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
---
12
---
11
util/throttle.c | 15 +++++++--------
13
contrib/libvhost-user/libvhost-user.c | 1 +
12
1 file changed, 7 insertions(+), 8 deletions(-)
14
1 file changed, 1 insertion(+)
13
15
14
diff --git a/util/throttle.c b/util/throttle.c
16
diff --git a/contrib/libvhost-user/libvhost-user.c b/contrib/libvhost-user/libvhost-user.c
15
index XXXXXXX..XXXXXXX 100644
17
index XXXXXXX..XXXXXXX 100644
16
--- a/util/throttle.c
18
--- a/contrib/libvhost-user/libvhost-user.c
17
+++ b/util/throttle.c
19
+++ b/contrib/libvhost-user/libvhost-user.c
18
@@ -XXX,XX +XXX,XX @@ bool throttle_is_valid(ThrottleConfig *cfg, Error **errp)
20
@@ -XXX,XX +XXX,XX @@ vu_deinit(VuDev *dev)
19
}
20
21
for (i = 0; i < BUCKETS_COUNT; i++) {
22
- if (cfg->buckets[i].avg < 0 ||
23
- cfg->buckets[i].max < 0 ||
24
- cfg->buckets[i].avg > THROTTLE_VALUE_MAX ||
25
- cfg->buckets[i].max > THROTTLE_VALUE_MAX) {
26
+ LeakyBucket *bkt = &cfg->buckets[i];
27
+ if (bkt->avg < 0 || bkt->max < 0 ||
28
+ bkt->avg > THROTTLE_VALUE_MAX || bkt->max > THROTTLE_VALUE_MAX) {
29
error_setg(errp, "bps/iops/max values must be within [0, %lld]",
30
THROTTLE_VALUE_MAX);
31
return false;
32
}
21
}
33
22
34
- if (!cfg->buckets[i].burst_length) {
23
if (vq->kick_fd != -1) {
35
+ if (!bkt->burst_length) {
24
+ dev->remove_watch(dev, vq->kick_fd);
36
error_setg(errp, "the burst length cannot be 0");
25
close(vq->kick_fd);
37
return false;
26
vq->kick_fd = -1;
38
}
39
40
- if (cfg->buckets[i].burst_length > 1 && !cfg->buckets[i].max) {
41
+ if (bkt->burst_length > 1 && !bkt->max) {
42
error_setg(errp, "burst length set without burst rate");
43
return false;
44
}
45
46
- if (cfg->buckets[i].max && !cfg->buckets[i].avg) {
47
+ if (bkt->max && !bkt->avg) {
48
error_setg(errp, "bps_max/iops_max require corresponding"
49
" bps/iops values");
50
return false;
51
}
52
53
- if (cfg->buckets[i].max && cfg->buckets[i].max < cfg->buckets[i].avg) {
54
+ if (bkt->max && bkt->max < bkt->avg) {
55
error_setg(errp, "bps_max/iops_max cannot be lower than bps/iops");
56
return false;
57
}
27
}
58
--
28
--
59
2.13.5
29
2.26.2
60
30
61
diff view generated by jsdifflib
New patch
1
From: Coiby Xu <coiby.xu@gmail.com>
1
2
3
Sharing QEMU devices via vhost-user protocol.
4
5
Only one vhost-user client can connect to the server one time.
6
7
Suggested-by: Kevin Wolf <kwolf@redhat.com>
8
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9
Signed-off-by: Coiby Xu <coiby.xu@gmail.com>
10
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
11
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
12
Message-id: 20200918080912.321299-4-coiby.xu@gmail.com
13
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
14
---
15
util/vhost-user-server.h | 65 ++++++
16
util/vhost-user-server.c | 428 +++++++++++++++++++++++++++++++++++++++
17
util/meson.build | 1 +
18
3 files changed, 494 insertions(+)
19
create mode 100644 util/vhost-user-server.h
20
create mode 100644 util/vhost-user-server.c
21
22
diff --git a/util/vhost-user-server.h b/util/vhost-user-server.h
23
new file mode 100644
24
index XXXXXXX..XXXXXXX
25
--- /dev/null
26
+++ b/util/vhost-user-server.h
27
@@ -XXX,XX +XXX,XX @@
28
+/*
29
+ * Sharing QEMU devices via vhost-user protocol
30
+ *
31
+ * Copyright (c) Coiby Xu <coiby.xu@gmail.com>.
32
+ * Copyright (c) 2020 Red Hat, Inc.
33
+ *
34
+ * This work is licensed under the terms of the GNU GPL, version 2 or
35
+ * later. See the COPYING file in the top-level directory.
36
+ */
37
+
38
+#ifndef VHOST_USER_SERVER_H
39
+#define VHOST_USER_SERVER_H
40
+
41
+#include "contrib/libvhost-user/libvhost-user.h"
42
+#include "io/channel-socket.h"
43
+#include "io/channel-file.h"
44
+#include "io/net-listener.h"
45
+#include "qemu/error-report.h"
46
+#include "qapi/error.h"
47
+#include "standard-headers/linux/virtio_blk.h"
48
+
49
+typedef struct VuFdWatch {
50
+ VuDev *vu_dev;
51
+ int fd; /*kick fd*/
52
+ void *pvt;
53
+ vu_watch_cb cb;
54
+ bool processing;
55
+ QTAILQ_ENTRY(VuFdWatch) next;
56
+} VuFdWatch;
57
+
58
+typedef struct VuServer VuServer;
59
+typedef void DevicePanicNotifierFn(VuServer *server);
60
+
61
+struct VuServer {
62
+ QIONetListener *listener;
63
+ AioContext *ctx;
64
+ DevicePanicNotifierFn *device_panic_notifier;
65
+ int max_queues;
66
+ const VuDevIface *vu_iface;
67
+ VuDev vu_dev;
68
+ QIOChannel *ioc; /* The I/O channel with the client */
69
+ QIOChannelSocket *sioc; /* The underlying data channel with the client */
70
+ /* IOChannel for fd provided via VHOST_USER_SET_SLAVE_REQ_FD */
71
+ QIOChannel *ioc_slave;
72
+ QIOChannelSocket *sioc_slave;
73
+ Coroutine *co_trip; /* coroutine for processing VhostUserMsg */
74
+ QTAILQ_HEAD(, VuFdWatch) vu_fd_watches;
75
+ /* restart coroutine co_trip if AIOContext is changed */
76
+ bool aio_context_changed;
77
+ bool processing_msg;
78
+};
79
+
80
+bool vhost_user_server_start(VuServer *server,
81
+ SocketAddress *unix_socket,
82
+ AioContext *ctx,
83
+ uint16_t max_queues,
84
+ DevicePanicNotifierFn *device_panic_notifier,
85
+ const VuDevIface *vu_iface,
86
+ Error **errp);
87
+
88
+void vhost_user_server_stop(VuServer *server);
89
+
90
+void vhost_user_server_set_aio_context(VuServer *server, AioContext *ctx);
91
+
92
+#endif /* VHOST_USER_SERVER_H */
93
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
94
new file mode 100644
95
index XXXXXXX..XXXXXXX
96
--- /dev/null
97
+++ b/util/vhost-user-server.c
98
@@ -XXX,XX +XXX,XX @@
99
+/*
100
+ * Sharing QEMU devices via vhost-user protocol
101
+ *
102
+ * Copyright (c) Coiby Xu <coiby.xu@gmail.com>.
103
+ * Copyright (c) 2020 Red Hat, Inc.
104
+ *
105
+ * This work is licensed under the terms of the GNU GPL, version 2 or
106
+ * later. See the COPYING file in the top-level directory.
107
+ */
108
+#include "qemu/osdep.h"
109
+#include "qemu/main-loop.h"
110
+#include "vhost-user-server.h"
111
+
112
+static void vmsg_close_fds(VhostUserMsg *vmsg)
113
+{
114
+ int i;
115
+ for (i = 0; i < vmsg->fd_num; i++) {
116
+ close(vmsg->fds[i]);
117
+ }
118
+}
119
+
120
+static void vmsg_unblock_fds(VhostUserMsg *vmsg)
121
+{
122
+ int i;
123
+ for (i = 0; i < vmsg->fd_num; i++) {
124
+ qemu_set_nonblock(vmsg->fds[i]);
125
+ }
126
+}
127
+
128
+static void vu_accept(QIONetListener *listener, QIOChannelSocket *sioc,
129
+ gpointer opaque);
130
+
131
+static void close_client(VuServer *server)
132
+{
133
+ /*
134
+ * Before closing the client
135
+ *
136
+ * 1. Let vu_client_trip stop processing new vhost-user msg
137
+ *
138
+ * 2. remove kick_handler
139
+ *
140
+ * 3. wait for the kick handler to be finished
141
+ *
142
+ * 4. wait for the current vhost-user msg to be finished processing
143
+ */
144
+
145
+ QIOChannelSocket *sioc = server->sioc;
146
+ /* When this is set vu_client_trip will stop new processing vhost-user message */
147
+ server->sioc = NULL;
148
+
149
+ VuFdWatch *vu_fd_watch, *next;
150
+ QTAILQ_FOREACH_SAFE(vu_fd_watch, &server->vu_fd_watches, next, next) {
151
+ aio_set_fd_handler(server->ioc->ctx, vu_fd_watch->fd, true, NULL,
152
+ NULL, NULL, NULL);
153
+ }
154
+
155
+ while (!QTAILQ_EMPTY(&server->vu_fd_watches)) {
156
+ QTAILQ_FOREACH_SAFE(vu_fd_watch, &server->vu_fd_watches, next, next) {
157
+ if (!vu_fd_watch->processing) {
158
+ QTAILQ_REMOVE(&server->vu_fd_watches, vu_fd_watch, next);
159
+ g_free(vu_fd_watch);
160
+ }
161
+ }
162
+ }
163
+
164
+ while (server->processing_msg) {
165
+ if (server->ioc->read_coroutine) {
166
+ server->ioc->read_coroutine = NULL;
167
+ qio_channel_set_aio_fd_handler(server->ioc, server->ioc->ctx, NULL,
168
+ NULL, server->ioc);
169
+ server->processing_msg = false;
170
+ }
171
+ }
172
+
173
+ vu_deinit(&server->vu_dev);
174
+ object_unref(OBJECT(sioc));
175
+ object_unref(OBJECT(server->ioc));
176
+}
177
+
178
+static void panic_cb(VuDev *vu_dev, const char *buf)
179
+{
180
+ VuServer *server = container_of(vu_dev, VuServer, vu_dev);
181
+
182
+ /* avoid while loop in close_client */
183
+ server->processing_msg = false;
184
+
185
+ if (buf) {
186
+ error_report("vu_panic: %s", buf);
187
+ }
188
+
189
+ if (server->sioc) {
190
+ close_client(server);
191
+ }
192
+
193
+ if (server->device_panic_notifier) {
194
+ server->device_panic_notifier(server);
195
+ }
196
+
197
+ /*
198
+ * Set the callback function for network listener so another
199
+ * vhost-user client can connect to this server
200
+ */
201
+ qio_net_listener_set_client_func(server->listener,
202
+ vu_accept,
203
+ server,
204
+ NULL);
205
+}
206
+
207
+static bool coroutine_fn
208
+vu_message_read(VuDev *vu_dev, int conn_fd, VhostUserMsg *vmsg)
209
+{
210
+ struct iovec iov = {
211
+ .iov_base = (char *)vmsg,
212
+ .iov_len = VHOST_USER_HDR_SIZE,
213
+ };
214
+ int rc, read_bytes = 0;
215
+ Error *local_err = NULL;
216
+ /*
217
+ * Store fds/nfds returned from qio_channel_readv_full into
218
+ * temporary variables.
219
+ *
220
+ * VhostUserMsg is a packed structure, gcc will complain about passing
221
+ * pointer to a packed structure member if we pass &VhostUserMsg.fd_num
222
+ * and &VhostUserMsg.fds directly when calling qio_channel_readv_full,
223
+ * thus two temporary variables nfds and fds are used here.
224
+ */
225
+ size_t nfds = 0, nfds_t = 0;
226
+ const size_t max_fds = G_N_ELEMENTS(vmsg->fds);
227
+ int *fds_t = NULL;
228
+ VuServer *server = container_of(vu_dev, VuServer, vu_dev);
229
+ QIOChannel *ioc = server->ioc;
230
+
231
+ if (!ioc) {
232
+ error_report_err(local_err);
233
+ goto fail;
234
+ }
235
+
236
+ assert(qemu_in_coroutine());
237
+ do {
238
+ /*
239
+ * qio_channel_readv_full may have short reads, keeping calling it
240
+ * until getting VHOST_USER_HDR_SIZE or 0 bytes in total
241
+ */
242
+ rc = qio_channel_readv_full(ioc, &iov, 1, &fds_t, &nfds_t, &local_err);
243
+ if (rc < 0) {
244
+ if (rc == QIO_CHANNEL_ERR_BLOCK) {
245
+ qio_channel_yield(ioc, G_IO_IN);
246
+ continue;
247
+ } else {
248
+ error_report_err(local_err);
249
+ return false;
250
+ }
251
+ }
252
+ read_bytes += rc;
253
+ if (nfds_t > 0) {
254
+ if (nfds + nfds_t > max_fds) {
255
+ error_report("A maximum of %zu fds are allowed, "
256
+ "however got %lu fds now",
257
+ max_fds, nfds + nfds_t);
258
+ goto fail;
259
+ }
260
+ memcpy(vmsg->fds + nfds, fds_t,
261
+ nfds_t *sizeof(vmsg->fds[0]));
262
+ nfds += nfds_t;
263
+ g_free(fds_t);
264
+ }
265
+ if (read_bytes == VHOST_USER_HDR_SIZE || rc == 0) {
266
+ break;
267
+ }
268
+ iov.iov_base = (char *)vmsg + read_bytes;
269
+ iov.iov_len = VHOST_USER_HDR_SIZE - read_bytes;
270
+ } while (true);
271
+
272
+ vmsg->fd_num = nfds;
273
+ /* qio_channel_readv_full will make socket fds blocking, unblock them */
274
+ vmsg_unblock_fds(vmsg);
275
+ if (vmsg->size > sizeof(vmsg->payload)) {
276
+ error_report("Error: too big message request: %d, "
277
+ "size: vmsg->size: %u, "
278
+ "while sizeof(vmsg->payload) = %zu",
279
+ vmsg->request, vmsg->size, sizeof(vmsg->payload));
280
+ goto fail;
281
+ }
282
+
283
+ struct iovec iov_payload = {
284
+ .iov_base = (char *)&vmsg->payload,
285
+ .iov_len = vmsg->size,
286
+ };
287
+ if (vmsg->size) {
288
+ rc = qio_channel_readv_all_eof(ioc, &iov_payload, 1, &local_err);
289
+ if (rc == -1) {
290
+ error_report_err(local_err);
291
+ goto fail;
292
+ }
293
+ }
294
+
295
+ return true;
296
+
297
+fail:
298
+ vmsg_close_fds(vmsg);
299
+
300
+ return false;
301
+}
302
+
303
+
304
+static void vu_client_start(VuServer *server);
305
+static coroutine_fn void vu_client_trip(void *opaque)
306
+{
307
+ VuServer *server = opaque;
308
+
309
+ while (!server->aio_context_changed && server->sioc) {
310
+ server->processing_msg = true;
311
+ vu_dispatch(&server->vu_dev);
312
+ server->processing_msg = false;
313
+ }
314
+
315
+ if (server->aio_context_changed && server->sioc) {
316
+ server->aio_context_changed = false;
317
+ vu_client_start(server);
318
+ }
319
+}
320
+
321
+static void vu_client_start(VuServer *server)
322
+{
323
+ server->co_trip = qemu_coroutine_create(vu_client_trip, server);
324
+ aio_co_enter(server->ctx, server->co_trip);
325
+}
326
+
327
+/*
328
+ * a wrapper for vu_kick_cb
329
+ *
330
+ * since aio_dispatch can only pass one user data pointer to the
331
+ * callback function, pack VuDev and pvt into a struct. Then unpack it
332
+ * and pass them to vu_kick_cb
333
+ */
334
+static void kick_handler(void *opaque)
335
+{
336
+ VuFdWatch *vu_fd_watch = opaque;
337
+ vu_fd_watch->processing = true;
338
+ vu_fd_watch->cb(vu_fd_watch->vu_dev, 0, vu_fd_watch->pvt);
339
+ vu_fd_watch->processing = false;
340
+}
341
+
342
+
343
+static VuFdWatch *find_vu_fd_watch(VuServer *server, int fd)
344
+{
345
+
346
+ VuFdWatch *vu_fd_watch, *next;
347
+ QTAILQ_FOREACH_SAFE(vu_fd_watch, &server->vu_fd_watches, next, next) {
348
+ if (vu_fd_watch->fd == fd) {
349
+ return vu_fd_watch;
350
+ }
351
+ }
352
+ return NULL;
353
+}
354
+
355
+static void
356
+set_watch(VuDev *vu_dev, int fd, int vu_evt,
357
+ vu_watch_cb cb, void *pvt)
358
+{
359
+
360
+ VuServer *server = container_of(vu_dev, VuServer, vu_dev);
361
+ g_assert(vu_dev);
362
+ g_assert(fd >= 0);
363
+ g_assert(cb);
364
+
365
+ VuFdWatch *vu_fd_watch = find_vu_fd_watch(server, fd);
366
+
367
+ if (!vu_fd_watch) {
368
+ VuFdWatch *vu_fd_watch = g_new0(VuFdWatch, 1);
369
+
370
+ QTAILQ_INSERT_TAIL(&server->vu_fd_watches, vu_fd_watch, next);
371
+
372
+ vu_fd_watch->fd = fd;
373
+ vu_fd_watch->cb = cb;
374
+ qemu_set_nonblock(fd);
375
+ aio_set_fd_handler(server->ioc->ctx, fd, true, kick_handler,
376
+ NULL, NULL, vu_fd_watch);
377
+ vu_fd_watch->vu_dev = vu_dev;
378
+ vu_fd_watch->pvt = pvt;
379
+ }
380
+}
381
+
382
+
383
+static void remove_watch(VuDev *vu_dev, int fd)
384
+{
385
+ VuServer *server;
386
+ g_assert(vu_dev);
387
+ g_assert(fd >= 0);
388
+
389
+ server = container_of(vu_dev, VuServer, vu_dev);
390
+
391
+ VuFdWatch *vu_fd_watch = find_vu_fd_watch(server, fd);
392
+
393
+ if (!vu_fd_watch) {
394
+ return;
395
+ }
396
+ aio_set_fd_handler(server->ioc->ctx, fd, true, NULL, NULL, NULL, NULL);
397
+
398
+ QTAILQ_REMOVE(&server->vu_fd_watches, vu_fd_watch, next);
399
+ g_free(vu_fd_watch);
400
+}
401
+
402
+
403
+static void vu_accept(QIONetListener *listener, QIOChannelSocket *sioc,
404
+ gpointer opaque)
405
+{
406
+ VuServer *server = opaque;
407
+
408
+ if (server->sioc) {
409
+ warn_report("Only one vhost-user client is allowed to "
410
+ "connect the server one time");
411
+ return;
412
+ }
413
+
414
+ if (!vu_init(&server->vu_dev, server->max_queues, sioc->fd, panic_cb,
415
+ vu_message_read, set_watch, remove_watch, server->vu_iface)) {
416
+ error_report("Failed to initialize libvhost-user");
417
+ return;
418
+ }
419
+
420
+ /*
421
+ * Unset the callback function for network listener to make another
422
+ * vhost-user client keeping waiting until this client disconnects
423
+ */
424
+ qio_net_listener_set_client_func(server->listener,
425
+ NULL,
426
+ NULL,
427
+ NULL);
428
+ server->sioc = sioc;
429
+ /*
430
+ * Increase the object reference, so sioc will not freed by
431
+ * qio_net_listener_channel_func which will call object_unref(OBJECT(sioc))
432
+ */
433
+ object_ref(OBJECT(server->sioc));
434
+ qio_channel_set_name(QIO_CHANNEL(sioc), "vhost-user client");
435
+ server->ioc = QIO_CHANNEL(sioc);
436
+ object_ref(OBJECT(server->ioc));
437
+ qio_channel_attach_aio_context(server->ioc, server->ctx);
438
+ qio_channel_set_blocking(QIO_CHANNEL(server->sioc), false, NULL);
439
+ vu_client_start(server);
440
+}
441
+
442
+
443
+void vhost_user_server_stop(VuServer *server)
444
+{
445
+ if (server->sioc) {
446
+ close_client(server);
447
+ }
448
+
449
+ if (server->listener) {
450
+ qio_net_listener_disconnect(server->listener);
451
+ object_unref(OBJECT(server->listener));
452
+ }
453
+
454
+}
455
+
456
+void vhost_user_server_set_aio_context(VuServer *server, AioContext *ctx)
457
+{
458
+ VuFdWatch *vu_fd_watch, *next;
459
+ void *opaque = NULL;
460
+ IOHandler *io_read = NULL;
461
+ bool attach;
462
+
463
+ server->ctx = ctx ? ctx : qemu_get_aio_context();
464
+
465
+ if (!server->sioc) {
466
+ /* not yet serving any client*/
467
+ return;
468
+ }
469
+
470
+ if (ctx) {
471
+ qio_channel_attach_aio_context(server->ioc, ctx);
472
+ server->aio_context_changed = true;
473
+ io_read = kick_handler;
474
+ attach = true;
475
+ } else {
476
+ qio_channel_detach_aio_context(server->ioc);
477
+ /* server->ioc->ctx keeps the old AioConext */
478
+ ctx = server->ioc->ctx;
479
+ attach = false;
480
+ }
481
+
482
+ QTAILQ_FOREACH_SAFE(vu_fd_watch, &server->vu_fd_watches, next, next) {
483
+ if (vu_fd_watch->cb) {
484
+ opaque = attach ? vu_fd_watch : NULL;
485
+ aio_set_fd_handler(ctx, vu_fd_watch->fd, true,
486
+ io_read, NULL, NULL,
487
+ opaque);
488
+ }
489
+ }
490
+}
491
+
492
+
493
+bool vhost_user_server_start(VuServer *server,
494
+ SocketAddress *socket_addr,
495
+ AioContext *ctx,
496
+ uint16_t max_queues,
497
+ DevicePanicNotifierFn *device_panic_notifier,
498
+ const VuDevIface *vu_iface,
499
+ Error **errp)
500
+{
501
+ QIONetListener *listener = qio_net_listener_new();
502
+ if (qio_net_listener_open_sync(listener, socket_addr, 1,
503
+ errp) < 0) {
504
+ object_unref(OBJECT(listener));
505
+ return false;
506
+ }
507
+
508
+ /* zero out unspecified fileds */
509
+ *server = (VuServer) {
510
+ .listener = listener,
511
+ .vu_iface = vu_iface,
512
+ .max_queues = max_queues,
513
+ .ctx = ctx,
514
+ .device_panic_notifier = device_panic_notifier,
515
+ };
516
+
517
+ qio_net_listener_set_name(server->listener, "vhost-user-backend-listener");
518
+
519
+ qio_net_listener_set_client_func(server->listener,
520
+ vu_accept,
521
+ server,
522
+ NULL);
523
+
524
+ QTAILQ_INIT(&server->vu_fd_watches);
525
+ return true;
526
+}
527
diff --git a/util/meson.build b/util/meson.build
528
index XXXXXXX..XXXXXXX 100644
529
--- a/util/meson.build
530
+++ b/util/meson.build
531
@@ -XXX,XX +XXX,XX @@ if have_block
532
util_ss.add(files('main-loop.c'))
533
util_ss.add(files('nvdimm-utils.c'))
534
util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c'))
535
+ util_ss.add(when: 'CONFIG_LINUX', if_true: files('vhost-user-server.c'))
536
util_ss.add(files('qemu-coroutine-sleep.c'))
537
util_ss.add(files('qemu-co-shared-resource.c'))
538
util_ss.add(files('thread-pool.c', 'qemu-timer.c'))
539
--
540
2.26.2
541
diff view generated by jsdifflib
New patch
1
From: Coiby Xu <coiby.xu@gmail.com>
1
2
3
Move the constants from hw/core/qdev-properties.c to
4
util/block-helpers.h so that knowledge of the min/max values is
5
6
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
7
Signed-off-by: Coiby Xu <coiby.xu@gmail.com>
8
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
9
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
10
Acked-by: Eduardo Habkost <ehabkost@redhat.com>
11
Message-id: 20200918080912.321299-5-coiby.xu@gmail.com
12
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
13
---
14
util/block-helpers.h | 19 +++++++++++++
15
hw/core/qdev-properties-system.c | 31 ++++-----------------
16
util/block-helpers.c | 46 ++++++++++++++++++++++++++++++++
17
util/meson.build | 1 +
18
4 files changed, 71 insertions(+), 26 deletions(-)
19
create mode 100644 util/block-helpers.h
20
create mode 100644 util/block-helpers.c
21
22
diff --git a/util/block-helpers.h b/util/block-helpers.h
23
new file mode 100644
24
index XXXXXXX..XXXXXXX
25
--- /dev/null
26
+++ b/util/block-helpers.h
27
@@ -XXX,XX +XXX,XX @@
28
+#ifndef BLOCK_HELPERS_H
29
+#define BLOCK_HELPERS_H
30
+
31
+#include "qemu/units.h"
32
+
33
+/* lower limit is sector size */
34
+#define MIN_BLOCK_SIZE INT64_C(512)
35
+#define MIN_BLOCK_SIZE_STR "512 B"
36
+/*
37
+ * upper limit is arbitrary, 2 MiB looks sufficient for all sensible uses, and
38
+ * matches qcow2 cluster size limit
39
+ */
40
+#define MAX_BLOCK_SIZE (2 * MiB)
41
+#define MAX_BLOCK_SIZE_STR "2 MiB"
42
+
43
+void check_block_size(const char *id, const char *name, int64_t value,
44
+ Error **errp);
45
+
46
+#endif /* BLOCK_HELPERS_H */
47
diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c
48
index XXXXXXX..XXXXXXX 100644
49
--- a/hw/core/qdev-properties-system.c
50
+++ b/hw/core/qdev-properties-system.c
51
@@ -XXX,XX +XXX,XX @@
52
#include "sysemu/blockdev.h"
53
#include "net/net.h"
54
#include "hw/pci/pci.h"
55
+#include "util/block-helpers.h"
56
57
static bool check_prop_still_unset(DeviceState *dev, const char *name,
58
const void *old_val, const char *new_val,
59
@@ -XXX,XX +XXX,XX @@ const PropertyInfo qdev_prop_losttickpolicy = {
60
61
/* --- blocksize --- */
62
63
-/* lower limit is sector size */
64
-#define MIN_BLOCK_SIZE 512
65
-#define MIN_BLOCK_SIZE_STR "512 B"
66
-/*
67
- * upper limit is arbitrary, 2 MiB looks sufficient for all sensible uses, and
68
- * matches qcow2 cluster size limit
69
- */
70
-#define MAX_BLOCK_SIZE (2 * MiB)
71
-#define MAX_BLOCK_SIZE_STR "2 MiB"
72
-
73
static void set_blocksize(Object *obj, Visitor *v, const char *name,
74
void *opaque, Error **errp)
75
{
76
@@ -XXX,XX +XXX,XX @@ static void set_blocksize(Object *obj, Visitor *v, const char *name,
77
Property *prop = opaque;
78
uint32_t *ptr = qdev_get_prop_ptr(dev, prop);
79
uint64_t value;
80
+ Error *local_err = NULL;
81
82
if (dev->realized) {
83
qdev_prop_set_after_realize(dev, name, errp);
84
@@ -XXX,XX +XXX,XX @@ static void set_blocksize(Object *obj, Visitor *v, const char *name,
85
if (!visit_type_size(v, name, &value, errp)) {
86
return;
87
}
88
- /* value of 0 means "unset" */
89
- if (value && (value < MIN_BLOCK_SIZE || value > MAX_BLOCK_SIZE)) {
90
- error_setg(errp,
91
- "Property %s.%s doesn't take value %" PRIu64
92
- " (minimum: " MIN_BLOCK_SIZE_STR
93
- ", maximum: " MAX_BLOCK_SIZE_STR ")",
94
- dev->id ? : "", name, value);
95
+ check_block_size(dev->id ? : "", name, value, &local_err);
96
+ if (local_err) {
97
+ error_propagate(errp, local_err);
98
return;
99
}
100
-
101
- /* We rely on power-of-2 blocksizes for bitmasks */
102
- if ((value & (value - 1)) != 0) {
103
- error_setg(errp,
104
- "Property %s.%s doesn't take value '%" PRId64 "', "
105
- "it's not a power of 2", dev->id ?: "", name, (int64_t)value);
106
- return;
107
- }
108
-
109
*ptr = value;
110
}
111
112
diff --git a/util/block-helpers.c b/util/block-helpers.c
113
new file mode 100644
114
index XXXXXXX..XXXXXXX
115
--- /dev/null
116
+++ b/util/block-helpers.c
117
@@ -XXX,XX +XXX,XX @@
118
+/*
119
+ * Block utility functions
120
+ *
121
+ * Copyright IBM, Corp. 2011
122
+ * Copyright (c) 2020 Coiby Xu <coiby.xu@gmail.com>
123
+ *
124
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
125
+ * See the COPYING file in the top-level directory.
126
+ */
127
+
128
+#include "qemu/osdep.h"
129
+#include "qapi/error.h"
130
+#include "qapi/qmp/qerror.h"
131
+#include "block-helpers.h"
132
+
133
+/**
134
+ * check_block_size:
135
+ * @id: The unique ID of the object
136
+ * @name: The name of the property being validated
137
+ * @value: The block size in bytes
138
+ * @errp: A pointer to an area to store an error
139
+ *
140
+ * This function checks that the block size meets the following conditions:
141
+ * 1. At least MIN_BLOCK_SIZE
142
+ * 2. No larger than MAX_BLOCK_SIZE
143
+ * 3. A power of 2
144
+ */
145
+void check_block_size(const char *id, const char *name, int64_t value,
146
+ Error **errp)
147
+{
148
+ /* value of 0 means "unset" */
149
+ if (value && (value < MIN_BLOCK_SIZE || value > MAX_BLOCK_SIZE)) {
150
+ error_setg(errp, QERR_PROPERTY_VALUE_OUT_OF_RANGE,
151
+ id, name, value, MIN_BLOCK_SIZE, MAX_BLOCK_SIZE);
152
+ return;
153
+ }
154
+
155
+ /* We rely on power-of-2 blocksizes for bitmasks */
156
+ if ((value & (value - 1)) != 0) {
157
+ error_setg(errp,
158
+ "Property %s.%s doesn't take value '%" PRId64
159
+ "', it's not a power of 2",
160
+ id, name, value);
161
+ return;
162
+ }
163
+}
164
diff --git a/util/meson.build b/util/meson.build
165
index XXXXXXX..XXXXXXX 100644
166
--- a/util/meson.build
167
+++ b/util/meson.build
168
@@ -XXX,XX +XXX,XX @@ if have_block
169
util_ss.add(files('nvdimm-utils.c'))
170
util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c'))
171
util_ss.add(when: 'CONFIG_LINUX', if_true: files('vhost-user-server.c'))
172
+ util_ss.add(files('block-helpers.c'))
173
util_ss.add(files('qemu-coroutine-sleep.c'))
174
util_ss.add(files('qemu-co-shared-resource.c'))
175
util_ss.add(files('thread-pool.c', 'qemu-timer.c'))
176
--
177
2.26.2
178
diff view generated by jsdifflib
New patch
1
From: Coiby Xu <coiby.xu@gmail.com>
1
2
3
By making use of libvhost-user, block device drive can be shared to
4
the connected vhost-user client. Only one client can connect to the
5
server one time.
6
7
Since vhost-user-server needs a block drive to be created first, delay
8
the creation of this object.
9
10
Suggested-by: Kevin Wolf <kwolf@redhat.com>
11
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
12
Signed-off-by: Coiby Xu <coiby.xu@gmail.com>
13
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
14
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
15
Message-id: 20200918080912.321299-6-coiby.xu@gmail.com
16
[Shorten "vhost_user_blk_server" string to "vhost_user_blk" to avoid the
17
following compiler warning:
18
../block/export/vhost-user-blk-server.c:178:50: error: ‘%s’ directive output truncated writing 21 bytes into a region of size 20 [-Werror=format-truncation=]
19
--Stefan]
20
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
21
---
22
block/export/vhost-user-blk-server.h | 36 ++
23
block/export/vhost-user-blk-server.c | 661 +++++++++++++++++++++++++++
24
softmmu/vl.c | 4 +
25
block/meson.build | 1 +
26
4 files changed, 702 insertions(+)
27
create mode 100644 block/export/vhost-user-blk-server.h
28
create mode 100644 block/export/vhost-user-blk-server.c
29
30
diff --git a/block/export/vhost-user-blk-server.h b/block/export/vhost-user-blk-server.h
31
new file mode 100644
32
index XXXXXXX..XXXXXXX
33
--- /dev/null
34
+++ b/block/export/vhost-user-blk-server.h
35
@@ -XXX,XX +XXX,XX @@
36
+/*
37
+ * Sharing QEMU block devices via vhost-user protocal
38
+ *
39
+ * Copyright (c) Coiby Xu <coiby.xu@gmail.com>.
40
+ * Copyright (c) 2020 Red Hat, Inc.
41
+ *
42
+ * This work is licensed under the terms of the GNU GPL, version 2 or
43
+ * later. See the COPYING file in the top-level directory.
44
+ */
45
+
46
+#ifndef VHOST_USER_BLK_SERVER_H
47
+#define VHOST_USER_BLK_SERVER_H
48
+#include "util/vhost-user-server.h"
49
+
50
+typedef struct VuBlockDev VuBlockDev;
51
+#define TYPE_VHOST_USER_BLK_SERVER "vhost-user-blk-server"
52
+#define VHOST_USER_BLK_SERVER(obj) \
53
+ OBJECT_CHECK(VuBlockDev, obj, TYPE_VHOST_USER_BLK_SERVER)
54
+
55
+/* vhost user block device */
56
+struct VuBlockDev {
57
+ Object parent_obj;
58
+ char *node_name;
59
+ SocketAddress *addr;
60
+ AioContext *ctx;
61
+ VuServer vu_server;
62
+ bool running;
63
+ uint32_t blk_size;
64
+ BlockBackend *backend;
65
+ QIOChannelSocket *sioc;
66
+ QTAILQ_ENTRY(VuBlockDev) next;
67
+ struct virtio_blk_config blkcfg;
68
+ bool writable;
69
+};
70
+
71
+#endif /* VHOST_USER_BLK_SERVER_H */
72
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
73
new file mode 100644
74
index XXXXXXX..XXXXXXX
75
--- /dev/null
76
+++ b/block/export/vhost-user-blk-server.c
77
@@ -XXX,XX +XXX,XX @@
78
+/*
79
+ * Sharing QEMU block devices via vhost-user protocal
80
+ *
81
+ * Parts of the code based on nbd/server.c.
82
+ *
83
+ * Copyright (c) Coiby Xu <coiby.xu@gmail.com>.
84
+ * Copyright (c) 2020 Red Hat, Inc.
85
+ *
86
+ * This work is licensed under the terms of the GNU GPL, version 2 or
87
+ * later. See the COPYING file in the top-level directory.
88
+ */
89
+#include "qemu/osdep.h"
90
+#include "block/block.h"
91
+#include "vhost-user-blk-server.h"
92
+#include "qapi/error.h"
93
+#include "qom/object_interfaces.h"
94
+#include "sysemu/block-backend.h"
95
+#include "util/block-helpers.h"
96
+
97
+enum {
98
+ VHOST_USER_BLK_MAX_QUEUES = 1,
99
+};
100
+struct virtio_blk_inhdr {
101
+ unsigned char status;
102
+};
103
+
104
+typedef struct VuBlockReq {
105
+ VuVirtqElement *elem;
106
+ int64_t sector_num;
107
+ size_t size;
108
+ struct virtio_blk_inhdr *in;
109
+ struct virtio_blk_outhdr out;
110
+ VuServer *server;
111
+ struct VuVirtq *vq;
112
+} VuBlockReq;
113
+
114
+static void vu_block_req_complete(VuBlockReq *req)
115
+{
116
+ VuDev *vu_dev = &req->server->vu_dev;
117
+
118
+ /* IO size with 1 extra status byte */
119
+ vu_queue_push(vu_dev, req->vq, req->elem, req->size + 1);
120
+ vu_queue_notify(vu_dev, req->vq);
121
+
122
+ if (req->elem) {
123
+ free(req->elem);
124
+ }
125
+
126
+ g_free(req);
127
+}
128
+
129
+static VuBlockDev *get_vu_block_device_by_server(VuServer *server)
130
+{
131
+ return container_of(server, VuBlockDev, vu_server);
132
+}
133
+
134
+static int coroutine_fn
135
+vu_block_discard_write_zeroes(VuBlockReq *req, struct iovec *iov,
136
+ uint32_t iovcnt, uint32_t type)
137
+{
138
+ struct virtio_blk_discard_write_zeroes desc;
139
+ ssize_t size = iov_to_buf(iov, iovcnt, 0, &desc, sizeof(desc));
140
+ if (unlikely(size != sizeof(desc))) {
141
+ error_report("Invalid size %ld, expect %ld", size, sizeof(desc));
142
+ return -EINVAL;
143
+ }
144
+
145
+ VuBlockDev *vdev_blk = get_vu_block_device_by_server(req->server);
146
+ uint64_t range[2] = { le64_to_cpu(desc.sector) << 9,
147
+ le32_to_cpu(desc.num_sectors) << 9 };
148
+ if (type == VIRTIO_BLK_T_DISCARD) {
149
+ if (blk_co_pdiscard(vdev_blk->backend, range[0], range[1]) == 0) {
150
+ return 0;
151
+ }
152
+ } else if (type == VIRTIO_BLK_T_WRITE_ZEROES) {
153
+ if (blk_co_pwrite_zeroes(vdev_blk->backend,
154
+ range[0], range[1], 0) == 0) {
155
+ return 0;
156
+ }
157
+ }
158
+
159
+ return -EINVAL;
160
+}
161
+
162
+static void coroutine_fn vu_block_flush(VuBlockReq *req)
163
+{
164
+ VuBlockDev *vdev_blk = get_vu_block_device_by_server(req->server);
165
+ BlockBackend *backend = vdev_blk->backend;
166
+ blk_co_flush(backend);
167
+}
168
+
169
+struct req_data {
170
+ VuServer *server;
171
+ VuVirtq *vq;
172
+ VuVirtqElement *elem;
173
+};
174
+
175
+static void coroutine_fn vu_block_virtio_process_req(void *opaque)
176
+{
177
+ struct req_data *data = opaque;
178
+ VuServer *server = data->server;
179
+ VuVirtq *vq = data->vq;
180
+ VuVirtqElement *elem = data->elem;
181
+ uint32_t type;
182
+ VuBlockReq *req;
183
+
184
+ VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
185
+ BlockBackend *backend = vdev_blk->backend;
186
+
187
+ struct iovec *in_iov = elem->in_sg;
188
+ struct iovec *out_iov = elem->out_sg;
189
+ unsigned in_num = elem->in_num;
190
+ unsigned out_num = elem->out_num;
191
+ /* refer to hw/block/virtio_blk.c */
192
+ if (elem->out_num < 1 || elem->in_num < 1) {
193
+ error_report("virtio-blk request missing headers");
194
+ free(elem);
195
+ return;
196
+ }
197
+
198
+ req = g_new0(VuBlockReq, 1);
199
+ req->server = server;
200
+ req->vq = vq;
201
+ req->elem = elem;
202
+
203
+ if (unlikely(iov_to_buf(out_iov, out_num, 0, &req->out,
204
+ sizeof(req->out)) != sizeof(req->out))) {
205
+ error_report("virtio-blk request outhdr too short");
206
+ goto err;
207
+ }
208
+
209
+ iov_discard_front(&out_iov, &out_num, sizeof(req->out));
210
+
211
+ if (in_iov[in_num - 1].iov_len < sizeof(struct virtio_blk_inhdr)) {
212
+ error_report("virtio-blk request inhdr too short");
213
+ goto err;
214
+ }
215
+
216
+ /* We always touch the last byte, so just see how big in_iov is. */
217
+ req->in = (void *)in_iov[in_num - 1].iov_base
218
+ + in_iov[in_num - 1].iov_len
219
+ - sizeof(struct virtio_blk_inhdr);
220
+ iov_discard_back(in_iov, &in_num, sizeof(struct virtio_blk_inhdr));
221
+
222
+ type = le32_to_cpu(req->out.type);
223
+ switch (type & ~VIRTIO_BLK_T_BARRIER) {
224
+ case VIRTIO_BLK_T_IN:
225
+ case VIRTIO_BLK_T_OUT: {
226
+ ssize_t ret = 0;
227
+ bool is_write = type & VIRTIO_BLK_T_OUT;
228
+ req->sector_num = le64_to_cpu(req->out.sector);
229
+
230
+ int64_t offset = req->sector_num * vdev_blk->blk_size;
231
+ QEMUIOVector qiov;
232
+ if (is_write) {
233
+ qemu_iovec_init_external(&qiov, out_iov, out_num);
234
+ ret = blk_co_pwritev(backend, offset, qiov.size,
235
+ &qiov, 0);
236
+ } else {
237
+ qemu_iovec_init_external(&qiov, in_iov, in_num);
238
+ ret = blk_co_preadv(backend, offset, qiov.size,
239
+ &qiov, 0);
240
+ }
241
+ if (ret >= 0) {
242
+ req->in->status = VIRTIO_BLK_S_OK;
243
+ } else {
244
+ req->in->status = VIRTIO_BLK_S_IOERR;
245
+ }
246
+ break;
247
+ }
248
+ case VIRTIO_BLK_T_FLUSH:
249
+ vu_block_flush(req);
250
+ req->in->status = VIRTIO_BLK_S_OK;
251
+ break;
252
+ case VIRTIO_BLK_T_GET_ID: {
253
+ size_t size = MIN(iov_size(&elem->in_sg[0], in_num),
254
+ VIRTIO_BLK_ID_BYTES);
255
+ snprintf(elem->in_sg[0].iov_base, size, "%s", "vhost_user_blk");
256
+ req->in->status = VIRTIO_BLK_S_OK;
257
+ req->size = elem->in_sg[0].iov_len;
258
+ break;
259
+ }
260
+ case VIRTIO_BLK_T_DISCARD:
261
+ case VIRTIO_BLK_T_WRITE_ZEROES: {
262
+ int rc;
263
+ rc = vu_block_discard_write_zeroes(req, &elem->out_sg[1],
264
+ out_num, type);
265
+ if (rc == 0) {
266
+ req->in->status = VIRTIO_BLK_S_OK;
267
+ } else {
268
+ req->in->status = VIRTIO_BLK_S_IOERR;
269
+ }
270
+ break;
271
+ }
272
+ default:
273
+ req->in->status = VIRTIO_BLK_S_UNSUPP;
274
+ break;
275
+ }
276
+
277
+ vu_block_req_complete(req);
278
+ return;
279
+
280
+err:
281
+ free(elem);
282
+ g_free(req);
283
+ return;
284
+}
285
+
286
+static void vu_block_process_vq(VuDev *vu_dev, int idx)
287
+{
288
+ VuServer *server;
289
+ VuVirtq *vq;
290
+ struct req_data *req_data;
291
+
292
+ server = container_of(vu_dev, VuServer, vu_dev);
293
+ assert(server);
294
+
295
+ vq = vu_get_queue(vu_dev, idx);
296
+ assert(vq);
297
+ VuVirtqElement *elem;
298
+ while (1) {
299
+ elem = vu_queue_pop(vu_dev, vq, sizeof(VuVirtqElement) +
300
+ sizeof(VuBlockReq));
301
+ if (elem) {
302
+ req_data = g_new0(struct req_data, 1);
303
+ req_data->server = server;
304
+ req_data->vq = vq;
305
+ req_data->elem = elem;
306
+ Coroutine *co = qemu_coroutine_create(vu_block_virtio_process_req,
307
+ req_data);
308
+ aio_co_enter(server->ioc->ctx, co);
309
+ } else {
310
+ break;
311
+ }
312
+ }
313
+}
314
+
315
+static void vu_block_queue_set_started(VuDev *vu_dev, int idx, bool started)
316
+{
317
+ VuVirtq *vq;
318
+
319
+ assert(vu_dev);
320
+
321
+ vq = vu_get_queue(vu_dev, idx);
322
+ vu_set_queue_handler(vu_dev, vq, started ? vu_block_process_vq : NULL);
323
+}
324
+
325
+static uint64_t vu_block_get_features(VuDev *dev)
326
+{
327
+ uint64_t features;
328
+ VuServer *server = container_of(dev, VuServer, vu_dev);
329
+ VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
330
+ features = 1ull << VIRTIO_BLK_F_SIZE_MAX |
331
+ 1ull << VIRTIO_BLK_F_SEG_MAX |
332
+ 1ull << VIRTIO_BLK_F_TOPOLOGY |
333
+ 1ull << VIRTIO_BLK_F_BLK_SIZE |
334
+ 1ull << VIRTIO_BLK_F_FLUSH |
335
+ 1ull << VIRTIO_BLK_F_DISCARD |
336
+ 1ull << VIRTIO_BLK_F_WRITE_ZEROES |
337
+ 1ull << VIRTIO_BLK_F_CONFIG_WCE |
338
+ 1ull << VIRTIO_F_VERSION_1 |
339
+ 1ull << VIRTIO_RING_F_INDIRECT_DESC |
340
+ 1ull << VIRTIO_RING_F_EVENT_IDX |
341
+ 1ull << VHOST_USER_F_PROTOCOL_FEATURES;
342
+
343
+ if (!vdev_blk->writable) {
344
+ features |= 1ull << VIRTIO_BLK_F_RO;
345
+ }
346
+
347
+ return features;
348
+}
349
+
350
+static uint64_t vu_block_get_protocol_features(VuDev *dev)
351
+{
352
+ return 1ull << VHOST_USER_PROTOCOL_F_CONFIG |
353
+ 1ull << VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD;
354
+}
355
+
356
+static int
357
+vu_block_get_config(VuDev *vu_dev, uint8_t *config, uint32_t len)
358
+{
359
+ VuServer *server = container_of(vu_dev, VuServer, vu_dev);
360
+ VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
361
+ memcpy(config, &vdev_blk->blkcfg, len);
362
+
363
+ return 0;
364
+}
365
+
366
+static int
367
+vu_block_set_config(VuDev *vu_dev, const uint8_t *data,
368
+ uint32_t offset, uint32_t size, uint32_t flags)
369
+{
370
+ VuServer *server = container_of(vu_dev, VuServer, vu_dev);
371
+ VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
372
+ uint8_t wce;
373
+
374
+ /* don't support live migration */
375
+ if (flags != VHOST_SET_CONFIG_TYPE_MASTER) {
376
+ return -EINVAL;
377
+ }
378
+
379
+ if (offset != offsetof(struct virtio_blk_config, wce) ||
380
+ size != 1) {
381
+ return -EINVAL;
382
+ }
383
+
384
+ wce = *data;
385
+ vdev_blk->blkcfg.wce = wce;
386
+ blk_set_enable_write_cache(vdev_blk->backend, wce);
387
+ return 0;
388
+}
389
+
390
+/*
391
+ * When the client disconnects, it sends a VHOST_USER_NONE request
392
+ * and vu_process_message will simple call exit which cause the VM
393
+ * to exit abruptly.
394
+ * To avoid this issue, process VHOST_USER_NONE request ahead
395
+ * of vu_process_message.
396
+ *
397
+ */
398
+static int vu_block_process_msg(VuDev *dev, VhostUserMsg *vmsg, int *do_reply)
399
+{
400
+ if (vmsg->request == VHOST_USER_NONE) {
401
+ dev->panic(dev, "disconnect");
402
+ return true;
403
+ }
404
+ return false;
405
+}
406
+
407
+static const VuDevIface vu_block_iface = {
408
+ .get_features = vu_block_get_features,
409
+ .queue_set_started = vu_block_queue_set_started,
410
+ .get_protocol_features = vu_block_get_protocol_features,
411
+ .get_config = vu_block_get_config,
412
+ .set_config = vu_block_set_config,
413
+ .process_msg = vu_block_process_msg,
414
+};
415
+
416
+static void blk_aio_attached(AioContext *ctx, void *opaque)
417
+{
418
+ VuBlockDev *vub_dev = opaque;
419
+ aio_context_acquire(ctx);
420
+ vhost_user_server_set_aio_context(&vub_dev->vu_server, ctx);
421
+ aio_context_release(ctx);
422
+}
423
+
424
+static void blk_aio_detach(void *opaque)
425
+{
426
+ VuBlockDev *vub_dev = opaque;
427
+ AioContext *ctx = vub_dev->vu_server.ctx;
428
+ aio_context_acquire(ctx);
429
+ vhost_user_server_set_aio_context(&vub_dev->vu_server, NULL);
430
+ aio_context_release(ctx);
431
+}
432
+
433
+static void
434
+vu_block_initialize_config(BlockDriverState *bs,
435
+ struct virtio_blk_config *config, uint32_t blk_size)
436
+{
437
+ config->capacity = bdrv_getlength(bs) >> BDRV_SECTOR_BITS;
438
+ config->blk_size = blk_size;
439
+ config->size_max = 0;
440
+ config->seg_max = 128 - 2;
441
+ config->min_io_size = 1;
442
+ config->opt_io_size = 1;
443
+ config->num_queues = VHOST_USER_BLK_MAX_QUEUES;
444
+ config->max_discard_sectors = 32768;
445
+ config->max_discard_seg = 1;
446
+ config->discard_sector_alignment = config->blk_size >> 9;
447
+ config->max_write_zeroes_sectors = 32768;
448
+ config->max_write_zeroes_seg = 1;
449
+}
450
+
451
+static VuBlockDev *vu_block_init(VuBlockDev *vu_block_device, Error **errp)
452
+{
453
+
454
+ BlockBackend *blk;
455
+ Error *local_error = NULL;
456
+ const char *node_name = vu_block_device->node_name;
457
+ bool writable = vu_block_device->writable;
458
+ uint64_t perm = BLK_PERM_CONSISTENT_READ;
459
+ int ret;
460
+
461
+ AioContext *ctx;
462
+
463
+ BlockDriverState *bs = bdrv_lookup_bs(node_name, node_name, &local_error);
464
+
465
+ if (!bs) {
466
+ error_propagate(errp, local_error);
467
+ return NULL;
468
+ }
469
+
470
+ if (bdrv_is_read_only(bs)) {
471
+ writable = false;
472
+ }
473
+
474
+ if (writable) {
475
+ perm |= BLK_PERM_WRITE;
476
+ }
477
+
478
+ ctx = bdrv_get_aio_context(bs);
479
+ aio_context_acquire(ctx);
480
+ bdrv_invalidate_cache(bs, NULL);
481
+ aio_context_release(ctx);
482
+
483
+ /*
484
+ * Don't allow resize while the vhost user server is running,
485
+ * otherwise we don't care what happens with the node.
486
+ */
487
+ blk = blk_new(bdrv_get_aio_context(bs), perm,
488
+ BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED |
489
+ BLK_PERM_WRITE | BLK_PERM_GRAPH_MOD);
490
+ ret = blk_insert_bs(blk, bs, errp);
491
+
492
+ if (ret < 0) {
493
+ goto fail;
494
+ }
495
+
496
+ blk_set_enable_write_cache(blk, false);
497
+
498
+ blk_set_allow_aio_context_change(blk, true);
499
+
500
+ vu_block_device->blkcfg.wce = 0;
501
+ vu_block_device->backend = blk;
502
+ if (!vu_block_device->blk_size) {
503
+ vu_block_device->blk_size = BDRV_SECTOR_SIZE;
504
+ }
505
+ vu_block_device->blkcfg.blk_size = vu_block_device->blk_size;
506
+ blk_set_guest_block_size(blk, vu_block_device->blk_size);
507
+ vu_block_initialize_config(bs, &vu_block_device->blkcfg,
508
+ vu_block_device->blk_size);
509
+ return vu_block_device;
510
+
511
+fail:
512
+ blk_unref(blk);
513
+ return NULL;
514
+}
515
+
516
+static void vu_block_deinit(VuBlockDev *vu_block_device)
517
+{
518
+ if (vu_block_device->backend) {
519
+ blk_remove_aio_context_notifier(vu_block_device->backend, blk_aio_attached,
520
+ blk_aio_detach, vu_block_device);
521
+ }
522
+
523
+ blk_unref(vu_block_device->backend);
524
+}
525
+
526
+static void vhost_user_blk_server_stop(VuBlockDev *vu_block_device)
527
+{
528
+ vhost_user_server_stop(&vu_block_device->vu_server);
529
+ vu_block_deinit(vu_block_device);
530
+}
531
+
532
+static void vhost_user_blk_server_start(VuBlockDev *vu_block_device,
533
+ Error **errp)
534
+{
535
+ AioContext *ctx;
536
+ SocketAddress *addr = vu_block_device->addr;
537
+
538
+ if (!vu_block_init(vu_block_device, errp)) {
539
+ return;
540
+ }
541
+
542
+ ctx = bdrv_get_aio_context(blk_bs(vu_block_device->backend));
543
+
544
+ if (!vhost_user_server_start(&vu_block_device->vu_server, addr, ctx,
545
+ VHOST_USER_BLK_MAX_QUEUES,
546
+ NULL, &vu_block_iface,
547
+ errp)) {
548
+ goto error;
549
+ }
550
+
551
+ blk_add_aio_context_notifier(vu_block_device->backend, blk_aio_attached,
552
+ blk_aio_detach, vu_block_device);
553
+ vu_block_device->running = true;
554
+ return;
555
+
556
+ error:
557
+ vu_block_deinit(vu_block_device);
558
+}
559
+
560
+static bool vu_prop_modifiable(VuBlockDev *vus, Error **errp)
561
+{
562
+ if (vus->running) {
563
+ error_setg(errp, "The property can't be modified "
564
+ "while the server is running");
565
+ return false;
566
+ }
567
+ return true;
568
+}
569
+
570
+static void vu_set_node_name(Object *obj, const char *value, Error **errp)
571
+{
572
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
573
+
574
+ if (!vu_prop_modifiable(vus, errp)) {
575
+ return;
576
+ }
577
+
578
+ if (vus->node_name) {
579
+ g_free(vus->node_name);
580
+ }
581
+
582
+ vus->node_name = g_strdup(value);
583
+}
584
+
585
+static char *vu_get_node_name(Object *obj, Error **errp)
586
+{
587
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
588
+ return g_strdup(vus->node_name);
589
+}
590
+
591
+static void free_socket_addr(SocketAddress *addr)
592
+{
593
+ g_free(addr->u.q_unix.path);
594
+ g_free(addr);
595
+}
596
+
597
+static void vu_set_unix_socket(Object *obj, const char *value,
598
+ Error **errp)
599
+{
600
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
601
+
602
+ if (!vu_prop_modifiable(vus, errp)) {
603
+ return;
604
+ }
605
+
606
+ if (vus->addr) {
607
+ free_socket_addr(vus->addr);
608
+ }
609
+
610
+ SocketAddress *addr = g_new0(SocketAddress, 1);
611
+ addr->type = SOCKET_ADDRESS_TYPE_UNIX;
612
+ addr->u.q_unix.path = g_strdup(value);
613
+ vus->addr = addr;
614
+}
615
+
616
+static char *vu_get_unix_socket(Object *obj, Error **errp)
617
+{
618
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
619
+ return g_strdup(vus->addr->u.q_unix.path);
620
+}
621
+
622
+static bool vu_get_block_writable(Object *obj, Error **errp)
623
+{
624
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
625
+ return vus->writable;
626
+}
627
+
628
+static void vu_set_block_writable(Object *obj, bool value, Error **errp)
629
+{
630
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
631
+
632
+ if (!vu_prop_modifiable(vus, errp)) {
633
+ return;
634
+ }
635
+
636
+ vus->writable = value;
637
+}
638
+
639
+static void vu_get_blk_size(Object *obj, Visitor *v, const char *name,
640
+ void *opaque, Error **errp)
641
+{
642
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
643
+ uint32_t value = vus->blk_size;
644
+
645
+ visit_type_uint32(v, name, &value, errp);
646
+}
647
+
648
+static void vu_set_blk_size(Object *obj, Visitor *v, const char *name,
649
+ void *opaque, Error **errp)
650
+{
651
+ VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
652
+
653
+ Error *local_err = NULL;
654
+ uint32_t value;
655
+
656
+ if (!vu_prop_modifiable(vus, errp)) {
657
+ return;
658
+ }
659
+
660
+ visit_type_uint32(v, name, &value, &local_err);
661
+ if (local_err) {
662
+ goto out;
663
+ }
664
+
665
+ check_block_size(object_get_typename(obj), name, value, &local_err);
666
+ if (local_err) {
667
+ goto out;
668
+ }
669
+
670
+ vus->blk_size = value;
671
+
672
+out:
673
+ error_propagate(errp, local_err);
674
+}
675
+
676
+static void vhost_user_blk_server_instance_finalize(Object *obj)
677
+{
678
+ VuBlockDev *vub = VHOST_USER_BLK_SERVER(obj);
679
+
680
+ vhost_user_blk_server_stop(vub);
681
+
682
+ /*
683
+ * Unlike object_property_add_str, object_class_property_add_str
684
+ * doesn't have a release method. Thus manual memory freeing is
685
+ * needed.
686
+ */
687
+ free_socket_addr(vub->addr);
688
+ g_free(vub->node_name);
689
+}
690
+
691
+static void vhost_user_blk_server_complete(UserCreatable *obj, Error **errp)
692
+{
693
+ VuBlockDev *vub = VHOST_USER_BLK_SERVER(obj);
694
+
695
+ vhost_user_blk_server_start(vub, errp);
696
+}
697
+
698
+static void vhost_user_blk_server_class_init(ObjectClass *klass,
699
+ void *class_data)
700
+{
701
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
702
+ ucc->complete = vhost_user_blk_server_complete;
703
+
704
+ object_class_property_add_bool(klass, "writable",
705
+ vu_get_block_writable,
706
+ vu_set_block_writable);
707
+
708
+ object_class_property_add_str(klass, "node-name",
709
+ vu_get_node_name,
710
+ vu_set_node_name);
711
+
712
+ object_class_property_add_str(klass, "unix-socket",
713
+ vu_get_unix_socket,
714
+ vu_set_unix_socket);
715
+
716
+ object_class_property_add(klass, "logical-block-size", "uint32",
717
+ vu_get_blk_size, vu_set_blk_size,
718
+ NULL, NULL);
719
+}
720
+
721
+static const TypeInfo vhost_user_blk_server_info = {
722
+ .name = TYPE_VHOST_USER_BLK_SERVER,
723
+ .parent = TYPE_OBJECT,
724
+ .instance_size = sizeof(VuBlockDev),
725
+ .instance_finalize = vhost_user_blk_server_instance_finalize,
726
+ .class_init = vhost_user_blk_server_class_init,
727
+ .interfaces = (InterfaceInfo[]) {
728
+ {TYPE_USER_CREATABLE},
729
+ {}
730
+ },
731
+};
732
+
733
+static void vhost_user_blk_server_register_types(void)
734
+{
735
+ type_register_static(&vhost_user_blk_server_info);
736
+}
737
+
738
+type_init(vhost_user_blk_server_register_types)
739
diff --git a/softmmu/vl.c b/softmmu/vl.c
740
index XXXXXXX..XXXXXXX 100644
741
--- a/softmmu/vl.c
742
+++ b/softmmu/vl.c
743
@@ -XXX,XX +XXX,XX @@ static bool object_create_initial(const char *type, QemuOpts *opts)
744
}
745
#endif
746
747
+ /* Reason: vhost-user-blk-server property "node-name" */
748
+ if (g_str_equal(type, "vhost-user-blk-server")) {
749
+ return false;
750
+ }
751
/*
752
* Reason: filter-* property "netdev" etc.
753
*/
754
diff --git a/block/meson.build b/block/meson.build
755
index XXXXXXX..XXXXXXX 100644
756
--- a/block/meson.build
757
+++ b/block/meson.build
758
@@ -XXX,XX +XXX,XX @@ block_ss.add(when: 'CONFIG_WIN32', if_true: files('file-win32.c', 'win32-aio.c')
759
block_ss.add(when: 'CONFIG_POSIX', if_true: [files('file-posix.c'), coref, iokit])
760
block_ss.add(when: 'CONFIG_LIBISCSI', if_true: files('iscsi-opts.c'))
761
block_ss.add(when: 'CONFIG_LINUX', if_true: files('nvme.c'))
762
+block_ss.add(when: 'CONFIG_LINUX', if_true: files('export/vhost-user-blk-server.c', '../contrib/libvhost-user/libvhost-user.c'))
763
block_ss.add(when: 'CONFIG_REPLICATION', if_true: files('replication.c'))
764
block_ss.add(when: 'CONFIG_SHEEPDOG', if_true: files('sheepdog.c'))
765
block_ss.add(when: ['CONFIG_LINUX_AIO', libaio], if_true: files('linux-aio.c'))
766
--
767
2.26.2
768
diff view generated by jsdifflib
1
The minimum Python version supported by QEMU is 2.6. The argparse
1
From: Coiby Xu <coiby.xu@gmail.com>
2
standard library module was only added in Python 2.7. Many scripts
3
would like to use argparse because it supports command-line
4
sub-commands.
5
2
6
This patch adds argparse. See the top of argparse.py for details.
3
This test case has the same tests as tests/virtio-blk-test.c except for
4
tests have block_resize. Since vhost-user server can only server one
5
client one time, two instances of vhost-user-blk-server are started by
6
qemu-storage-daemon for the hotplug test.
7
7
8
Suggested-by: Daniel P. Berrange <berrange@redhat.com>
8
In order to not block scripts/tap-driver.pl, vhost-user-blk-server will
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9
send "quit" command to qemu-storage-daemon's QMP monitor. So a function
10
Acked-by: John Snow <jsnow@redhat.com>
10
is added to libqtest.c to establish socket connection with socket
11
Message-id: 20170825155732.15665-2-stefanha@redhat.com
11
server.
12
13
Suggested-by: Thomas Huth <thuth@redhat.com>
14
Signed-off-by: Coiby Xu <coiby.xu@gmail.com>
15
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
16
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
17
Message-id: 20200918080912.321299-7-coiby.xu@gmail.com
18
[Update meson.build to only test when CONFIG_TOOLS has built
19
qemu-storage-daemon. This prevents CI failures with --disable-tools.
20
--Stefan]
12
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
21
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
13
---
22
---
14
COPYING.PYTHON | 270 ++++++
23
tests/qtest/libqos/libqtest.h | 17 +
15
scripts/argparse.py | 2406 +++++++++++++++++++++++++++++++++++++++++++++++++++
24
tests/qtest/libqos/vhost-user-blk.h | 48 ++
16
2 files changed, 2676 insertions(+)
25
tests/qtest/libqos/vhost-user-blk.c | 129 +++++
17
create mode 100644 COPYING.PYTHON
26
tests/qtest/libqtest.c | 36 +-
18
create mode 100644 scripts/argparse.py
27
tests/qtest/vhost-user-blk-test.c | 751 ++++++++++++++++++++++++++++
28
tests/qtest/libqos/meson.build | 1 +
29
tests/qtest/meson.build | 4 +-
30
7 files changed, 983 insertions(+), 3 deletions(-)
31
create mode 100644 tests/qtest/libqos/vhost-user-blk.h
32
create mode 100644 tests/qtest/libqos/vhost-user-blk.c
33
create mode 100644 tests/qtest/vhost-user-blk-test.c
19
34
20
diff --git a/COPYING.PYTHON b/COPYING.PYTHON
35
diff --git a/tests/qtest/libqos/libqtest.h b/tests/qtest/libqos/libqtest.h
36
index XXXXXXX..XXXXXXX 100644
37
--- a/tests/qtest/libqos/libqtest.h
38
+++ b/tests/qtest/libqos/libqtest.h
39
@@ -XXX,XX +XXX,XX @@ void qtest_qmp_send(QTestState *s, const char *fmt, ...)
40
void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...)
41
GCC_FMT_ATTR(2, 3);
42
43
+/**
44
+ * qtest_socket_client:
45
+ * @server_socket_path: the socket server's path
46
+ *
47
+ * Connect to a socket server.
48
+ */
49
+int qtest_socket_client(char *server_socket_path);
50
+
51
+/**
52
+ * qtest_create_state_with_qmp_fd:
53
+ * @fd: socket fd
54
+ *
55
+ * Wrap socket fd in QTestState to make use of qtest_qmp*
56
+ * functions
57
+ */
58
+QTestState *qtest_create_state_with_qmp_fd(int fd);
59
+
60
/**
61
* qtest_vqmp_fds:
62
* @s: #QTestState instance to operate on.
63
diff --git a/tests/qtest/libqos/vhost-user-blk.h b/tests/qtest/libqos/vhost-user-blk.h
21
new file mode 100644
64
new file mode 100644
22
index XXXXXXX..XXXXXXX
65
index XXXXXXX..XXXXXXX
23
--- /dev/null
66
--- /dev/null
24
+++ b/COPYING.PYTHON
67
+++ b/tests/qtest/libqos/vhost-user-blk.h
25
@@ -XXX,XX +XXX,XX @@
68
@@ -XXX,XX +XXX,XX @@
26
+A. HISTORY OF THE SOFTWARE
69
+/*
27
+==========================
70
+ * libqos driver framework
28
+
71
+ *
29
+Python was created in the early 1990s by Guido van Rossum at Stichting
72
+ * Based on tests/qtest/libqos/virtio-blk.c
30
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
73
+ *
31
+as a successor of a language called ABC. Guido remains Python's
74
+ * Copyright (c) 2020 Coiby Xu <coiby.xu@gmail.com>
32
+principal author, although it includes many contributions from others.
75
+ *
33
+
76
+ * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
34
+In 1995, Guido continued his work on Python at the Corporation for
77
+ *
35
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
78
+ * This library is free software; you can redistribute it and/or
36
+in Reston, Virginia where he released several versions of the
79
+ * modify it under the terms of the GNU Lesser General Public
37
+software.
80
+ * License version 2 as published by the Free Software Foundation.
38
+
81
+ *
39
+In May 2000, Guido and the Python core development team moved to
82
+ * This library is distributed in the hope that it will be useful,
40
+BeOpen.com to form the BeOpen PythonLabs team. In October of the same
83
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
41
+year, the PythonLabs team moved to Digital Creations (now Zope
84
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
42
+Corporation, see http://www.zope.com). In 2001, the Python Software
85
+ * Lesser General Public License for more details.
43
+Foundation (PSF, see http://www.python.org/psf/) was formed, a
86
+ *
44
+non-profit organization created specifically to own Python-related
87
+ * You should have received a copy of the GNU Lesser General Public
45
+Intellectual Property. Zope Corporation is a sponsoring member of
88
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
46
+the PSF.
89
+ */
47
+
90
+
48
+All Python releases are Open Source (see http://www.opensource.org for
91
+#ifndef TESTS_LIBQOS_VHOST_USER_BLK_H
49
+the Open Source Definition). Historically, most, but not all, Python
92
+#define TESTS_LIBQOS_VHOST_USER_BLK_H
50
+releases have also been GPL-compatible; the table below summarizes
93
+
51
+the various releases.
94
+#include "qgraph.h"
52
+
95
+#include "virtio.h"
53
+ Release Derived Year Owner GPL-
96
+#include "virtio-pci.h"
54
+ from compatible? (1)
97
+
55
+
98
+typedef struct QVhostUserBlk QVhostUserBlk;
56
+ 0.9.0 thru 1.2 1991-1995 CWI yes
99
+typedef struct QVhostUserBlkPCI QVhostUserBlkPCI;
57
+ 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
100
+typedef struct QVhostUserBlkDevice QVhostUserBlkDevice;
58
+ 1.6 1.5.2 2000 CNRI no
101
+
59
+ 2.0 1.6 2000 BeOpen.com no
102
+struct QVhostUserBlk {
60
+ 1.6.1 1.6 2001 CNRI yes (2)
103
+ QVirtioDevice *vdev;
61
+ 2.1 2.0+1.6.1 2001 PSF no
104
+};
62
+ 2.0.1 2.0+1.6.1 2001 PSF yes
105
+
63
+ 2.1.1 2.1+2.0.1 2001 PSF yes
106
+struct QVhostUserBlkPCI {
64
+ 2.2 2.1.1 2001 PSF yes
107
+ QVirtioPCIDevice pci_vdev;
65
+ 2.1.2 2.1.1 2002 PSF yes
108
+ QVhostUserBlk blk;
66
+ 2.1.3 2.1.2 2002 PSF yes
109
+};
67
+ 2.2.1 2.2 2002 PSF yes
110
+
68
+ 2.2.2 2.2.1 2002 PSF yes
111
+struct QVhostUserBlkDevice {
69
+ 2.2.3 2.2.2 2003 PSF yes
112
+ QOSGraphObject obj;
70
+ 2.3 2.2.2 2002-2003 PSF yes
113
+ QVhostUserBlk blk;
71
+ 2.3.1 2.3 2002-2003 PSF yes
114
+};
72
+ 2.3.2 2.3.1 2002-2003 PSF yes
115
+
73
+ 2.3.3 2.3.2 2002-2003 PSF yes
116
+#endif
74
+ 2.3.4 2.3.3 2004 PSF yes
117
diff --git a/tests/qtest/libqos/vhost-user-blk.c b/tests/qtest/libqos/vhost-user-blk.c
75
+ 2.3.5 2.3.4 2005 PSF yes
76
+ 2.4 2.3 2004 PSF yes
77
+ 2.4.1 2.4 2005 PSF yes
78
+ 2.4.2 2.4.1 2005 PSF yes
79
+ 2.4.3 2.4.2 2006 PSF yes
80
+ 2.5 2.4 2006 PSF yes
81
+ 2.7 2.6 2010 PSF yes
82
+
83
+Footnotes:
84
+
85
+(1) GPL-compatible doesn't mean that we're distributing Python under
86
+ the GPL. All Python licenses, unlike the GPL, let you distribute
87
+ a modified version without making your changes open source. The
88
+ GPL-compatible licenses make it possible to combine Python with
89
+ other software that is released under the GPL; the others don't.
90
+
91
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
92
+ because its license has a choice of law clause. According to
93
+ CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
94
+ is "not incompatible" with the GPL.
95
+
96
+Thanks to the many outside volunteers who have worked under Guido's
97
+direction to make these releases possible.
98
+
99
+
100
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
101
+===============================================================
102
+
103
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
104
+--------------------------------------------
105
+
106
+1. This LICENSE AGREEMENT is between the Python Software Foundation
107
+("PSF"), and the Individual or Organization ("Licensee") accessing and
108
+otherwise using this software ("Python") in source or binary form and
109
+its associated documentation.
110
+
111
+2. Subject to the terms and conditions of this License Agreement, PSF
112
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
113
+license to reproduce, analyze, test, perform and/or display publicly,
114
+prepare derivative works, distribute, and otherwise use Python
115
+alone or in any derivative version, provided, however, that PSF's
116
+License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
117
+2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights
118
+Reserved" are retained in Python alone or in any derivative version
119
+prepared by Licensee.
120
+
121
+3. In the event Licensee prepares a derivative work that is based on
122
+or incorporates Python or any part thereof, and wants to make
123
+the derivative work available to others as provided herein, then
124
+Licensee hereby agrees to include in any such work a brief summary of
125
+the changes made to Python.
126
+
127
+4. PSF is making Python available to Licensee on an "AS IS"
128
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
129
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
130
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
131
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
132
+INFRINGE ANY THIRD PARTY RIGHTS.
133
+
134
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
135
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
136
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
137
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
138
+
139
+6. This License Agreement will automatically terminate upon a material
140
+breach of its terms and conditions.
141
+
142
+7. Nothing in this License Agreement shall be deemed to create any
143
+relationship of agency, partnership, or joint venture between PSF and
144
+Licensee. This License Agreement does not grant permission to use PSF
145
+trademarks or trade name in a trademark sense to endorse or promote
146
+products or services of Licensee, or any third party.
147
+
148
+8. By copying, installing or otherwise using Python, Licensee
149
+agrees to be bound by the terms and conditions of this License
150
+Agreement.
151
+
152
+
153
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
154
+-------------------------------------------
155
+
156
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
157
+
158
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
159
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
160
+Individual or Organization ("Licensee") accessing and otherwise using
161
+this software in source or binary form and its associated
162
+documentation ("the Software").
163
+
164
+2. Subject to the terms and conditions of this BeOpen Python License
165
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
166
+royalty-free, world-wide license to reproduce, analyze, test, perform
167
+and/or display publicly, prepare derivative works, distribute, and
168
+otherwise use the Software alone or in any derivative version,
169
+provided, however, that the BeOpen Python License is retained in the
170
+Software, alone or in any derivative version prepared by Licensee.
171
+
172
+3. BeOpen is making the Software available to Licensee on an "AS IS"
173
+basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
174
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
175
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
176
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
177
+INFRINGE ANY THIRD PARTY RIGHTS.
178
+
179
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
180
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
181
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
182
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
183
+
184
+5. This License Agreement will automatically terminate upon a material
185
+breach of its terms and conditions.
186
+
187
+6. This License Agreement shall be governed by and interpreted in all
188
+respects by the law of the State of California, excluding conflict of
189
+law provisions. Nothing in this License Agreement shall be deemed to
190
+create any relationship of agency, partnership, or joint venture
191
+between BeOpen and Licensee. This License Agreement does not grant
192
+permission to use BeOpen trademarks or trade names in a trademark
193
+sense to endorse or promote products or services of Licensee, or any
194
+third party. As an exception, the "BeOpen Python" logos available at
195
+http://www.pythonlabs.com/logos.html may be used according to the
196
+permissions granted on that web page.
197
+
198
+7. By copying, installing or otherwise using the software, Licensee
199
+agrees to be bound by the terms and conditions of this License
200
+Agreement.
201
+
202
+
203
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
204
+---------------------------------------
205
+
206
+1. This LICENSE AGREEMENT is between the Corporation for National
207
+Research Initiatives, having an office at 1895 Preston White Drive,
208
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
209
+("Licensee") accessing and otherwise using Python 1.6.1 software in
210
+source or binary form and its associated documentation.
211
+
212
+2. Subject to the terms and conditions of this License Agreement, CNRI
213
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
214
+license to reproduce, analyze, test, perform and/or display publicly,
215
+prepare derivative works, distribute, and otherwise use Python 1.6.1
216
+alone or in any derivative version, provided, however, that CNRI's
217
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
218
+1995-2001 Corporation for National Research Initiatives; All Rights
219
+Reserved" are retained in Python 1.6.1 alone or in any derivative
220
+version prepared by Licensee. Alternately, in lieu of CNRI's License
221
+Agreement, Licensee may substitute the following text (omitting the
222
+quotes): "Python 1.6.1 is made available subject to the terms and
223
+conditions in CNRI's License Agreement. This Agreement together with
224
+Python 1.6.1 may be located on the Internet using the following
225
+unique, persistent identifier (known as a handle): 1895.22/1013. This
226
+Agreement may also be obtained from a proxy server on the Internet
227
+using the following URL: http://hdl.handle.net/1895.22/1013".
228
+
229
+3. In the event Licensee prepares a derivative work that is based on
230
+or incorporates Python 1.6.1 or any part thereof, and wants to make
231
+the derivative work available to others as provided herein, then
232
+Licensee hereby agrees to include in any such work a brief summary of
233
+the changes made to Python 1.6.1.
234
+
235
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
236
+basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
237
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
238
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
239
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
240
+INFRINGE ANY THIRD PARTY RIGHTS.
241
+
242
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
243
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
244
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
245
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
246
+
247
+6. This License Agreement will automatically terminate upon a material
248
+breach of its terms and conditions.
249
+
250
+7. This License Agreement shall be governed by the federal
251
+intellectual property law of the United States, including without
252
+limitation the federal copyright law, and, to the extent such
253
+U.S. federal law does not apply, by the law of the Commonwealth of
254
+Virginia, excluding Virginia's conflict of law provisions.
255
+Notwithstanding the foregoing, with regard to derivative works based
256
+on Python 1.6.1 that incorporate non-separable material that was
257
+previously distributed under the GNU General Public License (GPL), the
258
+law of the Commonwealth of Virginia shall govern this License
259
+Agreement only as to issues arising under or with respect to
260
+Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
261
+License Agreement shall be deemed to create any relationship of
262
+agency, partnership, or joint venture between CNRI and Licensee. This
263
+License Agreement does not grant permission to use CNRI trademarks or
264
+trade name in a trademark sense to endorse or promote products or
265
+services of Licensee, or any third party.
266
+
267
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
268
+installing or otherwise using Python 1.6.1, Licensee agrees to be
269
+bound by the terms and conditions of this License Agreement.
270
+
271
+ ACCEPT
272
+
273
+
274
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
275
+--------------------------------------------------
276
+
277
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
278
+The Netherlands. All rights reserved.
279
+
280
+Permission to use, copy, modify, and distribute this software and its
281
+documentation for any purpose and without fee is hereby granted,
282
+provided that the above copyright notice appear in all copies and that
283
+both that copyright notice and this permission notice appear in
284
+supporting documentation, and that the name of Stichting Mathematisch
285
+Centrum or CWI not be used in advertising or publicity pertaining to
286
+distribution of the software without specific, written prior
287
+permission.
288
+
289
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
290
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
291
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
292
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
293
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
294
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
295
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
296
diff --git a/scripts/argparse.py b/scripts/argparse.py
297
new file mode 100644
118
new file mode 100644
298
index XXXXXXX..XXXXXXX
119
index XXXXXXX..XXXXXXX
299
--- /dev/null
120
--- /dev/null
300
+++ b/scripts/argparse.py
121
+++ b/tests/qtest/libqos/vhost-user-blk.c
301
@@ -XXX,XX +XXX,XX @@
122
@@ -XXX,XX +XXX,XX @@
302
+# This is a local copy of the standard library argparse module taken from PyPI.
123
+/*
303
+# It is licensed under the Python Software Foundation License. This is a
124
+ * libqos driver framework
304
+# fallback for Python 2.6 which does not include this module. Python 2.7+ and
125
+ *
305
+# 3+ will never load this module because built-in modules are loaded before
126
+ * Based on tests/qtest/libqos/virtio-blk.c
306
+# anything in sys.path.
127
+ *
307
+#
128
+ * Copyright (c) 2020 Coiby Xu <coiby.xu@gmail.com>
308
+# If your script is not located in the same directory as this file, import it
129
+ *
309
+# like this:
130
+ * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
310
+#
131
+ *
311
+# import os
132
+ * This library is free software; you can redistribute it and/or
312
+# import sys
133
+ * modify it under the terms of the GNU Lesser General Public
313
+# sys.path.append(os.path.join(os.path.dirname(__file__), ..., 'scripts'))
134
+ * License version 2.1 as published by the Free Software Foundation.
314
+# import argparse
135
+ *
315
+
136
+ * This library is distributed in the hope that it will be useful,
316
+# Author: Steven J. Bethard <steven.bethard@gmail.com>.
137
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
317
+# Maintainer: Thomas Waldmann <tw@waldmann-edv.de>
138
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
318
+
139
+ * Lesser General Public License for more details.
319
+"""Command-line parsing library
140
+ *
320
+
141
+ * You should have received a copy of the GNU Lesser General Public
321
+This module is an optparse-inspired command-line parsing library that:
142
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
322
+
143
+ */
323
+ - handles both optional and positional arguments
144
+
324
+ - produces highly informative usage messages
145
+#include "qemu/osdep.h"
325
+ - supports parsers that dispatch to sub-parsers
146
+#include "libqtest.h"
326
+
147
+#include "qemu/module.h"
327
+The following is a simple usage example that sums integers from the
148
+#include "standard-headers/linux/virtio_blk.h"
328
+command-line and writes the result to a file::
149
+#include "vhost-user-blk.h"
329
+
150
+
330
+ parser = argparse.ArgumentParser(
151
+#define PCI_SLOT 0x04
331
+ description='sum the integers at the command line')
152
+#define PCI_FN 0x00
332
+ parser.add_argument(
153
+
333
+ 'integers', metavar='int', nargs='+', type=int,
154
+/* virtio-blk-device */
334
+ help='an integer to be summed')
155
+static void *qvhost_user_blk_get_driver(QVhostUserBlk *v_blk,
335
+ parser.add_argument(
156
+ const char *interface)
336
+ '--log', default=sys.stdout, type=argparse.FileType('w'),
157
+{
337
+ help='the file where the sum should be written')
158
+ if (!g_strcmp0(interface, "vhost-user-blk")) {
338
+ args = parser.parse_args()
159
+ return v_blk;
339
+ args.log.write('%s' % sum(args.integers))
160
+ }
340
+ args.log.close()
161
+ if (!g_strcmp0(interface, "virtio")) {
341
+
162
+ return v_blk->vdev;
342
+The module contains the following public classes:
163
+ }
343
+
164
+
344
+ - ArgumentParser -- The main entry point for command-line parsing. As the
165
+ fprintf(stderr, "%s not present in vhost-user-blk-device\n", interface);
345
+ example above shows, the add_argument() method is used to populate
166
+ g_assert_not_reached();
346
+ the parser with actions for optional and positional arguments. Then
167
+}
347
+ the parse_args() method is invoked to convert the args at the
168
+
348
+ command-line into an object with attributes.
169
+static void *qvhost_user_blk_device_get_driver(void *object,
349
+
170
+ const char *interface)
350
+ - ArgumentError -- The exception raised by ArgumentParser objects when
171
+{
351
+ there are errors with the parser's actions. Errors raised while
172
+ QVhostUserBlkDevice *v_blk = object;
352
+ parsing the command-line are caught by ArgumentParser and emitted
173
+ return qvhost_user_blk_get_driver(&v_blk->blk, interface);
353
+ as command-line messages.
174
+}
354
+
175
+
355
+ - FileType -- A factory for defining types of files to be created. As the
176
+static void *vhost_user_blk_device_create(void *virtio_dev,
356
+ example above shows, instances of FileType are typically passed as
177
+ QGuestAllocator *t_alloc,
357
+ the type= argument of add_argument() calls.
178
+ void *addr)
358
+
179
+{
359
+ - Action -- The base class for parser actions. Typically actions are
180
+ QVhostUserBlkDevice *vhost_user_blk = g_new0(QVhostUserBlkDevice, 1);
360
+ selected by passing strings like 'store_true' or 'append_const' to
181
+ QVhostUserBlk *interface = &vhost_user_blk->blk;
361
+ the action= argument of add_argument(). However, for greater
182
+
362
+ customization of ArgumentParser actions, subclasses of Action may
183
+ interface->vdev = virtio_dev;
363
+ be defined and passed as the action= argument.
184
+
364
+
185
+ vhost_user_blk->obj.get_driver = qvhost_user_blk_device_get_driver;
365
+ - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
186
+
366
+ ArgumentDefaultsHelpFormatter -- Formatter classes which
187
+ return &vhost_user_blk->obj;
367
+ may be passed as the formatter_class= argument to the
188
+}
368
+ ArgumentParser constructor. HelpFormatter is the default,
189
+
369
+ RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
190
+/* virtio-blk-pci */
370
+ not to change the formatting for help text, and
191
+static void *qvhost_user_blk_pci_get_driver(void *object, const char *interface)
371
+ ArgumentDefaultsHelpFormatter adds information about argument defaults
192
+{
372
+ to the help.
193
+ QVhostUserBlkPCI *v_blk = object;
373
+
194
+ if (!g_strcmp0(interface, "pci-device")) {
374
+All other classes in this module are considered implementation details.
195
+ return v_blk->pci_vdev.pdev;
375
+(Also note that HelpFormatter and RawDescriptionHelpFormatter are only
196
+ }
376
+considered public as object names -- the API of the formatter objects is
197
+ return qvhost_user_blk_get_driver(&v_blk->blk, interface);
377
+still considered an implementation detail.)
198
+}
378
+"""
199
+
379
+
200
+static void *vhost_user_blk_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
380
+__version__ = '1.4.0' # we use our own version number independant of the
201
+ void *addr)
381
+ # one in stdlib and we release this on pypi.
202
+{
382
+
203
+ QVhostUserBlkPCI *vhost_user_blk = g_new0(QVhostUserBlkPCI, 1);
383
+__external_lib__ = True # to make sure the tests really test THIS lib,
204
+ QVhostUserBlk *interface = &vhost_user_blk->blk;
384
+ # not the builtin one in Python stdlib
205
+ QOSGraphObject *obj = &vhost_user_blk->pci_vdev.obj;
385
+
206
+
386
+__all__ = [
207
+ virtio_pci_init(&vhost_user_blk->pci_vdev, pci_bus, addr);
387
+ 'ArgumentParser',
208
+ interface->vdev = &vhost_user_blk->pci_vdev.vdev;
388
+ 'ArgumentError',
209
+
389
+ 'ArgumentTypeError',
210
+ g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_BLOCK);
390
+ 'FileType',
211
+
391
+ 'HelpFormatter',
212
+ obj->get_driver = qvhost_user_blk_pci_get_driver;
392
+ 'ArgumentDefaultsHelpFormatter',
213
+
393
+ 'RawDescriptionHelpFormatter',
214
+ return obj;
394
+ 'RawTextHelpFormatter',
215
+}
395
+ 'Namespace',
216
+
396
+ 'Action',
217
+static void vhost_user_blk_register_nodes(void)
397
+ 'ONE_OR_MORE',
218
+{
398
+ 'OPTIONAL',
219
+ /*
399
+ 'PARSER',
220
+ * FIXME: every test using these two nodes needs to setup a
400
+ 'REMAINDER',
221
+ * -drive,id=drive0 otherwise QEMU is not going to start.
401
+ 'SUPPRESS',
222
+ * Therefore, we do not include "produces" edge for virtio
402
+ 'ZERO_OR_MORE',
223
+ * and pci-device yet.
403
+]
224
+ */
404
+
225
+
405
+
226
+ char *arg = g_strdup_printf("id=drv0,chardev=char1,addr=%x.%x",
406
+import copy as _copy
227
+ PCI_SLOT, PCI_FN);
407
+import os as _os
228
+
408
+import re as _re
229
+ QPCIAddress addr = {
409
+import sys as _sys
230
+ .devfn = QPCI_DEVFN(PCI_SLOT, PCI_FN),
410
+import textwrap as _textwrap
231
+ };
411
+
232
+
412
+from gettext import gettext as _
233
+ QOSGraphEdgeOptions opts = { };
413
+
234
+
414
+try:
235
+ /* virtio-blk-device */
415
+ set
236
+ /** opts.extra_device_opts = "drive=drive0"; */
416
+except NameError:
237
+ qos_node_create_driver("vhost-user-blk-device", vhost_user_blk_device_create);
417
+ # for python < 2.4 compatibility (sets module is there since 2.3):
238
+ qos_node_consumes("vhost-user-blk-device", "virtio-bus", &opts);
418
+ from sets import Set as set
239
+ qos_node_produces("vhost-user-blk-device", "vhost-user-blk");
419
+
240
+
420
+try:
241
+ /* virtio-blk-pci */
421
+ basestring
242
+ opts.extra_device_opts = arg;
422
+except NameError:
243
+ add_qpci_address(&opts, &addr);
423
+ basestring = str
244
+ qos_node_create_driver("vhost-user-blk-pci", vhost_user_blk_pci_create);
424
+
245
+ qos_node_consumes("vhost-user-blk-pci", "pci-bus", &opts);
425
+try:
246
+ qos_node_produces("vhost-user-blk-pci", "vhost-user-blk");
426
+ sorted
247
+
427
+except NameError:
248
+ g_free(arg);
428
+ # for python < 2.4 compatibility:
249
+}
429
+ def sorted(iterable, reverse=False):
250
+
430
+ result = list(iterable)
251
+libqos_init(vhost_user_blk_register_nodes);
431
+ result.sort()
252
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
432
+ if reverse:
253
index XXXXXXX..XXXXXXX 100644
433
+ result.reverse()
254
--- a/tests/qtest/libqtest.c
434
+ return result
255
+++ b/tests/qtest/libqtest.c
435
+
256
@@ -XXX,XX +XXX,XX @@
436
+
257
* Copyright IBM, Corp. 2012
437
+def _callable(obj):
258
* Copyright Red Hat, Inc. 2012
438
+ return hasattr(obj, '__call__') or hasattr(obj, '__bases__')
259
* Copyright SUSE LINUX Products GmbH 2013
439
+
260
+ * Copyright Copyright (c) Coiby Xu
440
+
261
*
441
+SUPPRESS = '==SUPPRESS=='
262
* Authors:
442
+
263
* Anthony Liguori <aliguori@us.ibm.com>
443
+OPTIONAL = '?'
264
* Paolo Bonzini <pbonzini@redhat.com>
444
+ZERO_OR_MORE = '*'
265
* Andreas Färber <afaerber@suse.de>
445
+ONE_OR_MORE = '+'
266
+ * Coiby Xu <coiby.xu@gmail.com>
446
+PARSER = 'A...'
267
*
447
+REMAINDER = '...'
268
* This work is licensed under the terms of the GNU GPL, version 2 or later.
448
+_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args'
269
* See the COPYING file in the top-level directory.
449
+
270
@@ -XXX,XX +XXX,XX @@ typedef struct QTestClientTransportOps {
450
+# =============================
271
QTestRecvFn recv_line; /* for receiving qtest command responses */
451
+# Utility functions and classes
272
} QTestTransportOps;
452
+# =============================
273
453
+
274
-struct QTestState
454
+class _AttributeHolder(object):
275
-{
455
+ """Abstract base class that provides __repr__.
276
+struct QTestState {
456
+
277
int fd;
457
+ The __repr__ method returns a string in the format::
278
int qmp_fd;
458
+ ClassName(attr=name, attr=name, ...)
279
pid_t qemu_pid; /* our child QEMU process */
459
+ The attributes are determined either by a class-level attribute,
280
@@ -XXX,XX +XXX,XX @@ QDict *qtest_qmp_receive(QTestState *s)
460
+ '_kwarg_names', or by inspecting the instance __dict__.
281
return qmp_fd_receive(s->qmp_fd);
461
+ """
282
}
462
+
283
463
+ def __repr__(self):
284
+QTestState *qtest_create_state_with_qmp_fd(int fd)
464
+ type_name = type(self).__name__
285
+{
465
+ arg_strings = []
286
+ QTestState *qmp_test_state = g_new0(QTestState, 1);
466
+ for arg in self._get_args():
287
+ qmp_test_state->qmp_fd = fd;
467
+ arg_strings.append(repr(arg))
288
+ return qmp_test_state;
468
+ for name, value in self._get_kwargs():
289
+}
469
+ arg_strings.append('%s=%r' % (name, value))
290
+
470
+ return '%s(%s)' % (type_name, ', '.join(arg_strings))
291
+int qtest_socket_client(char *server_socket_path)
471
+
292
+{
472
+ def _get_kwargs(self):
293
+ struct sockaddr_un serv_addr;
473
+ return sorted(self.__dict__.items())
294
+ int sock;
474
+
295
+ int ret;
475
+ def _get_args(self):
296
+ int retries = 0;
476
+ return []
297
+ sock = socket(PF_UNIX, SOCK_STREAM, 0);
477
+
298
+ g_assert_cmpint(sock, !=, -1);
478
+
299
+ serv_addr.sun_family = AF_UNIX;
479
+def _ensure_value(namespace, name, value):
300
+ snprintf(serv_addr.sun_path, sizeof(serv_addr.sun_path), "%s",
480
+ if getattr(namespace, name, None) is None:
301
+ server_socket_path);
481
+ setattr(namespace, name, value)
302
+
482
+ return getattr(namespace, name)
303
+ for (retries = 0; retries < 3; retries++) {
483
+
304
+ ret = connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
484
+
305
+ if (ret == 0) {
485
+# ===============
306
+ break;
486
+# Formatting Help
307
+ }
487
+# ===============
308
+ g_usleep(G_USEC_PER_SEC);
488
+
309
+ }
489
+class HelpFormatter(object):
310
+
490
+ """Formatter for generating usage messages and argument help strings.
311
+ g_assert_cmpint(ret, ==, 0);
491
+
312
+ return sock;
492
+ Only the name of this class is considered a public API. All the methods
313
+}
493
+ provided by the class are considered an implementation detail.
314
+
494
+ """
315
/**
495
+
316
* Allow users to send a message without waiting for the reply,
496
+ def __init__(self,
317
* in the case that they choose to discard all replies up until
497
+ prog,
318
diff --git a/tests/qtest/vhost-user-blk-test.c b/tests/qtest/vhost-user-blk-test.c
498
+ indent_increment=2,
319
new file mode 100644
499
+ max_help_position=24,
320
index XXXXXXX..XXXXXXX
500
+ width=None):
321
--- /dev/null
501
+
322
+++ b/tests/qtest/vhost-user-blk-test.c
502
+ # default setting for width
323
@@ -XXX,XX +XXX,XX @@
503
+ if width is None:
324
+/*
504
+ try:
325
+ * QTest testcase for Vhost-user Block Device
505
+ width = int(_os.environ['COLUMNS'])
326
+ *
506
+ except (KeyError, ValueError):
327
+ * Based on tests/qtest//virtio-blk-test.c
507
+ width = 80
328
+
508
+ width -= 2
329
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
509
+
330
+ * Copyright (c) 2014 Marc Marí
510
+ self._prog = prog
331
+ * Copyright (c) 2020 Coiby Xu
511
+ self._indent_increment = indent_increment
332
+ *
512
+ self._max_help_position = max_help_position
333
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
513
+ self._width = width
334
+ * See the COPYING file in the top-level directory.
514
+
335
+ */
515
+ self._current_indent = 0
336
+
516
+ self._level = 0
337
+#include "qemu/osdep.h"
517
+ self._action_max_length = 0
338
+#include "libqtest-single.h"
518
+
339
+#include "qemu/bswap.h"
519
+ self._root_section = self._Section(self, None)
340
+#include "qemu/module.h"
520
+ self._current_section = self._root_section
341
+#include "standard-headers/linux/virtio_blk.h"
521
+
342
+#include "standard-headers/linux/virtio_pci.h"
522
+ self._whitespace_matcher = _re.compile(r'\s+')
343
+#include "libqos/qgraph.h"
523
+ self._long_break_matcher = _re.compile(r'\n\n\n+')
344
+#include "libqos/vhost-user-blk.h"
524
+
345
+#include "libqos/libqos-pc.h"
525
+ # ===============================
346
+
526
+ # Section and indentation methods
347
+#define TEST_IMAGE_SIZE (64 * 1024 * 1024)
527
+ # ===============================
348
+#define QVIRTIO_BLK_TIMEOUT_US (30 * 1000 * 1000)
528
+ def _indent(self):
349
+#define PCI_SLOT_HP 0x06
529
+ self._current_indent += self._indent_increment
350
+
530
+ self._level += 1
351
+typedef struct QVirtioBlkReq {
531
+
352
+ uint32_t type;
532
+ def _dedent(self):
353
+ uint32_t ioprio;
533
+ self._current_indent -= self._indent_increment
354
+ uint64_t sector;
534
+ assert self._current_indent >= 0, 'Indent decreased below 0.'
355
+ char *data;
535
+ self._level -= 1
356
+ uint8_t status;
536
+
357
+} QVirtioBlkReq;
537
+ class _Section(object):
358
+
538
+
359
+#ifdef HOST_WORDS_BIGENDIAN
539
+ def __init__(self, formatter, parent, heading=None):
360
+static const bool host_is_big_endian = true;
540
+ self.formatter = formatter
361
+#else
541
+ self.parent = parent
362
+static const bool host_is_big_endian; /* false */
542
+ self.heading = heading
363
+#endif
543
+ self.items = []
364
+
544
+
365
+static inline void virtio_blk_fix_request(QVirtioDevice *d, QVirtioBlkReq *req)
545
+ def format_help(self):
366
+{
546
+ # format the indented section
367
+ if (qvirtio_is_big_endian(d) != host_is_big_endian) {
547
+ if self.parent is not None:
368
+ req->type = bswap32(req->type);
548
+ self.formatter._indent()
369
+ req->ioprio = bswap32(req->ioprio);
549
+ join = self.formatter._join_parts
370
+ req->sector = bswap64(req->sector);
550
+ for func, args in self.items:
371
+ }
551
+ func(*args)
372
+}
552
+ item_help = join([func(*args) for func, args in self.items])
373
+
553
+ if self.parent is not None:
374
+static inline void virtio_blk_fix_dwz_hdr(QVirtioDevice *d,
554
+ self.formatter._dedent()
375
+ struct virtio_blk_discard_write_zeroes *dwz_hdr)
555
+
376
+{
556
+ # return nothing if the section was empty
377
+ if (qvirtio_is_big_endian(d) != host_is_big_endian) {
557
+ if not item_help:
378
+ dwz_hdr->sector = bswap64(dwz_hdr->sector);
558
+ return ''
379
+ dwz_hdr->num_sectors = bswap32(dwz_hdr->num_sectors);
559
+
380
+ dwz_hdr->flags = bswap32(dwz_hdr->flags);
560
+ # add the heading if the section was non-empty
381
+ }
561
+ if self.heading is not SUPPRESS and self.heading is not None:
382
+}
562
+ current_indent = self.formatter._current_indent
383
+
563
+ heading = '%*s%s:\n' % (current_indent, '', self.heading)
384
+static uint64_t virtio_blk_request(QGuestAllocator *alloc, QVirtioDevice *d,
564
+ else:
385
+ QVirtioBlkReq *req, uint64_t data_size)
565
+ heading = ''
386
+{
566
+
387
+ uint64_t addr;
567
+ # join the section-initial newline, the heading and the help
388
+ uint8_t status = 0xFF;
568
+ return join(['\n', heading, item_help, '\n'])
389
+ QTestState *qts = global_qtest;
569
+
390
+
570
+ def _add_item(self, func, args):
391
+ switch (req->type) {
571
+ self._current_section.items.append((func, args))
392
+ case VIRTIO_BLK_T_IN:
572
+
393
+ case VIRTIO_BLK_T_OUT:
573
+ # ========================
394
+ g_assert_cmpuint(data_size % 512, ==, 0);
574
+ # Message building methods
395
+ break;
575
+ # ========================
396
+ case VIRTIO_BLK_T_DISCARD:
576
+ def start_section(self, heading):
397
+ case VIRTIO_BLK_T_WRITE_ZEROES:
577
+ self._indent()
398
+ g_assert_cmpuint(data_size %
578
+ section = self._Section(self, self._current_section, heading)
399
+ sizeof(struct virtio_blk_discard_write_zeroes), ==, 0);
579
+ self._add_item(section.format_help, [])
400
+ break;
580
+ self._current_section = section
401
+ default:
581
+
402
+ g_assert_cmpuint(data_size, ==, 0);
582
+ def end_section(self):
403
+ }
583
+ self._current_section = self._current_section.parent
404
+
584
+ self._dedent()
405
+ addr = guest_alloc(alloc, sizeof(*req) + data_size);
585
+
406
+
586
+ def add_text(self, text):
407
+ virtio_blk_fix_request(d, req);
587
+ if text is not SUPPRESS and text is not None:
408
+
588
+ self._add_item(self._format_text, [text])
409
+ qtest_memwrite(qts, addr, req, 16);
589
+
410
+ qtest_memwrite(qts, addr + 16, req->data, data_size);
590
+ def add_usage(self, usage, actions, groups, prefix=None):
411
+ qtest_memwrite(qts, addr + 16 + data_size, &status, sizeof(status));
591
+ if usage is not SUPPRESS:
412
+
592
+ args = usage, actions, groups, prefix
413
+ return addr;
593
+ self._add_item(self._format_usage, args)
414
+}
594
+
415
+
595
+ def add_argument(self, action):
416
+/* Returns the request virtqueue so the caller can perform further tests */
596
+ if action.help is not SUPPRESS:
417
+static QVirtQueue *test_basic(QVirtioDevice *dev, QGuestAllocator *alloc)
597
+
418
+{
598
+ # find all invocations
419
+ QVirtioBlkReq req;
599
+ get_invocation = self._format_action_invocation
420
+ uint64_t req_addr;
600
+ invocations = [get_invocation(action)]
421
+ uint64_t capacity;
601
+ for subaction in self._iter_indented_subactions(action):
422
+ uint64_t features;
602
+ invocations.append(get_invocation(subaction))
423
+ uint32_t free_head;
603
+
424
+ uint8_t status;
604
+ # update the maximum item length
425
+ char *data;
605
+ invocation_length = max([len(s) for s in invocations])
426
+ QTestState *qts = global_qtest;
606
+ action_length = invocation_length + self._current_indent
427
+ QVirtQueue *vq;
607
+ self._action_max_length = max(self._action_max_length,
428
+
608
+ action_length)
429
+ features = qvirtio_get_features(dev);
609
+
430
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
610
+ # add the item to the list
431
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
611
+ self._add_item(self._format_action, [action])
432
+ (1u << VIRTIO_RING_F_EVENT_IDX) |
612
+
433
+ (1u << VIRTIO_BLK_F_SCSI));
613
+ def add_arguments(self, actions):
434
+ qvirtio_set_features(dev, features);
614
+ for action in actions:
435
+
615
+ self.add_argument(action)
436
+ capacity = qvirtio_config_readq(dev, 0);
616
+
437
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
617
+ # =======================
438
+
618
+ # Help-formatting methods
439
+ vq = qvirtqueue_setup(dev, alloc, 0);
619
+ # =======================
440
+
620
+ def format_help(self):
441
+ qvirtio_set_driver_ok(dev);
621
+ help = self._root_section.format_help()
442
+
622
+ if help:
443
+ /* Write and read with 3 descriptor layout */
623
+ help = self._long_break_matcher.sub('\n\n', help)
444
+ /* Write request */
624
+ help = help.strip('\n') + '\n'
445
+ req.type = VIRTIO_BLK_T_OUT;
625
+ return help
446
+ req.ioprio = 1;
626
+
447
+ req.sector = 0;
627
+ def _join_parts(self, part_strings):
448
+ req.data = g_malloc0(512);
628
+ return ''.join([part
449
+ strcpy(req.data, "TEST");
629
+ for part in part_strings
450
+
630
+ if part and part is not SUPPRESS])
451
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
631
+
452
+
632
+ def _format_usage(self, usage, actions, groups, prefix):
453
+ g_free(req.data);
633
+ if prefix is None:
454
+
634
+ prefix = _('usage: ')
455
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
635
+
456
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
636
+ # if usage is specified, use that
457
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
637
+ if usage is not None:
458
+
638
+ usage = usage % dict(prog=self._prog)
459
+ qvirtqueue_kick(qts, dev, vq, free_head);
639
+
460
+
640
+ # if no optionals or positionals are available, usage is just prog
461
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
641
+ elif usage is None and not actions:
462
+ QVIRTIO_BLK_TIMEOUT_US);
642
+ usage = '%(prog)s' % dict(prog=self._prog)
463
+ status = readb(req_addr + 528);
643
+
464
+ g_assert_cmpint(status, ==, 0);
644
+ # if optionals and positionals are available, calculate usage
465
+
645
+ elif usage is None:
466
+ guest_free(alloc, req_addr);
646
+ prog = '%(prog)s' % dict(prog=self._prog)
467
+
647
+
468
+ /* Read request */
648
+ # split optionals from positionals
469
+ req.type = VIRTIO_BLK_T_IN;
649
+ optionals = []
470
+ req.ioprio = 1;
650
+ positionals = []
471
+ req.sector = 0;
651
+ for action in actions:
472
+ req.data = g_malloc0(512);
652
+ if action.option_strings:
473
+
653
+ optionals.append(action)
474
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
654
+ else:
475
+
655
+ positionals.append(action)
476
+ g_free(req.data);
656
+
477
+
657
+ # build full usage string
478
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
658
+ format = self._format_actions_usage
479
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
659
+ action_usage = format(optionals + positionals, groups)
480
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
660
+ usage = ' '.join([s for s in [prog, action_usage] if s])
481
+
661
+
482
+ qvirtqueue_kick(qts, dev, vq, free_head);
662
+ # wrap the usage parts if it's too long
483
+
663
+ text_width = self._width - self._current_indent
484
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
664
+ if len(prefix) + len(usage) > text_width:
485
+ QVIRTIO_BLK_TIMEOUT_US);
665
+
486
+ status = readb(req_addr + 528);
666
+ # break usage into wrappable parts
487
+ g_assert_cmpint(status, ==, 0);
667
+ part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
488
+
668
+ opt_usage = format(optionals, groups)
489
+ data = g_malloc0(512);
669
+ pos_usage = format(positionals, groups)
490
+ qtest_memread(qts, req_addr + 16, data, 512);
670
+ opt_parts = _re.findall(part_regexp, opt_usage)
491
+ g_assert_cmpstr(data, ==, "TEST");
671
+ pos_parts = _re.findall(part_regexp, pos_usage)
492
+ g_free(data);
672
+ assert ' '.join(opt_parts) == opt_usage
493
+
673
+ assert ' '.join(pos_parts) == pos_usage
494
+ guest_free(alloc, req_addr);
674
+
495
+
675
+ # helper for wrapping lines
496
+ if (features & (1u << VIRTIO_BLK_F_WRITE_ZEROES)) {
676
+ def get_lines(parts, indent, prefix=None):
497
+ struct virtio_blk_discard_write_zeroes dwz_hdr;
677
+ lines = []
498
+ void *expected;
678
+ line = []
499
+
679
+ if prefix is not None:
500
+ /*
680
+ line_len = len(prefix) - 1
501
+ * WRITE_ZEROES request on the same sector of previous test where
681
+ else:
502
+ * we wrote "TEST".
682
+ line_len = len(indent) - 1
503
+ */
683
+ for part in parts:
504
+ req.type = VIRTIO_BLK_T_WRITE_ZEROES;
684
+ if line_len + 1 + len(part) > text_width:
505
+ req.data = (char *) &dwz_hdr;
685
+ lines.append(indent + ' '.join(line))
506
+ dwz_hdr.sector = 0;
686
+ line = []
507
+ dwz_hdr.num_sectors = 1;
687
+ line_len = len(indent) - 1
508
+ dwz_hdr.flags = 0;
688
+ line.append(part)
509
+
689
+ line_len += len(part) + 1
510
+ virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
690
+ if line:
511
+
691
+ lines.append(indent + ' '.join(line))
512
+ req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
692
+ if prefix is not None:
513
+
693
+ lines[0] = lines[0][len(indent):]
514
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
694
+ return lines
515
+ qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
695
+
516
+ qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr), 1, true,
696
+ # if prog is short, follow it with optionals or positionals
517
+ false);
697
+ if len(prefix) + len(prog) <= 0.75 * text_width:
518
+
698
+ indent = ' ' * (len(prefix) + len(prog) + 1)
519
+ qvirtqueue_kick(qts, dev, vq, free_head);
699
+ if opt_parts:
520
+
700
+ lines = get_lines([prog] + opt_parts, indent, prefix)
521
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
701
+ lines.extend(get_lines(pos_parts, indent))
522
+ QVIRTIO_BLK_TIMEOUT_US);
702
+ elif pos_parts:
523
+ status = readb(req_addr + 16 + sizeof(dwz_hdr));
703
+ lines = get_lines([prog] + pos_parts, indent, prefix)
524
+ g_assert_cmpint(status, ==, 0);
704
+ else:
525
+
705
+ lines = [prog]
526
+ guest_free(alloc, req_addr);
706
+
527
+
707
+ # if prog is long, put it on its own line
528
+ /* Read request to check if the sector contains all zeroes */
708
+ else:
529
+ req.type = VIRTIO_BLK_T_IN;
709
+ indent = ' ' * len(prefix)
530
+ req.ioprio = 1;
710
+ parts = opt_parts + pos_parts
531
+ req.sector = 0;
711
+ lines = get_lines(parts, indent)
532
+ req.data = g_malloc0(512);
712
+ if len(lines) > 1:
533
+
713
+ lines = []
534
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
714
+ lines.extend(get_lines(opt_parts, indent))
535
+
715
+ lines.extend(get_lines(pos_parts, indent))
536
+ g_free(req.data);
716
+ lines = [prog] + lines
537
+
717
+
538
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
718
+ # join lines into usage
539
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
719
+ usage = '\n'.join(lines)
540
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
720
+
541
+
721
+ # prefix with 'usage:'
542
+ qvirtqueue_kick(qts, dev, vq, free_head);
722
+ return '%s%s\n\n' % (prefix, usage)
543
+
723
+
544
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
724
+ def _format_actions_usage(self, actions, groups):
545
+ QVIRTIO_BLK_TIMEOUT_US);
725
+ # find group indices and identify actions in groups
546
+ status = readb(req_addr + 528);
726
+ group_actions = set()
547
+ g_assert_cmpint(status, ==, 0);
727
+ inserts = {}
548
+
728
+ for group in groups:
549
+ data = g_malloc(512);
729
+ try:
550
+ expected = g_malloc0(512);
730
+ start = actions.index(group._group_actions[0])
551
+ qtest_memread(qts, req_addr + 16, data, 512);
731
+ except ValueError:
552
+ g_assert_cmpmem(data, 512, expected, 512);
732
+ continue
553
+ g_free(expected);
733
+ else:
554
+ g_free(data);
734
+ end = start + len(group._group_actions)
555
+
735
+ if actions[start:end] == group._group_actions:
556
+ guest_free(alloc, req_addr);
736
+ for action in group._group_actions:
557
+ }
737
+ group_actions.add(action)
558
+
738
+ if not group.required:
559
+ if (features & (1u << VIRTIO_BLK_F_DISCARD)) {
739
+ if start in inserts:
560
+ struct virtio_blk_discard_write_zeroes dwz_hdr;
740
+ inserts[start] += ' ['
561
+
741
+ else:
562
+ req.type = VIRTIO_BLK_T_DISCARD;
742
+ inserts[start] = '['
563
+ req.data = (char *) &dwz_hdr;
743
+ inserts[end] = ']'
564
+ dwz_hdr.sector = 0;
744
+ else:
565
+ dwz_hdr.num_sectors = 1;
745
+ if start in inserts:
566
+ dwz_hdr.flags = 0;
746
+ inserts[start] += ' ('
567
+
747
+ else:
568
+ virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
748
+ inserts[start] = '('
569
+
749
+ inserts[end] = ')'
570
+ req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
750
+ for i in range(start + 1, end):
571
+
751
+ inserts[i] = '|'
572
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
752
+
573
+ qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
753
+ # collect all actions format strings
574
+ qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr),
754
+ parts = []
575
+ 1, true, false);
755
+ for i, action in enumerate(actions):
576
+
756
+
577
+ qvirtqueue_kick(qts, dev, vq, free_head);
757
+ # suppressed arguments are marked with None
578
+
758
+ # remove | separators for suppressed arguments
579
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
759
+ if action.help is SUPPRESS:
580
+ QVIRTIO_BLK_TIMEOUT_US);
760
+ parts.append(None)
581
+ status = readb(req_addr + 16 + sizeof(dwz_hdr));
761
+ if inserts.get(i) == '|':
582
+ g_assert_cmpint(status, ==, 0);
762
+ inserts.pop(i)
583
+
763
+ elif inserts.get(i + 1) == '|':
584
+ guest_free(alloc, req_addr);
764
+ inserts.pop(i + 1)
585
+ }
765
+
586
+
766
+ # produce all arg strings
587
+ if (features & (1u << VIRTIO_F_ANY_LAYOUT)) {
767
+ elif not action.option_strings:
588
+ /* Write and read with 2 descriptor layout */
768
+ part = self._format_args(action, action.dest)
589
+ /* Write request */
769
+
590
+ req.type = VIRTIO_BLK_T_OUT;
770
+ # if it's in a group, strip the outer []
591
+ req.ioprio = 1;
771
+ if action in group_actions:
592
+ req.sector = 1;
772
+ if part[0] == '[' and part[-1] == ']':
593
+ req.data = g_malloc0(512);
773
+ part = part[1:-1]
594
+ strcpy(req.data, "TEST");
774
+
595
+
775
+ # add the action string to the list
596
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
776
+ parts.append(part)
597
+
777
+
598
+ g_free(req.data);
778
+ # produce the first way to invoke the option in brackets
599
+
779
+ else:
600
+ free_head = qvirtqueue_add(qts, vq, req_addr, 528, false, true);
780
+ option_string = action.option_strings[0]
601
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
781
+
602
+ qvirtqueue_kick(qts, dev, vq, free_head);
782
+ # if the Optional doesn't take a value, format is:
603
+
783
+ # -s or --long
604
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
784
+ if action.nargs == 0:
605
+ QVIRTIO_BLK_TIMEOUT_US);
785
+ part = '%s' % option_string
606
+ status = readb(req_addr + 528);
786
+
607
+ g_assert_cmpint(status, ==, 0);
787
+ # if the Optional takes a value, format is:
608
+
788
+ # -s ARGS or --long ARGS
609
+ guest_free(alloc, req_addr);
789
+ else:
610
+
790
+ default = action.dest.upper()
611
+ /* Read request */
791
+ args_string = self._format_args(action, default)
612
+ req.type = VIRTIO_BLK_T_IN;
792
+ part = '%s %s' % (option_string, args_string)
613
+ req.ioprio = 1;
793
+
614
+ req.sector = 1;
794
+ # make it look optional if it's not required or in a group
615
+ req.data = g_malloc0(512);
795
+ if not action.required and action not in group_actions:
616
+
796
+ part = '[%s]' % part
617
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
797
+
618
+
798
+ # add the action string to the list
619
+ g_free(req.data);
799
+ parts.append(part)
620
+
800
+
621
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
801
+ # insert things at the necessary indices
622
+ qvirtqueue_add(qts, vq, req_addr + 16, 513, true, false);
802
+ for i in sorted(inserts, reverse=True):
623
+
803
+ parts[i:i] = [inserts[i]]
624
+ qvirtqueue_kick(qts, dev, vq, free_head);
804
+
625
+
805
+ # join all the action items with spaces
626
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
806
+ text = ' '.join([item for item in parts if item is not None])
627
+ QVIRTIO_BLK_TIMEOUT_US);
807
+
628
+ status = readb(req_addr + 528);
808
+ # clean up separators for mutually exclusive groups
629
+ g_assert_cmpint(status, ==, 0);
809
+ open = r'[\[(]'
630
+
810
+ close = r'[\])]'
631
+ data = g_malloc0(512);
811
+ text = _re.sub(r'(%s) ' % open, r'\1', text)
632
+ qtest_memread(qts, req_addr + 16, data, 512);
812
+ text = _re.sub(r' (%s)' % close, r'\1', text)
633
+ g_assert_cmpstr(data, ==, "TEST");
813
+ text = _re.sub(r'%s *%s' % (open, close), r'', text)
634
+ g_free(data);
814
+ text = _re.sub(r'\(([^|]*)\)', r'\1', text)
635
+
815
+ text = text.strip()
636
+ guest_free(alloc, req_addr);
816
+
637
+ }
817
+ # return the text
638
+
818
+ return text
639
+ return vq;
819
+
640
+}
820
+ def _format_text(self, text):
641
+
821
+ if '%(prog)' in text:
642
+static void basic(void *obj, void *data, QGuestAllocator *t_alloc)
822
+ text = text % dict(prog=self._prog)
643
+{
823
+ text_width = self._width - self._current_indent
644
+ QVhostUserBlk *blk_if = obj;
824
+ indent = ' ' * self._current_indent
645
+ QVirtQueue *vq;
825
+ return self._fill_text(text, text_width, indent) + '\n\n'
646
+
826
+
647
+ vq = test_basic(blk_if->vdev, t_alloc);
827
+ def _format_action(self, action):
648
+ qvirtqueue_cleanup(blk_if->vdev->bus, vq, t_alloc);
828
+ # determine the required width and the entry label
649
+
829
+ help_position = min(self._action_max_length + 2,
650
+}
830
+ self._max_help_position)
651
+
831
+ help_width = self._width - help_position
652
+static void indirect(void *obj, void *u_data, QGuestAllocator *t_alloc)
832
+ action_width = help_position - self._current_indent - 2
653
+{
833
+ action_header = self._format_action_invocation(action)
654
+ QVirtQueue *vq;
834
+
655
+ QVhostUserBlk *blk_if = obj;
835
+ # ho nelp; start on same line and add a final newline
656
+ QVirtioDevice *dev = blk_if->vdev;
836
+ if not action.help:
657
+ QVirtioBlkReq req;
837
+ tup = self._current_indent, '', action_header
658
+ QVRingIndirectDesc *indirect;
838
+ action_header = '%*s%s\n' % tup
659
+ uint64_t req_addr;
839
+
660
+ uint64_t capacity;
840
+ # short action name; start on the same line and pad two spaces
661
+ uint64_t features;
841
+ elif len(action_header) <= action_width:
662
+ uint32_t free_head;
842
+ tup = self._current_indent, '', action_width, action_header
663
+ uint8_t status;
843
+ action_header = '%*s%-*s ' % tup
664
+ char *data;
844
+ indent_first = 0
665
+ QTestState *qts = global_qtest;
845
+
666
+
846
+ # long action name; start on the next line
667
+ features = qvirtio_get_features(dev);
847
+ else:
668
+ g_assert_cmphex(features & (1u << VIRTIO_RING_F_INDIRECT_DESC), !=, 0);
848
+ tup = self._current_indent, '', action_header
669
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
849
+ action_header = '%*s%s\n' % tup
670
+ (1u << VIRTIO_RING_F_EVENT_IDX) |
850
+ indent_first = help_position
671
+ (1u << VIRTIO_BLK_F_SCSI));
851
+
672
+ qvirtio_set_features(dev, features);
852
+ # collect the pieces of the action help
673
+
853
+ parts = [action_header]
674
+ capacity = qvirtio_config_readq(dev, 0);
854
+
675
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
855
+ # if there was help for the action, add lines of help text
676
+
856
+ if action.help:
677
+ vq = qvirtqueue_setup(dev, t_alloc, 0);
857
+ help_text = self._expand_help(action)
678
+ qvirtio_set_driver_ok(dev);
858
+ help_lines = self._split_lines(help_text, help_width)
679
+
859
+ parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
680
+ /* Write request */
860
+ for line in help_lines[1:]:
681
+ req.type = VIRTIO_BLK_T_OUT;
861
+ parts.append('%*s%s\n' % (help_position, '', line))
682
+ req.ioprio = 1;
862
+
683
+ req.sector = 0;
863
+ # or add a newline if the description doesn't end with one
684
+ req.data = g_malloc0(512);
864
+ elif not action_header.endswith('\n'):
685
+ strcpy(req.data, "TEST");
865
+ parts.append('\n')
686
+
866
+
687
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
867
+ # if there are any sub-actions, add their help as well
688
+
868
+ for subaction in self._iter_indented_subactions(action):
689
+ g_free(req.data);
869
+ parts.append(self._format_action(subaction))
690
+
870
+
691
+ indirect = qvring_indirect_desc_setup(qts, dev, t_alloc, 2);
871
+ # return a single string
692
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr, 528, false);
872
+ return self._join_parts(parts)
693
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr + 528, 1, true);
873
+
694
+ free_head = qvirtqueue_add_indirect(qts, vq, indirect);
874
+ def _format_action_invocation(self, action):
695
+ qvirtqueue_kick(qts, dev, vq, free_head);
875
+ if not action.option_strings:
696
+
876
+ metavar, = self._metavar_formatter(action, action.dest)(1)
697
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
877
+ return metavar
698
+ QVIRTIO_BLK_TIMEOUT_US);
878
+
699
+ status = readb(req_addr + 528);
879
+ else:
700
+ g_assert_cmpint(status, ==, 0);
880
+ parts = []
701
+
881
+
702
+ g_free(indirect);
882
+ # if the Optional doesn't take a value, format is:
703
+ guest_free(t_alloc, req_addr);
883
+ # -s, --long
704
+
884
+ if action.nargs == 0:
705
+ /* Read request */
885
+ parts.extend(action.option_strings)
706
+ req.type = VIRTIO_BLK_T_IN;
886
+
707
+ req.ioprio = 1;
887
+ # if the Optional takes a value, format is:
708
+ req.sector = 0;
888
+ # -s ARGS, --long ARGS
709
+ req.data = g_malloc0(512);
889
+ else:
710
+ strcpy(req.data, "TEST");
890
+ default = action.dest.upper()
711
+
891
+ args_string = self._format_args(action, default)
712
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
892
+ for option_string in action.option_strings:
713
+
893
+ parts.append('%s %s' % (option_string, args_string))
714
+ g_free(req.data);
894
+
715
+
895
+ return ', '.join(parts)
716
+ indirect = qvring_indirect_desc_setup(qts, dev, t_alloc, 2);
896
+
717
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr, 16, false);
897
+ def _metavar_formatter(self, action, default_metavar):
718
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr + 16, 513, true);
898
+ if action.metavar is not None:
719
+ free_head = qvirtqueue_add_indirect(qts, vq, indirect);
899
+ result = action.metavar
720
+ qvirtqueue_kick(qts, dev, vq, free_head);
900
+ elif action.choices is not None:
721
+
901
+ choice_strs = [str(choice) for choice in action.choices]
722
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
902
+ result = '{%s}' % ','.join(choice_strs)
723
+ QVIRTIO_BLK_TIMEOUT_US);
903
+ else:
724
+ status = readb(req_addr + 528);
904
+ result = default_metavar
725
+ g_assert_cmpint(status, ==, 0);
905
+
726
+
906
+ def format(tuple_size):
727
+ data = g_malloc0(512);
907
+ if isinstance(result, tuple):
728
+ qtest_memread(qts, req_addr + 16, data, 512);
908
+ return result
729
+ g_assert_cmpstr(data, ==, "TEST");
909
+ else:
730
+ g_free(data);
910
+ return (result, ) * tuple_size
731
+
911
+ return format
732
+ g_free(indirect);
912
+
733
+ guest_free(t_alloc, req_addr);
913
+ def _format_args(self, action, default_metavar):
734
+ qvirtqueue_cleanup(dev->bus, vq, t_alloc);
914
+ get_metavar = self._metavar_formatter(action, default_metavar)
735
+}
915
+ if action.nargs is None:
736
+
916
+ result = '%s' % get_metavar(1)
737
+static void idx(void *obj, void *u_data, QGuestAllocator *t_alloc)
917
+ elif action.nargs == OPTIONAL:
738
+{
918
+ result = '[%s]' % get_metavar(1)
739
+ QVirtQueue *vq;
919
+ elif action.nargs == ZERO_OR_MORE:
740
+ QVhostUserBlkPCI *blk = obj;
920
+ result = '[%s [%s ...]]' % get_metavar(2)
741
+ QVirtioPCIDevice *pdev = &blk->pci_vdev;
921
+ elif action.nargs == ONE_OR_MORE:
742
+ QVirtioDevice *dev = &pdev->vdev;
922
+ result = '%s [%s ...]' % get_metavar(2)
743
+ QVirtioBlkReq req;
923
+ elif action.nargs == REMAINDER:
744
+ uint64_t req_addr;
924
+ result = '...'
745
+ uint64_t capacity;
925
+ elif action.nargs == PARSER:
746
+ uint64_t features;
926
+ result = '%s ...' % get_metavar(1)
747
+ uint32_t free_head;
927
+ else:
748
+ uint32_t write_head;
928
+ formats = ['%s' for _ in range(action.nargs)]
749
+ uint32_t desc_idx;
929
+ result = ' '.join(formats) % get_metavar(action.nargs)
750
+ uint8_t status;
930
+ return result
751
+ char *data;
931
+
752
+ QOSGraphObject *blk_object = obj;
932
+ def _expand_help(self, action):
753
+ QPCIDevice *pci_dev = blk_object->get_driver(blk_object, "pci-device");
933
+ params = dict(vars(action), prog=self._prog)
754
+ QTestState *qts = global_qtest;
934
+ for name in list(params):
755
+
935
+ if params[name] is SUPPRESS:
756
+ if (qpci_check_buggy_msi(pci_dev)) {
936
+ del params[name]
757
+ return;
937
+ for name in list(params):
758
+ }
938
+ if hasattr(params[name], '__name__'):
759
+
939
+ params[name] = params[name].__name__
760
+ qpci_msix_enable(pdev->pdev);
940
+ if params.get('choices') is not None:
761
+ qvirtio_pci_set_msix_configuration_vector(pdev, t_alloc, 0);
941
+ choices_str = ', '.join([str(c) for c in params['choices']])
762
+
942
+ params['choices'] = choices_str
763
+ features = qvirtio_get_features(dev);
943
+ return self._get_help_string(action) % params
764
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
944
+
765
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
945
+ def _iter_indented_subactions(self, action):
766
+ (1u << VIRTIO_F_NOTIFY_ON_EMPTY) |
946
+ try:
767
+ (1u << VIRTIO_BLK_F_SCSI));
947
+ get_subactions = action._get_subactions
768
+ qvirtio_set_features(dev, features);
948
+ except AttributeError:
769
+
949
+ pass
770
+ capacity = qvirtio_config_readq(dev, 0);
950
+ else:
771
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
951
+ self._indent()
772
+
952
+ for subaction in get_subactions():
773
+ vq = qvirtqueue_setup(dev, t_alloc, 0);
953
+ yield subaction
774
+ qvirtqueue_pci_msix_setup(pdev, (QVirtQueuePCI *)vq, t_alloc, 1);
954
+ self._dedent()
775
+
955
+
776
+ qvirtio_set_driver_ok(dev);
956
+ def _split_lines(self, text, width):
777
+
957
+ text = self._whitespace_matcher.sub(' ', text).strip()
778
+ /* Write request */
958
+ return _textwrap.wrap(text, width)
779
+ req.type = VIRTIO_BLK_T_OUT;
959
+
780
+ req.ioprio = 1;
960
+ def _fill_text(self, text, width, indent):
781
+ req.sector = 0;
961
+ text = self._whitespace_matcher.sub(' ', text).strip()
782
+ req.data = g_malloc0(512);
962
+ return _textwrap.fill(text, width, initial_indent=indent,
783
+ strcpy(req.data, "TEST");
963
+ subsequent_indent=indent)
784
+
964
+
785
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
965
+ def _get_help_string(self, action):
786
+
966
+ return action.help
787
+ g_free(req.data);
967
+
788
+
968
+
789
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
969
+class RawDescriptionHelpFormatter(HelpFormatter):
790
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
970
+ """Help message formatter which retains any formatting in descriptions.
791
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
971
+
792
+ qvirtqueue_kick(qts, dev, vq, free_head);
972
+ Only the name of this class is considered a public API. All the methods
793
+
973
+ provided by the class are considered an implementation detail.
794
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
974
+ """
795
+ QVIRTIO_BLK_TIMEOUT_US);
975
+
796
+
976
+ def _fill_text(self, text, width, indent):
797
+ /* Write request */
977
+ return ''.join([indent + line for line in text.splitlines(True)])
798
+ req.type = VIRTIO_BLK_T_OUT;
978
+
799
+ req.ioprio = 1;
979
+
800
+ req.sector = 1;
980
+class RawTextHelpFormatter(RawDescriptionHelpFormatter):
801
+ req.data = g_malloc0(512);
981
+ """Help message formatter which retains formatting of all help text.
802
+ strcpy(req.data, "TEST");
982
+
803
+
983
+ Only the name of this class is considered a public API. All the methods
804
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
984
+ provided by the class are considered an implementation detail.
805
+
985
+ """
806
+ g_free(req.data);
986
+
807
+
987
+ def _split_lines(self, text, width):
808
+ /* Notify after processing the third request */
988
+ return text.splitlines()
809
+ qvirtqueue_set_used_event(qts, vq, 2);
989
+
810
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
990
+
811
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
991
+class ArgumentDefaultsHelpFormatter(HelpFormatter):
812
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
992
+ """Help message formatter which adds default values to argument help.
813
+ qvirtqueue_kick(qts, dev, vq, free_head);
993
+
814
+ write_head = free_head;
994
+ Only the name of this class is considered a public API. All the methods
815
+
995
+ provided by the class are considered an implementation detail.
816
+ /* No notification expected */
996
+ """
817
+ status = qvirtio_wait_status_byte_no_isr(qts, dev,
997
+
818
+ vq, req_addr + 528,
998
+ def _get_help_string(self, action):
819
+ QVIRTIO_BLK_TIMEOUT_US);
999
+ help = action.help
820
+ g_assert_cmpint(status, ==, 0);
1000
+ if '%(default)' not in action.help:
821
+
1001
+ if action.default is not SUPPRESS:
822
+ guest_free(t_alloc, req_addr);
1002
+ defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
823
+
1003
+ if action.option_strings or action.nargs in defaulting_nargs:
824
+ /* Read request */
1004
+ help += ' (default: %(default)s)'
825
+ req.type = VIRTIO_BLK_T_IN;
1005
+ return help
826
+ req.ioprio = 1;
1006
+
827
+ req.sector = 1;
1007
+
828
+ req.data = g_malloc0(512);
1008
+# =====================
829
+
1009
+# Options and Arguments
830
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
1010
+# =====================
831
+
1011
+
832
+ g_free(req.data);
1012
+def _get_action_name(argument):
833
+
1013
+ if argument is None:
834
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
1014
+ return None
835
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
1015
+ elif argument.option_strings:
836
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
1016
+ return '/'.join(argument.option_strings)
837
+
1017
+ elif argument.metavar not in (None, SUPPRESS):
838
+ qvirtqueue_kick(qts, dev, vq, free_head);
1018
+ return argument.metavar
839
+
1019
+ elif argument.dest not in (None, SUPPRESS):
840
+ /* We get just one notification for both requests */
1020
+ return argument.dest
841
+ qvirtio_wait_used_elem(qts, dev, vq, write_head, NULL,
1021
+ else:
842
+ QVIRTIO_BLK_TIMEOUT_US);
1022
+ return None
843
+ g_assert(qvirtqueue_get_buf(qts, vq, &desc_idx, NULL));
1023
+
844
+ g_assert_cmpint(desc_idx, ==, free_head);
1024
+
845
+
1025
+class ArgumentError(Exception):
846
+ status = readb(req_addr + 528);
1026
+ """An error from creating or using an argument (optional or positional).
847
+ g_assert_cmpint(status, ==, 0);
1027
+
848
+
1028
+ The string value of this exception is the message, augmented with
849
+ data = g_malloc0(512);
1029
+ information about the argument that caused it.
850
+ qtest_memread(qts, req_addr + 16, data, 512);
1030
+ """
851
+ g_assert_cmpstr(data, ==, "TEST");
1031
+
852
+ g_free(data);
1032
+ def __init__(self, argument, message):
853
+
1033
+ self.argument_name = _get_action_name(argument)
854
+ guest_free(t_alloc, req_addr);
1034
+ self.message = message
855
+
1035
+
856
+ /* End test */
1036
+ def __str__(self):
857
+ qpci_msix_disable(pdev->pdev);
1037
+ if self.argument_name is None:
858
+
1038
+ format = '%(message)s'
859
+ qvirtqueue_cleanup(dev->bus, vq, t_alloc);
1039
+ else:
860
+}
1040
+ format = 'argument %(argument_name)s: %(message)s'
861
+
1041
+ return format % dict(message=self.message,
862
+static void pci_hotplug(void *obj, void *data, QGuestAllocator *t_alloc)
1042
+ argument_name=self.argument_name)
863
+{
1043
+
864
+ QVirtioPCIDevice *dev1 = obj;
1044
+
865
+ QVirtioPCIDevice *dev;
1045
+class ArgumentTypeError(Exception):
866
+ QTestState *qts = dev1->pdev->bus->qts;
1046
+ """An error from trying to convert a command line string to a type."""
867
+
1047
+ pass
868
+ /* plug secondary disk */
1048
+
869
+ qtest_qmp_device_add(qts, "vhost-user-blk-pci", "drv1",
1049
+
870
+ "{'addr': %s, 'chardev': 'char2'}",
1050
+# ==============
871
+ stringify(PCI_SLOT_HP) ".0");
1051
+# Action classes
872
+
1052
+# ==============
873
+ dev = virtio_pci_new(dev1->pdev->bus,
1053
+
874
+ &(QPCIAddress) { .devfn = QPCI_DEVFN(PCI_SLOT_HP, 0)
1054
+class Action(_AttributeHolder):
875
+ });
1055
+ """Information about how to convert command line strings to Python objects.
876
+ g_assert_nonnull(dev);
1056
+
877
+ g_assert_cmpint(dev->vdev.device_type, ==, VIRTIO_ID_BLOCK);
1057
+ Action objects are used by an ArgumentParser to represent the information
878
+ qvirtio_pci_device_disable(dev);
1058
+ needed to parse a single argument from one or more strings from the
879
+ qos_object_destroy((QOSGraphObject *)dev);
1059
+ command line. The keyword arguments to the Action constructor are also
880
+
1060
+ all attributes of Action instances.
881
+ /* unplug secondary disk */
1061
+
882
+ qpci_unplug_acpi_device_test(qts, "drv1", PCI_SLOT_HP);
1062
+ Keyword Arguments:
883
+}
1063
+
884
+
1064
+ - option_strings -- A list of command-line option strings which
885
+/*
1065
+ should be associated with this action.
886
+ * Check that setting the vring addr on a non-existent virtqueue does
1066
+
887
+ * not crash.
1067
+ - dest -- The name of the attribute to hold the created object(s)
888
+ */
1068
+
889
+static void test_nonexistent_virtqueue(void *obj, void *data,
1069
+ - nargs -- The number of command-line arguments that should be
890
+ QGuestAllocator *t_alloc)
1070
+ consumed. By default, one argument will be consumed and a single
891
+{
1071
+ value will be produced. Other values include:
892
+ QVhostUserBlkPCI *blk = obj;
1072
+ - N (an integer) consumes N arguments (and produces a list)
893
+ QVirtioPCIDevice *pdev = &blk->pci_vdev;
1073
+ - '?' consumes zero or one arguments
894
+ QPCIBar bar0;
1074
+ - '*' consumes zero or more arguments (and produces a list)
895
+ QPCIDevice *dev;
1075
+ - '+' consumes one or more arguments (and produces a list)
896
+
1076
+ Note that the difference between the default and nargs=1 is that
897
+ dev = qpci_device_find(pdev->pdev->bus, QPCI_DEVFN(4, 0));
1077
+ with the default, a single value will be produced, while with
898
+ g_assert(dev != NULL);
1078
+ nargs=1, a list containing a single value will be produced.
899
+ qpci_device_enable(dev);
1079
+
900
+
1080
+ - const -- The value to be produced if the option is specified and the
901
+ bar0 = qpci_iomap(dev, 0, NULL);
1081
+ option uses an action that takes no values.
902
+
1082
+
903
+ qpci_io_writeb(dev, bar0, VIRTIO_PCI_QUEUE_SEL, 2);
1083
+ - default -- The value to be produced if the option is not specified.
904
+ qpci_io_writel(dev, bar0, VIRTIO_PCI_QUEUE_PFN, 1);
1084
+
905
+
1085
+ - type -- The type which the command-line arguments should be converted
906
+ g_free(dev);
1086
+ to, should be one of 'string', 'int', 'float', 'complex' or a
907
+}
1087
+ callable object that accepts a single string argument. If None,
908
+
1088
+ 'string' is assumed.
909
+static const char *qtest_qemu_storage_daemon_binary(void)
1089
+
910
+{
1090
+ - choices -- A container of values that should be allowed. If not None,
911
+ const char *qemu_storage_daemon_bin;
1091
+ after a command-line argument has been converted to the appropriate
912
+
1092
+ type, an exception will be raised if it is not a member of this
913
+ qemu_storage_daemon_bin = getenv("QTEST_QEMU_STORAGE_DAEMON_BINARY");
1093
+ collection.
914
+ if (!qemu_storage_daemon_bin) {
1094
+
915
+ fprintf(stderr, "Environment variable "
1095
+ - required -- True if the action must always be specified at the
916
+ "QTEST_QEMU_STORAGE_DAEMON_BINARY required\n");
1096
+ command line. This is only meaningful for optional command-line
917
+ exit(0);
1097
+ arguments.
918
+ }
1098
+
919
+
1099
+ - help -- The help string describing the argument.
920
+ return qemu_storage_daemon_bin;
1100
+
921
+}
1101
+ - metavar -- The name to be used for the option's argument with the
922
+
1102
+ help string. If None, the 'dest' value will be used as the name.
923
+static void drive_destroy(void *path)
1103
+ """
924
+{
1104
+
925
+ unlink(path);
1105
+ def __init__(self,
926
+ g_free(path);
1106
+ option_strings,
927
+ qos_invalidate_command_line();
1107
+ dest,
928
+}
1108
+ nargs=None,
929
+
1109
+ const=None,
930
+static char *drive_create(void)
1110
+ default=None,
931
+{
1111
+ type=None,
932
+ int fd, ret;
1112
+ choices=None,
933
+ /** vhost-user-blk won't recognize drive located in /tmp */
1113
+ required=False,
934
+ char *t_path = g_strdup("qtest.XXXXXX");
1114
+ help=None,
935
+
1115
+ metavar=None):
936
+ /** Create a temporary raw image */
1116
+ self.option_strings = option_strings
937
+ fd = mkstemp(t_path);
1117
+ self.dest = dest
938
+ g_assert_cmpint(fd, >=, 0);
1118
+ self.nargs = nargs
939
+ ret = ftruncate(fd, TEST_IMAGE_SIZE);
1119
+ self.const = const
940
+ g_assert_cmpint(ret, ==, 0);
1120
+ self.default = default
941
+ close(fd);
1121
+ self.type = type
942
+
1122
+ self.choices = choices
943
+ g_test_queue_destroy(drive_destroy, t_path);
1123
+ self.required = required
944
+ return t_path;
1124
+ self.help = help
945
+}
1125
+ self.metavar = metavar
946
+
1126
+
947
+static char sock_path_tempate[] = "/tmp/qtest.vhost_user_blk.XXXXXX";
1127
+ def _get_kwargs(self):
948
+static char qmp_sock_path_tempate[] = "/tmp/qtest.vhost_user_blk.qmp.XXXXXX";
1128
+ names = [
949
+
1129
+ 'option_strings',
950
+static void quit_storage_daemon(void *qmp_test_state)
1130
+ 'dest',
951
+{
1131
+ 'nargs',
952
+ const char quit_str[] = "{ 'execute': 'quit' }";
1132
+ 'const',
953
+
1133
+ 'default',
954
+ /* Before quiting storate-daemon, quit qemu to avoid dubious messages */
1134
+ 'type',
955
+ qobject_unref(qtest_qmp(global_qtest, quit_str));
1135
+ 'choices',
956
+
1136
+ 'help',
957
+ /*
1137
+ 'metavar',
958
+ * Give storage-daemon enough time to wake up&terminate
1138
+ ]
959
+ * vu_client_trip coroutine so the Coroutine object could
1139
+ return [(name, getattr(self, name)) for name in names]
960
+ * be cleaned up. Otherwise LeakSanitizer would complain
1140
+
961
+ * about memory leaks.
1141
+ def __call__(self, parser, namespace, values, option_string=None):
962
+ */
1142
+ raise NotImplementedError(_('.__call__() not defined'))
963
+ g_usleep(1000);
1143
+
964
+
1144
+
965
+ qobject_unref(qtest_qmp((QTestState *)qmp_test_state, quit_str));
1145
+class _StoreAction(Action):
966
+ g_free(qmp_test_state);
1146
+
967
+}
1147
+ def __init__(self,
968
+
1148
+ option_strings,
969
+static char *start_vhost_user_blk(GString *cmd_line, int vus_instances)
1149
+ dest,
970
+{
1150
+ nargs=None,
971
+ const char *vhost_user_blk_bin = qtest_qemu_storage_daemon_binary();
1151
+ const=None,
972
+ int fd, qmp_fd, i;
1152
+ default=None,
973
+ QTestState *qmp_test_state;
1153
+ type=None,
974
+ gchar *img_path;
1154
+ choices=None,
975
+ char *sock_path = NULL;
1155
+ required=False,
976
+ char *qmp_sock_path = g_strdup(qmp_sock_path_tempate);
1156
+ help=None,
977
+ GString *storage_daemon_command = g_string_new(NULL);
1157
+ metavar=None):
978
+
1158
+ if nargs == 0:
979
+ qmp_fd = mkstemp(qmp_sock_path);
1159
+ raise ValueError('nargs for store actions must be > 0; if you '
980
+ g_assert_cmpint(qmp_fd, >=, 0);
1160
+ 'have nothing to store, actions such as store '
981
+ g_test_queue_destroy(drive_destroy, qmp_sock_path);
1161
+ 'true or store const may be more appropriate')
982
+
1162
+ if const is not None and nargs != OPTIONAL:
983
+ g_string_append_printf(storage_daemon_command,
1163
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
984
+ "exec %s "
1164
+ super(_StoreAction, self).__init__(
985
+ "--chardev socket,id=qmp,path=%s,server,nowait --monitor chardev=qmp ",
1165
+ option_strings=option_strings,
986
+ vhost_user_blk_bin, qmp_sock_path);
1166
+ dest=dest,
987
+
1167
+ nargs=nargs,
988
+ g_string_append_printf(cmd_line,
1168
+ const=const,
989
+ " -object memory-backend-memfd,id=mem,size=128M,share=on -numa node,memdev=mem ");
1169
+ default=default,
990
+
1170
+ type=type,
991
+ for (i = 0; i < vus_instances; i++) {
1171
+ choices=choices,
992
+ sock_path = g_strdup(sock_path_tempate);
1172
+ required=required,
993
+ fd = mkstemp(sock_path);
1173
+ help=help,
994
+ g_assert_cmpint(fd, >=, 0);
1174
+ metavar=metavar)
995
+ g_test_queue_destroy(drive_destroy, sock_path);
1175
+
996
+ /* create image file */
1176
+ def __call__(self, parser, namespace, values, option_string=None):
997
+ img_path = drive_create();
1177
+ setattr(namespace, self.dest, values)
998
+ g_string_append_printf(storage_daemon_command,
1178
+
999
+ "--blockdev driver=file,node-name=disk%d,filename=%s "
1179
+
1000
+ "--object vhost-user-blk-server,id=disk%d,unix-socket=%s,"
1180
+class _StoreConstAction(Action):
1001
+ "node-name=disk%i,writable=on ",
1181
+
1002
+ i, img_path, i, sock_path, i);
1182
+ def __init__(self,
1003
+
1183
+ option_strings,
1004
+ g_string_append_printf(cmd_line, "-chardev socket,id=char%d,path=%s ",
1184
+ dest,
1005
+ i + 1, sock_path);
1185
+ const,
1006
+ }
1186
+ default=None,
1007
+
1187
+ required=False,
1008
+ g_test_message("starting vhost-user backend: %s",
1188
+ help=None,
1009
+ storage_daemon_command->str);
1189
+ metavar=None):
1010
+ pid_t pid = fork();
1190
+ super(_StoreConstAction, self).__init__(
1011
+ if (pid == 0) {
1191
+ option_strings=option_strings,
1012
+ execlp("/bin/sh", "sh", "-c", storage_daemon_command->str, NULL);
1192
+ dest=dest,
1013
+ exit(1);
1193
+ nargs=0,
1014
+ }
1194
+ const=const,
1015
+ g_string_free(storage_daemon_command, true);
1195
+ default=default,
1016
+
1196
+ required=required,
1017
+ qmp_test_state = qtest_create_state_with_qmp_fd(
1197
+ help=help)
1018
+ qtest_socket_client(qmp_sock_path));
1198
+
1019
+ /*
1199
+ def __call__(self, parser, namespace, values, option_string=None):
1020
+ * Ask qemu-storage-daemon to quit so it
1200
+ setattr(namespace, self.dest, self.const)
1021
+ * will not block scripts/tap-driver.pl.
1201
+
1022
+ */
1202
+
1023
+ g_test_queue_destroy(quit_storage_daemon, qmp_test_state);
1203
+class _StoreTrueAction(_StoreConstAction):
1024
+
1204
+
1025
+ qobject_unref(qtest_qmp(qmp_test_state, "{'execute': 'qmp_capabilities'}"));
1205
+ def __init__(self,
1026
+ return sock_path;
1206
+ option_strings,
1027
+}
1207
+ dest,
1028
+
1208
+ default=False,
1029
+static void *vhost_user_blk_test_setup(GString *cmd_line, void *arg)
1209
+ required=False,
1030
+{
1210
+ help=None):
1031
+ start_vhost_user_blk(cmd_line, 1);
1211
+ super(_StoreTrueAction, self).__init__(
1032
+ return arg;
1212
+ option_strings=option_strings,
1033
+}
1213
+ dest=dest,
1034
+
1214
+ const=True,
1035
+/*
1215
+ default=default,
1036
+ * Setup for hotplug.
1216
+ required=required,
1037
+ *
1217
+ help=help)
1038
+ * Since vhost-user server only serves one vhost-user client one time,
1218
+
1039
+ * another exprot
1219
+
1040
+ *
1220
+class _StoreFalseAction(_StoreConstAction):
1041
+ */
1221
+
1042
+static void *vhost_user_blk_hotplug_test_setup(GString *cmd_line, void *arg)
1222
+ def __init__(self,
1043
+{
1223
+ option_strings,
1044
+ /* "-chardev socket,id=char2" is used for pci_hotplug*/
1224
+ dest,
1045
+ start_vhost_user_blk(cmd_line, 2);
1225
+ default=True,
1046
+ return arg;
1226
+ required=False,
1047
+}
1227
+ help=None):
1048
+
1228
+ super(_StoreFalseAction, self).__init__(
1049
+static void register_vhost_user_blk_test(void)
1229
+ option_strings=option_strings,
1050
+{
1230
+ dest=dest,
1051
+ QOSGraphTestOptions opts = {
1231
+ const=False,
1052
+ .before = vhost_user_blk_test_setup,
1232
+ default=default,
1053
+ };
1233
+ required=required,
1054
+
1234
+ help=help)
1055
+ /*
1235
+
1056
+ * tests for vhost-user-blk and vhost-user-blk-pci
1236
+
1057
+ * The tests are borrowed from tests/virtio-blk-test.c. But some tests
1237
+class _AppendAction(Action):
1058
+ * regarding block_resize don't work for vhost-user-blk.
1238
+
1059
+ * vhost-user-blk device doesn't have -drive, so tests containing
1239
+ def __init__(self,
1060
+ * block_resize are also abandoned,
1240
+ option_strings,
1061
+ * - config
1241
+ dest,
1062
+ * - resize
1242
+ nargs=None,
1063
+ */
1243
+ const=None,
1064
+ qos_add_test("basic", "vhost-user-blk", basic, &opts);
1244
+ default=None,
1065
+ qos_add_test("indirect", "vhost-user-blk", indirect, &opts);
1245
+ type=None,
1066
+ qos_add_test("idx", "vhost-user-blk-pci", idx, &opts);
1246
+ choices=None,
1067
+ qos_add_test("nxvirtq", "vhost-user-blk-pci",
1247
+ required=False,
1068
+ test_nonexistent_virtqueue, &opts);
1248
+ help=None,
1069
+
1249
+ metavar=None):
1070
+ opts.before = vhost_user_blk_hotplug_test_setup;
1250
+ if nargs == 0:
1071
+ qos_add_test("hotplug", "vhost-user-blk-pci", pci_hotplug, &opts);
1251
+ raise ValueError('nargs for append actions must be > 0; if arg '
1072
+}
1252
+ 'strings are not supplying the value to append, '
1073
+
1253
+ 'the append const action may be more appropriate')
1074
+libqos_init(register_vhost_user_blk_test);
1254
+ if const is not None and nargs != OPTIONAL:
1075
diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
1255
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
1076
index XXXXXXX..XXXXXXX 100644
1256
+ super(_AppendAction, self).__init__(
1077
--- a/tests/qtest/libqos/meson.build
1257
+ option_strings=option_strings,
1078
+++ b/tests/qtest/libqos/meson.build
1258
+ dest=dest,
1079
@@ -XXX,XX +XXX,XX @@ libqos_srcs = files('../libqtest.c',
1259
+ nargs=nargs,
1080
'virtio-9p.c',
1260
+ const=const,
1081
'virtio-balloon.c',
1261
+ default=default,
1082
'virtio-blk.c',
1262
+ type=type,
1083
+ 'vhost-user-blk.c',
1263
+ choices=choices,
1084
'virtio-mmio.c',
1264
+ required=required,
1085
'virtio-net.c',
1265
+ help=help,
1086
'virtio-pci.c',
1266
+ metavar=metavar)
1087
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
1267
+
1088
index XXXXXXX..XXXXXXX 100644
1268
+ def __call__(self, parser, namespace, values, option_string=None):
1089
--- a/tests/qtest/meson.build
1269
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
1090
+++ b/tests/qtest/meson.build
1270
+ items.append(values)
1091
@@ -XXX,XX +XXX,XX @@ qos_test_ss.add(
1271
+ setattr(namespace, self.dest, items)
1092
)
1272
+
1093
qos_test_ss.add(when: 'CONFIG_VIRTFS', if_true: files('virtio-9p-test.c'))
1273
+
1094
qos_test_ss.add(when: 'CONFIG_VHOST_USER', if_true: files('vhost-user-test.c'))
1274
+class _AppendConstAction(Action):
1095
+qos_test_ss.add(when: ['CONFIG_LINUX', 'CONFIG_TOOLS'], if_true: files('vhost-user-blk-test.c'))
1275
+
1096
1276
+ def __init__(self,
1097
extra_qtest_deps = {
1277
+ option_strings,
1098
'bios-tables-test': [io],
1278
+ dest,
1099
@@ -XXX,XX +XXX,XX @@ foreach dir : target_dirs
1279
+ const,
1100
endif
1280
+ default=None,
1101
qtest_env.set('G_TEST_DBUS_DAEMON', meson.source_root() / 'tests/dbus-vmstate-daemon.sh')
1281
+ required=False,
1102
qtest_env.set('QTEST_QEMU_BINARY', './qemu-system-' + target_base)
1282
+ help=None,
1103
-
1283
+ metavar=None):
1104
+ qtest_env.set('QTEST_QEMU_STORAGE_DAEMON_BINARY', './storage-daemon/qemu-storage-daemon')
1284
+ super(_AppendConstAction, self).__init__(
1105
+
1285
+ option_strings=option_strings,
1106
foreach test : qtests
1286
+ dest=dest,
1107
# Executables are shared across targets, declare them only the first time we
1287
+ nargs=0,
1108
# encounter them
1288
+ const=const,
1289
+ default=default,
1290
+ required=required,
1291
+ help=help,
1292
+ metavar=metavar)
1293
+
1294
+ def __call__(self, parser, namespace, values, option_string=None):
1295
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
1296
+ items.append(self.const)
1297
+ setattr(namespace, self.dest, items)
1298
+
1299
+
1300
+class _CountAction(Action):
1301
+
1302
+ def __init__(self,
1303
+ option_strings,
1304
+ dest,
1305
+ default=None,
1306
+ required=False,
1307
+ help=None):
1308
+ super(_CountAction, self).__init__(
1309
+ option_strings=option_strings,
1310
+ dest=dest,
1311
+ nargs=0,
1312
+ default=default,
1313
+ required=required,
1314
+ help=help)
1315
+
1316
+ def __call__(self, parser, namespace, values, option_string=None):
1317
+ new_count = _ensure_value(namespace, self.dest, 0) + 1
1318
+ setattr(namespace, self.dest, new_count)
1319
+
1320
+
1321
+class _HelpAction(Action):
1322
+
1323
+ def __init__(self,
1324
+ option_strings,
1325
+ dest=SUPPRESS,
1326
+ default=SUPPRESS,
1327
+ help=None):
1328
+ super(_HelpAction, self).__init__(
1329
+ option_strings=option_strings,
1330
+ dest=dest,
1331
+ default=default,
1332
+ nargs=0,
1333
+ help=help)
1334
+
1335
+ def __call__(self, parser, namespace, values, option_string=None):
1336
+ parser.print_help()
1337
+ parser.exit()
1338
+
1339
+
1340
+class _VersionAction(Action):
1341
+
1342
+ def __init__(self,
1343
+ option_strings,
1344
+ version=None,
1345
+ dest=SUPPRESS,
1346
+ default=SUPPRESS,
1347
+ help="show program's version number and exit"):
1348
+ super(_VersionAction, self).__init__(
1349
+ option_strings=option_strings,
1350
+ dest=dest,
1351
+ default=default,
1352
+ nargs=0,
1353
+ help=help)
1354
+ self.version = version
1355
+
1356
+ def __call__(self, parser, namespace, values, option_string=None):
1357
+ version = self.version
1358
+ if version is None:
1359
+ version = parser.version
1360
+ formatter = parser._get_formatter()
1361
+ formatter.add_text(version)
1362
+ parser.exit(message=formatter.format_help())
1363
+
1364
+
1365
+class _SubParsersAction(Action):
1366
+
1367
+ class _ChoicesPseudoAction(Action):
1368
+
1369
+ def __init__(self, name, aliases, help):
1370
+ metavar = dest = name
1371
+ if aliases:
1372
+ metavar += ' (%s)' % ', '.join(aliases)
1373
+ sup = super(_SubParsersAction._ChoicesPseudoAction, self)
1374
+ sup.__init__(option_strings=[], dest=dest, help=help,
1375
+ metavar=metavar)
1376
+
1377
+ def __init__(self,
1378
+ option_strings,
1379
+ prog,
1380
+ parser_class,
1381
+ dest=SUPPRESS,
1382
+ help=None,
1383
+ metavar=None):
1384
+
1385
+ self._prog_prefix = prog
1386
+ self._parser_class = parser_class
1387
+ self._name_parser_map = {}
1388
+ self._choices_actions = []
1389
+
1390
+ super(_SubParsersAction, self).__init__(
1391
+ option_strings=option_strings,
1392
+ dest=dest,
1393
+ nargs=PARSER,
1394
+ choices=self._name_parser_map,
1395
+ help=help,
1396
+ metavar=metavar)
1397
+
1398
+ def add_parser(self, name, **kwargs):
1399
+ # set prog from the existing prefix
1400
+ if kwargs.get('prog') is None:
1401
+ kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
1402
+
1403
+ aliases = kwargs.pop('aliases', ())
1404
+
1405
+ # create a pseudo-action to hold the choice help
1406
+ if 'help' in kwargs:
1407
+ help = kwargs.pop('help')
1408
+ choice_action = self._ChoicesPseudoAction(name, aliases, help)
1409
+ self._choices_actions.append(choice_action)
1410
+
1411
+ # create the parser and add it to the map
1412
+ parser = self._parser_class(**kwargs)
1413
+ self._name_parser_map[name] = parser
1414
+
1415
+ # make parser available under aliases also
1416
+ for alias in aliases:
1417
+ self._name_parser_map[alias] = parser
1418
+
1419
+ return parser
1420
+
1421
+ def _get_subactions(self):
1422
+ return self._choices_actions
1423
+
1424
+ def __call__(self, parser, namespace, values, option_string=None):
1425
+ parser_name = values[0]
1426
+ arg_strings = values[1:]
1427
+
1428
+ # set the parser name if requested
1429
+ if self.dest is not SUPPRESS:
1430
+ setattr(namespace, self.dest, parser_name)
1431
+
1432
+ # select the parser
1433
+ try:
1434
+ parser = self._name_parser_map[parser_name]
1435
+ except KeyError:
1436
+ tup = parser_name, ', '.join(self._name_parser_map)
1437
+ msg = _('unknown parser %r (choices: %s)' % tup)
1438
+ raise ArgumentError(self, msg)
1439
+
1440
+ # parse all the remaining options into the namespace
1441
+ # store any unrecognized options on the object, so that the top
1442
+ # level parser can decide what to do with them
1443
+ namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
1444
+ if arg_strings:
1445
+ vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
1446
+ getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
1447
+
1448
+
1449
+# ==============
1450
+# Type classes
1451
+# ==============
1452
+
1453
+class FileType(object):
1454
+ """Factory for creating file object types
1455
+
1456
+ Instances of FileType are typically passed as type= arguments to the
1457
+ ArgumentParser add_argument() method.
1458
+
1459
+ Keyword Arguments:
1460
+ - mode -- A string indicating how the file is to be opened. Accepts the
1461
+ same values as the builtin open() function.
1462
+ - bufsize -- The file's desired buffer size. Accepts the same values as
1463
+ the builtin open() function.
1464
+ """
1465
+
1466
+ def __init__(self, mode='r', bufsize=None):
1467
+ self._mode = mode
1468
+ self._bufsize = bufsize
1469
+
1470
+ def __call__(self, string):
1471
+ # the special argument "-" means sys.std{in,out}
1472
+ if string == '-':
1473
+ if 'r' in self._mode:
1474
+ return _sys.stdin
1475
+ elif 'w' in self._mode:
1476
+ return _sys.stdout
1477
+ else:
1478
+ msg = _('argument "-" with mode %r' % self._mode)
1479
+ raise ValueError(msg)
1480
+
1481
+ try:
1482
+ # all other arguments are used as file names
1483
+ if self._bufsize:
1484
+ return open(string, self._mode, self._bufsize)
1485
+ else:
1486
+ return open(string, self._mode)
1487
+ except IOError:
1488
+ err = _sys.exc_info()[1]
1489
+ message = _("can't open '%s': %s")
1490
+ raise ArgumentTypeError(message % (string, err))
1491
+
1492
+ def __repr__(self):
1493
+ args = [self._mode, self._bufsize]
1494
+ args_str = ', '.join([repr(arg) for arg in args if arg is not None])
1495
+ return '%s(%s)' % (type(self).__name__, args_str)
1496
+
1497
+# ===========================
1498
+# Optional and Positional Parsing
1499
+# ===========================
1500
+
1501
+class Namespace(_AttributeHolder):
1502
+ """Simple object for storing attributes.
1503
+
1504
+ Implements equality by attribute names and values, and provides a simple
1505
+ string representation.
1506
+ """
1507
+
1508
+ def __init__(self, **kwargs):
1509
+ for name in kwargs:
1510
+ setattr(self, name, kwargs[name])
1511
+
1512
+ __hash__ = None
1513
+
1514
+ def __eq__(self, other):
1515
+ return vars(self) == vars(other)
1516
+
1517
+ def __ne__(self, other):
1518
+ return not (self == other)
1519
+
1520
+ def __contains__(self, key):
1521
+ return key in self.__dict__
1522
+
1523
+
1524
+class _ActionsContainer(object):
1525
+
1526
+ def __init__(self,
1527
+ description,
1528
+ prefix_chars,
1529
+ argument_default,
1530
+ conflict_handler):
1531
+ super(_ActionsContainer, self).__init__()
1532
+
1533
+ self.description = description
1534
+ self.argument_default = argument_default
1535
+ self.prefix_chars = prefix_chars
1536
+ self.conflict_handler = conflict_handler
1537
+
1538
+ # set up registries
1539
+ self._registries = {}
1540
+
1541
+ # register actions
1542
+ self.register('action', None, _StoreAction)
1543
+ self.register('action', 'store', _StoreAction)
1544
+ self.register('action', 'store_const', _StoreConstAction)
1545
+ self.register('action', 'store_true', _StoreTrueAction)
1546
+ self.register('action', 'store_false', _StoreFalseAction)
1547
+ self.register('action', 'append', _AppendAction)
1548
+ self.register('action', 'append_const', _AppendConstAction)
1549
+ self.register('action', 'count', _CountAction)
1550
+ self.register('action', 'help', _HelpAction)
1551
+ self.register('action', 'version', _VersionAction)
1552
+ self.register('action', 'parsers', _SubParsersAction)
1553
+
1554
+ # raise an exception if the conflict handler is invalid
1555
+ self._get_handler()
1556
+
1557
+ # action storage
1558
+ self._actions = []
1559
+ self._option_string_actions = {}
1560
+
1561
+ # groups
1562
+ self._action_groups = []
1563
+ self._mutually_exclusive_groups = []
1564
+
1565
+ # defaults storage
1566
+ self._defaults = {}
1567
+
1568
+ # determines whether an "option" looks like a negative number
1569
+ self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
1570
+
1571
+ # whether or not there are any optionals that look like negative
1572
+ # numbers -- uses a list so it can be shared and edited
1573
+ self._has_negative_number_optionals = []
1574
+
1575
+ # ====================
1576
+ # Registration methods
1577
+ # ====================
1578
+ def register(self, registry_name, value, object):
1579
+ registry = self._registries.setdefault(registry_name, {})
1580
+ registry[value] = object
1581
+
1582
+ def _registry_get(self, registry_name, value, default=None):
1583
+ return self._registries[registry_name].get(value, default)
1584
+
1585
+ # ==================================
1586
+ # Namespace default accessor methods
1587
+ # ==================================
1588
+ def set_defaults(self, **kwargs):
1589
+ self._defaults.update(kwargs)
1590
+
1591
+ # if these defaults match any existing arguments, replace
1592
+ # the previous default on the object with the new one
1593
+ for action in self._actions:
1594
+ if action.dest in kwargs:
1595
+ action.default = kwargs[action.dest]
1596
+
1597
+ def get_default(self, dest):
1598
+ for action in self._actions:
1599
+ if action.dest == dest and action.default is not None:
1600
+ return action.default
1601
+ return self._defaults.get(dest, None)
1602
+
1603
+
1604
+ # =======================
1605
+ # Adding argument actions
1606
+ # =======================
1607
+ def add_argument(self, *args, **kwargs):
1608
+ """
1609
+ add_argument(dest, ..., name=value, ...)
1610
+ add_argument(option_string, option_string, ..., name=value, ...)
1611
+ """
1612
+
1613
+ # if no positional args are supplied or only one is supplied and
1614
+ # it doesn't look like an option string, parse a positional
1615
+ # argument
1616
+ chars = self.prefix_chars
1617
+ if not args or len(args) == 1 and args[0][0] not in chars:
1618
+ if args and 'dest' in kwargs:
1619
+ raise ValueError('dest supplied twice for positional argument')
1620
+ kwargs = self._get_positional_kwargs(*args, **kwargs)
1621
+
1622
+ # otherwise, we're adding an optional argument
1623
+ else:
1624
+ kwargs = self._get_optional_kwargs(*args, **kwargs)
1625
+
1626
+ # if no default was supplied, use the parser-level default
1627
+ if 'default' not in kwargs:
1628
+ dest = kwargs['dest']
1629
+ if dest in self._defaults:
1630
+ kwargs['default'] = self._defaults[dest]
1631
+ elif self.argument_default is not None:
1632
+ kwargs['default'] = self.argument_default
1633
+
1634
+ # create the action object, and add it to the parser
1635
+ action_class = self._pop_action_class(kwargs)
1636
+ if not _callable(action_class):
1637
+ raise ValueError('unknown action "%s"' % action_class)
1638
+ action = action_class(**kwargs)
1639
+
1640
+ # raise an error if the action type is not callable
1641
+ type_func = self._registry_get('type', action.type, action.type)
1642
+ if not _callable(type_func):
1643
+ raise ValueError('%r is not callable' % type_func)
1644
+
1645
+ return self._add_action(action)
1646
+
1647
+ def add_argument_group(self, *args, **kwargs):
1648
+ group = _ArgumentGroup(self, *args, **kwargs)
1649
+ self._action_groups.append(group)
1650
+ return group
1651
+
1652
+ def add_mutually_exclusive_group(self, **kwargs):
1653
+ group = _MutuallyExclusiveGroup(self, **kwargs)
1654
+ self._mutually_exclusive_groups.append(group)
1655
+ return group
1656
+
1657
+ def _add_action(self, action):
1658
+ # resolve any conflicts
1659
+ self._check_conflict(action)
1660
+
1661
+ # add to actions list
1662
+ self._actions.append(action)
1663
+ action.container = self
1664
+
1665
+ # index the action by any option strings it has
1666
+ for option_string in action.option_strings:
1667
+ self._option_string_actions[option_string] = action
1668
+
1669
+ # set the flag if any option strings look like negative numbers
1670
+ for option_string in action.option_strings:
1671
+ if self._negative_number_matcher.match(option_string):
1672
+ if not self._has_negative_number_optionals:
1673
+ self._has_negative_number_optionals.append(True)
1674
+
1675
+ # return the created action
1676
+ return action
1677
+
1678
+ def _remove_action(self, action):
1679
+ self._actions.remove(action)
1680
+
1681
+ def _add_container_actions(self, container):
1682
+ # collect groups by titles
1683
+ title_group_map = {}
1684
+ for group in self._action_groups:
1685
+ if group.title in title_group_map:
1686
+ msg = _('cannot merge actions - two groups are named %r')
1687
+ raise ValueError(msg % (group.title))
1688
+ title_group_map[group.title] = group
1689
+
1690
+ # map each action to its group
1691
+ group_map = {}
1692
+ for group in container._action_groups:
1693
+
1694
+ # if a group with the title exists, use that, otherwise
1695
+ # create a new group matching the container's group
1696
+ if group.title not in title_group_map:
1697
+ title_group_map[group.title] = self.add_argument_group(
1698
+ title=group.title,
1699
+ description=group.description,
1700
+ conflict_handler=group.conflict_handler)
1701
+
1702
+ # map the actions to their new group
1703
+ for action in group._group_actions:
1704
+ group_map[action] = title_group_map[group.title]
1705
+
1706
+ # add container's mutually exclusive groups
1707
+ # NOTE: if add_mutually_exclusive_group ever gains title= and
1708
+ # description= then this code will need to be expanded as above
1709
+ for group in container._mutually_exclusive_groups:
1710
+ mutex_group = self.add_mutually_exclusive_group(
1711
+ required=group.required)
1712
+
1713
+ # map the actions to their new mutex group
1714
+ for action in group._group_actions:
1715
+ group_map[action] = mutex_group
1716
+
1717
+ # add all actions to this container or their group
1718
+ for action in container._actions:
1719
+ group_map.get(action, self)._add_action(action)
1720
+
1721
+ def _get_positional_kwargs(self, dest, **kwargs):
1722
+ # make sure required is not specified
1723
+ if 'required' in kwargs:
1724
+ msg = _("'required' is an invalid argument for positionals")
1725
+ raise TypeError(msg)
1726
+
1727
+ # mark positional arguments as required if at least one is
1728
+ # always required
1729
+ if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
1730
+ kwargs['required'] = True
1731
+ if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
1732
+ kwargs['required'] = True
1733
+
1734
+ # return the keyword arguments with no option strings
1735
+ return dict(kwargs, dest=dest, option_strings=[])
1736
+
1737
+ def _get_optional_kwargs(self, *args, **kwargs):
1738
+ # determine short and long option strings
1739
+ option_strings = []
1740
+ long_option_strings = []
1741
+ for option_string in args:
1742
+ # error on strings that don't start with an appropriate prefix
1743
+ if not option_string[0] in self.prefix_chars:
1744
+ msg = _('invalid option string %r: '
1745
+ 'must start with a character %r')
1746
+ tup = option_string, self.prefix_chars
1747
+ raise ValueError(msg % tup)
1748
+
1749
+ # strings starting with two prefix characters are long options
1750
+ option_strings.append(option_string)
1751
+ if option_string[0] in self.prefix_chars:
1752
+ if len(option_string) > 1:
1753
+ if option_string[1] in self.prefix_chars:
1754
+ long_option_strings.append(option_string)
1755
+
1756
+ # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
1757
+ dest = kwargs.pop('dest', None)
1758
+ if dest is None:
1759
+ if long_option_strings:
1760
+ dest_option_string = long_option_strings[0]
1761
+ else:
1762
+ dest_option_string = option_strings[0]
1763
+ dest = dest_option_string.lstrip(self.prefix_chars)
1764
+ if not dest:
1765
+ msg = _('dest= is required for options like %r')
1766
+ raise ValueError(msg % option_string)
1767
+ dest = dest.replace('-', '_')
1768
+
1769
+ # return the updated keyword arguments
1770
+ return dict(kwargs, dest=dest, option_strings=option_strings)
1771
+
1772
+ def _pop_action_class(self, kwargs, default=None):
1773
+ action = kwargs.pop('action', default)
1774
+ return self._registry_get('action', action, action)
1775
+
1776
+ def _get_handler(self):
1777
+ # determine function from conflict handler string
1778
+ handler_func_name = '_handle_conflict_%s' % self.conflict_handler
1779
+ try:
1780
+ return getattr(self, handler_func_name)
1781
+ except AttributeError:
1782
+ msg = _('invalid conflict_resolution value: %r')
1783
+ raise ValueError(msg % self.conflict_handler)
1784
+
1785
+ def _check_conflict(self, action):
1786
+
1787
+ # find all options that conflict with this option
1788
+ confl_optionals = []
1789
+ for option_string in action.option_strings:
1790
+ if option_string in self._option_string_actions:
1791
+ confl_optional = self._option_string_actions[option_string]
1792
+ confl_optionals.append((option_string, confl_optional))
1793
+
1794
+ # resolve any conflicts
1795
+ if confl_optionals:
1796
+ conflict_handler = self._get_handler()
1797
+ conflict_handler(action, confl_optionals)
1798
+
1799
+ def _handle_conflict_error(self, action, conflicting_actions):
1800
+ message = _('conflicting option string(s): %s')
1801
+ conflict_string = ', '.join([option_string
1802
+ for option_string, action
1803
+ in conflicting_actions])
1804
+ raise ArgumentError(action, message % conflict_string)
1805
+
1806
+ def _handle_conflict_resolve(self, action, conflicting_actions):
1807
+
1808
+ # remove all conflicting options
1809
+ for option_string, action in conflicting_actions:
1810
+
1811
+ # remove the conflicting option
1812
+ action.option_strings.remove(option_string)
1813
+ self._option_string_actions.pop(option_string, None)
1814
+
1815
+ # if the option now has no option string, remove it from the
1816
+ # container holding it
1817
+ if not action.option_strings:
1818
+ action.container._remove_action(action)
1819
+
1820
+
1821
+class _ArgumentGroup(_ActionsContainer):
1822
+
1823
+ def __init__(self, container, title=None, description=None, **kwargs):
1824
+ # add any missing keyword arguments by checking the container
1825
+ update = kwargs.setdefault
1826
+ update('conflict_handler', container.conflict_handler)
1827
+ update('prefix_chars', container.prefix_chars)
1828
+ update('argument_default', container.argument_default)
1829
+ super_init = super(_ArgumentGroup, self).__init__
1830
+ super_init(description=description, **kwargs)
1831
+
1832
+ # group attributes
1833
+ self.title = title
1834
+ self._group_actions = []
1835
+
1836
+ # share most attributes with the container
1837
+ self._registries = container._registries
1838
+ self._actions = container._actions
1839
+ self._option_string_actions = container._option_string_actions
1840
+ self._defaults = container._defaults
1841
+ self._has_negative_number_optionals = \
1842
+ container._has_negative_number_optionals
1843
+
1844
+ def _add_action(self, action):
1845
+ action = super(_ArgumentGroup, self)._add_action(action)
1846
+ self._group_actions.append(action)
1847
+ return action
1848
+
1849
+ def _remove_action(self, action):
1850
+ super(_ArgumentGroup, self)._remove_action(action)
1851
+ self._group_actions.remove(action)
1852
+
1853
+
1854
+class _MutuallyExclusiveGroup(_ArgumentGroup):
1855
+
1856
+ def __init__(self, container, required=False):
1857
+ super(_MutuallyExclusiveGroup, self).__init__(container)
1858
+ self.required = required
1859
+ self._container = container
1860
+
1861
+ def _add_action(self, action):
1862
+ if action.required:
1863
+ msg = _('mutually exclusive arguments must be optional')
1864
+ raise ValueError(msg)
1865
+ action = self._container._add_action(action)
1866
+ self._group_actions.append(action)
1867
+ return action
1868
+
1869
+ def _remove_action(self, action):
1870
+ self._container._remove_action(action)
1871
+ self._group_actions.remove(action)
1872
+
1873
+
1874
+class ArgumentParser(_AttributeHolder, _ActionsContainer):
1875
+ """Object for parsing command line strings into Python objects.
1876
+
1877
+ Keyword Arguments:
1878
+ - prog -- The name of the program (default: sys.argv[0])
1879
+ - usage -- A usage message (default: auto-generated from arguments)
1880
+ - description -- A description of what the program does
1881
+ - epilog -- Text following the argument descriptions
1882
+ - parents -- Parsers whose arguments should be copied into this one
1883
+ - formatter_class -- HelpFormatter class for printing help messages
1884
+ - prefix_chars -- Characters that prefix optional arguments
1885
+ - fromfile_prefix_chars -- Characters that prefix files containing
1886
+ additional arguments
1887
+ - argument_default -- The default value for all arguments
1888
+ - conflict_handler -- String indicating how to handle conflicts
1889
+ - add_help -- Add a -h/-help option
1890
+ """
1891
+
1892
+ def __init__(self,
1893
+ prog=None,
1894
+ usage=None,
1895
+ description=None,
1896
+ epilog=None,
1897
+ version=None,
1898
+ parents=[],
1899
+ formatter_class=HelpFormatter,
1900
+ prefix_chars='-',
1901
+ fromfile_prefix_chars=None,
1902
+ argument_default=None,
1903
+ conflict_handler='error',
1904
+ add_help=True):
1905
+
1906
+ if version is not None:
1907
+ import warnings
1908
+ warnings.warn(
1909
+ """The "version" argument to ArgumentParser is deprecated. """
1910
+ """Please use """
1911
+ """"add_argument(..., action='version', version="N", ...)" """
1912
+ """instead""", DeprecationWarning)
1913
+
1914
+ superinit = super(ArgumentParser, self).__init__
1915
+ superinit(description=description,
1916
+ prefix_chars=prefix_chars,
1917
+ argument_default=argument_default,
1918
+ conflict_handler=conflict_handler)
1919
+
1920
+ # default setting for prog
1921
+ if prog is None:
1922
+ prog = _os.path.basename(_sys.argv[0])
1923
+
1924
+ self.prog = prog
1925
+ self.usage = usage
1926
+ self.epilog = epilog
1927
+ self.version = version
1928
+ self.formatter_class = formatter_class
1929
+ self.fromfile_prefix_chars = fromfile_prefix_chars
1930
+ self.add_help = add_help
1931
+
1932
+ add_group = self.add_argument_group
1933
+ self._positionals = add_group(_('positional arguments'))
1934
+ self._optionals = add_group(_('optional arguments'))
1935
+ self._subparsers = None
1936
+
1937
+ # register types
1938
+ def identity(string):
1939
+ return string
1940
+ self.register('type', None, identity)
1941
+
1942
+ # add help and version arguments if necessary
1943
+ # (using explicit default to override global argument_default)
1944
+ if '-' in prefix_chars:
1945
+ default_prefix = '-'
1946
+ else:
1947
+ default_prefix = prefix_chars[0]
1948
+ if self.add_help:
1949
+ self.add_argument(
1950
+ default_prefix+'h', default_prefix*2+'help',
1951
+ action='help', default=SUPPRESS,
1952
+ help=_('show this help message and exit'))
1953
+ if self.version:
1954
+ self.add_argument(
1955
+ default_prefix+'v', default_prefix*2+'version',
1956
+ action='version', default=SUPPRESS,
1957
+ version=self.version,
1958
+ help=_("show program's version number and exit"))
1959
+
1960
+ # add parent arguments and defaults
1961
+ for parent in parents:
1962
+ self._add_container_actions(parent)
1963
+ try:
1964
+ defaults = parent._defaults
1965
+ except AttributeError:
1966
+ pass
1967
+ else:
1968
+ self._defaults.update(defaults)
1969
+
1970
+ # =======================
1971
+ # Pretty __repr__ methods
1972
+ # =======================
1973
+ def _get_kwargs(self):
1974
+ names = [
1975
+ 'prog',
1976
+ 'usage',
1977
+ 'description',
1978
+ 'version',
1979
+ 'formatter_class',
1980
+ 'conflict_handler',
1981
+ 'add_help',
1982
+ ]
1983
+ return [(name, getattr(self, name)) for name in names]
1984
+
1985
+ # ==================================
1986
+ # Optional/Positional adding methods
1987
+ # ==================================
1988
+ def add_subparsers(self, **kwargs):
1989
+ if self._subparsers is not None:
1990
+ self.error(_('cannot have multiple subparser arguments'))
1991
+
1992
+ # add the parser class to the arguments if it's not present
1993
+ kwargs.setdefault('parser_class', type(self))
1994
+
1995
+ if 'title' in kwargs or 'description' in kwargs:
1996
+ title = _(kwargs.pop('title', 'subcommands'))
1997
+ description = _(kwargs.pop('description', None))
1998
+ self._subparsers = self.add_argument_group(title, description)
1999
+ else:
2000
+ self._subparsers = self._positionals
2001
+
2002
+ # prog defaults to the usage message of this parser, skipping
2003
+ # optional arguments and with no "usage:" prefix
2004
+ if kwargs.get('prog') is None:
2005
+ formatter = self._get_formatter()
2006
+ positionals = self._get_positional_actions()
2007
+ groups = self._mutually_exclusive_groups
2008
+ formatter.add_usage(self.usage, positionals, groups, '')
2009
+ kwargs['prog'] = formatter.format_help().strip()
2010
+
2011
+ # create the parsers action and add it to the positionals list
2012
+ parsers_class = self._pop_action_class(kwargs, 'parsers')
2013
+ action = parsers_class(option_strings=[], **kwargs)
2014
+ self._subparsers._add_action(action)
2015
+
2016
+ # return the created parsers action
2017
+ return action
2018
+
2019
+ def _add_action(self, action):
2020
+ if action.option_strings:
2021
+ self._optionals._add_action(action)
2022
+ else:
2023
+ self._positionals._add_action(action)
2024
+ return action
2025
+
2026
+ def _get_optional_actions(self):
2027
+ return [action
2028
+ for action in self._actions
2029
+ if action.option_strings]
2030
+
2031
+ def _get_positional_actions(self):
2032
+ return [action
2033
+ for action in self._actions
2034
+ if not action.option_strings]
2035
+
2036
+ # =====================================
2037
+ # Command line argument parsing methods
2038
+ # =====================================
2039
+ def parse_args(self, args=None, namespace=None):
2040
+ args, argv = self.parse_known_args(args, namespace)
2041
+ if argv:
2042
+ msg = _('unrecognized arguments: %s')
2043
+ self.error(msg % ' '.join(argv))
2044
+ return args
2045
+
2046
+ def parse_known_args(self, args=None, namespace=None):
2047
+ # args default to the system args
2048
+ if args is None:
2049
+ args = _sys.argv[1:]
2050
+
2051
+ # default Namespace built from parser defaults
2052
+ if namespace is None:
2053
+ namespace = Namespace()
2054
+
2055
+ # add any action defaults that aren't present
2056
+ for action in self._actions:
2057
+ if action.dest is not SUPPRESS:
2058
+ if not hasattr(namespace, action.dest):
2059
+ if action.default is not SUPPRESS:
2060
+ setattr(namespace, action.dest, action.default)
2061
+
2062
+ # add any parser defaults that aren't present
2063
+ for dest in self._defaults:
2064
+ if not hasattr(namespace, dest):
2065
+ setattr(namespace, dest, self._defaults[dest])
2066
+
2067
+ # parse the arguments and exit if there are any errors
2068
+ try:
2069
+ namespace, args = self._parse_known_args(args, namespace)
2070
+ if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
2071
+ args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
2072
+ delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
2073
+ return namespace, args
2074
+ except ArgumentError:
2075
+ err = _sys.exc_info()[1]
2076
+ self.error(str(err))
2077
+
2078
+ def _parse_known_args(self, arg_strings, namespace):
2079
+ # replace arg strings that are file references
2080
+ if self.fromfile_prefix_chars is not None:
2081
+ arg_strings = self._read_args_from_files(arg_strings)
2082
+
2083
+ # map all mutually exclusive arguments to the other arguments
2084
+ # they can't occur with
2085
+ action_conflicts = {}
2086
+ for mutex_group in self._mutually_exclusive_groups:
2087
+ group_actions = mutex_group._group_actions
2088
+ for i, mutex_action in enumerate(mutex_group._group_actions):
2089
+ conflicts = action_conflicts.setdefault(mutex_action, [])
2090
+ conflicts.extend(group_actions[:i])
2091
+ conflicts.extend(group_actions[i + 1:])
2092
+
2093
+ # find all option indices, and determine the arg_string_pattern
2094
+ # which has an 'O' if there is an option at an index,
2095
+ # an 'A' if there is an argument, or a '-' if there is a '--'
2096
+ option_string_indices = {}
2097
+ arg_string_pattern_parts = []
2098
+ arg_strings_iter = iter(arg_strings)
2099
+ for i, arg_string in enumerate(arg_strings_iter):
2100
+
2101
+ # all args after -- are non-options
2102
+ if arg_string == '--':
2103
+ arg_string_pattern_parts.append('-')
2104
+ for arg_string in arg_strings_iter:
2105
+ arg_string_pattern_parts.append('A')
2106
+
2107
+ # otherwise, add the arg to the arg strings
2108
+ # and note the index if it was an option
2109
+ else:
2110
+ option_tuple = self._parse_optional(arg_string)
2111
+ if option_tuple is None:
2112
+ pattern = 'A'
2113
+ else:
2114
+ option_string_indices[i] = option_tuple
2115
+ pattern = 'O'
2116
+ arg_string_pattern_parts.append(pattern)
2117
+
2118
+ # join the pieces together to form the pattern
2119
+ arg_strings_pattern = ''.join(arg_string_pattern_parts)
2120
+
2121
+ # converts arg strings to the appropriate and then takes the action
2122
+ seen_actions = set()
2123
+ seen_non_default_actions = set()
2124
+
2125
+ def take_action(action, argument_strings, option_string=None):
2126
+ seen_actions.add(action)
2127
+ argument_values = self._get_values(action, argument_strings)
2128
+
2129
+ # error if this argument is not allowed with other previously
2130
+ # seen arguments, assuming that actions that use the default
2131
+ # value don't really count as "present"
2132
+ if argument_values is not action.default:
2133
+ seen_non_default_actions.add(action)
2134
+ for conflict_action in action_conflicts.get(action, []):
2135
+ if conflict_action in seen_non_default_actions:
2136
+ msg = _('not allowed with argument %s')
2137
+ action_name = _get_action_name(conflict_action)
2138
+ raise ArgumentError(action, msg % action_name)
2139
+
2140
+ # take the action if we didn't receive a SUPPRESS value
2141
+ # (e.g. from a default)
2142
+ if argument_values is not SUPPRESS:
2143
+ action(self, namespace, argument_values, option_string)
2144
+
2145
+ # function to convert arg_strings into an optional action
2146
+ def consume_optional(start_index):
2147
+
2148
+ # get the optional identified at this index
2149
+ option_tuple = option_string_indices[start_index]
2150
+ action, option_string, explicit_arg = option_tuple
2151
+
2152
+ # identify additional optionals in the same arg string
2153
+ # (e.g. -xyz is the same as -x -y -z if no args are required)
2154
+ match_argument = self._match_argument
2155
+ action_tuples = []
2156
+ while True:
2157
+
2158
+ # if we found no optional action, skip it
2159
+ if action is None:
2160
+ extras.append(arg_strings[start_index])
2161
+ return start_index + 1
2162
+
2163
+ # if there is an explicit argument, try to match the
2164
+ # optional's string arguments to only this
2165
+ if explicit_arg is not None:
2166
+ arg_count = match_argument(action, 'A')
2167
+
2168
+ # if the action is a single-dash option and takes no
2169
+ # arguments, try to parse more single-dash options out
2170
+ # of the tail of the option string
2171
+ chars = self.prefix_chars
2172
+ if arg_count == 0 and option_string[1] not in chars:
2173
+ action_tuples.append((action, [], option_string))
2174
+ char = option_string[0]
2175
+ option_string = char + explicit_arg[0]
2176
+ new_explicit_arg = explicit_arg[1:] or None
2177
+ optionals_map = self._option_string_actions
2178
+ if option_string in optionals_map:
2179
+ action = optionals_map[option_string]
2180
+ explicit_arg = new_explicit_arg
2181
+ else:
2182
+ msg = _('ignored explicit argument %r')
2183
+ raise ArgumentError(action, msg % explicit_arg)
2184
+
2185
+ # if the action expect exactly one argument, we've
2186
+ # successfully matched the option; exit the loop
2187
+ elif arg_count == 1:
2188
+ stop = start_index + 1
2189
+ args = [explicit_arg]
2190
+ action_tuples.append((action, args, option_string))
2191
+ break
2192
+
2193
+ # error if a double-dash option did not use the
2194
+ # explicit argument
2195
+ else:
2196
+ msg = _('ignored explicit argument %r')
2197
+ raise ArgumentError(action, msg % explicit_arg)
2198
+
2199
+ # if there is no explicit argument, try to match the
2200
+ # optional's string arguments with the following strings
2201
+ # if successful, exit the loop
2202
+ else:
2203
+ start = start_index + 1
2204
+ selected_patterns = arg_strings_pattern[start:]
2205
+ arg_count = match_argument(action, selected_patterns)
2206
+ stop = start + arg_count
2207
+ args = arg_strings[start:stop]
2208
+ action_tuples.append((action, args, option_string))
2209
+ break
2210
+
2211
+ # add the Optional to the list and return the index at which
2212
+ # the Optional's string args stopped
2213
+ assert action_tuples
2214
+ for action, args, option_string in action_tuples:
2215
+ take_action(action, args, option_string)
2216
+ return stop
2217
+
2218
+ # the list of Positionals left to be parsed; this is modified
2219
+ # by consume_positionals()
2220
+ positionals = self._get_positional_actions()
2221
+
2222
+ # function to convert arg_strings into positional actions
2223
+ def consume_positionals(start_index):
2224
+ # match as many Positionals as possible
2225
+ match_partial = self._match_arguments_partial
2226
+ selected_pattern = arg_strings_pattern[start_index:]
2227
+ arg_counts = match_partial(positionals, selected_pattern)
2228
+
2229
+ # slice off the appropriate arg strings for each Positional
2230
+ # and add the Positional and its args to the list
2231
+ for action, arg_count in zip(positionals, arg_counts):
2232
+ args = arg_strings[start_index: start_index + arg_count]
2233
+ start_index += arg_count
2234
+ take_action(action, args)
2235
+
2236
+ # slice off the Positionals that we just parsed and return the
2237
+ # index at which the Positionals' string args stopped
2238
+ positionals[:] = positionals[len(arg_counts):]
2239
+ return start_index
2240
+
2241
+ # consume Positionals and Optionals alternately, until we have
2242
+ # passed the last option string
2243
+ extras = []
2244
+ start_index = 0
2245
+ if option_string_indices:
2246
+ max_option_string_index = max(option_string_indices)
2247
+ else:
2248
+ max_option_string_index = -1
2249
+ while start_index <= max_option_string_index:
2250
+
2251
+ # consume any Positionals preceding the next option
2252
+ next_option_string_index = min([
2253
+ index
2254
+ for index in option_string_indices
2255
+ if index >= start_index])
2256
+ if start_index != next_option_string_index:
2257
+ positionals_end_index = consume_positionals(start_index)
2258
+
2259
+ # only try to parse the next optional if we didn't consume
2260
+ # the option string during the positionals parsing
2261
+ if positionals_end_index > start_index:
2262
+ start_index = positionals_end_index
2263
+ continue
2264
+ else:
2265
+ start_index = positionals_end_index
2266
+
2267
+ # if we consumed all the positionals we could and we're not
2268
+ # at the index of an option string, there were extra arguments
2269
+ if start_index not in option_string_indices:
2270
+ strings = arg_strings[start_index:next_option_string_index]
2271
+ extras.extend(strings)
2272
+ start_index = next_option_string_index
2273
+
2274
+ # consume the next optional and any arguments for it
2275
+ start_index = consume_optional(start_index)
2276
+
2277
+ # consume any positionals following the last Optional
2278
+ stop_index = consume_positionals(start_index)
2279
+
2280
+ # if we didn't consume all the argument strings, there were extras
2281
+ extras.extend(arg_strings[stop_index:])
2282
+
2283
+ # if we didn't use all the Positional objects, there were too few
2284
+ # arg strings supplied.
2285
+ if positionals:
2286
+ self.error(_('too few arguments'))
2287
+
2288
+ # make sure all required actions were present, and convert defaults.
2289
+ for action in self._actions:
2290
+ if action not in seen_actions:
2291
+ if action.required:
2292
+ name = _get_action_name(action)
2293
+ self.error(_('argument %s is required') % name)
2294
+ else:
2295
+ # Convert action default now instead of doing it before
2296
+ # parsing arguments to avoid calling convert functions
2297
+ # twice (which may fail) if the argument was given, but
2298
+ # only if it was defined already in the namespace
2299
+ if (action.default is not None and
2300
+ isinstance(action.default, basestring) and
2301
+ hasattr(namespace, action.dest) and
2302
+ action.default is getattr(namespace, action.dest)):
2303
+ setattr(namespace, action.dest,
2304
+ self._get_value(action, action.default))
2305
+
2306
+ # make sure all required groups had one option present
2307
+ for group in self._mutually_exclusive_groups:
2308
+ if group.required:
2309
+ for action in group._group_actions:
2310
+ if action in seen_non_default_actions:
2311
+ break
2312
+
2313
+ # if no actions were used, report the error
2314
+ else:
2315
+ names = [_get_action_name(action)
2316
+ for action in group._group_actions
2317
+ if action.help is not SUPPRESS]
2318
+ msg = _('one of the arguments %s is required')
2319
+ self.error(msg % ' '.join(names))
2320
+
2321
+ # return the updated namespace and the extra arguments
2322
+ return namespace, extras
2323
+
2324
+ def _read_args_from_files(self, arg_strings):
2325
+ # expand arguments referencing files
2326
+ new_arg_strings = []
2327
+ for arg_string in arg_strings:
2328
+
2329
+ # for regular arguments, just add them back into the list
2330
+ if arg_string[0] not in self.fromfile_prefix_chars:
2331
+ new_arg_strings.append(arg_string)
2332
+
2333
+ # replace arguments referencing files with the file content
2334
+ else:
2335
+ try:
2336
+ args_file = open(arg_string[1:])
2337
+ try:
2338
+ arg_strings = []
2339
+ for arg_line in args_file.read().splitlines():
2340
+ for arg in self.convert_arg_line_to_args(arg_line):
2341
+ arg_strings.append(arg)
2342
+ arg_strings = self._read_args_from_files(arg_strings)
2343
+ new_arg_strings.extend(arg_strings)
2344
+ finally:
2345
+ args_file.close()
2346
+ except IOError:
2347
+ err = _sys.exc_info()[1]
2348
+ self.error(str(err))
2349
+
2350
+ # return the modified argument list
2351
+ return new_arg_strings
2352
+
2353
+ def convert_arg_line_to_args(self, arg_line):
2354
+ return [arg_line]
2355
+
2356
+ def _match_argument(self, action, arg_strings_pattern):
2357
+ # match the pattern for this action to the arg strings
2358
+ nargs_pattern = self._get_nargs_pattern(action)
2359
+ match = _re.match(nargs_pattern, arg_strings_pattern)
2360
+
2361
+ # raise an exception if we weren't able to find a match
2362
+ if match is None:
2363
+ nargs_errors = {
2364
+ None: _('expected one argument'),
2365
+ OPTIONAL: _('expected at most one argument'),
2366
+ ONE_OR_MORE: _('expected at least one argument'),
2367
+ }
2368
+ default = _('expected %s argument(s)') % action.nargs
2369
+ msg = nargs_errors.get(action.nargs, default)
2370
+ raise ArgumentError(action, msg)
2371
+
2372
+ # return the number of arguments matched
2373
+ return len(match.group(1))
2374
+
2375
+ def _match_arguments_partial(self, actions, arg_strings_pattern):
2376
+ # progressively shorten the actions list by slicing off the
2377
+ # final actions until we find a match
2378
+ result = []
2379
+ for i in range(len(actions), 0, -1):
2380
+ actions_slice = actions[:i]
2381
+ pattern = ''.join([self._get_nargs_pattern(action)
2382
+ for action in actions_slice])
2383
+ match = _re.match(pattern, arg_strings_pattern)
2384
+ if match is not None:
2385
+ result.extend([len(string) for string in match.groups()])
2386
+ break
2387
+
2388
+ # return the list of arg string counts
2389
+ return result
2390
+
2391
+ def _parse_optional(self, arg_string):
2392
+ # if it's an empty string, it was meant to be a positional
2393
+ if not arg_string:
2394
+ return None
2395
+
2396
+ # if it doesn't start with a prefix, it was meant to be positional
2397
+ if not arg_string[0] in self.prefix_chars:
2398
+ return None
2399
+
2400
+ # if the option string is present in the parser, return the action
2401
+ if arg_string in self._option_string_actions:
2402
+ action = self._option_string_actions[arg_string]
2403
+ return action, arg_string, None
2404
+
2405
+ # if it's just a single character, it was meant to be positional
2406
+ if len(arg_string) == 1:
2407
+ return None
2408
+
2409
+ # if the option string before the "=" is present, return the action
2410
+ if '=' in arg_string:
2411
+ option_string, explicit_arg = arg_string.split('=', 1)
2412
+ if option_string in self._option_string_actions:
2413
+ action = self._option_string_actions[option_string]
2414
+ return action, option_string, explicit_arg
2415
+
2416
+ # search through all possible prefixes of the option string
2417
+ # and all actions in the parser for possible interpretations
2418
+ option_tuples = self._get_option_tuples(arg_string)
2419
+
2420
+ # if multiple actions match, the option string was ambiguous
2421
+ if len(option_tuples) > 1:
2422
+ options = ', '.join([option_string
2423
+ for action, option_string, explicit_arg in option_tuples])
2424
+ tup = arg_string, options
2425
+ self.error(_('ambiguous option: %s could match %s') % tup)
2426
+
2427
+ # if exactly one action matched, this segmentation is good,
2428
+ # so return the parsed action
2429
+ elif len(option_tuples) == 1:
2430
+ option_tuple, = option_tuples
2431
+ return option_tuple
2432
+
2433
+ # if it was not found as an option, but it looks like a negative
2434
+ # number, it was meant to be positional
2435
+ # unless there are negative-number-like options
2436
+ if self._negative_number_matcher.match(arg_string):
2437
+ if not self._has_negative_number_optionals:
2438
+ return None
2439
+
2440
+ # if it contains a space, it was meant to be a positional
2441
+ if ' ' in arg_string:
2442
+ return None
2443
+
2444
+ # it was meant to be an optional but there is no such option
2445
+ # in this parser (though it might be a valid option in a subparser)
2446
+ return None, arg_string, None
2447
+
2448
+ def _get_option_tuples(self, option_string):
2449
+ result = []
2450
+
2451
+ # option strings starting with two prefix characters are only
2452
+ # split at the '='
2453
+ chars = self.prefix_chars
2454
+ if option_string[0] in chars and option_string[1] in chars:
2455
+ if '=' in option_string:
2456
+ option_prefix, explicit_arg = option_string.split('=', 1)
2457
+ else:
2458
+ option_prefix = option_string
2459
+ explicit_arg = None
2460
+ for option_string in self._option_string_actions:
2461
+ if option_string.startswith(option_prefix):
2462
+ action = self._option_string_actions[option_string]
2463
+ tup = action, option_string, explicit_arg
2464
+ result.append(tup)
2465
+
2466
+ # single character options can be concatenated with their arguments
2467
+ # but multiple character options always have to have their argument
2468
+ # separate
2469
+ elif option_string[0] in chars and option_string[1] not in chars:
2470
+ option_prefix = option_string
2471
+ explicit_arg = None
2472
+ short_option_prefix = option_string[:2]
2473
+ short_explicit_arg = option_string[2:]
2474
+
2475
+ for option_string in self._option_string_actions:
2476
+ if option_string == short_option_prefix:
2477
+ action = self._option_string_actions[option_string]
2478
+ tup = action, option_string, short_explicit_arg
2479
+ result.append(tup)
2480
+ elif option_string.startswith(option_prefix):
2481
+ action = self._option_string_actions[option_string]
2482
+ tup = action, option_string, explicit_arg
2483
+ result.append(tup)
2484
+
2485
+ # shouldn't ever get here
2486
+ else:
2487
+ self.error(_('unexpected option string: %s') % option_string)
2488
+
2489
+ # return the collected option tuples
2490
+ return result
2491
+
2492
+ def _get_nargs_pattern(self, action):
2493
+ # in all examples below, we have to allow for '--' args
2494
+ # which are represented as '-' in the pattern
2495
+ nargs = action.nargs
2496
+
2497
+ # the default (None) is assumed to be a single argument
2498
+ if nargs is None:
2499
+ nargs_pattern = '(-*A-*)'
2500
+
2501
+ # allow zero or one arguments
2502
+ elif nargs == OPTIONAL:
2503
+ nargs_pattern = '(-*A?-*)'
2504
+
2505
+ # allow zero or more arguments
2506
+ elif nargs == ZERO_OR_MORE:
2507
+ nargs_pattern = '(-*[A-]*)'
2508
+
2509
+ # allow one or more arguments
2510
+ elif nargs == ONE_OR_MORE:
2511
+ nargs_pattern = '(-*A[A-]*)'
2512
+
2513
+ # allow any number of options or arguments
2514
+ elif nargs == REMAINDER:
2515
+ nargs_pattern = '([-AO]*)'
2516
+
2517
+ # allow one argument followed by any number of options or arguments
2518
+ elif nargs == PARSER:
2519
+ nargs_pattern = '(-*A[-AO]*)'
2520
+
2521
+ # all others should be integers
2522
+ else:
2523
+ nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
2524
+
2525
+ # if this is an optional action, -- is not allowed
2526
+ if action.option_strings:
2527
+ nargs_pattern = nargs_pattern.replace('-*', '')
2528
+ nargs_pattern = nargs_pattern.replace('-', '')
2529
+
2530
+ # return the pattern
2531
+ return nargs_pattern
2532
+
2533
+ # ========================
2534
+ # Value conversion methods
2535
+ # ========================
2536
+ def _get_values(self, action, arg_strings):
2537
+ # for everything but PARSER args, strip out '--'
2538
+ if action.nargs not in [PARSER, REMAINDER]:
2539
+ arg_strings = [s for s in arg_strings if s != '--']
2540
+
2541
+ # optional argument produces a default when not present
2542
+ if not arg_strings and action.nargs == OPTIONAL:
2543
+ if action.option_strings:
2544
+ value = action.const
2545
+ else:
2546
+ value = action.default
2547
+ if isinstance(value, basestring):
2548
+ value = self._get_value(action, value)
2549
+ self._check_value(action, value)
2550
+
2551
+ # when nargs='*' on a positional, if there were no command-line
2552
+ # args, use the default if it is anything other than None
2553
+ elif (not arg_strings and action.nargs == ZERO_OR_MORE and
2554
+ not action.option_strings):
2555
+ if action.default is not None:
2556
+ value = action.default
2557
+ else:
2558
+ value = arg_strings
2559
+ self._check_value(action, value)
2560
+
2561
+ # single argument or optional argument produces a single value
2562
+ elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
2563
+ arg_string, = arg_strings
2564
+ value = self._get_value(action, arg_string)
2565
+ self._check_value(action, value)
2566
+
2567
+ # REMAINDER arguments convert all values, checking none
2568
+ elif action.nargs == REMAINDER:
2569
+ value = [self._get_value(action, v) for v in arg_strings]
2570
+
2571
+ # PARSER arguments convert all values, but check only the first
2572
+ elif action.nargs == PARSER:
2573
+ value = [self._get_value(action, v) for v in arg_strings]
2574
+ self._check_value(action, value[0])
2575
+
2576
+ # all other types of nargs produce a list
2577
+ else:
2578
+ value = [self._get_value(action, v) for v in arg_strings]
2579
+ for v in value:
2580
+ self._check_value(action, v)
2581
+
2582
+ # return the converted value
2583
+ return value
2584
+
2585
+ def _get_value(self, action, arg_string):
2586
+ type_func = self._registry_get('type', action.type, action.type)
2587
+ if not _callable(type_func):
2588
+ msg = _('%r is not callable')
2589
+ raise ArgumentError(action, msg % type_func)
2590
+
2591
+ # convert the value to the appropriate type
2592
+ try:
2593
+ result = type_func(arg_string)
2594
+
2595
+ # ArgumentTypeErrors indicate errors
2596
+ except ArgumentTypeError:
2597
+ name = getattr(action.type, '__name__', repr(action.type))
2598
+ msg = str(_sys.exc_info()[1])
2599
+ raise ArgumentError(action, msg)
2600
+
2601
+ # TypeErrors or ValueErrors also indicate errors
2602
+ except (TypeError, ValueError):
2603
+ name = getattr(action.type, '__name__', repr(action.type))
2604
+ msg = _('invalid %s value: %r')
2605
+ raise ArgumentError(action, msg % (name, arg_string))
2606
+
2607
+ # return the converted value
2608
+ return result
2609
+
2610
+ def _check_value(self, action, value):
2611
+ # converted value must be one of the choices (if specified)
2612
+ if action.choices is not None and value not in action.choices:
2613
+ tup = value, ', '.join(map(repr, action.choices))
2614
+ msg = _('invalid choice: %r (choose from %s)') % tup
2615
+ raise ArgumentError(action, msg)
2616
+
2617
+ # =======================
2618
+ # Help-formatting methods
2619
+ # =======================
2620
+ def format_usage(self):
2621
+ formatter = self._get_formatter()
2622
+ formatter.add_usage(self.usage, self._actions,
2623
+ self._mutually_exclusive_groups)
2624
+ return formatter.format_help()
2625
+
2626
+ def format_help(self):
2627
+ formatter = self._get_formatter()
2628
+
2629
+ # usage
2630
+ formatter.add_usage(self.usage, self._actions,
2631
+ self._mutually_exclusive_groups)
2632
+
2633
+ # description
2634
+ formatter.add_text(self.description)
2635
+
2636
+ # positionals, optionals and user-defined groups
2637
+ for action_group in self._action_groups:
2638
+ formatter.start_section(action_group.title)
2639
+ formatter.add_text(action_group.description)
2640
+ formatter.add_arguments(action_group._group_actions)
2641
+ formatter.end_section()
2642
+
2643
+ # epilog
2644
+ formatter.add_text(self.epilog)
2645
+
2646
+ # determine help from format above
2647
+ return formatter.format_help()
2648
+
2649
+ def format_version(self):
2650
+ import warnings
2651
+ warnings.warn(
2652
+ 'The format_version method is deprecated -- the "version" '
2653
+ 'argument to ArgumentParser is no longer supported.',
2654
+ DeprecationWarning)
2655
+ formatter = self._get_formatter()
2656
+ formatter.add_text(self.version)
2657
+ return formatter.format_help()
2658
+
2659
+ def _get_formatter(self):
2660
+ return self.formatter_class(prog=self.prog)
2661
+
2662
+ # =====================
2663
+ # Help-printing methods
2664
+ # =====================
2665
+ def print_usage(self, file=None):
2666
+ if file is None:
2667
+ file = _sys.stdout
2668
+ self._print_message(self.format_usage(), file)
2669
+
2670
+ def print_help(self, file=None):
2671
+ if file is None:
2672
+ file = _sys.stdout
2673
+ self._print_message(self.format_help(), file)
2674
+
2675
+ def print_version(self, file=None):
2676
+ import warnings
2677
+ warnings.warn(
2678
+ 'The print_version method is deprecated -- the "version" '
2679
+ 'argument to ArgumentParser is no longer supported.',
2680
+ DeprecationWarning)
2681
+ self._print_message(self.format_version(), file)
2682
+
2683
+ def _print_message(self, message, file=None):
2684
+ if message:
2685
+ if file is None:
2686
+ file = _sys.stderr
2687
+ file.write(message)
2688
+
2689
+ # ===============
2690
+ # Exiting methods
2691
+ # ===============
2692
+ def exit(self, status=0, message=None):
2693
+ if message:
2694
+ self._print_message(message, _sys.stderr)
2695
+ _sys.exit(status)
2696
+
2697
+ def error(self, message):
2698
+ """error(message: string)
2699
+
2700
+ Prints a usage message incorporating the message to stderr and
2701
+ exits.
2702
+
2703
+ If you override this in a subclass, it should not return -- it
2704
+ should either exit or raise an exception.
2705
+ """
2706
+ self.print_usage(_sys.stderr)
2707
+ self.exit(2, _('%s: error: %s\n') % (self.prog, message))
2708
--
1109
--
2709
2.13.5
1110
2.26.2
2710
1111
2711
diff view generated by jsdifflib
New patch
1
From: Coiby Xu <coiby.xu@gmail.com>
1
2
3
Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
4
Signed-off-by: Coiby Xu <coiby.xu@gmail.com>
5
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
6
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
7
Message-id: 20200918080912.321299-8-coiby.xu@gmail.com
8
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9
---
10
MAINTAINERS | 8 ++++++++
11
1 file changed, 8 insertions(+)
12
13
diff --git a/MAINTAINERS b/MAINTAINERS
14
index XXXXXXX..XXXXXXX 100644
15
--- a/MAINTAINERS
16
+++ b/MAINTAINERS
17
@@ -XXX,XX +XXX,XX @@ L: qemu-block@nongnu.org
18
S: Supported
19
F: tests/image-fuzzer/
20
21
+Vhost-user block device backend server
22
+M: Coiby Xu <Coiby.Xu@gmail.com>
23
+S: Maintained
24
+F: block/export/vhost-user-blk-server.c
25
+F: util/vhost-user-server.c
26
+F: tests/qtest/vhost-user-blk-test.c
27
+F: tests/qtest/libqos/vhost-user-blk.c
28
+
29
Replication
30
M: Wen Congyang <wencongyang2@huawei.com>
31
M: Xie Changlong <xiechanglong.d@gmail.com>
32
--
33
2.26.2
34
diff view generated by jsdifflib
New patch
1
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
2
Message-id: 20200924151549.913737-3-stefanha@redhat.com
3
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
4
---
5
util/vhost-user-server.c | 2 +-
6
1 file changed, 1 insertion(+), 1 deletion(-)
1
7
8
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
9
index XXXXXXX..XXXXXXX 100644
10
--- a/util/vhost-user-server.c
11
+++ b/util/vhost-user-server.c
12
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
13
return false;
14
}
15
16
- /* zero out unspecified fileds */
17
+ /* zero out unspecified fields */
18
*server = (VuServer) {
19
.listener = listener,
20
.vu_iface = vu_iface,
21
--
22
2.26.2
23
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
We already have access to the value with the correct type (ioc and sioc
2
are the same QIOChannel).
2
3
3
The level of the burst bucket is stored in bkt.burst_level, not
4
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
4
bkt.burst_length.
5
Message-id: 20200924151549.913737-4-stefanha@redhat.com
5
6
Signed-off-by: Alberto Garcia <berto@igalia.com>
7
Reviewed-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
8
Message-id: 49aab2711d02f285567f3b3b13a113847af33812.1503580370.git.berto@igalia.com
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
---
7
---
11
include/qemu/throttle.h | 2 +-
8
util/vhost-user-server.c | 2 +-
12
1 file changed, 1 insertion(+), 1 deletion(-)
9
1 file changed, 1 insertion(+), 1 deletion(-)
13
10
14
diff --git a/include/qemu/throttle.h b/include/qemu/throttle.h
11
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
15
index XXXXXXX..XXXXXXX 100644
12
index XXXXXXX..XXXXXXX 100644
16
--- a/include/qemu/throttle.h
13
--- a/util/vhost-user-server.c
17
+++ b/include/qemu/throttle.h
14
+++ b/util/vhost-user-server.c
18
@@ -XXX,XX +XXX,XX @@ typedef enum {
15
@@ -XXX,XX +XXX,XX @@ static void vu_accept(QIONetListener *listener, QIOChannelSocket *sioc,
19
* - The bkt.avg rate does not apply until the bucket is full,
16
server->ioc = QIO_CHANNEL(sioc);
20
* allowing the user to do bursts until then. The I/O limit during
17
object_ref(OBJECT(server->ioc));
21
* bursts is bkt.max. To enforce this limit we keep an additional
18
qio_channel_attach_aio_context(server->ioc, server->ctx);
22
- * bucket in bkt.burst_length that leaks at a rate of bkt.max units
19
- qio_channel_set_blocking(QIO_CHANNEL(server->sioc), false, NULL);
23
+ * bucket in bkt.burst_level that leaks at a rate of bkt.max units
20
+ qio_channel_set_blocking(server->ioc, false, NULL);
24
* per second.
21
vu_client_start(server);
25
*
22
}
26
* - Because of all of the above, the user can perform I/O at a
23
27
--
24
--
28
2.13.5
25
2.26.2
29
26
30
diff view generated by jsdifflib
New patch
1
Explicitly deleting watches is not necessary since libvhost-user calls
2
remove_watch() during vu_deinit(). Add an assertion to check this
3
though.
1
4
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
Message-id: 20200924151549.913737-5-stefanha@redhat.com
7
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
8
---
9
util/vhost-user-server.c | 19 ++++---------------
10
1 file changed, 4 insertions(+), 15 deletions(-)
11
12
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
13
index XXXXXXX..XXXXXXX 100644
14
--- a/util/vhost-user-server.c
15
+++ b/util/vhost-user-server.c
16
@@ -XXX,XX +XXX,XX @@ static void close_client(VuServer *server)
17
/* When this is set vu_client_trip will stop new processing vhost-user message */
18
server->sioc = NULL;
19
20
- VuFdWatch *vu_fd_watch, *next;
21
- QTAILQ_FOREACH_SAFE(vu_fd_watch, &server->vu_fd_watches, next, next) {
22
- aio_set_fd_handler(server->ioc->ctx, vu_fd_watch->fd, true, NULL,
23
- NULL, NULL, NULL);
24
- }
25
-
26
- while (!QTAILQ_EMPTY(&server->vu_fd_watches)) {
27
- QTAILQ_FOREACH_SAFE(vu_fd_watch, &server->vu_fd_watches, next, next) {
28
- if (!vu_fd_watch->processing) {
29
- QTAILQ_REMOVE(&server->vu_fd_watches, vu_fd_watch, next);
30
- g_free(vu_fd_watch);
31
- }
32
- }
33
- }
34
-
35
while (server->processing_msg) {
36
if (server->ioc->read_coroutine) {
37
server->ioc->read_coroutine = NULL;
38
@@ -XXX,XX +XXX,XX @@ static void close_client(VuServer *server)
39
}
40
41
vu_deinit(&server->vu_dev);
42
+
43
+ /* vu_deinit() should have called remove_watch() */
44
+ assert(QTAILQ_EMPTY(&server->vu_fd_watches));
45
+
46
object_unref(OBJECT(sioc));
47
object_unref(OBJECT(server->ioc));
48
}
49
--
50
2.26.2
51
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
Only one struct is needed per request. Drop req_data and the separate
2
VuBlockReq instance. Instead let vu_queue_pop() allocate everything at
3
once.
2
4
3
Both the throttling limits set with the throttling.iops-* and
5
This fixes the req_data memory leak in vu_block_virtio_process_req().
4
throttling.bps-* options and their QMP equivalents defined in the
5
BlockIOThrottle struct are integer values.
6
6
7
Those limits are also reported in the BlockDeviceInfo struct and they
7
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
8
are integers there as well.
8
Message-id: 20200924151549.913737-6-stefanha@redhat.com
9
10
Therefore there's no reason to store them internally as double and do
11
the conversion everytime we're setting or querying them, so this patch
12
uses uint64_t for those types. Let's also use an unsigned type because
13
we don't allow negative values anyway.
14
15
LeakyBucket.level and LeakyBucket.burst_level do however remain double
16
because their value changes depending on the fraction of time elapsed
17
since the previous I/O operation.
18
19
Signed-off-by: Alberto Garcia <berto@igalia.com>
20
Message-id: f29b840422767b5be2c41c2dfdbbbf6c5f8fedf8.1503580370.git.berto@igalia.com
21
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
22
---
10
---
23
include/qemu/throttle.h | 4 ++--
11
block/export/vhost-user-blk-server.c | 68 +++++++++-------------------
24
tests/test-throttle.c | 3 ++-
12
1 file changed, 21 insertions(+), 47 deletions(-)
25
util/throttle.c | 7 +++----
26
3 files changed, 7 insertions(+), 7 deletions(-)
27
13
28
diff --git a/include/qemu/throttle.h b/include/qemu/throttle.h
14
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
29
index XXXXXXX..XXXXXXX 100644
15
index XXXXXXX..XXXXXXX 100644
30
--- a/include/qemu/throttle.h
16
--- a/block/export/vhost-user-blk-server.c
31
+++ b/include/qemu/throttle.h
17
+++ b/block/export/vhost-user-blk-server.c
32
@@ -XXX,XX +XXX,XX @@ typedef enum {
18
@@ -XXX,XX +XXX,XX @@ struct virtio_blk_inhdr {
33
*/
19
};
34
20
35
typedef struct LeakyBucket {
21
typedef struct VuBlockReq {
36
- double avg; /* average goal in units per second */
22
- VuVirtqElement *elem;
37
- double max; /* leaky bucket max burst in units */
23
+ VuVirtqElement elem;
38
+ uint64_t avg; /* average goal in units per second */
24
int64_t sector_num;
39
+ uint64_t max; /* leaky bucket max burst in units */
25
size_t size;
40
double level; /* bucket level in units */
26
struct virtio_blk_inhdr *in;
41
double burst_level; /* bucket level in units (for computing bursts) */
27
@@ -XXX,XX +XXX,XX @@ static void vu_block_req_complete(VuBlockReq *req)
42
unsigned burst_length; /* max length of the burst period, in seconds */
28
VuDev *vu_dev = &req->server->vu_dev;
43
diff --git a/tests/test-throttle.c b/tests/test-throttle.c
29
44
index XXXXXXX..XXXXXXX 100644
30
/* IO size with 1 extra status byte */
45
--- a/tests/test-throttle.c
31
- vu_queue_push(vu_dev, req->vq, req->elem, req->size + 1);
46
+++ b/tests/test-throttle.c
32
+ vu_queue_push(vu_dev, req->vq, &req->elem, req->size + 1);
47
@@ -XXX,XX +XXX,XX @@ static void test_enabled(void)
33
vu_queue_notify(vu_dev, req->vq);
48
for (i = 0; i < BUCKETS_COUNT; i++) {
34
49
throttle_config_init(&cfg);
35
- if (req->elem) {
50
set_cfg_value(false, i, 150);
36
- free(req->elem);
51
+ g_assert(throttle_is_valid(&cfg, NULL));
37
- }
52
g_assert(throttle_enabled(&cfg));
38
-
39
- g_free(req);
40
+ free(req);
41
}
42
43
static VuBlockDev *get_vu_block_device_by_server(VuServer *server)
44
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_flush(VuBlockReq *req)
45
blk_co_flush(backend);
46
}
47
48
-struct req_data {
49
- VuServer *server;
50
- VuVirtq *vq;
51
- VuVirtqElement *elem;
52
-};
53
-
54
static void coroutine_fn vu_block_virtio_process_req(void *opaque)
55
{
56
- struct req_data *data = opaque;
57
- VuServer *server = data->server;
58
- VuVirtq *vq = data->vq;
59
- VuVirtqElement *elem = data->elem;
60
+ VuBlockReq *req = opaque;
61
+ VuServer *server = req->server;
62
+ VuVirtqElement *elem = &req->elem;
63
uint32_t type;
64
- VuBlockReq *req;
65
66
VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
67
BlockBackend *backend = vdev_blk->backend;
68
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_virtio_process_req(void *opaque)
69
struct iovec *out_iov = elem->out_sg;
70
unsigned in_num = elem->in_num;
71
unsigned out_num = elem->out_num;
72
+
73
/* refer to hw/block/virtio_blk.c */
74
if (elem->out_num < 1 || elem->in_num < 1) {
75
error_report("virtio-blk request missing headers");
76
- free(elem);
77
- return;
78
+ goto err;
53
}
79
}
54
80
55
for (i = 0; i < BUCKETS_COUNT; i++) {
81
- req = g_new0(VuBlockReq, 1);
56
throttle_config_init(&cfg);
82
- req->server = server;
57
set_cfg_value(false, i, -150);
83
- req->vq = vq;
58
- g_assert(!throttle_enabled(&cfg));
84
- req->elem = elem;
59
+ g_assert(!throttle_is_valid(&cfg, NULL));
85
-
86
if (unlikely(iov_to_buf(out_iov, out_num, 0, &req->out,
87
sizeof(req->out)) != sizeof(req->out))) {
88
error_report("virtio-blk request outhdr too short");
89
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_virtio_process_req(void *opaque)
90
91
err:
92
free(elem);
93
- g_free(req);
94
- return;
95
}
96
97
static void vu_block_process_vq(VuDev *vu_dev, int idx)
98
{
99
- VuServer *server;
100
- VuVirtq *vq;
101
- struct req_data *req_data;
102
+ VuServer *server = container_of(vu_dev, VuServer, vu_dev);
103
+ VuVirtq *vq = vu_get_queue(vu_dev, idx);
104
105
- server = container_of(vu_dev, VuServer, vu_dev);
106
- assert(server);
107
-
108
- vq = vu_get_queue(vu_dev, idx);
109
- assert(vq);
110
- VuVirtqElement *elem;
111
while (1) {
112
- elem = vu_queue_pop(vu_dev, vq, sizeof(VuVirtqElement) +
113
- sizeof(VuBlockReq));
114
- if (elem) {
115
- req_data = g_new0(struct req_data, 1);
116
- req_data->server = server;
117
- req_data->vq = vq;
118
- req_data->elem = elem;
119
- Coroutine *co = qemu_coroutine_create(vu_block_virtio_process_req,
120
- req_data);
121
- aio_co_enter(server->ioc->ctx, co);
122
- } else {
123
+ VuBlockReq *req;
124
+
125
+ req = vu_queue_pop(vu_dev, vq, sizeof(VuBlockReq));
126
+ if (!req) {
127
break;
128
}
129
+
130
+ req->server = server;
131
+ req->vq = vq;
132
+
133
+ Coroutine *co =
134
+ qemu_coroutine_create(vu_block_virtio_process_req, req);
135
+ qemu_coroutine_enter(co);
60
}
136
}
61
}
137
}
62
138
63
diff --git a/util/throttle.c b/util/throttle.c
64
index XXXXXXX..XXXXXXX 100644
65
--- a/util/throttle.c
66
+++ b/util/throttle.c
67
@@ -XXX,XX +XXX,XX @@ int64_t throttle_compute_wait(LeakyBucket *bkt)
68
/* If bkt->max is 0 we still want to allow short bursts of I/O
69
* from the guest, otherwise every other request will be throttled
70
* and performance will suffer considerably. */
71
- bucket_size = bkt->avg / 10;
72
+ bucket_size = (double) bkt->avg / 10;
73
burst_bucket_size = 0;
74
} else {
75
/* If we have a burst limit then we have to wait until all I/O
76
* at burst rate has finished before throttling to bkt->avg */
77
bucket_size = bkt->max * bkt->burst_length;
78
- burst_bucket_size = bkt->max / 10;
79
+ burst_bucket_size = (double) bkt->max / 10;
80
}
81
82
/* If the main bucket is full then we have to wait */
83
@@ -XXX,XX +XXX,XX @@ bool throttle_is_valid(ThrottleConfig *cfg, Error **errp)
84
85
for (i = 0; i < BUCKETS_COUNT; i++) {
86
LeakyBucket *bkt = &cfg->buckets[i];
87
- if (bkt->avg < 0 || bkt->max < 0 ||
88
- bkt->avg > THROTTLE_VALUE_MAX || bkt->max > THROTTLE_VALUE_MAX) {
89
+ if (bkt->avg > THROTTLE_VALUE_MAX || bkt->max > THROTTLE_VALUE_MAX) {
90
error_setg(errp, "bps/iops/max values must be within [0, %lld]",
91
THROTTLE_VALUE_MAX);
92
return false;
93
--
139
--
94
2.13.5
140
2.26.2
95
141
96
diff view generated by jsdifflib
New patch
1
The device panic notifier callback is not used. Drop it.
1
2
3
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
4
Message-id: 20200924151549.913737-7-stefanha@redhat.com
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
---
7
util/vhost-user-server.h | 3 ---
8
block/export/vhost-user-blk-server.c | 3 +--
9
util/vhost-user-server.c | 6 ------
10
3 files changed, 1 insertion(+), 11 deletions(-)
11
12
diff --git a/util/vhost-user-server.h b/util/vhost-user-server.h
13
index XXXXXXX..XXXXXXX 100644
14
--- a/util/vhost-user-server.h
15
+++ b/util/vhost-user-server.h
16
@@ -XXX,XX +XXX,XX @@ typedef struct VuFdWatch {
17
} VuFdWatch;
18
19
typedef struct VuServer VuServer;
20
-typedef void DevicePanicNotifierFn(VuServer *server);
21
22
struct VuServer {
23
QIONetListener *listener;
24
AioContext *ctx;
25
- DevicePanicNotifierFn *device_panic_notifier;
26
int max_queues;
27
const VuDevIface *vu_iface;
28
VuDev vu_dev;
29
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
30
SocketAddress *unix_socket,
31
AioContext *ctx,
32
uint16_t max_queues,
33
- DevicePanicNotifierFn *device_panic_notifier,
34
const VuDevIface *vu_iface,
35
Error **errp);
36
37
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
38
index XXXXXXX..XXXXXXX 100644
39
--- a/block/export/vhost-user-blk-server.c
40
+++ b/block/export/vhost-user-blk-server.c
41
@@ -XXX,XX +XXX,XX @@ static void vhost_user_blk_server_start(VuBlockDev *vu_block_device,
42
ctx = bdrv_get_aio_context(blk_bs(vu_block_device->backend));
43
44
if (!vhost_user_server_start(&vu_block_device->vu_server, addr, ctx,
45
- VHOST_USER_BLK_MAX_QUEUES,
46
- NULL, &vu_block_iface,
47
+ VHOST_USER_BLK_MAX_QUEUES, &vu_block_iface,
48
errp)) {
49
goto error;
50
}
51
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
52
index XXXXXXX..XXXXXXX 100644
53
--- a/util/vhost-user-server.c
54
+++ b/util/vhost-user-server.c
55
@@ -XXX,XX +XXX,XX @@ static void panic_cb(VuDev *vu_dev, const char *buf)
56
close_client(server);
57
}
58
59
- if (server->device_panic_notifier) {
60
- server->device_panic_notifier(server);
61
- }
62
-
63
/*
64
* Set the callback function for network listener so another
65
* vhost-user client can connect to this server
66
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
67
SocketAddress *socket_addr,
68
AioContext *ctx,
69
uint16_t max_queues,
70
- DevicePanicNotifierFn *device_panic_notifier,
71
const VuDevIface *vu_iface,
72
Error **errp)
73
{
74
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
75
.vu_iface = vu_iface,
76
.max_queues = max_queues,
77
.ctx = ctx,
78
- .device_panic_notifier = device_panic_notifier,
79
};
80
81
qio_net_listener_set_name(server->listener, "vhost-user-backend-listener");
82
--
83
2.26.2
84
diff view generated by jsdifflib
New patch
1
fds[] is leaked when qio_channel_readv_full() fails.
1
2
3
Use vmsg->fds[] instead of keeping a local fds[] array. Then we can
4
reuse goto fail to clean up fds. vmsg->fd_num must be zeroed before the
5
loop to make this safe.
6
7
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
8
Message-id: 20200924151549.913737-8-stefanha@redhat.com
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
---
11
util/vhost-user-server.c | 50 ++++++++++++++++++----------------------
12
1 file changed, 23 insertions(+), 27 deletions(-)
13
14
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
15
index XXXXXXX..XXXXXXX 100644
16
--- a/util/vhost-user-server.c
17
+++ b/util/vhost-user-server.c
18
@@ -XXX,XX +XXX,XX @@ vu_message_read(VuDev *vu_dev, int conn_fd, VhostUserMsg *vmsg)
19
};
20
int rc, read_bytes = 0;
21
Error *local_err = NULL;
22
- /*
23
- * Store fds/nfds returned from qio_channel_readv_full into
24
- * temporary variables.
25
- *
26
- * VhostUserMsg is a packed structure, gcc will complain about passing
27
- * pointer to a packed structure member if we pass &VhostUserMsg.fd_num
28
- * and &VhostUserMsg.fds directly when calling qio_channel_readv_full,
29
- * thus two temporary variables nfds and fds are used here.
30
- */
31
- size_t nfds = 0, nfds_t = 0;
32
const size_t max_fds = G_N_ELEMENTS(vmsg->fds);
33
- int *fds_t = NULL;
34
VuServer *server = container_of(vu_dev, VuServer, vu_dev);
35
QIOChannel *ioc = server->ioc;
36
37
+ vmsg->fd_num = 0;
38
if (!ioc) {
39
error_report_err(local_err);
40
goto fail;
41
@@ -XXX,XX +XXX,XX @@ vu_message_read(VuDev *vu_dev, int conn_fd, VhostUserMsg *vmsg)
42
43
assert(qemu_in_coroutine());
44
do {
45
+ size_t nfds = 0;
46
+ int *fds = NULL;
47
+
48
/*
49
* qio_channel_readv_full may have short reads, keeping calling it
50
* until getting VHOST_USER_HDR_SIZE or 0 bytes in total
51
*/
52
- rc = qio_channel_readv_full(ioc, &iov, 1, &fds_t, &nfds_t, &local_err);
53
+ rc = qio_channel_readv_full(ioc, &iov, 1, &fds, &nfds, &local_err);
54
if (rc < 0) {
55
if (rc == QIO_CHANNEL_ERR_BLOCK) {
56
+ assert(local_err == NULL);
57
qio_channel_yield(ioc, G_IO_IN);
58
continue;
59
} else {
60
error_report_err(local_err);
61
- return false;
62
+ goto fail;
63
}
64
}
65
- read_bytes += rc;
66
- if (nfds_t > 0) {
67
- if (nfds + nfds_t > max_fds) {
68
+
69
+ if (nfds > 0) {
70
+ if (vmsg->fd_num + nfds > max_fds) {
71
error_report("A maximum of %zu fds are allowed, "
72
"however got %lu fds now",
73
- max_fds, nfds + nfds_t);
74
+ max_fds, vmsg->fd_num + nfds);
75
+ g_free(fds);
76
goto fail;
77
}
78
- memcpy(vmsg->fds + nfds, fds_t,
79
- nfds_t *sizeof(vmsg->fds[0]));
80
- nfds += nfds_t;
81
- g_free(fds_t);
82
+ memcpy(vmsg->fds + vmsg->fd_num, fds, nfds * sizeof(vmsg->fds[0]));
83
+ vmsg->fd_num += nfds;
84
+ g_free(fds);
85
}
86
- if (read_bytes == VHOST_USER_HDR_SIZE || rc == 0) {
87
- break;
88
+
89
+ if (rc == 0) { /* socket closed */
90
+ goto fail;
91
}
92
- iov.iov_base = (char *)vmsg + read_bytes;
93
- iov.iov_len = VHOST_USER_HDR_SIZE - read_bytes;
94
- } while (true);
95
96
- vmsg->fd_num = nfds;
97
+ iov.iov_base += rc;
98
+ iov.iov_len -= rc;
99
+ read_bytes += rc;
100
+ } while (read_bytes != VHOST_USER_HDR_SIZE);
101
+
102
/* qio_channel_readv_full will make socket fds blocking, unblock them */
103
vmsg_unblock_fds(vmsg);
104
if (vmsg->size > sizeof(vmsg->payload)) {
105
--
106
2.26.2
107
diff view generated by jsdifflib
New patch
1
Unexpected EOF is an error that must be reported.
1
2
3
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
4
Message-id: 20200924151549.913737-9-stefanha@redhat.com
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
---
7
util/vhost-user-server.c | 6 ++++--
8
1 file changed, 4 insertions(+), 2 deletions(-)
9
10
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
11
index XXXXXXX..XXXXXXX 100644
12
--- a/util/vhost-user-server.c
13
+++ b/util/vhost-user-server.c
14
@@ -XXX,XX +XXX,XX @@ vu_message_read(VuDev *vu_dev, int conn_fd, VhostUserMsg *vmsg)
15
};
16
if (vmsg->size) {
17
rc = qio_channel_readv_all_eof(ioc, &iov_payload, 1, &local_err);
18
- if (rc == -1) {
19
- error_report_err(local_err);
20
+ if (rc != 1) {
21
+ if (local_err) {
22
+ error_report_err(local_err);
23
+ }
24
goto fail;
25
}
26
}
27
--
28
2.26.2
29
diff view generated by jsdifflib
New patch
1
The vu_client_trip() coroutine is leaked during AioContext switching. It
2
is also unsafe to destroy the vu_dev in panic_cb() since its callers
3
still access it in some cases.
1
4
5
Rework the lifecycle to solve these safety issues.
6
7
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
8
Message-id: 20200924151549.913737-10-stefanha@redhat.com
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
---
11
util/vhost-user-server.h | 29 ++--
12
block/export/vhost-user-blk-server.c | 9 +-
13
util/vhost-user-server.c | 245 +++++++++++++++------------
14
3 files changed, 155 insertions(+), 128 deletions(-)
15
16
diff --git a/util/vhost-user-server.h b/util/vhost-user-server.h
17
index XXXXXXX..XXXXXXX 100644
18
--- a/util/vhost-user-server.h
19
+++ b/util/vhost-user-server.h
20
@@ -XXX,XX +XXX,XX @@
21
#include "qapi/error.h"
22
#include "standard-headers/linux/virtio_blk.h"
23
24
+/* A kick fd that we monitor on behalf of libvhost-user */
25
typedef struct VuFdWatch {
26
VuDev *vu_dev;
27
int fd; /*kick fd*/
28
void *pvt;
29
vu_watch_cb cb;
30
- bool processing;
31
QTAILQ_ENTRY(VuFdWatch) next;
32
} VuFdWatch;
33
34
-typedef struct VuServer VuServer;
35
-
36
-struct VuServer {
37
+/**
38
+ * VuServer:
39
+ * A vhost-user server instance with user-defined VuDevIface callbacks.
40
+ * Vhost-user device backends can be implemented using VuServer. VuDevIface
41
+ * callbacks and virtqueue kicks run in the given AioContext.
42
+ */
43
+typedef struct {
44
QIONetListener *listener;
45
+ QEMUBH *restart_listener_bh;
46
AioContext *ctx;
47
int max_queues;
48
const VuDevIface *vu_iface;
49
+
50
+ /* Protected by ctx lock */
51
VuDev vu_dev;
52
QIOChannel *ioc; /* The I/O channel with the client */
53
QIOChannelSocket *sioc; /* The underlying data channel with the client */
54
- /* IOChannel for fd provided via VHOST_USER_SET_SLAVE_REQ_FD */
55
- QIOChannel *ioc_slave;
56
- QIOChannelSocket *sioc_slave;
57
- Coroutine *co_trip; /* coroutine for processing VhostUserMsg */
58
QTAILQ_HEAD(, VuFdWatch) vu_fd_watches;
59
- /* restart coroutine co_trip if AIOContext is changed */
60
- bool aio_context_changed;
61
- bool processing_msg;
62
-};
63
+
64
+ Coroutine *co_trip; /* coroutine for processing VhostUserMsg */
65
+} VuServer;
66
67
bool vhost_user_server_start(VuServer *server,
68
SocketAddress *unix_socket,
69
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
70
71
void vhost_user_server_stop(VuServer *server);
72
73
-void vhost_user_server_set_aio_context(VuServer *server, AioContext *ctx);
74
+void vhost_user_server_attach_aio_context(VuServer *server, AioContext *ctx);
75
+void vhost_user_server_detach_aio_context(VuServer *server);
76
77
#endif /* VHOST_USER_SERVER_H */
78
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
79
index XXXXXXX..XXXXXXX 100644
80
--- a/block/export/vhost-user-blk-server.c
81
+++ b/block/export/vhost-user-blk-server.c
82
@@ -XXX,XX +XXX,XX @@ static const VuDevIface vu_block_iface = {
83
static void blk_aio_attached(AioContext *ctx, void *opaque)
84
{
85
VuBlockDev *vub_dev = opaque;
86
- aio_context_acquire(ctx);
87
- vhost_user_server_set_aio_context(&vub_dev->vu_server, ctx);
88
- aio_context_release(ctx);
89
+ vhost_user_server_attach_aio_context(&vub_dev->vu_server, ctx);
90
}
91
92
static void blk_aio_detach(void *opaque)
93
{
94
VuBlockDev *vub_dev = opaque;
95
- AioContext *ctx = vub_dev->vu_server.ctx;
96
- aio_context_acquire(ctx);
97
- vhost_user_server_set_aio_context(&vub_dev->vu_server, NULL);
98
- aio_context_release(ctx);
99
+ vhost_user_server_detach_aio_context(&vub_dev->vu_server);
100
}
101
102
static void
103
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
104
index XXXXXXX..XXXXXXX 100644
105
--- a/util/vhost-user-server.c
106
+++ b/util/vhost-user-server.c
107
@@ -XXX,XX +XXX,XX @@
108
*/
109
#include "qemu/osdep.h"
110
#include "qemu/main-loop.h"
111
+#include "block/aio-wait.h"
112
#include "vhost-user-server.h"
113
114
+/*
115
+ * Theory of operation:
116
+ *
117
+ * VuServer is started and stopped by vhost_user_server_start() and
118
+ * vhost_user_server_stop() from the main loop thread. Starting the server
119
+ * opens a vhost-user UNIX domain socket and listens for incoming connections.
120
+ * Only one connection is allowed at a time.
121
+ *
122
+ * The connection is handled by the vu_client_trip() coroutine in the
123
+ * VuServer->ctx AioContext. The coroutine consists of a vu_dispatch() loop
124
+ * where libvhost-user calls vu_message_read() to receive the next vhost-user
125
+ * protocol messages over the UNIX domain socket.
126
+ *
127
+ * When virtqueues are set up libvhost-user calls set_watch() to monitor kick
128
+ * fds. These fds are also handled in the VuServer->ctx AioContext.
129
+ *
130
+ * Both vu_client_trip() and kick fd monitoring can be stopped by shutting down
131
+ * the socket connection. Shutting down the socket connection causes
132
+ * vu_message_read() to fail since no more data can be received from the socket.
133
+ * After vu_dispatch() fails, vu_client_trip() calls vu_deinit() to stop
134
+ * libvhost-user before terminating the coroutine. vu_deinit() calls
135
+ * remove_watch() to stop monitoring kick fds and this stops virtqueue
136
+ * processing.
137
+ *
138
+ * When vu_client_trip() has finished cleaning up it schedules a BH in the main
139
+ * loop thread to accept the next client connection.
140
+ *
141
+ * When libvhost-user detects an error it calls panic_cb() and sets the
142
+ * dev->broken flag. Both vu_client_trip() and kick fd processing stop when
143
+ * the dev->broken flag is set.
144
+ *
145
+ * It is possible to switch AioContexts using
146
+ * vhost_user_server_detach_aio_context() and
147
+ * vhost_user_server_attach_aio_context(). They stop monitoring fds in the old
148
+ * AioContext and resume monitoring in the new AioContext. The vu_client_trip()
149
+ * coroutine remains in a yielded state during the switch. This is made
150
+ * possible by QIOChannel's support for spurious coroutine re-entry in
151
+ * qio_channel_yield(). The coroutine will restart I/O when re-entered from the
152
+ * new AioContext.
153
+ */
154
+
155
static void vmsg_close_fds(VhostUserMsg *vmsg)
156
{
157
int i;
158
@@ -XXX,XX +XXX,XX @@ static void vmsg_unblock_fds(VhostUserMsg *vmsg)
159
}
160
}
161
162
-static void vu_accept(QIONetListener *listener, QIOChannelSocket *sioc,
163
- gpointer opaque);
164
-
165
-static void close_client(VuServer *server)
166
-{
167
- /*
168
- * Before closing the client
169
- *
170
- * 1. Let vu_client_trip stop processing new vhost-user msg
171
- *
172
- * 2. remove kick_handler
173
- *
174
- * 3. wait for the kick handler to be finished
175
- *
176
- * 4. wait for the current vhost-user msg to be finished processing
177
- */
178
-
179
- QIOChannelSocket *sioc = server->sioc;
180
- /* When this is set vu_client_trip will stop new processing vhost-user message */
181
- server->sioc = NULL;
182
-
183
- while (server->processing_msg) {
184
- if (server->ioc->read_coroutine) {
185
- server->ioc->read_coroutine = NULL;
186
- qio_channel_set_aio_fd_handler(server->ioc, server->ioc->ctx, NULL,
187
- NULL, server->ioc);
188
- server->processing_msg = false;
189
- }
190
- }
191
-
192
- vu_deinit(&server->vu_dev);
193
-
194
- /* vu_deinit() should have called remove_watch() */
195
- assert(QTAILQ_EMPTY(&server->vu_fd_watches));
196
-
197
- object_unref(OBJECT(sioc));
198
- object_unref(OBJECT(server->ioc));
199
-}
200
-
201
static void panic_cb(VuDev *vu_dev, const char *buf)
202
{
203
- VuServer *server = container_of(vu_dev, VuServer, vu_dev);
204
-
205
- /* avoid while loop in close_client */
206
- server->processing_msg = false;
207
-
208
- if (buf) {
209
- error_report("vu_panic: %s", buf);
210
- }
211
-
212
- if (server->sioc) {
213
- close_client(server);
214
- }
215
-
216
- /*
217
- * Set the callback function for network listener so another
218
- * vhost-user client can connect to this server
219
- */
220
- qio_net_listener_set_client_func(server->listener,
221
- vu_accept,
222
- server,
223
- NULL);
224
+ error_report("vu_panic: %s", buf);
225
}
226
227
static bool coroutine_fn
228
@@ -XXX,XX +XXX,XX @@ fail:
229
return false;
230
}
231
232
-
233
-static void vu_client_start(VuServer *server);
234
static coroutine_fn void vu_client_trip(void *opaque)
235
{
236
VuServer *server = opaque;
237
+ VuDev *vu_dev = &server->vu_dev;
238
239
- while (!server->aio_context_changed && server->sioc) {
240
- server->processing_msg = true;
241
- vu_dispatch(&server->vu_dev);
242
- server->processing_msg = false;
243
+ while (!vu_dev->broken && vu_dispatch(vu_dev)) {
244
+ /* Keep running */
245
}
246
247
- if (server->aio_context_changed && server->sioc) {
248
- server->aio_context_changed = false;
249
- vu_client_start(server);
250
- }
251
-}
252
+ vu_deinit(vu_dev);
253
+
254
+ /* vu_deinit() should have called remove_watch() */
255
+ assert(QTAILQ_EMPTY(&server->vu_fd_watches));
256
+
257
+ object_unref(OBJECT(server->sioc));
258
+ server->sioc = NULL;
259
260
-static void vu_client_start(VuServer *server)
261
-{
262
- server->co_trip = qemu_coroutine_create(vu_client_trip, server);
263
- aio_co_enter(server->ctx, server->co_trip);
264
+ object_unref(OBJECT(server->ioc));
265
+ server->ioc = NULL;
266
+
267
+ server->co_trip = NULL;
268
+ if (server->restart_listener_bh) {
269
+ qemu_bh_schedule(server->restart_listener_bh);
270
+ }
271
+ aio_wait_kick();
272
}
273
274
/*
275
@@ -XXX,XX +XXX,XX @@ static void vu_client_start(VuServer *server)
276
static void kick_handler(void *opaque)
277
{
278
VuFdWatch *vu_fd_watch = opaque;
279
- vu_fd_watch->processing = true;
280
- vu_fd_watch->cb(vu_fd_watch->vu_dev, 0, vu_fd_watch->pvt);
281
- vu_fd_watch->processing = false;
282
+ VuDev *vu_dev = vu_fd_watch->vu_dev;
283
+
284
+ vu_fd_watch->cb(vu_dev, 0, vu_fd_watch->pvt);
285
+
286
+ /* Stop vu_client_trip() if an error occurred in vu_fd_watch->cb() */
287
+ if (vu_dev->broken) {
288
+ VuServer *server = container_of(vu_dev, VuServer, vu_dev);
289
+
290
+ qio_channel_shutdown(server->ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
291
+ }
292
}
293
294
-
295
static VuFdWatch *find_vu_fd_watch(VuServer *server, int fd)
296
{
297
298
@@ -XXX,XX +XXX,XX @@ static void vu_accept(QIONetListener *listener, QIOChannelSocket *sioc,
299
qio_channel_set_name(QIO_CHANNEL(sioc), "vhost-user client");
300
server->ioc = QIO_CHANNEL(sioc);
301
object_ref(OBJECT(server->ioc));
302
- qio_channel_attach_aio_context(server->ioc, server->ctx);
303
+
304
+ /* TODO vu_message_write() spins if non-blocking! */
305
qio_channel_set_blocking(server->ioc, false, NULL);
306
- vu_client_start(server);
307
+
308
+ server->co_trip = qemu_coroutine_create(vu_client_trip, server);
309
+
310
+ aio_context_acquire(server->ctx);
311
+ vhost_user_server_attach_aio_context(server, server->ctx);
312
+ aio_context_release(server->ctx);
313
}
314
315
-
316
void vhost_user_server_stop(VuServer *server)
317
{
318
+ aio_context_acquire(server->ctx);
319
+
320
+ qemu_bh_delete(server->restart_listener_bh);
321
+ server->restart_listener_bh = NULL;
322
+
323
if (server->sioc) {
324
- close_client(server);
325
+ VuFdWatch *vu_fd_watch;
326
+
327
+ QTAILQ_FOREACH(vu_fd_watch, &server->vu_fd_watches, next) {
328
+ aio_set_fd_handler(server->ctx, vu_fd_watch->fd, true,
329
+ NULL, NULL, NULL, vu_fd_watch);
330
+ }
331
+
332
+ qio_channel_shutdown(server->ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
333
+
334
+ AIO_WAIT_WHILE(server->ctx, server->co_trip);
335
}
336
337
+ aio_context_release(server->ctx);
338
+
339
if (server->listener) {
340
qio_net_listener_disconnect(server->listener);
341
object_unref(OBJECT(server->listener));
342
}
343
+}
344
+
345
+/*
346
+ * Allow the next client to connect to the server. Called from a BH in the main
347
+ * loop.
348
+ */
349
+static void restart_listener_bh(void *opaque)
350
+{
351
+ VuServer *server = opaque;
352
353
+ qio_net_listener_set_client_func(server->listener, vu_accept, server,
354
+ NULL);
355
}
356
357
-void vhost_user_server_set_aio_context(VuServer *server, AioContext *ctx)
358
+/* Called with ctx acquired */
359
+void vhost_user_server_attach_aio_context(VuServer *server, AioContext *ctx)
360
{
361
- VuFdWatch *vu_fd_watch, *next;
362
- void *opaque = NULL;
363
- IOHandler *io_read = NULL;
364
- bool attach;
365
+ VuFdWatch *vu_fd_watch;
366
367
- server->ctx = ctx ? ctx : qemu_get_aio_context();
368
+ server->ctx = ctx;
369
370
if (!server->sioc) {
371
- /* not yet serving any client*/
372
return;
373
}
374
375
- if (ctx) {
376
- qio_channel_attach_aio_context(server->ioc, ctx);
377
- server->aio_context_changed = true;
378
- io_read = kick_handler;
379
- attach = true;
380
- } else {
381
+ qio_channel_attach_aio_context(server->ioc, ctx);
382
+
383
+ QTAILQ_FOREACH(vu_fd_watch, &server->vu_fd_watches, next) {
384
+ aio_set_fd_handler(ctx, vu_fd_watch->fd, true, kick_handler, NULL,
385
+ NULL, vu_fd_watch);
386
+ }
387
+
388
+ aio_co_schedule(ctx, server->co_trip);
389
+}
390
+
391
+/* Called with server->ctx acquired */
392
+void vhost_user_server_detach_aio_context(VuServer *server)
393
+{
394
+ if (server->sioc) {
395
+ VuFdWatch *vu_fd_watch;
396
+
397
+ QTAILQ_FOREACH(vu_fd_watch, &server->vu_fd_watches, next) {
398
+ aio_set_fd_handler(server->ctx, vu_fd_watch->fd, true,
399
+ NULL, NULL, NULL, vu_fd_watch);
400
+ }
401
+
402
qio_channel_detach_aio_context(server->ioc);
403
- /* server->ioc->ctx keeps the old AioConext */
404
- ctx = server->ioc->ctx;
405
- attach = false;
406
}
407
408
- QTAILQ_FOREACH_SAFE(vu_fd_watch, &server->vu_fd_watches, next, next) {
409
- if (vu_fd_watch->cb) {
410
- opaque = attach ? vu_fd_watch : NULL;
411
- aio_set_fd_handler(ctx, vu_fd_watch->fd, true,
412
- io_read, NULL, NULL,
413
- opaque);
414
- }
415
- }
416
+ server->ctx = NULL;
417
}
418
419
-
420
bool vhost_user_server_start(VuServer *server,
421
SocketAddress *socket_addr,
422
AioContext *ctx,
423
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
424
const VuDevIface *vu_iface,
425
Error **errp)
426
{
427
+ QEMUBH *bh;
428
QIONetListener *listener = qio_net_listener_new();
429
if (qio_net_listener_open_sync(listener, socket_addr, 1,
430
errp) < 0) {
431
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
432
return false;
433
}
434
435
+ bh = qemu_bh_new(restart_listener_bh, server);
436
+
437
/* zero out unspecified fields */
438
*server = (VuServer) {
439
.listener = listener,
440
+ .restart_listener_bh = bh,
441
.vu_iface = vu_iface,
442
.max_queues = max_queues,
443
.ctx = ctx,
444
--
445
2.26.2
446
diff view generated by jsdifflib
New patch
1
Propagate the flush return value since errors are possible.
1
2
3
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
4
Message-id: 20200924151549.913737-11-stefanha@redhat.com
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
---
7
block/export/vhost-user-blk-server.c | 11 +++++++----
8
1 file changed, 7 insertions(+), 4 deletions(-)
9
10
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
11
index XXXXXXX..XXXXXXX 100644
12
--- a/block/export/vhost-user-blk-server.c
13
+++ b/block/export/vhost-user-blk-server.c
14
@@ -XXX,XX +XXX,XX @@ vu_block_discard_write_zeroes(VuBlockReq *req, struct iovec *iov,
15
return -EINVAL;
16
}
17
18
-static void coroutine_fn vu_block_flush(VuBlockReq *req)
19
+static int coroutine_fn vu_block_flush(VuBlockReq *req)
20
{
21
VuBlockDev *vdev_blk = get_vu_block_device_by_server(req->server);
22
BlockBackend *backend = vdev_blk->backend;
23
- blk_co_flush(backend);
24
+ return blk_co_flush(backend);
25
}
26
27
static void coroutine_fn vu_block_virtio_process_req(void *opaque)
28
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_virtio_process_req(void *opaque)
29
break;
30
}
31
case VIRTIO_BLK_T_FLUSH:
32
- vu_block_flush(req);
33
- req->in->status = VIRTIO_BLK_S_OK;
34
+ if (vu_block_flush(req) == 0) {
35
+ req->in->status = VIRTIO_BLK_S_OK;
36
+ } else {
37
+ req->in->status = VIRTIO_BLK_S_IOERR;
38
+ }
39
break;
40
case VIRTIO_BLK_T_GET_ID: {
41
size_t size = MIN(iov_size(&elem->in_sg[0], in_num),
42
--
43
2.26.2
44
diff view generated by jsdifflib
New patch
1
1
Use the new QAPI block exports API instead of defining our own QOM
2
objects.
3
4
This is a large change because the lifecycle of VuBlockDev needs to
5
follow BlockExportDriver. QOM properties are replaced by QAPI options
6
objects.
7
8
VuBlockDev is renamed VuBlkExport and contains a BlockExport field.
9
Several fields can be dropped since BlockExport already has equivalents.
10
11
The file names and meson build integration will be adjusted in a future
12
patch. libvhost-user should probably be built as a static library that
13
is linked into QEMU instead of as a .c file that results in duplicate
14
compilation.
15
16
The new command-line syntax is:
17
18
$ qemu-storage-daemon \
19
--blockdev file,node-name=drive0,filename=test.img \
20
--export vhost-user-blk,node-name=drive0,id=export0,unix-socket=/tmp/vhost-user-blk.sock
21
22
Note that unix-socket is optional because we may wish to accept chardevs
23
too in the future.
24
25
Markus noted that supported address families are not explicit in the
26
QAPI schema. It is unlikely that support for more address families will
27
be added since file descriptor passing is required and few address
28
families support it. If a new address family needs to be added, then the
29
QAPI 'features' syntax can be used to advertize them.
30
31
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
32
Acked-by: Markus Armbruster <armbru@redhat.com>
33
Message-id: 20200924151549.913737-12-stefanha@redhat.com
34
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
35
---
36
qapi/block-export.json | 21 +-
37
block/export/vhost-user-blk-server.h | 23 +-
38
block/export/export.c | 6 +
39
block/export/vhost-user-blk-server.c | 452 +++++++--------------------
40
tests/qtest/vhost-user-blk-test.c | 2 +-
41
util/vhost-user-server.c | 10 +-
42
block/export/meson.build | 1 +
43
block/meson.build | 1 -
44
8 files changed, 157 insertions(+), 359 deletions(-)
45
46
diff --git a/qapi/block-export.json b/qapi/block-export.json
47
index XXXXXXX..XXXXXXX 100644
48
--- a/qapi/block-export.json
49
+++ b/qapi/block-export.json
50
@@ -XXX,XX +XXX,XX @@
51
'data': { '*name': 'str', '*description': 'str',
52
'*bitmap': 'str' } }
53
54
+##
55
+# @BlockExportOptionsVhostUserBlk:
56
+#
57
+# A vhost-user-blk block export.
58
+#
59
+# @addr: The vhost-user socket on which to listen. Both 'unix' and 'fd'
60
+# SocketAddress types are supported. Passed fds must be UNIX domain
61
+# sockets.
62
+# @logical-block-size: Logical block size in bytes. Defaults to 512 bytes.
63
+#
64
+# Since: 5.2
65
+##
66
+{ 'struct': 'BlockExportOptionsVhostUserBlk',
67
+ 'data': { 'addr': 'SocketAddress', '*logical-block-size': 'size' } }
68
+
69
##
70
# @NbdServerAddOptions:
71
#
72
@@ -XXX,XX +XXX,XX @@
73
# An enumeration of block export types
74
#
75
# @nbd: NBD export
76
+# @vhost-user-blk: vhost-user-blk export (since 5.2)
77
#
78
# Since: 4.2
79
##
80
{ 'enum': 'BlockExportType',
81
- 'data': [ 'nbd' ] }
82
+ 'data': [ 'nbd', 'vhost-user-blk' ] }
83
84
##
85
# @BlockExportOptions:
86
@@ -XXX,XX +XXX,XX @@
87
'*writethrough': 'bool' },
88
'discriminator': 'type',
89
'data': {
90
- 'nbd': 'BlockExportOptionsNbd'
91
+ 'nbd': 'BlockExportOptionsNbd',
92
+ 'vhost-user-blk': 'BlockExportOptionsVhostUserBlk'
93
} }
94
95
##
96
diff --git a/block/export/vhost-user-blk-server.h b/block/export/vhost-user-blk-server.h
97
index XXXXXXX..XXXXXXX 100644
98
--- a/block/export/vhost-user-blk-server.h
99
+++ b/block/export/vhost-user-blk-server.h
100
@@ -XXX,XX +XXX,XX @@
101
102
#ifndef VHOST_USER_BLK_SERVER_H
103
#define VHOST_USER_BLK_SERVER_H
104
-#include "util/vhost-user-server.h"
105
106
-typedef struct VuBlockDev VuBlockDev;
107
-#define TYPE_VHOST_USER_BLK_SERVER "vhost-user-blk-server"
108
-#define VHOST_USER_BLK_SERVER(obj) \
109
- OBJECT_CHECK(VuBlockDev, obj, TYPE_VHOST_USER_BLK_SERVER)
110
+#include "block/export.h"
111
112
-/* vhost user block device */
113
-struct VuBlockDev {
114
- Object parent_obj;
115
- char *node_name;
116
- SocketAddress *addr;
117
- AioContext *ctx;
118
- VuServer vu_server;
119
- bool running;
120
- uint32_t blk_size;
121
- BlockBackend *backend;
122
- QIOChannelSocket *sioc;
123
- QTAILQ_ENTRY(VuBlockDev) next;
124
- struct virtio_blk_config blkcfg;
125
- bool writable;
126
-};
127
+/* For block/export/export.c */
128
+extern const BlockExportDriver blk_exp_vhost_user_blk;
129
130
#endif /* VHOST_USER_BLK_SERVER_H */
131
diff --git a/block/export/export.c b/block/export/export.c
132
index XXXXXXX..XXXXXXX 100644
133
--- a/block/export/export.c
134
+++ b/block/export/export.c
135
@@ -XXX,XX +XXX,XX @@
136
#include "sysemu/block-backend.h"
137
#include "block/export.h"
138
#include "block/nbd.h"
139
+#if CONFIG_LINUX
140
+#include "block/export/vhost-user-blk-server.h"
141
+#endif
142
#include "qapi/error.h"
143
#include "qapi/qapi-commands-block-export.h"
144
#include "qapi/qapi-events-block-export.h"
145
@@ -XXX,XX +XXX,XX @@
146
147
static const BlockExportDriver *blk_exp_drivers[] = {
148
&blk_exp_nbd,
149
+#if CONFIG_LINUX
150
+ &blk_exp_vhost_user_blk,
151
+#endif
152
};
153
154
/* Only accessed from the main thread */
155
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
156
index XXXXXXX..XXXXXXX 100644
157
--- a/block/export/vhost-user-blk-server.c
158
+++ b/block/export/vhost-user-blk-server.c
159
@@ -XXX,XX +XXX,XX @@
160
*/
161
#include "qemu/osdep.h"
162
#include "block/block.h"
163
+#include "contrib/libvhost-user/libvhost-user.h"
164
+#include "standard-headers/linux/virtio_blk.h"
165
+#include "util/vhost-user-server.h"
166
#include "vhost-user-blk-server.h"
167
#include "qapi/error.h"
168
#include "qom/object_interfaces.h"
169
@@ -XXX,XX +XXX,XX @@ struct virtio_blk_inhdr {
170
unsigned char status;
171
};
172
173
-typedef struct VuBlockReq {
174
+typedef struct VuBlkReq {
175
VuVirtqElement elem;
176
int64_t sector_num;
177
size_t size;
178
@@ -XXX,XX +XXX,XX @@ typedef struct VuBlockReq {
179
struct virtio_blk_outhdr out;
180
VuServer *server;
181
struct VuVirtq *vq;
182
-} VuBlockReq;
183
+} VuBlkReq;
184
185
-static void vu_block_req_complete(VuBlockReq *req)
186
+/* vhost user block device */
187
+typedef struct {
188
+ BlockExport export;
189
+ VuServer vu_server;
190
+ uint32_t blk_size;
191
+ QIOChannelSocket *sioc;
192
+ struct virtio_blk_config blkcfg;
193
+ bool writable;
194
+} VuBlkExport;
195
+
196
+static void vu_blk_req_complete(VuBlkReq *req)
197
{
198
VuDev *vu_dev = &req->server->vu_dev;
199
200
@@ -XXX,XX +XXX,XX @@ static void vu_block_req_complete(VuBlockReq *req)
201
free(req);
202
}
203
204
-static VuBlockDev *get_vu_block_device_by_server(VuServer *server)
205
-{
206
- return container_of(server, VuBlockDev, vu_server);
207
-}
208
-
209
static int coroutine_fn
210
-vu_block_discard_write_zeroes(VuBlockReq *req, struct iovec *iov,
211
- uint32_t iovcnt, uint32_t type)
212
+vu_blk_discard_write_zeroes(BlockBackend *blk, struct iovec *iov,
213
+ uint32_t iovcnt, uint32_t type)
214
{
215
struct virtio_blk_discard_write_zeroes desc;
216
ssize_t size = iov_to_buf(iov, iovcnt, 0, &desc, sizeof(desc));
217
@@ -XXX,XX +XXX,XX @@ vu_block_discard_write_zeroes(VuBlockReq *req, struct iovec *iov,
218
return -EINVAL;
219
}
220
221
- VuBlockDev *vdev_blk = get_vu_block_device_by_server(req->server);
222
uint64_t range[2] = { le64_to_cpu(desc.sector) << 9,
223
le32_to_cpu(desc.num_sectors) << 9 };
224
if (type == VIRTIO_BLK_T_DISCARD) {
225
- if (blk_co_pdiscard(vdev_blk->backend, range[0], range[1]) == 0) {
226
+ if (blk_co_pdiscard(blk, range[0], range[1]) == 0) {
227
return 0;
228
}
229
} else if (type == VIRTIO_BLK_T_WRITE_ZEROES) {
230
- if (blk_co_pwrite_zeroes(vdev_blk->backend,
231
- range[0], range[1], 0) == 0) {
232
+ if (blk_co_pwrite_zeroes(blk, range[0], range[1], 0) == 0) {
233
return 0;
234
}
235
}
236
@@ -XXX,XX +XXX,XX @@ vu_block_discard_write_zeroes(VuBlockReq *req, struct iovec *iov,
237
return -EINVAL;
238
}
239
240
-static int coroutine_fn vu_block_flush(VuBlockReq *req)
241
+static void coroutine_fn vu_blk_virtio_process_req(void *opaque)
242
{
243
- VuBlockDev *vdev_blk = get_vu_block_device_by_server(req->server);
244
- BlockBackend *backend = vdev_blk->backend;
245
- return blk_co_flush(backend);
246
-}
247
-
248
-static void coroutine_fn vu_block_virtio_process_req(void *opaque)
249
-{
250
- VuBlockReq *req = opaque;
251
+ VuBlkReq *req = opaque;
252
VuServer *server = req->server;
253
VuVirtqElement *elem = &req->elem;
254
uint32_t type;
255
256
- VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
257
- BlockBackend *backend = vdev_blk->backend;
258
+ VuBlkExport *vexp = container_of(server, VuBlkExport, vu_server);
259
+ BlockBackend *blk = vexp->export.blk;
260
261
struct iovec *in_iov = elem->in_sg;
262
struct iovec *out_iov = elem->out_sg;
263
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_virtio_process_req(void *opaque)
264
bool is_write = type & VIRTIO_BLK_T_OUT;
265
req->sector_num = le64_to_cpu(req->out.sector);
266
267
- int64_t offset = req->sector_num * vdev_blk->blk_size;
268
+ if (is_write && !vexp->writable) {
269
+ req->in->status = VIRTIO_BLK_S_IOERR;
270
+ break;
271
+ }
272
+
273
+ int64_t offset = req->sector_num * vexp->blk_size;
274
QEMUIOVector qiov;
275
if (is_write) {
276
qemu_iovec_init_external(&qiov, out_iov, out_num);
277
- ret = blk_co_pwritev(backend, offset, qiov.size,
278
- &qiov, 0);
279
+ ret = blk_co_pwritev(blk, offset, qiov.size, &qiov, 0);
280
} else {
281
qemu_iovec_init_external(&qiov, in_iov, in_num);
282
- ret = blk_co_preadv(backend, offset, qiov.size,
283
- &qiov, 0);
284
+ ret = blk_co_preadv(blk, offset, qiov.size, &qiov, 0);
285
}
286
if (ret >= 0) {
287
req->in->status = VIRTIO_BLK_S_OK;
288
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_virtio_process_req(void *opaque)
289
break;
290
}
291
case VIRTIO_BLK_T_FLUSH:
292
- if (vu_block_flush(req) == 0) {
293
+ if (blk_co_flush(blk) == 0) {
294
req->in->status = VIRTIO_BLK_S_OK;
295
} else {
296
req->in->status = VIRTIO_BLK_S_IOERR;
297
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_virtio_process_req(void *opaque)
298
case VIRTIO_BLK_T_DISCARD:
299
case VIRTIO_BLK_T_WRITE_ZEROES: {
300
int rc;
301
- rc = vu_block_discard_write_zeroes(req, &elem->out_sg[1],
302
- out_num, type);
303
+
304
+ if (!vexp->writable) {
305
+ req->in->status = VIRTIO_BLK_S_IOERR;
306
+ break;
307
+ }
308
+
309
+ rc = vu_blk_discard_write_zeroes(blk, &elem->out_sg[1], out_num, type);
310
if (rc == 0) {
311
req->in->status = VIRTIO_BLK_S_OK;
312
} else {
313
@@ -XXX,XX +XXX,XX @@ static void coroutine_fn vu_block_virtio_process_req(void *opaque)
314
break;
315
}
316
317
- vu_block_req_complete(req);
318
+ vu_blk_req_complete(req);
319
return;
320
321
err:
322
- free(elem);
323
+ free(req);
324
}
325
326
-static void vu_block_process_vq(VuDev *vu_dev, int idx)
327
+static void vu_blk_process_vq(VuDev *vu_dev, int idx)
328
{
329
VuServer *server = container_of(vu_dev, VuServer, vu_dev);
330
VuVirtq *vq = vu_get_queue(vu_dev, idx);
331
332
while (1) {
333
- VuBlockReq *req;
334
+ VuBlkReq *req;
335
336
- req = vu_queue_pop(vu_dev, vq, sizeof(VuBlockReq));
337
+ req = vu_queue_pop(vu_dev, vq, sizeof(VuBlkReq));
338
if (!req) {
339
break;
340
}
341
@@ -XXX,XX +XXX,XX @@ static void vu_block_process_vq(VuDev *vu_dev, int idx)
342
req->vq = vq;
343
344
Coroutine *co =
345
- qemu_coroutine_create(vu_block_virtio_process_req, req);
346
+ qemu_coroutine_create(vu_blk_virtio_process_req, req);
347
qemu_coroutine_enter(co);
348
}
349
}
350
351
-static void vu_block_queue_set_started(VuDev *vu_dev, int idx, bool started)
352
+static void vu_blk_queue_set_started(VuDev *vu_dev, int idx, bool started)
353
{
354
VuVirtq *vq;
355
356
assert(vu_dev);
357
358
vq = vu_get_queue(vu_dev, idx);
359
- vu_set_queue_handler(vu_dev, vq, started ? vu_block_process_vq : NULL);
360
+ vu_set_queue_handler(vu_dev, vq, started ? vu_blk_process_vq : NULL);
361
}
362
363
-static uint64_t vu_block_get_features(VuDev *dev)
364
+static uint64_t vu_blk_get_features(VuDev *dev)
365
{
366
uint64_t features;
367
VuServer *server = container_of(dev, VuServer, vu_dev);
368
- VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
369
+ VuBlkExport *vexp = container_of(server, VuBlkExport, vu_server);
370
features = 1ull << VIRTIO_BLK_F_SIZE_MAX |
371
1ull << VIRTIO_BLK_F_SEG_MAX |
372
1ull << VIRTIO_BLK_F_TOPOLOGY |
373
@@ -XXX,XX +XXX,XX @@ static uint64_t vu_block_get_features(VuDev *dev)
374
1ull << VIRTIO_RING_F_EVENT_IDX |
375
1ull << VHOST_USER_F_PROTOCOL_FEATURES;
376
377
- if (!vdev_blk->writable) {
378
+ if (!vexp->writable) {
379
features |= 1ull << VIRTIO_BLK_F_RO;
380
}
381
382
return features;
383
}
384
385
-static uint64_t vu_block_get_protocol_features(VuDev *dev)
386
+static uint64_t vu_blk_get_protocol_features(VuDev *dev)
387
{
388
return 1ull << VHOST_USER_PROTOCOL_F_CONFIG |
389
1ull << VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD;
390
}
391
392
static int
393
-vu_block_get_config(VuDev *vu_dev, uint8_t *config, uint32_t len)
394
+vu_blk_get_config(VuDev *vu_dev, uint8_t *config, uint32_t len)
395
{
396
+ /* TODO blkcfg must be little-endian for VIRTIO 1.0 */
397
VuServer *server = container_of(vu_dev, VuServer, vu_dev);
398
- VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
399
- memcpy(config, &vdev_blk->blkcfg, len);
400
-
401
+ VuBlkExport *vexp = container_of(server, VuBlkExport, vu_server);
402
+ memcpy(config, &vexp->blkcfg, len);
403
return 0;
404
}
405
406
static int
407
-vu_block_set_config(VuDev *vu_dev, const uint8_t *data,
408
+vu_blk_set_config(VuDev *vu_dev, const uint8_t *data,
409
uint32_t offset, uint32_t size, uint32_t flags)
410
{
411
VuServer *server = container_of(vu_dev, VuServer, vu_dev);
412
- VuBlockDev *vdev_blk = get_vu_block_device_by_server(server);
413
+ VuBlkExport *vexp = container_of(server, VuBlkExport, vu_server);
414
uint8_t wce;
415
416
/* don't support live migration */
417
@@ -XXX,XX +XXX,XX @@ vu_block_set_config(VuDev *vu_dev, const uint8_t *data,
418
}
419
420
wce = *data;
421
- vdev_blk->blkcfg.wce = wce;
422
- blk_set_enable_write_cache(vdev_blk->backend, wce);
423
+ vexp->blkcfg.wce = wce;
424
+ blk_set_enable_write_cache(vexp->export.blk, wce);
425
return 0;
426
}
427
428
@@ -XXX,XX +XXX,XX @@ vu_block_set_config(VuDev *vu_dev, const uint8_t *data,
429
* of vu_process_message.
430
*
431
*/
432
-static int vu_block_process_msg(VuDev *dev, VhostUserMsg *vmsg, int *do_reply)
433
+static int vu_blk_process_msg(VuDev *dev, VhostUserMsg *vmsg, int *do_reply)
434
{
435
if (vmsg->request == VHOST_USER_NONE) {
436
dev->panic(dev, "disconnect");
437
@@ -XXX,XX +XXX,XX @@ static int vu_block_process_msg(VuDev *dev, VhostUserMsg *vmsg, int *do_reply)
438
return false;
439
}
440
441
-static const VuDevIface vu_block_iface = {
442
- .get_features = vu_block_get_features,
443
- .queue_set_started = vu_block_queue_set_started,
444
- .get_protocol_features = vu_block_get_protocol_features,
445
- .get_config = vu_block_get_config,
446
- .set_config = vu_block_set_config,
447
- .process_msg = vu_block_process_msg,
448
+static const VuDevIface vu_blk_iface = {
449
+ .get_features = vu_blk_get_features,
450
+ .queue_set_started = vu_blk_queue_set_started,
451
+ .get_protocol_features = vu_blk_get_protocol_features,
452
+ .get_config = vu_blk_get_config,
453
+ .set_config = vu_blk_set_config,
454
+ .process_msg = vu_blk_process_msg,
455
};
456
457
static void blk_aio_attached(AioContext *ctx, void *opaque)
458
{
459
- VuBlockDev *vub_dev = opaque;
460
- vhost_user_server_attach_aio_context(&vub_dev->vu_server, ctx);
461
+ VuBlkExport *vexp = opaque;
462
+ vhost_user_server_attach_aio_context(&vexp->vu_server, ctx);
463
}
464
465
static void blk_aio_detach(void *opaque)
466
{
467
- VuBlockDev *vub_dev = opaque;
468
- vhost_user_server_detach_aio_context(&vub_dev->vu_server);
469
+ VuBlkExport *vexp = opaque;
470
+ vhost_user_server_detach_aio_context(&vexp->vu_server);
471
}
472
473
static void
474
-vu_block_initialize_config(BlockDriverState *bs,
475
+vu_blk_initialize_config(BlockDriverState *bs,
476
struct virtio_blk_config *config, uint32_t blk_size)
477
{
478
config->capacity = bdrv_getlength(bs) >> BDRV_SECTOR_BITS;
479
@@ -XXX,XX +XXX,XX @@ vu_block_initialize_config(BlockDriverState *bs,
480
config->max_write_zeroes_seg = 1;
481
}
482
483
-static VuBlockDev *vu_block_init(VuBlockDev *vu_block_device, Error **errp)
484
+static void vu_blk_exp_request_shutdown(BlockExport *exp)
485
{
486
+ VuBlkExport *vexp = container_of(exp, VuBlkExport, export);
487
488
- BlockBackend *blk;
489
- Error *local_error = NULL;
490
- const char *node_name = vu_block_device->node_name;
491
- bool writable = vu_block_device->writable;
492
- uint64_t perm = BLK_PERM_CONSISTENT_READ;
493
- int ret;
494
-
495
- AioContext *ctx;
496
-
497
- BlockDriverState *bs = bdrv_lookup_bs(node_name, node_name, &local_error);
498
-
499
- if (!bs) {
500
- error_propagate(errp, local_error);
501
- return NULL;
502
- }
503
-
504
- if (bdrv_is_read_only(bs)) {
505
- writable = false;
506
- }
507
-
508
- if (writable) {
509
- perm |= BLK_PERM_WRITE;
510
- }
511
-
512
- ctx = bdrv_get_aio_context(bs);
513
- aio_context_acquire(ctx);
514
- bdrv_invalidate_cache(bs, NULL);
515
- aio_context_release(ctx);
516
-
517
- /*
518
- * Don't allow resize while the vhost user server is running,
519
- * otherwise we don't care what happens with the node.
520
- */
521
- blk = blk_new(bdrv_get_aio_context(bs), perm,
522
- BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED |
523
- BLK_PERM_WRITE | BLK_PERM_GRAPH_MOD);
524
- ret = blk_insert_bs(blk, bs, errp);
525
-
526
- if (ret < 0) {
527
- goto fail;
528
- }
529
-
530
- blk_set_enable_write_cache(blk, false);
531
-
532
- blk_set_allow_aio_context_change(blk, true);
533
-
534
- vu_block_device->blkcfg.wce = 0;
535
- vu_block_device->backend = blk;
536
- if (!vu_block_device->blk_size) {
537
- vu_block_device->blk_size = BDRV_SECTOR_SIZE;
538
- }
539
- vu_block_device->blkcfg.blk_size = vu_block_device->blk_size;
540
- blk_set_guest_block_size(blk, vu_block_device->blk_size);
541
- vu_block_initialize_config(bs, &vu_block_device->blkcfg,
542
- vu_block_device->blk_size);
543
- return vu_block_device;
544
-
545
-fail:
546
- blk_unref(blk);
547
- return NULL;
548
-}
549
-
550
-static void vu_block_deinit(VuBlockDev *vu_block_device)
551
-{
552
- if (vu_block_device->backend) {
553
- blk_remove_aio_context_notifier(vu_block_device->backend, blk_aio_attached,
554
- blk_aio_detach, vu_block_device);
555
- }
556
-
557
- blk_unref(vu_block_device->backend);
558
-}
559
-
560
-static void vhost_user_blk_server_stop(VuBlockDev *vu_block_device)
561
-{
562
- vhost_user_server_stop(&vu_block_device->vu_server);
563
- vu_block_deinit(vu_block_device);
564
-}
565
-
566
-static void vhost_user_blk_server_start(VuBlockDev *vu_block_device,
567
- Error **errp)
568
-{
569
- AioContext *ctx;
570
- SocketAddress *addr = vu_block_device->addr;
571
-
572
- if (!vu_block_init(vu_block_device, errp)) {
573
- return;
574
- }
575
-
576
- ctx = bdrv_get_aio_context(blk_bs(vu_block_device->backend));
577
-
578
- if (!vhost_user_server_start(&vu_block_device->vu_server, addr, ctx,
579
- VHOST_USER_BLK_MAX_QUEUES, &vu_block_iface,
580
- errp)) {
581
- goto error;
582
- }
583
-
584
- blk_add_aio_context_notifier(vu_block_device->backend, blk_aio_attached,
585
- blk_aio_detach, vu_block_device);
586
- vu_block_device->running = true;
587
- return;
588
-
589
- error:
590
- vu_block_deinit(vu_block_device);
591
-}
592
-
593
-static bool vu_prop_modifiable(VuBlockDev *vus, Error **errp)
594
-{
595
- if (vus->running) {
596
- error_setg(errp, "The property can't be modified "
597
- "while the server is running");
598
- return false;
599
- }
600
- return true;
601
-}
602
-
603
-static void vu_set_node_name(Object *obj, const char *value, Error **errp)
604
-{
605
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
606
-
607
- if (!vu_prop_modifiable(vus, errp)) {
608
- return;
609
- }
610
-
611
- if (vus->node_name) {
612
- g_free(vus->node_name);
613
- }
614
-
615
- vus->node_name = g_strdup(value);
616
-}
617
-
618
-static char *vu_get_node_name(Object *obj, Error **errp)
619
-{
620
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
621
- return g_strdup(vus->node_name);
622
-}
623
-
624
-static void free_socket_addr(SocketAddress *addr)
625
-{
626
- g_free(addr->u.q_unix.path);
627
- g_free(addr);
628
-}
629
-
630
-static void vu_set_unix_socket(Object *obj, const char *value,
631
- Error **errp)
632
-{
633
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
634
-
635
- if (!vu_prop_modifiable(vus, errp)) {
636
- return;
637
- }
638
-
639
- if (vus->addr) {
640
- free_socket_addr(vus->addr);
641
- }
642
-
643
- SocketAddress *addr = g_new0(SocketAddress, 1);
644
- addr->type = SOCKET_ADDRESS_TYPE_UNIX;
645
- addr->u.q_unix.path = g_strdup(value);
646
- vus->addr = addr;
647
+ vhost_user_server_stop(&vexp->vu_server);
648
}
649
650
-static char *vu_get_unix_socket(Object *obj, Error **errp)
651
+static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
652
+ Error **errp)
653
{
654
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
655
- return g_strdup(vus->addr->u.q_unix.path);
656
-}
657
-
658
-static bool vu_get_block_writable(Object *obj, Error **errp)
659
-{
660
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
661
- return vus->writable;
662
-}
663
-
664
-static void vu_set_block_writable(Object *obj, bool value, Error **errp)
665
-{
666
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
667
-
668
- if (!vu_prop_modifiable(vus, errp)) {
669
- return;
670
- }
671
-
672
- vus->writable = value;
673
-}
674
-
675
-static void vu_get_blk_size(Object *obj, Visitor *v, const char *name,
676
- void *opaque, Error **errp)
677
-{
678
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
679
- uint32_t value = vus->blk_size;
680
-
681
- visit_type_uint32(v, name, &value, errp);
682
-}
683
-
684
-static void vu_set_blk_size(Object *obj, Visitor *v, const char *name,
685
- void *opaque, Error **errp)
686
-{
687
- VuBlockDev *vus = VHOST_USER_BLK_SERVER(obj);
688
-
689
+ VuBlkExport *vexp = container_of(exp, VuBlkExport, export);
690
+ BlockExportOptionsVhostUserBlk *vu_opts = &opts->u.vhost_user_blk;
691
Error *local_err = NULL;
692
- uint32_t value;
693
+ uint64_t logical_block_size;
694
695
- if (!vu_prop_modifiable(vus, errp)) {
696
- return;
697
- }
698
+ vexp->writable = opts->writable;
699
+ vexp->blkcfg.wce = 0;
700
701
- visit_type_uint32(v, name, &value, &local_err);
702
- if (local_err) {
703
- goto out;
704
+ if (vu_opts->has_logical_block_size) {
705
+ logical_block_size = vu_opts->logical_block_size;
706
+ } else {
707
+ logical_block_size = BDRV_SECTOR_SIZE;
708
}
709
-
710
- check_block_size(object_get_typename(obj), name, value, &local_err);
711
+ check_block_size(exp->id, "logical-block-size", logical_block_size,
712
+ &local_err);
713
if (local_err) {
714
- goto out;
715
+ error_propagate(errp, local_err);
716
+ return -EINVAL;
717
+ }
718
+ vexp->blk_size = logical_block_size;
719
+ blk_set_guest_block_size(exp->blk, logical_block_size);
720
+ vu_blk_initialize_config(blk_bs(exp->blk), &vexp->blkcfg,
721
+ logical_block_size);
722
+
723
+ blk_set_allow_aio_context_change(exp->blk, true);
724
+ blk_add_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach,
725
+ vexp);
726
+
727
+ if (!vhost_user_server_start(&vexp->vu_server, vu_opts->addr, exp->ctx,
728
+ VHOST_USER_BLK_MAX_QUEUES, &vu_blk_iface,
729
+ errp)) {
730
+ blk_remove_aio_context_notifier(exp->blk, blk_aio_attached,
731
+ blk_aio_detach, vexp);
732
+ return -EADDRNOTAVAIL;
733
}
734
735
- vus->blk_size = value;
736
-
737
-out:
738
- error_propagate(errp, local_err);
739
-}
740
-
741
-static void vhost_user_blk_server_instance_finalize(Object *obj)
742
-{
743
- VuBlockDev *vub = VHOST_USER_BLK_SERVER(obj);
744
-
745
- vhost_user_blk_server_stop(vub);
746
-
747
- /*
748
- * Unlike object_property_add_str, object_class_property_add_str
749
- * doesn't have a release method. Thus manual memory freeing is
750
- * needed.
751
- */
752
- free_socket_addr(vub->addr);
753
- g_free(vub->node_name);
754
-}
755
-
756
-static void vhost_user_blk_server_complete(UserCreatable *obj, Error **errp)
757
-{
758
- VuBlockDev *vub = VHOST_USER_BLK_SERVER(obj);
759
-
760
- vhost_user_blk_server_start(vub, errp);
761
+ return 0;
762
}
763
764
-static void vhost_user_blk_server_class_init(ObjectClass *klass,
765
- void *class_data)
766
+static void vu_blk_exp_delete(BlockExport *exp)
767
{
768
- UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
769
- ucc->complete = vhost_user_blk_server_complete;
770
-
771
- object_class_property_add_bool(klass, "writable",
772
- vu_get_block_writable,
773
- vu_set_block_writable);
774
-
775
- object_class_property_add_str(klass, "node-name",
776
- vu_get_node_name,
777
- vu_set_node_name);
778
-
779
- object_class_property_add_str(klass, "unix-socket",
780
- vu_get_unix_socket,
781
- vu_set_unix_socket);
782
+ VuBlkExport *vexp = container_of(exp, VuBlkExport, export);
783
784
- object_class_property_add(klass, "logical-block-size", "uint32",
785
- vu_get_blk_size, vu_set_blk_size,
786
- NULL, NULL);
787
+ blk_remove_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach,
788
+ vexp);
789
}
790
791
-static const TypeInfo vhost_user_blk_server_info = {
792
- .name = TYPE_VHOST_USER_BLK_SERVER,
793
- .parent = TYPE_OBJECT,
794
- .instance_size = sizeof(VuBlockDev),
795
- .instance_finalize = vhost_user_blk_server_instance_finalize,
796
- .class_init = vhost_user_blk_server_class_init,
797
- .interfaces = (InterfaceInfo[]) {
798
- {TYPE_USER_CREATABLE},
799
- {}
800
- },
801
+const BlockExportDriver blk_exp_vhost_user_blk = {
802
+ .type = BLOCK_EXPORT_TYPE_VHOST_USER_BLK,
803
+ .instance_size = sizeof(VuBlkExport),
804
+ .create = vu_blk_exp_create,
805
+ .delete = vu_blk_exp_delete,
806
+ .request_shutdown = vu_blk_exp_request_shutdown,
807
};
808
-
809
-static void vhost_user_blk_server_register_types(void)
810
-{
811
- type_register_static(&vhost_user_blk_server_info);
812
-}
813
-
814
-type_init(vhost_user_blk_server_register_types)
815
diff --git a/tests/qtest/vhost-user-blk-test.c b/tests/qtest/vhost-user-blk-test.c
816
index XXXXXXX..XXXXXXX 100644
817
--- a/tests/qtest/vhost-user-blk-test.c
818
+++ b/tests/qtest/vhost-user-blk-test.c
819
@@ -XXX,XX +XXX,XX @@ static char *start_vhost_user_blk(GString *cmd_line, int vus_instances)
820
img_path = drive_create();
821
g_string_append_printf(storage_daemon_command,
822
"--blockdev driver=file,node-name=disk%d,filename=%s "
823
- "--object vhost-user-blk-server,id=disk%d,unix-socket=%s,"
824
+ "--export type=vhost-user-blk,id=disk%d,addr.type=unix,addr.path=%s,"
825
"node-name=disk%i,writable=on ",
826
i, img_path, i, sock_path, i);
827
828
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
829
index XXXXXXX..XXXXXXX 100644
830
--- a/util/vhost-user-server.c
831
+++ b/util/vhost-user-server.c
832
@@ -XXX,XX +XXX,XX @@ bool vhost_user_server_start(VuServer *server,
833
Error **errp)
834
{
835
QEMUBH *bh;
836
- QIONetListener *listener = qio_net_listener_new();
837
+ QIONetListener *listener;
838
+
839
+ if (socket_addr->type != SOCKET_ADDRESS_TYPE_UNIX &&
840
+ socket_addr->type != SOCKET_ADDRESS_TYPE_FD) {
841
+ error_setg(errp, "Only socket address types 'unix' and 'fd' are supported");
842
+ return false;
843
+ }
844
+
845
+ listener = qio_net_listener_new();
846
if (qio_net_listener_open_sync(listener, socket_addr, 1,
847
errp) < 0) {
848
object_unref(OBJECT(listener));
849
diff --git a/block/export/meson.build b/block/export/meson.build
850
index XXXXXXX..XXXXXXX 100644
851
--- a/block/export/meson.build
852
+++ b/block/export/meson.build
853
@@ -1 +1,2 @@
854
block_ss.add(files('export.c'))
855
+block_ss.add(when: 'CONFIG_LINUX', if_true: files('vhost-user-blk-server.c', '../../contrib/libvhost-user/libvhost-user.c'))
856
diff --git a/block/meson.build b/block/meson.build
857
index XXXXXXX..XXXXXXX 100644
858
--- a/block/meson.build
859
+++ b/block/meson.build
860
@@ -XXX,XX +XXX,XX @@ block_ss.add(when: 'CONFIG_WIN32', if_true: files('file-win32.c', 'win32-aio.c')
861
block_ss.add(when: 'CONFIG_POSIX', if_true: [files('file-posix.c'), coref, iokit])
862
block_ss.add(when: 'CONFIG_LIBISCSI', if_true: files('iscsi-opts.c'))
863
block_ss.add(when: 'CONFIG_LINUX', if_true: files('nvme.c'))
864
-block_ss.add(when: 'CONFIG_LINUX', if_true: files('export/vhost-user-blk-server.c', '../contrib/libvhost-user/libvhost-user.c'))
865
block_ss.add(when: 'CONFIG_REPLICATION', if_true: files('replication.c'))
866
block_ss.add(when: 'CONFIG_SHEEPDOG', if_true: files('sheepdog.c'))
867
block_ss.add(when: ['CONFIG_LINUX_AIO', libaio], if_true: files('linux-aio.c'))
868
--
869
2.26.2
870
diff view generated by jsdifflib
New patch
1
Headers used by other subsystems are located in include/. Also add the
2
vhost-user-server and vhost-user-blk-server headers to MAINTAINERS.
1
3
4
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
5
Message-id: 20200924151549.913737-13-stefanha@redhat.com
6
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
7
---
8
MAINTAINERS | 4 +++-
9
{util => include/qemu}/vhost-user-server.h | 0
10
block/export/vhost-user-blk-server.c | 2 +-
11
util/vhost-user-server.c | 2 +-
12
4 files changed, 5 insertions(+), 3 deletions(-)
13
rename {util => include/qemu}/vhost-user-server.h (100%)
14
15
diff --git a/MAINTAINERS b/MAINTAINERS
16
index XXXXXXX..XXXXXXX 100644
17
--- a/MAINTAINERS
18
+++ b/MAINTAINERS
19
@@ -XXX,XX +XXX,XX @@ Vhost-user block device backend server
20
M: Coiby Xu <Coiby.Xu@gmail.com>
21
S: Maintained
22
F: block/export/vhost-user-blk-server.c
23
-F: util/vhost-user-server.c
24
+F: block/export/vhost-user-blk-server.h
25
+F: include/qemu/vhost-user-server.h
26
F: tests/qtest/vhost-user-blk-test.c
27
F: tests/qtest/libqos/vhost-user-blk.c
28
+F: util/vhost-user-server.c
29
30
Replication
31
M: Wen Congyang <wencongyang2@huawei.com>
32
diff --git a/util/vhost-user-server.h b/include/qemu/vhost-user-server.h
33
similarity index 100%
34
rename from util/vhost-user-server.h
35
rename to include/qemu/vhost-user-server.h
36
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
37
index XXXXXXX..XXXXXXX 100644
38
--- a/block/export/vhost-user-blk-server.c
39
+++ b/block/export/vhost-user-blk-server.c
40
@@ -XXX,XX +XXX,XX @@
41
#include "block/block.h"
42
#include "contrib/libvhost-user/libvhost-user.h"
43
#include "standard-headers/linux/virtio_blk.h"
44
-#include "util/vhost-user-server.h"
45
+#include "qemu/vhost-user-server.h"
46
#include "vhost-user-blk-server.h"
47
#include "qapi/error.h"
48
#include "qom/object_interfaces.h"
49
diff --git a/util/vhost-user-server.c b/util/vhost-user-server.c
50
index XXXXXXX..XXXXXXX 100644
51
--- a/util/vhost-user-server.c
52
+++ b/util/vhost-user-server.c
53
@@ -XXX,XX +XXX,XX @@
54
*/
55
#include "qemu/osdep.h"
56
#include "qemu/main-loop.h"
57
+#include "qemu/vhost-user-server.h"
58
#include "block/aio-wait.h"
59
-#include "vhost-user-server.h"
60
61
/*
62
* Theory of operation:
63
--
64
2.26.2
65
diff view generated by jsdifflib
1
From: Fred Rolland <rollandf@gmail.com>
1
Don't compile contrib/libvhost-user/libvhost-user.c again. Instead build
2
the static library once and then reuse it throughout QEMU.
2
3
3
Update doc with the usage of UUID for initiator name.
4
Also switch from CONFIG_LINUX to CONFIG_VHOST_USER, which is what the
5
vhost-user tools (vhost-user-gpu, etc) do.
4
6
5
Related-To: https://bugzilla.redhat.com/1006468
7
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
Signed-off-by: Fred Rolland <frolland@redhat.com>
8
Message-id: 20200924151549.913737-14-stefanha@redhat.com
7
Message-id: 20170823084830.30500-1-frolland@redhat.com
8
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9
---
10
---
10
qemu-doc.texi | 5 +++--
11
block/export/export.c | 8 ++++----
11
1 file changed, 3 insertions(+), 2 deletions(-)
12
block/export/meson.build | 2 +-
13
contrib/libvhost-user/meson.build | 1 +
14
meson.build | 6 +++++-
15
tests/qtest/meson.build | 2 +-
16
util/meson.build | 4 +++-
17
6 files changed, 15 insertions(+), 8 deletions(-)
12
18
13
diff --git a/qemu-doc.texi b/qemu-doc.texi
19
diff --git a/block/export/export.c b/block/export/export.c
14
index XXXXXXX..XXXXXXX 100644
20
index XXXXXXX..XXXXXXX 100644
15
--- a/qemu-doc.texi
21
--- a/block/export/export.c
16
+++ b/qemu-doc.texi
22
+++ b/block/export/export.c
17
@@ -XXX,XX +XXX,XX @@ in a configuration file provided via '-readconfig' or directly on the
23
@@ -XXX,XX +XXX,XX @@
18
command line.
24
#include "sysemu/block-backend.h"
19
25
#include "block/export.h"
20
If the initiator-name is not specified qemu will use a default name
26
#include "block/nbd.h"
21
-of 'iqn.2008-11.org.linux-kvm[:<name>'] where <name> is the name of the
27
-#if CONFIG_LINUX
22
+of 'iqn.2008-11.org.linux-kvm[:<uuid>'] where <uuid> is the UUID of the
28
-#include "block/export/vhost-user-blk-server.h"
23
+virtual machine. If the UUID is not specified qemu will use
29
-#endif
24
+'iqn.2008-11.org.linux-kvm[:<name>'] where <name> is the name of the
30
#include "qapi/error.h"
25
virtual machine.
31
#include "qapi/qapi-commands-block-export.h"
26
32
#include "qapi/qapi-events-block-export.h"
27
-
33
#include "qemu/id.h"
28
@example
34
+#ifdef CONFIG_VHOST_USER
29
Setting a specific initiator name to use when logging in to the target
35
+#include "vhost-user-blk-server.h"
30
-iscsi initiator-name=iqn.qemu.test:my-initiator
36
+#endif
37
38
static const BlockExportDriver *blk_exp_drivers[] = {
39
&blk_exp_nbd,
40
-#if CONFIG_LINUX
41
+#ifdef CONFIG_VHOST_USER
42
&blk_exp_vhost_user_blk,
43
#endif
44
};
45
diff --git a/block/export/meson.build b/block/export/meson.build
46
index XXXXXXX..XXXXXXX 100644
47
--- a/block/export/meson.build
48
+++ b/block/export/meson.build
49
@@ -XXX,XX +XXX,XX @@
50
block_ss.add(files('export.c'))
51
-block_ss.add(when: 'CONFIG_LINUX', if_true: files('vhost-user-blk-server.c', '../../contrib/libvhost-user/libvhost-user.c'))
52
+block_ss.add(when: 'CONFIG_VHOST_USER', if_true: files('vhost-user-blk-server.c'))
53
diff --git a/contrib/libvhost-user/meson.build b/contrib/libvhost-user/meson.build
54
index XXXXXXX..XXXXXXX 100644
55
--- a/contrib/libvhost-user/meson.build
56
+++ b/contrib/libvhost-user/meson.build
57
@@ -XXX,XX +XXX,XX @@
58
libvhost_user = static_library('vhost-user',
59
files('libvhost-user.c', 'libvhost-user-glib.c'),
60
build_by_default: false)
61
+vhost_user = declare_dependency(link_with: libvhost_user)
62
diff --git a/meson.build b/meson.build
63
index XXXXXXX..XXXXXXX 100644
64
--- a/meson.build
65
+++ b/meson.build
66
@@ -XXX,XX +XXX,XX @@ trace_events_subdirs += [
67
'util',
68
]
69
70
+vhost_user = not_found
71
+if 'CONFIG_VHOST_USER' in config_host
72
+ subdir('contrib/libvhost-user')
73
+endif
74
+
75
subdir('qapi')
76
subdir('qobject')
77
subdir('stubs')
78
@@ -XXX,XX +XXX,XX @@ if have_tools
79
install: true)
80
81
if 'CONFIG_VHOST_USER' in config_host
82
- subdir('contrib/libvhost-user')
83
subdir('contrib/vhost-user-blk')
84
subdir('contrib/vhost-user-gpu')
85
subdir('contrib/vhost-user-input')
86
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
87
index XXXXXXX..XXXXXXX 100644
88
--- a/tests/qtest/meson.build
89
+++ b/tests/qtest/meson.build
90
@@ -XXX,XX +XXX,XX @@ qos_test_ss.add(
91
)
92
qos_test_ss.add(when: 'CONFIG_VIRTFS', if_true: files('virtio-9p-test.c'))
93
qos_test_ss.add(when: 'CONFIG_VHOST_USER', if_true: files('vhost-user-test.c'))
94
-qos_test_ss.add(when: ['CONFIG_LINUX', 'CONFIG_TOOLS'], if_true: files('vhost-user-blk-test.c'))
95
+qos_test_ss.add(when: ['CONFIG_VHOST_USER', 'CONFIG_TOOLS'], if_true: files('vhost-user-blk-test.c'))
96
97
extra_qtest_deps = {
98
'bios-tables-test': [io],
99
diff --git a/util/meson.build b/util/meson.build
100
index XXXXXXX..XXXXXXX 100644
101
--- a/util/meson.build
102
+++ b/util/meson.build
103
@@ -XXX,XX +XXX,XX @@ if have_block
104
util_ss.add(files('main-loop.c'))
105
util_ss.add(files('nvdimm-utils.c'))
106
util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c'))
107
- util_ss.add(when: 'CONFIG_LINUX', if_true: files('vhost-user-server.c'))
108
+ util_ss.add(when: 'CONFIG_VHOST_USER', if_true: [
109
+ files('vhost-user-server.c'), vhost_user
110
+ ])
111
util_ss.add(files('block-helpers.c'))
112
util_ss.add(files('qemu-coroutine-sleep.c'))
113
util_ss.add(files('qemu-co-shared-resource.c'))
31
--
114
--
32
2.13.5
115
2.26.2
33
116
34
diff view generated by jsdifflib
1
From: Eduardo Habkost <ehabkost@redhat.com>
1
Introduce libblkdev.fa to avoid recompiling blockdev_ss twice.
2
2
3
If QEMU is running on a system that's out of memory and mmap()
3
Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
4
fails, QEMU aborts with no error message at all, making it hard
4
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
5
to debug the reason for the failure.
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
6
Message-id: 20200929125516.186715-3-stefanha@redhat.com
7
Add perror() calls that will print error information before
8
aborting.
9
10
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
11
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
12
Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
13
Message-id: 20170829212053.6003-1-ehabkost@redhat.com
14
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
7
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
15
---
8
---
16
util/oslib-posix.c | 2 ++
9
meson.build | 12 ++++++++++--
17
1 file changed, 2 insertions(+)
10
storage-daemon/meson.build | 3 +--
11
2 files changed, 11 insertions(+), 4 deletions(-)
18
12
19
diff --git a/util/oslib-posix.c b/util/oslib-posix.c
13
diff --git a/meson.build b/meson.build
20
index XXXXXXX..XXXXXXX 100644
14
index XXXXXXX..XXXXXXX 100644
21
--- a/util/oslib-posix.c
15
--- a/meson.build
22
+++ b/util/oslib-posix.c
16
+++ b/meson.build
23
@@ -XXX,XX +XXX,XX @@ void *qemu_alloc_stack(size_t *sz)
17
@@ -XXX,XX +XXX,XX @@ blockdev_ss.add(files(
24
ptr = mmap(NULL, *sz, PROT_READ | PROT_WRITE,
18
blockdev_ss.add(when: 'CONFIG_POSIX', if_true: files('os-posix.c'))
25
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
19
softmmu_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')])
26
if (ptr == MAP_FAILED) {
20
27
+ perror("failed to allocate memory for stack");
21
-softmmu_ss.add_all(blockdev_ss)
28
abort();
22
softmmu_ss.add(files(
29
}
23
'bootdevice.c',
30
24
'dma-helpers.c',
31
@@ -XXX,XX +XXX,XX @@ void *qemu_alloc_stack(size_t *sz)
25
@@ -XXX,XX +XXX,XX @@ block = declare_dependency(link_whole: [libblock],
32
guardpage = ptr;
26
link_args: '@block.syms',
33
#endif
27
dependencies: [crypto, io])
34
if (mprotect(guardpage, pagesz, PROT_NONE) != 0) {
28
35
+ perror("failed to set up stack guard page");
29
+blockdev_ss = blockdev_ss.apply(config_host, strict: false)
36
abort();
30
+libblockdev = static_library('blockdev', blockdev_ss.sources() + genh,
37
}
31
+ dependencies: blockdev_ss.dependencies(),
32
+ name_suffix: 'fa',
33
+ build_by_default: false)
34
+
35
+blockdev = declare_dependency(link_whole: [libblockdev],
36
+ dependencies: [block])
37
+
38
qmp_ss = qmp_ss.apply(config_host, strict: false)
39
libqmp = static_library('qmp', qmp_ss.sources() + genh,
40
dependencies: qmp_ss.dependencies(),
41
@@ -XXX,XX +XXX,XX @@ foreach m : block_mods + softmmu_mods
42
install_dir: config_host['qemu_moddir'])
43
endforeach
44
45
-softmmu_ss.add(authz, block, chardev, crypto, io, qmp)
46
+softmmu_ss.add(authz, blockdev, chardev, crypto, io, qmp)
47
common_ss.add(qom, qemuutil)
48
49
common_ss.add_all(when: 'CONFIG_SOFTMMU', if_true: [softmmu_ss])
50
diff --git a/storage-daemon/meson.build b/storage-daemon/meson.build
51
index XXXXXXX..XXXXXXX 100644
52
--- a/storage-daemon/meson.build
53
+++ b/storage-daemon/meson.build
54
@@ -XXX,XX +XXX,XX @@
55
qsd_ss = ss.source_set()
56
qsd_ss.add(files('qemu-storage-daemon.c'))
57
-qsd_ss.add(block, chardev, qmp, qom, qemuutil)
58
-qsd_ss.add_all(blockdev_ss)
59
+qsd_ss.add(blockdev, chardev, qmp, qom, qemuutil)
60
61
subdir('qapi')
38
62
39
--
63
--
40
2.13.5
64
2.26.2
41
65
42
diff view generated by jsdifflib
1
Add the scripts/ directory to sys.path so Python 2.6 will be able to
1
Block exports are used by softmmu, qemu-storage-daemon, and qemu-nbd.
2
import argparse.
2
They are not used by other programs and are not otherwise needed in
3
libblock.
3
4
4
Cc: Daniel P. Berrange <berrange@redhat.com>
5
Undo the recent move of blockdev-nbd.c from blockdev_ss into block_ss.
6
Since bdrv_close_all() (libblock) calls blk_exp_close_all()
7
(libblockdev) a stub function is required..
8
9
Make qemu-nbd.c use signal handling utility functions instead of
10
duplicating the code. This helps because os-posix.c is in libblockdev
11
and it depends on a qemu_system_killed() symbol that qemu-nbd.c lacks.
12
Once we use the signal handling utility functions we also end up
13
providing the necessary symbol.
14
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
15
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
Acked-by: John Snow <jsnow@redhat.com>
16
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
7
Acked-by: Fam Zheng <famz@redhat.com>
17
Reviewed-by: Eric Blake <eblake@redhat.com>
8
Message-id: 20170825155732.15665-4-stefanha@redhat.com
18
Message-id: 20200929125516.186715-4-stefanha@redhat.com
19
[Fixed s/ndb/nbd/ typo in commit description as suggested by Eric Blake
20
--Stefan]
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
21
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
---
22
---
11
tests/migration/guestperf/shell.py | 8 +++++---
23
qemu-nbd.c | 25 +++++++++----------------
12
1 file changed, 5 insertions(+), 3 deletions(-)
24
stubs/blk-exp-close-all.c | 7 +++++++
25
block/export/meson.build | 4 ++--
26
meson.build | 4 ++--
27
nbd/meson.build | 2 ++
28
stubs/meson.build | 1 +
29
6 files changed, 23 insertions(+), 20 deletions(-)
30
create mode 100644 stubs/blk-exp-close-all.c
13
31
14
diff --git a/tests/migration/guestperf/shell.py b/tests/migration/guestperf/shell.py
32
diff --git a/qemu-nbd.c b/qemu-nbd.c
15
index XXXXXXX..XXXXXXX 100644
33
index XXXXXXX..XXXXXXX 100644
16
--- a/tests/migration/guestperf/shell.py
34
--- a/qemu-nbd.c
17
+++ b/tests/migration/guestperf/shell.py
35
+++ b/qemu-nbd.c
18
@@ -XXX,XX +XXX,XX @@
36
@@ -XXX,XX +XXX,XX @@
19
#
37
#include "qapi/error.h"
20
38
#include "qemu/cutils.h"
21
39
#include "sysemu/block-backend.h"
22
-import argparse
40
+#include "sysemu/runstate.h" /* for qemu_system_killed() prototype */
23
-import fnmatch
41
#include "block/block_int.h"
24
import os
42
#include "block/nbd.h"
25
import os.path
43
#include "qemu/main-loop.h"
26
-import platform
44
@@ -XXX,XX +XXX,XX @@ QEMU_COPYRIGHT "\n"
27
import sys
45
, name);
28
+sys.path.append(os.path.join(os.path.dirname(__file__),
46
}
29
+ '..', '..', '..', 'scripts'))
47
30
+import argparse
48
-#ifdef CONFIG_POSIX
31
+import fnmatch
49
-static void termsig_handler(int signum)
32
+import platform
50
+#if CONFIG_POSIX
33
51
+/*
34
from guestperf.hardware import Hardware
52
+ * The client thread uses SIGTERM to interrupt the server. A signal
35
from guestperf.engine import Engine
53
+ * handler ensures that "qemu-nbd -v -c" exits with a nice status code.
54
+ */
55
+void qemu_system_killed(int signum, pid_t pid)
56
{
57
qatomic_cmpxchg(&state, RUNNING, TERMINATE);
58
qemu_notify_event();
59
@@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv)
60
const char *pid_file_name = NULL;
61
BlockExportOptions *export_opts;
62
63
-#ifdef CONFIG_POSIX
64
- /*
65
- * Exit gracefully on various signals, which includes SIGTERM used
66
- * by 'qemu-nbd -v -c'.
67
- */
68
- struct sigaction sa_sigterm;
69
- memset(&sa_sigterm, 0, sizeof(sa_sigterm));
70
- sa_sigterm.sa_handler = termsig_handler;
71
- sigaction(SIGTERM, &sa_sigterm, NULL);
72
- sigaction(SIGINT, &sa_sigterm, NULL);
73
- sigaction(SIGHUP, &sa_sigterm, NULL);
74
-
75
- signal(SIGPIPE, SIG_IGN);
76
-#endif
77
+ os_setup_early_signal_handling();
78
+ os_setup_signal_handling();
79
80
socket_init();
81
error_init(argv[0]);
82
diff --git a/stubs/blk-exp-close-all.c b/stubs/blk-exp-close-all.c
83
new file mode 100644
84
index XXXXXXX..XXXXXXX
85
--- /dev/null
86
+++ b/stubs/blk-exp-close-all.c
87
@@ -XXX,XX +XXX,XX @@
88
+#include "qemu/osdep.h"
89
+#include "block/export.h"
90
+
91
+/* Only used in programs that support block exports (libblockdev.fa) */
92
+void blk_exp_close_all(void)
93
+{
94
+}
95
diff --git a/block/export/meson.build b/block/export/meson.build
96
index XXXXXXX..XXXXXXX 100644
97
--- a/block/export/meson.build
98
+++ b/block/export/meson.build
99
@@ -XXX,XX +XXX,XX @@
100
-block_ss.add(files('export.c'))
101
-block_ss.add(when: 'CONFIG_VHOST_USER', if_true: files('vhost-user-blk-server.c'))
102
+blockdev_ss.add(files('export.c'))
103
+blockdev_ss.add(when: 'CONFIG_VHOST_USER', if_true: files('vhost-user-blk-server.c'))
104
diff --git a/meson.build b/meson.build
105
index XXXXXXX..XXXXXXX 100644
106
--- a/meson.build
107
+++ b/meson.build
108
@@ -XXX,XX +XXX,XX @@ subdir('dump')
109
110
block_ss.add(files(
111
'block.c',
112
- 'blockdev-nbd.c',
113
'blockjob.c',
114
'job.c',
115
'qemu-io-cmds.c',
116
@@ -XXX,XX +XXX,XX @@ subdir('block')
117
118
blockdev_ss.add(files(
119
'blockdev.c',
120
+ 'blockdev-nbd.c',
121
'iothread.c',
122
'job-qmp.c',
123
))
124
@@ -XXX,XX +XXX,XX @@ if have_tools
125
qemu_io = executable('qemu-io', files('qemu-io.c'),
126
dependencies: [block, qemuutil], install: true)
127
qemu_nbd = executable('qemu-nbd', files('qemu-nbd.c'),
128
- dependencies: [block, qemuutil], install: true)
129
+ dependencies: [blockdev, qemuutil], install: true)
130
131
subdir('storage-daemon')
132
subdir('contrib/rdmacm-mux')
133
diff --git a/nbd/meson.build b/nbd/meson.build
134
index XXXXXXX..XXXXXXX 100644
135
--- a/nbd/meson.build
136
+++ b/nbd/meson.build
137
@@ -XXX,XX +XXX,XX @@
138
block_ss.add(files(
139
'client.c',
140
'common.c',
141
+))
142
+blockdev_ss.add(files(
143
'server.c',
144
))
145
diff --git a/stubs/meson.build b/stubs/meson.build
146
index XXXXXXX..XXXXXXX 100644
147
--- a/stubs/meson.build
148
+++ b/stubs/meson.build
149
@@ -XXX,XX +XXX,XX @@
150
stub_ss.add(files('arch_type.c'))
151
stub_ss.add(files('bdrv-next-monitor-owned.c'))
152
stub_ss.add(files('blk-commit-all.c'))
153
+stub_ss.add(files('blk-exp-close-all.c'))
154
stub_ss.add(files('blockdev-close-all-bdrv-states.c'))
155
stub_ss.add(files('change-state-handler.c'))
156
stub_ss.add(files('cmos.c'))
36
--
157
--
37
2.13.5
158
2.26.2
38
159
39
diff view generated by jsdifflib
1
Most qcow2 files are uncompressed so it is wasteful to allocate (32 + 1)
1
Make it possible to specify the iothread where the export will run. By
2
* cluster_size + 512 bytes upfront. Allocate s->cluster_cache and
2
default the block node can be moved to other AioContexts later and the
3
s->cluster_data when the first read operation is performance on a
3
export will follow. The fixed-iothread option forces strict behavior
4
compressed cluster.
4
that prevents changing AioContext while the export is active. See the
5
QAPI docs for details.
5
6
6
The buffers are freed in .bdrv_close(). .bdrv_open() no longer has any
7
code paths that can allocate these buffers, so remove the free functions
8
in the error code path.
9
10
This patch can result in significant memory savings when many qcow2
11
disks are attached or backing file chains are long:
12
13
Before 12.81% (1,023,193,088B)
14
After 5.36% (393,893,888B)
15
16
Reported-by: Alexey Kardashevskiy <aik@ozlabs.ru>
17
Tested-by: Alexey Kardashevskiy <aik@ozlabs.ru>
18
Reviewed-by: Eric Blake <eblake@redhat.com>
19
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
7
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
20
Message-id: 20170821135530.32344-1-stefanha@redhat.com
8
Message-id: 20200929125516.186715-5-stefanha@redhat.com
21
Cc: Kevin Wolf <kwolf@redhat.com>
9
[Fix stray '#' character in block-export.json and add missing "(since:
10
5.2)" as suggested by Eric Blake.
11
--Stefan]
22
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
12
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
23
---
13
---
24
block/qcow2-cluster.c | 17 +++++++++++++++++
14
qapi/block-export.json | 11 ++++++++++
25
block/qcow2.c | 12 ------------
15
block/export/export.c | 31 +++++++++++++++++++++++++++-
26
2 files changed, 17 insertions(+), 12 deletions(-)
16
block/export/vhost-user-blk-server.c | 5 ++++-
17
nbd/server.c | 2 --
18
4 files changed, 45 insertions(+), 4 deletions(-)
27
19
28
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
20
diff --git a/qapi/block-export.json b/qapi/block-export.json
29
index XXXXXXX..XXXXXXX 100644
21
index XXXXXXX..XXXXXXX 100644
30
--- a/block/qcow2-cluster.c
22
--- a/qapi/block-export.json
31
+++ b/block/qcow2-cluster.c
23
+++ b/qapi/block-export.json
32
@@ -XXX,XX +XXX,XX @@ int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset)
24
@@ -XXX,XX +XXX,XX @@
33
nb_csectors = ((cluster_offset >> s->csize_shift) & s->csize_mask) + 1;
25
# export before completion is signalled. (since: 5.2;
34
sector_offset = coffset & 511;
26
# default: false)
35
csize = nb_csectors * 512 - sector_offset;
27
#
28
+# @iothread: The name of the iothread object where the export will run. The
29
+# default is to use the thread currently associated with the
30
+# block node. (since: 5.2)
31
+#
32
+# @fixed-iothread: True prevents the block node from being moved to another
33
+# thread while the export is active. If true and @iothread is
34
+# given, export creation fails if the block node cannot be
35
+# moved to the iothread. The default is false. (since: 5.2)
36
+#
37
# Since: 4.2
38
##
39
{ 'union': 'BlockExportOptions',
40
'base': { 'type': 'BlockExportType',
41
'id': 'str',
42
+     '*fixed-iothread': 'bool',
43
+     '*iothread': 'str',
44
'node-name': 'str',
45
'*writable': 'bool',
46
'*writethrough': 'bool' },
47
diff --git a/block/export/export.c b/block/export/export.c
48
index XXXXXXX..XXXXXXX 100644
49
--- a/block/export/export.c
50
+++ b/block/export/export.c
51
@@ -XXX,XX +XXX,XX @@
52
53
#include "block/block.h"
54
#include "sysemu/block-backend.h"
55
+#include "sysemu/iothread.h"
56
#include "block/export.h"
57
#include "block/nbd.h"
58
#include "qapi/error.h"
59
@@ -XXX,XX +XXX,XX @@ static const BlockExportDriver *blk_exp_find_driver(BlockExportType type)
60
61
BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
62
{
63
+ bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread;
64
const BlockExportDriver *drv;
65
BlockExport *exp = NULL;
66
BlockDriverState *bs;
67
- BlockBackend *blk;
68
+ BlockBackend *blk = NULL;
69
AioContext *ctx;
70
uint64_t perm;
71
int ret;
72
@@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
73
ctx = bdrv_get_aio_context(bs);
74
aio_context_acquire(ctx);
75
76
+ if (export->has_iothread) {
77
+ IOThread *iothread;
78
+ AioContext *new_ctx;
36
+
79
+
37
+ /* Allocate buffers on first decompress operation, most images are
80
+ iothread = iothread_by_id(export->iothread);
38
+ * uncompressed and the memory overhead can be avoided. The buffers
81
+ if (!iothread) {
39
+ * are freed in .bdrv_close().
82
+ error_setg(errp, "iothread \"%s\" not found", export->iothread);
40
+ */
83
+ goto fail;
41
+ if (!s->cluster_data) {
42
+ /* one more sector for decompressed data alignment */
43
+ s->cluster_data = qemu_try_blockalign(bs->file->bs,
44
+ QCOW_MAX_CRYPT_CLUSTERS * s->cluster_size + 512);
45
+ if (!s->cluster_data) {
46
+ return -ENOMEM;
47
+ }
48
+ }
49
+ if (!s->cluster_cache) {
50
+ s->cluster_cache = g_malloc(s->cluster_size);
51
+ }
84
+ }
52
+
85
+
53
BLKDBG_EVENT(bs->file, BLKDBG_READ_COMPRESSED);
86
+ new_ctx = iothread_get_aio_context(iothread);
54
ret = bdrv_read(bs->file, coffset >> 9, s->cluster_data,
87
+
55
nb_csectors);
88
+ ret = bdrv_try_set_aio_context(bs, new_ctx, errp);
56
diff --git a/block/qcow2.c b/block/qcow2.c
89
+ if (ret == 0) {
90
+ aio_context_release(ctx);
91
+ aio_context_acquire(new_ctx);
92
+ ctx = new_ctx;
93
+ } else if (fixed_iothread) {
94
+ goto fail;
95
+ }
96
+ }
97
+
98
/*
99
* Block exports are used for non-shared storage migration. Make sure
100
* that BDRV_O_INACTIVE is cleared and the image is ready for write
101
@@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
102
}
103
104
blk = blk_new(ctx, perm, BLK_PERM_ALL);
105
+
106
+ if (!fixed_iothread) {
107
+ blk_set_allow_aio_context_change(blk, true);
108
+ }
109
+
110
ret = blk_insert_bs(blk, bs, errp);
111
if (ret < 0) {
112
goto fail;
113
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
57
index XXXXXXX..XXXXXXX 100644
114
index XXXXXXX..XXXXXXX 100644
58
--- a/block/qcow2.c
115
--- a/block/export/vhost-user-blk-server.c
59
+++ b/block/qcow2.c
116
+++ b/block/export/vhost-user-blk-server.c
60
@@ -XXX,XX +XXX,XX @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
117
@@ -XXX,XX +XXX,XX @@ static const VuDevIface vu_blk_iface = {
61
goto fail;
118
static void blk_aio_attached(AioContext *ctx, void *opaque)
119
{
120
VuBlkExport *vexp = opaque;
121
+
122
+ vexp->export.ctx = ctx;
123
vhost_user_server_attach_aio_context(&vexp->vu_server, ctx);
124
}
125
126
static void blk_aio_detach(void *opaque)
127
{
128
VuBlkExport *vexp = opaque;
129
+
130
vhost_user_server_detach_aio_context(&vexp->vu_server);
131
+ vexp->export.ctx = NULL;
132
}
133
134
static void
135
@@ -XXX,XX +XXX,XX @@ static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
136
vu_blk_initialize_config(blk_bs(exp->blk), &vexp->blkcfg,
137
logical_block_size);
138
139
- blk_set_allow_aio_context_change(exp->blk, true);
140
blk_add_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach,
141
vexp);
142
143
diff --git a/nbd/server.c b/nbd/server.c
144
index XXXXXXX..XXXXXXX 100644
145
--- a/nbd/server.c
146
+++ b/nbd/server.c
147
@@ -XXX,XX +XXX,XX @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
148
return ret;
62
}
149
}
63
150
64
- s->cluster_cache = g_malloc(s->cluster_size);
151
- blk_set_allow_aio_context_change(blk, true);
65
- /* one more sector for decompressed data alignment */
66
- s->cluster_data = qemu_try_blockalign(bs->file->bs, QCOW_MAX_CRYPT_CLUSTERS
67
- * s->cluster_size + 512);
68
- if (s->cluster_data == NULL) {
69
- error_setg(errp, "Could not allocate temporary cluster buffer");
70
- ret = -ENOMEM;
71
- goto fail;
72
- }
73
-
152
-
74
s->cluster_cache_offset = -1;
153
QTAILQ_INIT(&exp->clients);
75
s->flags = flags;
154
exp->name = g_strdup(arg->name);
76
155
exp->description = g_strdup(arg->description);
77
@@ -XXX,XX +XXX,XX @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
78
if (s->refcount_block_cache) {
79
qcow2_cache_destroy(bs, s->refcount_block_cache);
80
}
81
- g_free(s->cluster_cache);
82
- qemu_vfree(s->cluster_data);
83
qcrypto_block_free(s->crypto);
84
qapi_free_QCryptoBlockOpenOptions(s->crypto_opts);
85
return ret;
86
--
156
--
87
2.13.5
157
2.26.2
88
158
89
diff view generated by jsdifflib
1
Add the scripts/ directory to sys.path so Python 2.6 will be able to
1
Allow the number of queues to be configured using --export
2
import argparse.
2
vhost-user-blk,num-queues=N. This setting should match the QEMU --device
3
vhost-user-blk-pci,num-queues=N setting but QEMU vhost-user-blk.c lowers
4
its own value if the vhost-user-blk backend offers fewer queues than
5
QEMU.
3
6
4
Cc: Fam Zheng <famz@redhat.com>
7
The vhost-user-blk-server.c code is already capable of multi-queue. All
8
virtqueue processing runs in the same AioContext. No new locking is
9
needed.
10
11
Add the num-queues=N option and set the VIRTIO_BLK_F_MQ feature bit.
12
Note that the feature bit only announces the presence of the num_queues
13
configuration space field. It does not promise that there is more than 1
14
virtqueue, so we can set it unconditionally.
15
16
I tested multi-queue by running a random read fio test with numjobs=4 on
17
an -smp 4 guest. After the benchmark finished the guest /proc/interrupts
18
file showed activity on all 4 virtio-blk MSI-X. The /sys/block/vda/mq/
19
directory shows that Linux blk-mq has 4 queues configured.
20
21
An automated test is included in the next commit.
22
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
23
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
Acked-by: John Snow <jsnow@redhat.com>
24
Acked-by: Markus Armbruster <armbru@redhat.com>
7
Acked-by: Fam Zheng <famz@redhat.com>
25
Message-id: 20201001144604.559733-2-stefanha@redhat.com
8
Message-id: 20170825155732.15665-3-stefanha@redhat.com
26
[Fixed accidental tab characters as suggested by Markus Armbruster
27
--Stefan]
9
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
28
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
---
29
---
11
tests/docker/docker.py | 4 +++-
30
qapi/block-export.json | 10 +++++++---
12
1 file changed, 3 insertions(+), 1 deletion(-)
31
block/export/vhost-user-blk-server.c | 24 ++++++++++++++++++------
32
2 files changed, 25 insertions(+), 9 deletions(-)
13
33
14
diff --git a/tests/docker/docker.py b/tests/docker/docker.py
34
diff --git a/qapi/block-export.json b/qapi/block-export.json
15
index XXXXXXX..XXXXXXX 100755
35
index XXXXXXX..XXXXXXX 100644
16
--- a/tests/docker/docker.py
36
--- a/qapi/block-export.json
17
+++ b/tests/docker/docker.py
37
+++ b/qapi/block-export.json
18
@@ -XXX,XX +XXX,XX @@
38
@@ -XXX,XX +XXX,XX @@
19
39
# SocketAddress types are supported. Passed fds must be UNIX domain
20
import os
40
# sockets.
21
import sys
41
# @logical-block-size: Logical block size in bytes. Defaults to 512 bytes.
22
+sys.path.append(os.path.join(os.path.dirname(__file__),
42
+# @num-queues: Number of request virtqueues. Must be greater than 0. Defaults
23
+ '..', '..', 'scripts'))
43
+# to 1.
24
+import argparse
44
#
25
import subprocess
45
# Since: 5.2
26
import json
46
##
27
import hashlib
47
{ 'struct': 'BlockExportOptionsVhostUserBlk',
28
import atexit
48
- 'data': { 'addr': 'SocketAddress', '*logical-block-size': 'size' } }
29
import uuid
49
+ 'data': { 'addr': 'SocketAddress',
30
-import argparse
50
+     '*logical-block-size': 'size',
31
import tempfile
51
+ '*num-queues': 'uint16'} }
32
import re
52
33
import signal
53
##
54
# @NbdServerAddOptions:
55
@@ -XXX,XX +XXX,XX @@
56
{ 'union': 'BlockExportOptions',
57
'base': { 'type': 'BlockExportType',
58
'id': 'str',
59
-     '*fixed-iothread': 'bool',
60
-     '*iothread': 'str',
61
+ '*fixed-iothread': 'bool',
62
+ '*iothread': 'str',
63
'node-name': 'str',
64
'*writable': 'bool',
65
'*writethrough': 'bool' },
66
diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user-blk-server.c
67
index XXXXXXX..XXXXXXX 100644
68
--- a/block/export/vhost-user-blk-server.c
69
+++ b/block/export/vhost-user-blk-server.c
70
@@ -XXX,XX +XXX,XX @@
71
#include "util/block-helpers.h"
72
73
enum {
74
- VHOST_USER_BLK_MAX_QUEUES = 1,
75
+ VHOST_USER_BLK_NUM_QUEUES_DEFAULT = 1,
76
};
77
struct virtio_blk_inhdr {
78
unsigned char status;
79
@@ -XXX,XX +XXX,XX @@ static uint64_t vu_blk_get_features(VuDev *dev)
80
1ull << VIRTIO_BLK_F_DISCARD |
81
1ull << VIRTIO_BLK_F_WRITE_ZEROES |
82
1ull << VIRTIO_BLK_F_CONFIG_WCE |
83
+ 1ull << VIRTIO_BLK_F_MQ |
84
1ull << VIRTIO_F_VERSION_1 |
85
1ull << VIRTIO_RING_F_INDIRECT_DESC |
86
1ull << VIRTIO_RING_F_EVENT_IDX |
87
@@ -XXX,XX +XXX,XX @@ static void blk_aio_detach(void *opaque)
88
89
static void
90
vu_blk_initialize_config(BlockDriverState *bs,
91
- struct virtio_blk_config *config, uint32_t blk_size)
92
+ struct virtio_blk_config *config,
93
+ uint32_t blk_size,
94
+ uint16_t num_queues)
95
{
96
config->capacity = bdrv_getlength(bs) >> BDRV_SECTOR_BITS;
97
config->blk_size = blk_size;
98
@@ -XXX,XX +XXX,XX @@ vu_blk_initialize_config(BlockDriverState *bs,
99
config->seg_max = 128 - 2;
100
config->min_io_size = 1;
101
config->opt_io_size = 1;
102
- config->num_queues = VHOST_USER_BLK_MAX_QUEUES;
103
+ config->num_queues = num_queues;
104
config->max_discard_sectors = 32768;
105
config->max_discard_seg = 1;
106
config->discard_sector_alignment = config->blk_size >> 9;
107
@@ -XXX,XX +XXX,XX @@ static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
108
BlockExportOptionsVhostUserBlk *vu_opts = &opts->u.vhost_user_blk;
109
Error *local_err = NULL;
110
uint64_t logical_block_size;
111
+ uint16_t num_queues = VHOST_USER_BLK_NUM_QUEUES_DEFAULT;
112
113
vexp->writable = opts->writable;
114
vexp->blkcfg.wce = 0;
115
@@ -XXX,XX +XXX,XX @@ static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
116
}
117
vexp->blk_size = logical_block_size;
118
blk_set_guest_block_size(exp->blk, logical_block_size);
119
+
120
+ if (vu_opts->has_num_queues) {
121
+ num_queues = vu_opts->num_queues;
122
+ }
123
+ if (num_queues == 0) {
124
+ error_setg(errp, "num-queues must be greater than 0");
125
+ return -EINVAL;
126
+ }
127
+
128
vu_blk_initialize_config(blk_bs(exp->blk), &vexp->blkcfg,
129
- logical_block_size);
130
+ logical_block_size, num_queues);
131
132
blk_add_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach,
133
vexp);
134
135
if (!vhost_user_server_start(&vexp->vu_server, vu_opts->addr, exp->ctx,
136
- VHOST_USER_BLK_MAX_QUEUES, &vu_blk_iface,
137
- errp)) {
138
+ num_queues, &vu_blk_iface, errp)) {
139
blk_remove_aio_context_notifier(exp->blk, blk_aio_attached,
140
blk_aio_detach, vexp);
141
return -EADDRNOTAVAIL;
34
--
142
--
35
2.13.5
143
2.26.2
36
144
37
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
2
2
Message-id: 20201001144604.559733-3-stefanha@redhat.com
3
Signed-off-by: Alberto Garcia <berto@igalia.com>
4
Message-id: a57dd6274e1b6dc9c28769fec4c7ea543be5c5e3.1503580370.git.berto@igalia.com
5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
3
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
6
---
4
---
7
tests/test-throttle.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++
5
tests/qtest/vhost-user-blk-test.c | 81 +++++++++++++++++++++++++++++--
8
1 file changed, 77 insertions(+)
6
1 file changed, 76 insertions(+), 5 deletions(-)
9
7
10
diff --git a/tests/test-throttle.c b/tests/test-throttle.c
8
diff --git a/tests/qtest/vhost-user-blk-test.c b/tests/qtest/vhost-user-blk-test.c
11
index XXXXXXX..XXXXXXX 100644
9
index XXXXXXX..XXXXXXX 100644
12
--- a/tests/test-throttle.c
10
--- a/tests/qtest/vhost-user-blk-test.c
13
+++ b/tests/test-throttle.c
11
+++ b/tests/qtest/vhost-user-blk-test.c
14
@@ -XXX,XX +XXX,XX @@ static void test_is_valid(void)
12
@@ -XXX,XX +XXX,XX @@ static void pci_hotplug(void *obj, void *data, QGuestAllocator *t_alloc)
15
test_is_valid_for_value(1, true);
13
qpci_unplug_acpi_device_test(qts, "drv1", PCI_SLOT_HP);
16
}
14
}
17
15
18
+static void test_ranges(void)
16
+static void multiqueue(void *obj, void *data, QGuestAllocator *t_alloc)
19
+{
17
+{
20
+ int i;
18
+ QVirtioPCIDevice *pdev1 = obj;
19
+ QVirtioDevice *dev1 = &pdev1->vdev;
20
+ QVirtioPCIDevice *pdev8;
21
+ QVirtioDevice *dev8;
22
+ QTestState *qts = pdev1->pdev->bus->qts;
23
+ uint64_t features;
24
+ uint16_t num_queues;
21
+
25
+
22
+ for (i = 0; i < BUCKETS_COUNT; i++) {
26
+ /*
23
+ LeakyBucket *b = &cfg.buckets[i];
27
+ * The primary device has 1 queue and VIRTIO_BLK_F_MQ is not enabled. The
24
+ throttle_config_init(&cfg);
28
+ * VIRTIO specification allows VIRTIO_BLK_F_MQ to be enabled when there is
29
+ * only 1 virtqueue, but --device vhost-user-blk-pci doesn't do this (which
30
+ * is also spec-compliant).
31
+ */
32
+ features = qvirtio_get_features(dev1);
33
+ g_assert_cmpint(features & (1u << VIRTIO_BLK_F_MQ), ==, 0);
34
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
35
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
36
+ (1u << VIRTIO_F_NOTIFY_ON_EMPTY) |
37
+ (1u << VIRTIO_BLK_F_SCSI));
38
+ qvirtio_set_features(dev1, features);
25
+
39
+
26
+ /* avg = 0 means throttling is disabled, but the config is valid */
40
+ /* Hotplug a secondary device with 8 queues */
27
+ b->avg = 0;
41
+ qtest_qmp_device_add(qts, "vhost-user-blk-pci", "drv1",
28
+ g_assert(throttle_is_valid(&cfg, NULL));
42
+ "{'addr': %s, 'chardev': 'char2', 'num-queues': 8}",
29
+ g_assert(!throttle_enabled(&cfg));
43
+ stringify(PCI_SLOT_HP) ".0");
30
+
44
+
31
+ /* These are valid configurations (values <= THROTTLE_VALUE_MAX) */
45
+ pdev8 = virtio_pci_new(pdev1->pdev->bus,
32
+ b->avg = 1;
46
+ &(QPCIAddress) {
33
+ g_assert(throttle_is_valid(&cfg, NULL));
47
+ .devfn = QPCI_DEVFN(PCI_SLOT_HP, 0)
48
+ });
49
+ g_assert_nonnull(pdev8);
50
+ g_assert_cmpint(pdev8->vdev.device_type, ==, VIRTIO_ID_BLOCK);
34
+
51
+
35
+ b->avg = THROTTLE_VALUE_MAX;
52
+ qos_object_start_hw(&pdev8->obj);
36
+ g_assert(throttle_is_valid(&cfg, NULL));
37
+
53
+
38
+ b->avg = THROTTLE_VALUE_MAX;
54
+ dev8 = &pdev8->vdev;
39
+ b->max = THROTTLE_VALUE_MAX;
55
+ features = qvirtio_get_features(dev8);
40
+ g_assert(throttle_is_valid(&cfg, NULL));
56
+ g_assert_cmpint(features & (1u << VIRTIO_BLK_F_MQ),
57
+ ==,
58
+ (1u << VIRTIO_BLK_F_MQ));
59
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
60
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
61
+ (1u << VIRTIO_F_NOTIFY_ON_EMPTY) |
62
+ (1u << VIRTIO_BLK_F_SCSI) |
63
+ (1u << VIRTIO_BLK_F_MQ));
64
+ qvirtio_set_features(dev8, features);
41
+
65
+
42
+ /* Values over THROTTLE_VALUE_MAX are not allowed */
66
+ num_queues = qvirtio_config_readw(dev8,
43
+ b->avg = THROTTLE_VALUE_MAX + 1;
67
+ offsetof(struct virtio_blk_config, num_queues));
44
+ g_assert(!throttle_is_valid(&cfg, NULL));
68
+ g_assert_cmpint(num_queues, ==, 8);
45
+
69
+
46
+ b->avg = THROTTLE_VALUE_MAX;
70
+ qvirtio_pci_device_disable(pdev8);
47
+ b->max = THROTTLE_VALUE_MAX + 1;
71
+ qos_object_destroy(&pdev8->obj);
48
+ g_assert(!throttle_is_valid(&cfg, NULL));
49
+
72
+
50
+ /* burst_length must be between 1 and THROTTLE_VALUE_MAX */
73
+ /* unplug secondary disk */
51
+ b->avg = 1;
74
+ qpci_unplug_acpi_device_test(qts, "drv1", PCI_SLOT_HP);
52
+ b->max = 1;
53
+ b->burst_length = 0;
54
+ g_assert(!throttle_is_valid(&cfg, NULL));
55
+
56
+ b->avg = 1;
57
+ b->max = 1;
58
+ b->burst_length = 1;
59
+ g_assert(throttle_is_valid(&cfg, NULL));
60
+
61
+ b->avg = 1;
62
+ b->max = 1;
63
+ b->burst_length = THROTTLE_VALUE_MAX;
64
+ g_assert(throttle_is_valid(&cfg, NULL));
65
+
66
+ b->avg = 1;
67
+ b->max = 1;
68
+ b->burst_length = THROTTLE_VALUE_MAX + 1;
69
+ g_assert(!throttle_is_valid(&cfg, NULL));
70
+
71
+ /* burst_length * max cannot exceed THROTTLE_VALUE_MAX */
72
+ b->avg = 1;
73
+ b->max = 2;
74
+ b->burst_length = THROTTLE_VALUE_MAX / 2;
75
+ g_assert(throttle_is_valid(&cfg, NULL));
76
+
77
+ b->avg = 1;
78
+ b->max = 3;
79
+ b->burst_length = THROTTLE_VALUE_MAX / 2;
80
+ g_assert(!throttle_is_valid(&cfg, NULL));
81
+
82
+ b->avg = 1;
83
+ b->max = THROTTLE_VALUE_MAX;
84
+ b->burst_length = 1;
85
+ g_assert(throttle_is_valid(&cfg, NULL));
86
+
87
+ b->avg = 1;
88
+ b->max = THROTTLE_VALUE_MAX;
89
+ b->burst_length = 2;
90
+ g_assert(!throttle_is_valid(&cfg, NULL));
91
+ }
92
+}
75
+}
93
+
76
+
94
static void test_max_is_missing_limit(void)
77
/*
78
* Check that setting the vring addr on a non-existent virtqueue does
79
* not crash.
80
@@ -XXX,XX +XXX,XX @@ static void quit_storage_daemon(void *qmp_test_state)
81
g_free(qmp_test_state);
82
}
83
84
-static char *start_vhost_user_blk(GString *cmd_line, int vus_instances)
85
+static char *start_vhost_user_blk(GString *cmd_line, int vus_instances,
86
+ int num_queues)
95
{
87
{
96
int i;
88
const char *vhost_user_blk_bin = qtest_qemu_storage_daemon_binary();
97
@@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv)
89
int fd, qmp_fd, i;
98
g_test_add_func("/throttle/config/enabled", test_enabled);
90
@@ -XXX,XX +XXX,XX @@ static char *start_vhost_user_blk(GString *cmd_line, int vus_instances)
99
g_test_add_func("/throttle/config/conflicting", test_conflicting_config);
91
g_string_append_printf(storage_daemon_command,
100
g_test_add_func("/throttle/config/is_valid", test_is_valid);
92
"--blockdev driver=file,node-name=disk%d,filename=%s "
101
+ g_test_add_func("/throttle/config/ranges", test_ranges);
93
"--export type=vhost-user-blk,id=disk%d,addr.type=unix,addr.path=%s,"
102
g_test_add_func("/throttle/config/max", test_max_is_missing_limit);
94
- "node-name=disk%i,writable=on ",
103
g_test_add_func("/throttle/config/iops_size",
95
- i, img_path, i, sock_path, i);
104
test_iops_size_is_missing_limit);
96
+ "node-name=disk%i,writable=on,num-queues=%d ",
97
+ i, img_path, i, sock_path, i, num_queues);
98
99
g_string_append_printf(cmd_line, "-chardev socket,id=char%d,path=%s ",
100
i + 1, sock_path);
101
@@ -XXX,XX +XXX,XX @@ static char *start_vhost_user_blk(GString *cmd_line, int vus_instances)
102
103
static void *vhost_user_blk_test_setup(GString *cmd_line, void *arg)
104
{
105
- start_vhost_user_blk(cmd_line, 1);
106
+ start_vhost_user_blk(cmd_line, 1, 1);
107
return arg;
108
}
109
110
@@ -XXX,XX +XXX,XX @@ static void *vhost_user_blk_test_setup(GString *cmd_line, void *arg)
111
static void *vhost_user_blk_hotplug_test_setup(GString *cmd_line, void *arg)
112
{
113
/* "-chardev socket,id=char2" is used for pci_hotplug*/
114
- start_vhost_user_blk(cmd_line, 2);
115
+ start_vhost_user_blk(cmd_line, 2, 1);
116
+ return arg;
117
+}
118
+
119
+static void *vhost_user_blk_multiqueue_test_setup(GString *cmd_line, void *arg)
120
+{
121
+ start_vhost_user_blk(cmd_line, 2, 8);
122
return arg;
123
}
124
125
@@ -XXX,XX +XXX,XX @@ static void register_vhost_user_blk_test(void)
126
127
opts.before = vhost_user_blk_hotplug_test_setup;
128
qos_add_test("hotplug", "vhost-user-blk-pci", pci_hotplug, &opts);
129
+
130
+ opts.before = vhost_user_blk_multiqueue_test_setup;
131
+ qos_add_test("multiqueue", "vhost-user-blk-pci", multiqueue, &opts);
132
}
133
134
libqos_init(register_vhost_user_blk_test);
105
--
135
--
106
2.13.5
136
2.26.2
107
137
108
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
2
2
3
There's a few cases which we're passing an Error pointer to a function
3
bdrv_co_block_status_above has several design problems with handling
4
only to discard it immediately afterwards without checking it. In
4
short backing files:
5
these cases we can simply remove the variable and pass NULL instead.
6
5
7
Signed-off-by: Alberto Garcia <berto@igalia.com>
6
1. With want_zeros=true, it may return ret with BDRV_BLOCK_ZERO but
8
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
7
without BDRV_BLOCK_ALLOCATED flag, when actually short backing file
8
which produces these after-EOF zeros is inside requested backing
9
sequence.
10
11
2. With want_zero=false, it may return pnum=0 prior to actual EOF,
12
because of EOF of short backing file.
13
14
Fix these things, making logic about short backing files clearer.
15
16
With fixed bdrv_block_status_above we also have to improve is_zero in
17
qcow2 code, otherwise iotest 154 will fail, because with this patch we
18
stop to merge zeros of different types (produced by fully unallocated
19
in the whole backing chain regions vs produced by short backing files).
20
21
Note also, that this patch leaves for another day the general problem
22
around block-status: misuse of BDRV_BLOCK_ALLOCATED as is-fs-allocated
23
vs go-to-backing.
24
25
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
26
Reviewed-by: Alberto Garcia <berto@igalia.com>
9
Reviewed-by: Eric Blake <eblake@redhat.com>
27
Reviewed-by: Eric Blake <eblake@redhat.com>
10
Message-id: 20170829120836.16091-1-berto@igalia.com
28
Message-id: 20200924194003.22080-2-vsementsov@virtuozzo.com
29
[Fix s/comes/come/ as suggested by Eric Blake
30
--Stefan]
11
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
31
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
12
---
32
---
13
block/qcow.c | 12 +++---------
33
block/io.c | 68 ++++++++++++++++++++++++++++++++++++++++-----------
14
block/qcow2.c | 8 ++------
34
block/qcow2.c | 16 ++++++++++--
15
dump.c | 4 +---
35
2 files changed, 68 insertions(+), 16 deletions(-)
16
3 files changed, 6 insertions(+), 18 deletions(-)
17
36
18
diff --git a/block/qcow.c b/block/qcow.c
37
diff --git a/block/io.c b/block/io.c
19
index XXXXXXX..XXXXXXX 100644
38
index XXXXXXX..XXXXXXX 100644
20
--- a/block/qcow.c
39
--- a/block/io.c
21
+++ b/block/qcow.c
40
+++ b/block/io.c
22
@@ -XXX,XX +XXX,XX @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
41
@@ -XXX,XX +XXX,XX @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
23
start_sect = (offset & ~(s->cluster_size - 1)) >> 9;
42
int64_t *map,
24
for(i = 0; i < s->cluster_sectors; i++) {
43
BlockDriverState **file)
25
if (i < n_start || i >= n_end) {
44
{
26
- Error *err = NULL;
45
+ int ret;
27
memset(s->cluster_data, 0x00, 512);
46
BlockDriverState *p;
28
if (qcrypto_block_encrypt(s->crypto, start_sect + i,
47
- int ret = 0;
29
s->cluster_data,
48
- bool first = true;
30
BDRV_SECTOR_SIZE,
49
+ int64_t eof = 0;
31
- &err) < 0) {
50
32
- error_free(err);
51
assert(bs != base);
33
+ NULL) < 0) {
52
- for (p = bs; p != base; p = bdrv_filter_or_cow_bs(p)) {
34
errno = EIO;
53
+
35
return -1;
54
+ ret = bdrv_co_block_status(bs, want_zero, offset, bytes, pnum, map, file);
36
}
55
+ if (ret < 0 || *pnum == 0 || ret & BDRV_BLOCK_ALLOCATED) {
37
@@ -XXX,XX +XXX,XX @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
56
+ return ret;
38
QEMUIOVector hd_qiov;
57
+ }
39
uint8_t *buf;
58
+
40
void *orig_buf;
59
+ if (ret & BDRV_BLOCK_EOF) {
41
- Error *err = NULL;
60
+ eof = offset + *pnum;
42
61
+ }
43
if (qiov->niov > 1) {
62
+
44
buf = orig_buf = qemu_try_blockalign(bs, qiov->size);
63
+ assert(*pnum <= bytes);
45
@@ -XXX,XX +XXX,XX @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
64
+ bytes = *pnum;
46
if (bs->encrypted) {
65
+
47
assert(s->crypto);
66
+ for (p = bdrv_filter_or_cow_bs(bs); p != base;
48
if (qcrypto_block_decrypt(s->crypto, sector_num, buf,
67
+ p = bdrv_filter_or_cow_bs(p))
49
- n * BDRV_SECTOR_SIZE, &err) < 0) {
68
+ {
50
+ n * BDRV_SECTOR_SIZE, NULL) < 0) {
69
ret = bdrv_co_block_status(p, want_zero, offset, bytes, pnum, map,
51
goto fail;
70
file);
52
}
71
if (ret < 0) {
53
}
72
- break;
54
@@ -XXX,XX +XXX,XX @@ done:
73
+ return ret;
55
return ret;
74
}
56
75
- if (ret & BDRV_BLOCK_ZERO && ret & BDRV_BLOCK_EOF && !first) {
57
fail:
76
+ if (*pnum == 0) {
58
- error_free(err);
77
/*
59
ret = -EIO;
78
- * Reading beyond the end of the file continues to read
60
goto done;
79
- * zeroes, but we can only widen the result to the
61
}
80
- * unallocated length we learned from an earlier
62
@@ -XXX,XX +XXX,XX @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
81
- * iteration.
82
+ * The top layer deferred to this layer, and because this layer is
83
+ * short, any zeroes that we synthesize beyond EOF behave as if they
84
+ * were allocated at this layer.
85
+ *
86
+ * We don't include BDRV_BLOCK_EOF into ret, as upper layer may be
87
+ * larger. We'll add BDRV_BLOCK_EOF if needed at function end, see
88
+ * below.
89
*/
90
+ assert(ret & BDRV_BLOCK_EOF);
91
*pnum = bytes;
92
+ if (file) {
93
+ *file = p;
94
+ }
95
+ ret = BDRV_BLOCK_ZERO | BDRV_BLOCK_ALLOCATED;
96
+ break;
97
}
98
- if (ret & (BDRV_BLOCK_ZERO | BDRV_BLOCK_DATA)) {
99
+ if (ret & BDRV_BLOCK_ALLOCATED) {
100
+ /*
101
+ * We've found the node and the status, we must break.
102
+ *
103
+ * Drop BDRV_BLOCK_EOF, as it's not for upper layer, which may be
104
+ * larger. We'll add BDRV_BLOCK_EOF if needed at function end, see
105
+ * below.
106
+ */
107
+ ret &= ~BDRV_BLOCK_EOF;
63
break;
108
break;
64
}
109
}
65
if (bs->encrypted) {
110
- /* [offset, pnum] unallocated on this layer, which could be only
66
- Error *err = NULL;
111
- * the first part of [offset, bytes]. */
67
assert(s->crypto);
112
- bytes = MIN(bytes, *pnum);
68
if (qcrypto_block_encrypt(s->crypto, sector_num, buf,
113
- first = false;
69
- n * BDRV_SECTOR_SIZE, &err) < 0) {
114
+
70
- error_free(err);
115
+ /*
71
+ n * BDRV_SECTOR_SIZE, NULL) < 0) {
116
+ * OK, [offset, offset + *pnum) region is unallocated on this layer,
72
ret = -EIO;
117
+ * let's continue the diving.
73
break;
118
+ */
74
}
119
+ assert(*pnum <= bytes);
120
+ bytes = *pnum;
121
+ }
122
+
123
+ if (offset + *pnum == eof) {
124
+ ret |= BDRV_BLOCK_EOF;
125
}
126
+
127
return ret;
128
}
129
75
diff --git a/block/qcow2.c b/block/qcow2.c
130
diff --git a/block/qcow2.c b/block/qcow2.c
76
index XXXXXXX..XXXXXXX 100644
131
index XXXXXXX..XXXXXXX 100644
77
--- a/block/qcow2.c
132
--- a/block/qcow2.c
78
+++ b/block/qcow2.c
133
+++ b/block/qcow2.c
79
@@ -XXX,XX +XXX,XX @@ static coroutine_fn int qcow2_co_preadv(BlockDriverState *bs, uint64_t offset,
134
@@ -XXX,XX +XXX,XX @@ static bool is_zero(BlockDriverState *bs, int64_t offset, int64_t bytes)
80
assert(s->crypto);
135
if (!bytes) {
81
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
136
return true;
82
assert((cur_bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
137
}
83
- Error *err = NULL;
138
- res = bdrv_block_status_above(bs, NULL, offset, bytes, &nr, NULL, NULL);
84
if (qcrypto_block_decrypt(s->crypto,
139
- return res >= 0 && (res & BDRV_BLOCK_ZERO) && nr == bytes;
85
(s->crypt_physical_offset ?
140
+
86
cluster_offset + offset_in_cluster :
141
+ /*
87
offset) >> BDRV_SECTOR_BITS,
142
+ * bdrv_block_status_above doesn't merge different types of zeros, for
88
cluster_data,
143
+ * example, zeros which come from the region which is unallocated in
89
cur_bytes,
144
+ * the whole backing chain, and zeros which come because of a short
90
- &err) < 0) {
145
+ * backing file. So, we need a loop.
91
- error_free(err);
146
+ */
92
+ NULL) < 0) {
147
+ do {
93
ret = -EIO;
148
+ res = bdrv_block_status_above(bs, NULL, offset, bytes, &nr, NULL, NULL);
94
goto fail;
149
+ offset += nr;
95
}
150
+ bytes -= nr;
96
@@ -XXX,XX +XXX,XX @@ static coroutine_fn int qcow2_co_pwritev(BlockDriverState *bs, uint64_t offset,
151
+ } while (res >= 0 && (res & BDRV_BLOCK_ZERO) && nr && bytes);
97
qemu_iovec_concat(&hd_qiov, qiov, bytes_done, cur_bytes);
152
+
98
153
+ return res >= 0 && (res & BDRV_BLOCK_ZERO) && bytes == 0;
99
if (bs->encrypted) {
100
- Error *err = NULL;
101
assert(s->crypto);
102
if (!cluster_data) {
103
cluster_data = qemu_try_blockalign(bs->file->bs,
104
@@ -XXX,XX +XXX,XX @@ static coroutine_fn int qcow2_co_pwritev(BlockDriverState *bs, uint64_t offset,
105
cluster_offset + offset_in_cluster :
106
offset) >> BDRV_SECTOR_BITS,
107
cluster_data,
108
- cur_bytes, &err) < 0) {
109
- error_free(err);
110
+ cur_bytes, NULL) < 0) {
111
ret = -EIO;
112
goto fail;
113
}
114
diff --git a/dump.c b/dump.c
115
index XXXXXXX..XXXXXXX 100644
116
--- a/dump.c
117
+++ b/dump.c
118
@@ -XXX,XX +XXX,XX @@ static void dump_process(DumpState *s, Error **errp)
119
120
static void *dump_thread(void *data)
121
{
122
- Error *err = NULL;
123
DumpState *s = (DumpState *)data;
124
- dump_process(s, &err);
125
- error_free(err);
126
+ dump_process(s, NULL);
127
return NULL;
128
}
154
}
129
155
156
static coroutine_fn int qcow2_co_pwrite_zeroes(BlockDriverState *bs,
130
--
157
--
131
2.13.5
158
2.26.2
132
159
133
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
2
2
3
LeakyBucket.burst_length is defined as an unsigned integer but the
3
In order to reuse bdrv_common_block_status_above in
4
code never checks for overflows and it only makes sure that the value
4
bdrv_is_allocated_above, let's support include_base parameter.
5
is not 0.
6
5
7
In practice this means that the user can set something like
6
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
8
throttling.iops-total-max-length=4294967300 despite being larger than
7
Reviewed-by: Alberto Garcia <berto@igalia.com>
9
UINT_MAX and the final value after casting to unsigned int will be 4.
8
Reviewed-by: Eric Blake <eblake@redhat.com>
10
9
Message-id: 20200924194003.22080-3-vsementsov@virtuozzo.com
11
This patch changes the data type to uint64_t. This does not increase
12
the storage size of LeakyBucket, and allows us to assign the value
13
directly from qemu_opt_get_number() or BlockIOThrottle and then do the
14
checks directly in throttle_is_valid().
15
16
The value of burst_length does not have a specific upper limit,
17
but since the bucket size is defined by max * burst_length we have
18
to prevent overflows. Instead of going for UINT64_MAX or something
19
similar this patch reuses THROTTLE_VALUE_MAX, which allows I/O bursts
20
of 1 GiB/s for 10 days in a row.
21
22
Signed-off-by: Alberto Garcia <berto@igalia.com>
23
Message-id: 1b2e3049803f71cafb2e1fa1be4fb47147a0d398.1503580370.git.berto@igalia.com
24
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
25
---
11
---
26
include/qemu/throttle.h | 2 +-
12
block/coroutines.h | 2 ++
27
util/throttle.c | 5 +++++
13
block/io.c | 21 ++++++++++++++-------
28
2 files changed, 6 insertions(+), 1 deletion(-)
14
2 files changed, 16 insertions(+), 7 deletions(-)
29
15
30
diff --git a/include/qemu/throttle.h b/include/qemu/throttle.h
16
diff --git a/block/coroutines.h b/block/coroutines.h
31
index XXXXXXX..XXXXXXX 100644
17
index XXXXXXX..XXXXXXX 100644
32
--- a/include/qemu/throttle.h
18
--- a/block/coroutines.h
33
+++ b/include/qemu/throttle.h
19
+++ b/block/coroutines.h
34
@@ -XXX,XX +XXX,XX @@ typedef struct LeakyBucket {
20
@@ -XXX,XX +XXX,XX @@ bdrv_pwritev(BdrvChild *child, int64_t offset, unsigned int bytes,
35
uint64_t max; /* leaky bucket max burst in units */
21
int coroutine_fn
36
double level; /* bucket level in units */
22
bdrv_co_common_block_status_above(BlockDriverState *bs,
37
double burst_level; /* bucket level in units (for computing bursts) */
23
BlockDriverState *base,
38
- unsigned burst_length; /* max length of the burst period, in seconds */
24
+ bool include_base,
39
+ uint64_t burst_length; /* max length of the burst period, in seconds */
25
bool want_zero,
40
} LeakyBucket;
26
int64_t offset,
41
27
int64_t bytes,
42
/* The following structure is used to configure a ThrottleState
28
@@ -XXX,XX +XXX,XX @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
43
diff --git a/util/throttle.c b/util/throttle.c
29
int generated_co_wrapper
30
bdrv_common_block_status_above(BlockDriverState *bs,
31
BlockDriverState *base,
32
+ bool include_base,
33
bool want_zero,
34
int64_t offset,
35
int64_t bytes,
36
diff --git a/block/io.c b/block/io.c
44
index XXXXXXX..XXXXXXX 100644
37
index XXXXXXX..XXXXXXX 100644
45
--- a/util/throttle.c
38
--- a/block/io.c
46
+++ b/util/throttle.c
39
+++ b/block/io.c
47
@@ -XXX,XX +XXX,XX @@ bool throttle_is_valid(ThrottleConfig *cfg, Error **errp)
40
@@ -XXX,XX +XXX,XX @@ early_out:
48
return false;
41
int coroutine_fn
42
bdrv_co_common_block_status_above(BlockDriverState *bs,
43
BlockDriverState *base,
44
+ bool include_base,
45
bool want_zero,
46
int64_t offset,
47
int64_t bytes,
48
@@ -XXX,XX +XXX,XX @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
49
BlockDriverState *p;
50
int64_t eof = 0;
51
52
- assert(bs != base);
53
+ assert(include_base || bs != base);
54
+ assert(!include_base || base); /* Can't include NULL base */
55
56
ret = bdrv_co_block_status(bs, want_zero, offset, bytes, pnum, map, file);
57
- if (ret < 0 || *pnum == 0 || ret & BDRV_BLOCK_ALLOCATED) {
58
+ if (ret < 0 || *pnum == 0 || ret & BDRV_BLOCK_ALLOCATED || bs == base) {
59
return ret;
60
}
61
62
@@ -XXX,XX +XXX,XX @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
63
assert(*pnum <= bytes);
64
bytes = *pnum;
65
66
- for (p = bdrv_filter_or_cow_bs(bs); p != base;
67
+ for (p = bdrv_filter_or_cow_bs(bs); include_base || p != base;
68
p = bdrv_filter_or_cow_bs(p))
69
{
70
ret = bdrv_co_block_status(p, want_zero, offset, bytes, pnum, map,
71
@@ -XXX,XX +XXX,XX @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
72
break;
49
}
73
}
50
74
51
+ if (bkt->max && bkt->burst_length > THROTTLE_VALUE_MAX / bkt->max) {
75
+ if (p == base) {
52
+ error_setg(errp, "burst length too high for this burst rate");
76
+ assert(include_base);
53
+ return false;
77
+ break;
54
+ }
78
+ }
55
+
79
+
56
if (bkt->max && !bkt->avg) {
80
/*
57
error_setg(errp, "bps_max/iops_max require corresponding"
81
* OK, [offset, offset + *pnum) region is unallocated on this layer,
58
" bps/iops values");
82
* let's continue the diving.
83
@@ -XXX,XX +XXX,XX @@ int bdrv_block_status_above(BlockDriverState *bs, BlockDriverState *base,
84
int64_t offset, int64_t bytes, int64_t *pnum,
85
int64_t *map, BlockDriverState **file)
86
{
87
- return bdrv_common_block_status_above(bs, base, true, offset, bytes,
88
+ return bdrv_common_block_status_above(bs, base, false, true, offset, bytes,
89
pnum, map, file);
90
}
91
92
@@ -XXX,XX +XXX,XX @@ int coroutine_fn bdrv_is_allocated(BlockDriverState *bs, int64_t offset,
93
int ret;
94
int64_t dummy;
95
96
- ret = bdrv_common_block_status_above(bs, bdrv_filter_or_cow_bs(bs), false,
97
- offset, bytes, pnum ? pnum : &dummy,
98
- NULL, NULL);
99
+ ret = bdrv_common_block_status_above(bs, bs, true, false, offset,
100
+ bytes, pnum ? pnum : &dummy, NULL,
101
+ NULL);
102
if (ret < 0) {
103
return ret;
104
}
59
--
105
--
60
2.13.5
106
2.26.2
61
107
62
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
2
2
3
The way the throttling algorithm works is that requests start being
3
We are going to reuse bdrv_common_block_status_above in
4
throttled once the bucket level exceeds the burst limit. When we get
4
bdrv_is_allocated_above. bdrv_is_allocated_above may be called with
5
there the bucket leaks at the level set by the user (bkt->avg), and
5
include_base == false and still bs == base (for ex. from img_rebase()).
6
that leak rate is what prevents guest I/O from exceeding the desired
7
limit.
8
6
9
If we don't allow bursts (i.e. bkt->max == 0) then we can start
7
So, support this corner case.
10
throttling requests immediately. The problem with keeping the
11
threshold at 0 is that it only allows one request at a time, and as
12
soon as there's a bit of I/O from the guest every other request will
13
be throttled and performance will suffer considerably. That can even
14
make the guest unable to reach the throttle limit if that limit is
15
high enough, and that happens regardless of the block scheduler used
16
by the guest.
17
8
18
Increasing that threshold gives flexibility to the guest, allowing it
9
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
19
to perform short bursts of I/O before being throttled. Increasing the
10
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
20
threshold too much does not make a difference in the long run (because
11
Reviewed-by: Eric Blake <eblake@redhat.com>
21
it's the leak rate what defines the actual throughput) but it does
12
Reviewed-by: Alberto Garcia <berto@igalia.com>
22
allow the guest to perform longer initial bursts and exceed the
13
Message-id: 20200924194003.22080-4-vsementsov@virtuozzo.com
23
throttle limit for a short while.
24
25
A burst value of bkt->avg / 10 allows the guest to perform 100ms'
26
worth of I/O at the target rate without being throttled.
27
28
Signed-off-by: Alberto Garcia <berto@igalia.com>
29
Message-id: 31aae6645f0d1fbf3860fb2b528b757236f0c0a7.1503580370.git.berto@igalia.com
30
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
14
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
31
---
15
---
32
util/throttle.c | 11 +++--------
16
block/io.c | 6 +++++-
33
1 file changed, 3 insertions(+), 8 deletions(-)
17
1 file changed, 5 insertions(+), 1 deletion(-)
34
18
35
diff --git a/util/throttle.c b/util/throttle.c
19
diff --git a/block/io.c b/block/io.c
36
index XXXXXXX..XXXXXXX 100644
20
index XXXXXXX..XXXXXXX 100644
37
--- a/util/throttle.c
21
--- a/block/io.c
38
+++ b/util/throttle.c
22
+++ b/block/io.c
39
@@ -XXX,XX +XXX,XX @@ static void throttle_fix_bucket(LeakyBucket *bkt)
23
@@ -XXX,XX +XXX,XX @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
40
/* zero bucket level */
24
BlockDriverState *p;
41
bkt->level = bkt->burst_level = 0;
25
int64_t eof = 0;
42
26
43
- /* The following is done to cope with the Linux CFQ block scheduler
27
- assert(include_base || bs != base);
44
- * which regroup reads and writes by block of 100ms in the guest.
28
assert(!include_base || base); /* Can't include NULL base */
45
- * When they are two process one making reads and one making writes cfq
29
46
- * make a pattern looking like the following:
30
+ if (!include_base && bs == base) {
47
- * WWWWWWWWWWWRRRRRRRRRRRRRRWWWWWWWWWWWWWwRRRRRRRRRRRRRRRRR
31
+ *pnum = bytes;
48
- * Having a max burst value of 100ms of the average will help smooth the
32
+ return 0;
49
- * throttling
33
+ }
50
- */
34
+
51
+ /* If bkt->max is 0 we still want to allow short bursts of I/O
35
ret = bdrv_co_block_status(bs, want_zero, offset, bytes, pnum, map, file);
52
+ * from the guest, otherwise every other request will be throttled
36
if (ret < 0 || *pnum == 0 || ret & BDRV_BLOCK_ALLOCATED || bs == base) {
53
+ * and performance will suffer considerably. */
37
return ret;
54
min = bkt->avg / 10;
55
if (bkt->avg && !bkt->max) {
56
bkt->max = min;
57
--
38
--
58
2.13.5
39
2.26.2
59
40
60
diff view generated by jsdifflib
1
From: Alberto Garcia <berto@igalia.com>
1
From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
2
2
3
The throttling code can change internally the value of bkt->max if it
3
bdrv_is_allocated_above wrongly handles short backing files: it reports
4
hasn't been set by the user. The problem with this is that if we want
4
after-EOF space as UNALLOCATED which is wrong, as on read the data is
5
to retrieve the original value we have to undo this change first. This
5
generated on the level of short backing file (if all overlays have
6
is ugly and unnecessary: this patch removes the throttle_fix_bucket()
6
unallocated areas at that place).
7
and throttle_unfix_bucket() functions completely and moves the logic
8
to throttle_compute_wait().
9
7
10
Signed-off-by: Alberto Garcia <berto@igalia.com>
8
Reusing bdrv_common_block_status_above fixes the issue and unifies code
11
Reviewed-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
9
path.
12
Message-id: 5b0b9e1ac6eb208d709eddc7b09e7669a523bff3.1503580370.git.berto@igalia.com
10
11
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
12
Reviewed-by: Eric Blake <eblake@redhat.com>
13
Reviewed-by: Alberto Garcia <berto@igalia.com>
14
Message-id: 20200924194003.22080-5-vsementsov@virtuozzo.com
15
[Fix s/has/have/ as suggested by Eric Blake. Fix s/area/areas/.
16
--Stefan]
13
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
17
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
14
---
18
---
15
util/throttle.c | 62 +++++++++++++++++++++------------------------------------
19
block/io.c | 43 +++++--------------------------------------
16
1 file changed, 23 insertions(+), 39 deletions(-)
20
1 file changed, 5 insertions(+), 38 deletions(-)
17
21
18
diff --git a/util/throttle.c b/util/throttle.c
22
diff --git a/block/io.c b/block/io.c
19
index XXXXXXX..XXXXXXX 100644
23
index XXXXXXX..XXXXXXX 100644
20
--- a/util/throttle.c
24
--- a/block/io.c
21
+++ b/util/throttle.c
25
+++ b/block/io.c
22
@@ -XXX,XX +XXX,XX @@ static int64_t throttle_do_compute_wait(double limit, double extra)
26
@@ -XXX,XX +XXX,XX @@ int coroutine_fn bdrv_is_allocated(BlockDriverState *bs, int64_t offset,
23
int64_t throttle_compute_wait(LeakyBucket *bkt)
27
* at 'offset + *pnum' may return the same allocation status (in other
28
* words, the result is not necessarily the maximum possible range);
29
* but 'pnum' will only be 0 when end of file is reached.
30
- *
31
*/
32
int bdrv_is_allocated_above(BlockDriverState *top,
33
BlockDriverState *base,
34
bool include_base, int64_t offset,
35
int64_t bytes, int64_t *pnum)
24
{
36
{
25
double extra; /* the number of extra units blocking the io */
37
- BlockDriverState *intermediate;
26
+ double bucket_size; /* I/O before throttling to bkt->avg */
38
- int ret;
27
+ double burst_bucket_size; /* Before throttling to bkt->max */
39
- int64_t n = bytes;
28
40
-
29
if (!bkt->avg) {
41
- assert(base || !include_base);
30
return 0;
42
-
43
- intermediate = top;
44
- while (include_base || intermediate != base) {
45
- int64_t pnum_inter;
46
- int64_t size_inter;
47
-
48
- assert(intermediate);
49
- ret = bdrv_is_allocated(intermediate, offset, bytes, &pnum_inter);
50
- if (ret < 0) {
51
- return ret;
52
- }
53
- if (ret) {
54
- *pnum = pnum_inter;
55
- return 1;
56
- }
57
-
58
- size_inter = bdrv_getlength(intermediate);
59
- if (size_inter < 0) {
60
- return size_inter;
61
- }
62
- if (n > pnum_inter &&
63
- (intermediate == top || offset + pnum_inter < size_inter)) {
64
- n = pnum_inter;
65
- }
66
-
67
- if (intermediate == base) {
68
- break;
69
- }
70
-
71
- intermediate = bdrv_filter_or_cow_bs(intermediate);
72
+ int ret = bdrv_common_block_status_above(top, base, include_base, false,
73
+ offset, bytes, pnum, NULL, NULL);
74
+ if (ret < 0) {
75
+ return ret;
31
}
76
}
32
77
33
- /* If the bucket is full then we have to wait */
78
- *pnum = n;
34
- extra = bkt->level - bkt->max * bkt->burst_length;
79
- return 0;
35
+ if (!bkt->max) {
80
+ return !!(ret & BDRV_BLOCK_ALLOCATED);
36
+ /* If bkt->max is 0 we still want to allow short bursts of I/O
37
+ * from the guest, otherwise every other request will be throttled
38
+ * and performance will suffer considerably. */
39
+ bucket_size = bkt->avg / 10;
40
+ burst_bucket_size = 0;
41
+ } else {
42
+ /* If we have a burst limit then we have to wait until all I/O
43
+ * at burst rate has finished before throttling to bkt->avg */
44
+ bucket_size = bkt->max * bkt->burst_length;
45
+ burst_bucket_size = bkt->max / 10;
46
+ }
47
+
48
+ /* If the main bucket is full then we have to wait */
49
+ extra = bkt->level - bucket_size;
50
if (extra > 0) {
51
return throttle_do_compute_wait(bkt->avg, extra);
52
}
53
54
- /* If the bucket is not full yet we have to make sure that we
55
- * fulfill the goal of bkt->max units per second. */
56
+ /* If the main bucket is not full yet we still have to check the
57
+ * burst bucket in order to enforce the burst limit */
58
if (bkt->burst_length > 1) {
59
- /* We use 1/10 of the max value to smooth the throttling.
60
- * See throttle_fix_bucket() for more details. */
61
- extra = bkt->burst_level - bkt->max / 10;
62
+ extra = bkt->burst_level - burst_bucket_size;
63
if (extra > 0) {
64
return throttle_do_compute_wait(bkt->max, extra);
65
}
66
@@ -XXX,XX +XXX,XX @@ bool throttle_is_valid(ThrottleConfig *cfg, Error **errp)
67
return true;
68
}
81
}
69
82
70
-/* fix bucket parameters */
83
int coroutine_fn
71
-static void throttle_fix_bucket(LeakyBucket *bkt)
72
-{
73
- double min;
74
-
75
- /* zero bucket level */
76
- bkt->level = bkt->burst_level = 0;
77
-
78
- /* If bkt->max is 0 we still want to allow short bursts of I/O
79
- * from the guest, otherwise every other request will be throttled
80
- * and performance will suffer considerably. */
81
- min = bkt->avg / 10;
82
- if (bkt->avg && !bkt->max) {
83
- bkt->max = min;
84
- }
85
-}
86
-
87
-/* undo internal bucket parameter changes (see throttle_fix_bucket()) */
88
-static void throttle_unfix_bucket(LeakyBucket *bkt)
89
-{
90
- if (bkt->max < bkt->avg) {
91
- bkt->max = 0;
92
- }
93
-}
94
-
95
/* Used to configure the throttle
96
*
97
* @ts: the throttle state we are working on
98
@@ -XXX,XX +XXX,XX @@ void throttle_config(ThrottleState *ts,
99
100
ts->cfg = *cfg;
101
102
+ /* Zero bucket level */
103
for (i = 0; i < BUCKETS_COUNT; i++) {
104
- throttle_fix_bucket(&ts->cfg.buckets[i]);
105
+ ts->cfg.buckets[i].level = 0;
106
+ ts->cfg.buckets[i].burst_level = 0;
107
}
108
109
ts->previous_leak = qemu_clock_get_ns(clock_type);
110
@@ -XXX,XX +XXX,XX @@ void throttle_config(ThrottleState *ts,
111
*/
112
void throttle_get_config(ThrottleState *ts, ThrottleConfig *cfg)
113
{
114
- int i;
115
-
116
*cfg = ts->cfg;
117
-
118
- for (i = 0; i < BUCKETS_COUNT; i++) {
119
- throttle_unfix_bucket(&cfg->buckets[i]);
120
- }
121
}
122
123
124
--
84
--
125
2.13.5
85
2.26.2
126
86
127
diff view generated by jsdifflib
1
From: Dan Aloni <dan@kernelim.com>
1
From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
2
2
3
The number of queues that should be return by the admin command should:
3
These cases are fixed by previous patches around block_status and
4
is_allocated.
4
5
5
1) Only mention the number of non-admin queues.
6
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
6
2) It is zero-based, meaning that '0 == one non-admin queue',
7
Reviewed-by: Eric Blake <eblake@redhat.com>
7
'1 == two non-admin queues', and so forth.
8
Reviewed-by: Alberto Garcia <berto@igalia.com>
8
9
Message-id: 20200924194003.22080-6-vsementsov@virtuozzo.com
9
Because our `num_queues` means the number of queues _plus_ the admin
10
queue, then the right calculation for the number returned from the admin
11
command is `num_queues - 2`, combining the two requirements mentioned.
12
13
The issue was discovered by reducing num_queues from 64 to 8 and running
14
a Linux VM with an SMP parameter larger than that (e.g. 22). It tries to
15
utilize all queues, and therefore fails with an invalid queue number
16
when trying to queue I/Os on the last queue.
17
18
Signed-off-by: Dan Aloni <dan@kernelim.com>
19
CC: Alex Friedman <alex@e8storage.com>
20
CC: Keith Busch <keith.busch@intel.com>
21
CC: Stefan Hajnoczi <stefanha@redhat.com>
22
Reviewed-by: Keith Busch <keith.busch@intel.com>
23
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
10
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
24
---
11
---
25
hw/block/nvme.c | 4 ++--
12
tests/qemu-iotests/274 | 20 +++++++++++
26
1 file changed, 2 insertions(+), 2 deletions(-)
13
tests/qemu-iotests/274.out | 68 ++++++++++++++++++++++++++++++++++++++
14
2 files changed, 88 insertions(+)
27
15
28
diff --git a/hw/block/nvme.c b/hw/block/nvme.c
16
diff --git a/tests/qemu-iotests/274 b/tests/qemu-iotests/274
17
index XXXXXXX..XXXXXXX 100755
18
--- a/tests/qemu-iotests/274
19
+++ b/tests/qemu-iotests/274
20
@@ -XXX,XX +XXX,XX @@ with iotests.FilePath('base') as base, \
21
iotests.qemu_io_log('-c', 'read -P 1 0 %d' % size_short, mid)
22
iotests.qemu_io_log('-c', 'read -P 0 %d %d' % (size_short, size_diff), mid)
23
24
+ iotests.log('=== Testing qemu-img commit (top -> base) ===')
25
+
26
+ create_chain()
27
+ iotests.qemu_img_log('commit', '-b', base, top)
28
+ iotests.img_info_log(base)
29
+ iotests.qemu_io_log('-c', 'read -P 1 0 %d' % size_short, base)
30
+ iotests.qemu_io_log('-c', 'read -P 0 %d %d' % (size_short, size_diff), base)
31
+
32
+ iotests.log('=== Testing QMP active commit (top -> base) ===')
33
+
34
+ create_chain()
35
+ with create_vm() as vm:
36
+ vm.launch()
37
+ vm.qmp_log('block-commit', device='top', base_node='base',
38
+ job_id='job0', auto_dismiss=False)
39
+ vm.run_job('job0', wait=5)
40
+
41
+ iotests.img_info_log(mid)
42
+ iotests.qemu_io_log('-c', 'read -P 1 0 %d' % size_short, base)
43
+ iotests.qemu_io_log('-c', 'read -P 0 %d %d' % (size_short, size_diff), base)
44
45
iotests.log('== Resize tests ==')
46
47
diff --git a/tests/qemu-iotests/274.out b/tests/qemu-iotests/274.out
29
index XXXXXXX..XXXXXXX 100644
48
index XXXXXXX..XXXXXXX 100644
30
--- a/hw/block/nvme.c
49
--- a/tests/qemu-iotests/274.out
31
+++ b/hw/block/nvme.c
50
+++ b/tests/qemu-iotests/274.out
32
@@ -XXX,XX +XXX,XX @@ static uint16_t nvme_get_feature(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
51
@@ -XXX,XX +XXX,XX @@ read 1048576/1048576 bytes at offset 0
33
result = blk_enable_write_cache(n->conf.blk);
52
read 1048576/1048576 bytes at offset 1048576
34
break;
53
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
35
case NVME_NUMBER_OF_QUEUES:
54
36
- result = cpu_to_le32((n->num_queues - 1) | ((n->num_queues - 1) << 16));
55
+=== Testing qemu-img commit (top -> base) ===
37
+ result = cpu_to_le32((n->num_queues - 2) | ((n->num_queues - 2) << 16));
56
+Formatting 'TEST_DIR/PID-base', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=2097152 lazy_refcounts=off refcount_bits=16
38
break;
57
+
39
default:
58
+Formatting 'TEST_DIR/PID-mid', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=1048576 backing_file=TEST_DIR/PID-base backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
40
return NVME_INVALID_FIELD | NVME_DNR;
59
+
41
@@ -XXX,XX +XXX,XX @@ static uint16_t nvme_set_feature(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
60
+Formatting 'TEST_DIR/PID-top', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=2097152 backing_file=TEST_DIR/PID-mid backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
42
break;
61
+
43
case NVME_NUMBER_OF_QUEUES:
62
+wrote 2097152/2097152 bytes at offset 0
44
req->cqe.result =
63
+2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
45
- cpu_to_le32((n->num_queues - 1) | ((n->num_queues - 1) << 16));
64
+
46
+ cpu_to_le32((n->num_queues - 2) | ((n->num_queues - 2) << 16));
65
+Image committed.
47
break;
66
+
48
default:
67
+image: TEST_IMG
49
return NVME_INVALID_FIELD | NVME_DNR;
68
+file format: IMGFMT
69
+virtual size: 2 MiB (2097152 bytes)
70
+cluster_size: 65536
71
+Format specific information:
72
+ compat: 1.1
73
+ compression type: zlib
74
+ lazy refcounts: false
75
+ refcount bits: 16
76
+ corrupt: false
77
+ extended l2: false
78
+
79
+read 1048576/1048576 bytes at offset 0
80
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
81
+
82
+read 1048576/1048576 bytes at offset 1048576
83
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
84
+
85
+=== Testing QMP active commit (top -> base) ===
86
+Formatting 'TEST_DIR/PID-base', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=2097152 lazy_refcounts=off refcount_bits=16
87
+
88
+Formatting 'TEST_DIR/PID-mid', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=1048576 backing_file=TEST_DIR/PID-base backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
89
+
90
+Formatting 'TEST_DIR/PID-top', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=2097152 backing_file=TEST_DIR/PID-mid backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
91
+
92
+wrote 2097152/2097152 bytes at offset 0
93
+2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
94
+
95
+{"execute": "block-commit", "arguments": {"auto-dismiss": false, "base-node": "base", "device": "top", "job-id": "job0"}}
96
+{"return": {}}
97
+{"execute": "job-complete", "arguments": {"id": "job0"}}
98
+{"return": {}}
99
+{"data": {"device": "job0", "len": 1048576, "offset": 1048576, "speed": 0, "type": "commit"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
100
+{"data": {"device": "job0", "len": 1048576, "offset": 1048576, "speed": 0, "type": "commit"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
101
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
102
+{"return": {}}
103
+image: TEST_IMG
104
+file format: IMGFMT
105
+virtual size: 1 MiB (1048576 bytes)
106
+cluster_size: 65536
107
+backing file: TEST_DIR/PID-base
108
+backing file format: IMGFMT
109
+Format specific information:
110
+ compat: 1.1
111
+ compression type: zlib
112
+ lazy refcounts: false
113
+ refcount bits: 16
114
+ corrupt: false
115
+ extended l2: false
116
+
117
+read 1048576/1048576 bytes at offset 0
118
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
119
+
120
+read 1048576/1048576 bytes at offset 1048576
121
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
122
+
123
== Resize tests ==
124
=== preallocation=off ===
125
Formatting 'TEST_DIR/PID-base', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=6442450944 lazy_refcounts=off refcount_bits=16
50
--
126
--
51
2.13.5
127
2.26.2
52
128
53
diff view generated by jsdifflib