]>
Commit | Line | Data |
---|---|---|
059a9447 FG |
1 | From 1bfdb0492d8b70983342cf96576b294d60172e05 Mon Sep 17 00:00:00 2001 |
2 | From: John Snow <jsnow@redhat.com> | |
3 | Date: Tue, 9 Jul 2019 21:12:21 -0400 | |
4 | Subject: [PATCH qemu 34/39] drive-mirror: add support for sync=bitmap | |
5 | mode=never | |
6 | MIME-Version: 1.0 | |
7 | Content-Type: text/plain; charset=UTF-8 | |
8 | Content-Transfer-Encoding: 8bit | |
9 | ||
10 | This patch adds support for the "BITMAP" sync mode to drive-mirror and | |
11 | blockdev-mirror. It adds support only for the BitmapSyncMode "never," | |
12 | because it's the simplest mode. | |
13 | ||
14 | This mode simply uses a user-provided bitmap as an initial copy | |
15 | manifest, and then does not clear any bits in the bitmap at the | |
16 | conclusion of the operation. | |
17 | ||
18 | Any new writes dirtied during the operation are copied out, in contrast | |
19 | to backup. Note that whether these writes are reflected in the bitmap | |
20 | at the conclusion of the operation depends on whether that bitmap is | |
21 | actually recording! | |
22 | ||
23 | This patch was originally based on one by Ma Haocong, but it has since | |
24 | been modified pretty heavily. | |
25 | ||
26 | Suggested-by: Ma Haocong <mahaocong@didichuxing.com> | |
27 | Signed-off-by: Ma Haocong <mahaocong@didichuxing.com> | |
28 | Signed-off-by: John Snow <jsnow@redhat.com> | |
29 | Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com> | |
30 | --- | |
31 | include/block/block_int.h | 4 +- | |
32 | block/mirror.c | 98 ++++++++++++++++++++++++++++++------- | |
33 | blockdev.c | 39 +++++++++++++-- | |
34 | tests/test-block-iothread.c | 4 +- | |
35 | qapi/block-core.json | 29 +++++++++-- | |
36 | 5 files changed, 145 insertions(+), 29 deletions(-) | |
37 | ||
38 | diff --git a/include/block/block_int.h b/include/block/block_int.h | |
39 | index 43b00c15ac..bd2e11c4d9 100644 | |
40 | --- a/include/block/block_int.h | |
41 | +++ b/include/block/block_int.h | |
42 | @@ -1206,7 +1206,9 @@ void mirror_start(const char *job_id, BlockDriverState *bs, | |
43 | BlockDriverState *target, const char *replaces, | |
44 | int creation_flags, int64_t speed, | |
45 | uint32_t granularity, int64_t buf_size, | |
46 | - MirrorSyncMode mode, BlockMirrorBackingMode backing_mode, | |
47 | + MirrorSyncMode mode, BdrvDirtyBitmap *bitmap, | |
48 | + BitmapSyncMode bitmap_mode, | |
49 | + BlockMirrorBackingMode backing_mode, | |
50 | bool zero_target, | |
51 | BlockdevOnError on_source_error, | |
52 | BlockdevOnError on_target_error, | |
53 | diff --git a/block/mirror.c b/block/mirror.c | |
54 | index f0f2d9dff1..fd7f574365 100644 | |
55 | --- a/block/mirror.c | |
56 | +++ b/block/mirror.c | |
57 | @@ -49,7 +49,7 @@ typedef struct MirrorBlockJob { | |
58 | BlockDriverState *to_replace; | |
59 | /* Used to block operations on the drive-mirror-replace target */ | |
60 | Error *replace_blocker; | |
61 | - bool is_none_mode; | |
62 | + MirrorSyncMode sync_mode; | |
63 | BlockMirrorBackingMode backing_mode; | |
64 | /* Whether the target image requires explicit zero-initialization */ | |
65 | bool zero_target; | |
66 | @@ -64,6 +64,8 @@ typedef struct MirrorBlockJob { | |
67 | size_t buf_size; | |
68 | int64_t bdev_length; | |
69 | unsigned long *cow_bitmap; | |
70 | + BdrvDirtyBitmap *sync_bitmap; | |
71 | + BitmapSyncMode bitmap_mode; | |
72 | BdrvDirtyBitmap *dirty_bitmap; | |
73 | BdrvDirtyBitmapIter *dbi; | |
74 | uint8_t *buf; | |
75 | @@ -668,7 +670,8 @@ static int mirror_exit_common(Job *job) | |
76 | bdrv_child_refresh_perms(mirror_top_bs, mirror_top_bs->backing, | |
77 | &error_abort); | |
78 | if (!abort && s->backing_mode == MIRROR_SOURCE_BACKING_CHAIN) { | |
79 | - BlockDriverState *backing = s->is_none_mode ? src : s->base; | |
80 | + BlockDriverState *backing; | |
81 | + backing = s->sync_mode == MIRROR_SYNC_MODE_NONE ? src : s->base; | |
82 | if (backing_bs(target_bs) != backing) { | |
83 | bdrv_set_backing_hd(target_bs, backing, &local_err); | |
84 | if (local_err) { | |
85 | @@ -750,6 +753,16 @@ static void mirror_abort(Job *job) | |
86 | assert(ret == 0); | |
87 | } | |
88 | ||
89 | +/* Always called after commit/abort. */ | |
90 | +static void mirror_clean(Job *job) | |
91 | +{ | |
92 | + MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job); | |
93 | + | |
94 | + if (s->sync_bitmap) { | |
95 | + bdrv_dirty_bitmap_set_busy(s->sync_bitmap, false); | |
96 | + } | |
97 | +} | |
98 | + | |
99 | static void coroutine_fn mirror_throttle(MirrorBlockJob *s) | |
100 | { | |
101 | int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); | |
102 | @@ -928,7 +941,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp) | |
103 | mirror_free_init(s); | |
104 | ||
105 | s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); | |
106 | - if (!s->is_none_mode) { | |
107 | + if ((s->sync_mode == MIRROR_SYNC_MODE_TOP) || | |
108 | + (s->sync_mode == MIRROR_SYNC_MODE_FULL)) { | |
109 | ret = mirror_dirty_init(s); | |
110 | if (ret < 0 || job_is_cancelled(&s->common.job)) { | |
111 | goto immediate_exit; | |
112 | @@ -1160,6 +1174,7 @@ static const BlockJobDriver mirror_job_driver = { | |
113 | .run = mirror_run, | |
114 | .prepare = mirror_prepare, | |
115 | .abort = mirror_abort, | |
116 | + .clean = mirror_clean, | |
117 | .pause = mirror_pause, | |
118 | .complete = mirror_complete, | |
119 | }, | |
120 | @@ -1175,6 +1190,7 @@ static const BlockJobDriver commit_active_job_driver = { | |
121 | .run = mirror_run, | |
122 | .prepare = mirror_prepare, | |
123 | .abort = mirror_abort, | |
124 | + .clean = mirror_clean, | |
125 | .pause = mirror_pause, | |
126 | .complete = mirror_complete, | |
127 | }, | |
128 | @@ -1520,7 +1536,10 @@ static BlockJob *mirror_start_job( | |
129 | BlockCompletionFunc *cb, | |
130 | void *opaque, | |
131 | const BlockJobDriver *driver, | |
132 | - bool is_none_mode, BlockDriverState *base, | |
133 | + MirrorSyncMode sync_mode, | |
134 | + BdrvDirtyBitmap *bitmap, | |
135 | + BitmapSyncMode bitmap_mode, | |
136 | + BlockDriverState *base, | |
137 | bool auto_complete, const char *filter_node_name, | |
138 | bool is_mirror, MirrorCopyMode copy_mode, | |
139 | Error **errp) | |
140 | @@ -1533,10 +1552,39 @@ static BlockJob *mirror_start_job( | |
141 | Error *local_err = NULL; | |
142 | int ret; | |
143 | ||
144 | - if (granularity == 0) { | |
145 | - granularity = bdrv_get_default_bitmap_granularity(target); | |
146 | + if (sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) { | |
147 | + error_setg(errp, "Sync mode '%s' not supported", | |
148 | + MirrorSyncMode_str(sync_mode)); | |
149 | + return NULL; | |
150 | + } else if (sync_mode == MIRROR_SYNC_MODE_BITMAP) { | |
151 | + if (!bitmap) { | |
152 | + error_setg(errp, "Must provide a valid bitmap name for '%s'" | |
153 | + " sync mode", | |
154 | + MirrorSyncMode_str(sync_mode)); | |
155 | + return NULL; | |
156 | + } else if (bitmap_mode != BITMAP_SYNC_MODE_NEVER) { | |
157 | + error_setg(errp, | |
158 | + "Bitmap Sync Mode '%s' is not supported by Mirror", | |
159 | + BitmapSyncMode_str(bitmap_mode)); | |
160 | + } | |
161 | + } else if (bitmap) { | |
162 | + error_setg(errp, | |
163 | + "sync mode '%s' is not compatible with bitmaps", | |
164 | + MirrorSyncMode_str(sync_mode)); | |
165 | + return NULL; | |
166 | } | |
167 | ||
168 | + if (bitmap) { | |
169 | + if (granularity) { | |
170 | + error_setg(errp, "granularity (%d)" | |
171 | + "cannot be specified when a bitmap is provided", | |
172 | + granularity); | |
173 | + return NULL; | |
174 | + } | |
175 | + granularity = bdrv_dirty_bitmap_granularity(bitmap); | |
176 | + } else if (granularity == 0) { | |
177 | + granularity = bdrv_get_default_bitmap_granularity(target); | |
178 | + } | |
179 | assert(is_power_of_2(granularity)); | |
180 | ||
181 | if (buf_size < 0) { | |
182 | @@ -1640,7 +1688,9 @@ static BlockJob *mirror_start_job( | |
183 | s->replaces = g_strdup(replaces); | |
184 | s->on_source_error = on_source_error; | |
185 | s->on_target_error = on_target_error; | |
186 | - s->is_none_mode = is_none_mode; | |
187 | + s->sync_mode = sync_mode; | |
188 | + s->sync_bitmap = bitmap; | |
189 | + s->bitmap_mode = bitmap_mode; | |
190 | s->backing_mode = backing_mode; | |
191 | s->zero_target = zero_target; | |
192 | s->copy_mode = copy_mode; | |
193 | @@ -1660,6 +1710,18 @@ static BlockJob *mirror_start_job( | |
194 | bdrv_disable_dirty_bitmap(s->dirty_bitmap); | |
195 | } | |
196 | ||
197 | + if (s->sync_bitmap) { | |
198 | + bdrv_dirty_bitmap_set_busy(s->sync_bitmap, true); | |
199 | + } | |
200 | + | |
201 | + if (s->sync_mode == MIRROR_SYNC_MODE_BITMAP) { | |
202 | + bdrv_merge_dirty_bitmap(s->dirty_bitmap, s->sync_bitmap, | |
203 | + NULL, &local_err); | |
204 | + if (local_err) { | |
205 | + goto fail; | |
206 | + } | |
207 | + } | |
208 | + | |
209 | ret = block_job_add_bdrv(&s->common, "source", bs, 0, | |
210 | BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | | |
211 | BLK_PERM_CONSISTENT_READ, | |
212 | @@ -1713,6 +1775,9 @@ fail: | |
213 | if (s->dirty_bitmap) { | |
214 | bdrv_release_dirty_bitmap(s->dirty_bitmap); | |
215 | } | |
216 | + if (s->sync_bitmap) { | |
217 | + bdrv_dirty_bitmap_set_busy(s->sync_bitmap, false); | |
218 | + } | |
219 | job_early_fail(&s->common.job); | |
220 | } | |
221 | ||
222 | @@ -1730,29 +1795,23 @@ void mirror_start(const char *job_id, BlockDriverState *bs, | |
223 | BlockDriverState *target, const char *replaces, | |
224 | int creation_flags, int64_t speed, | |
225 | uint32_t granularity, int64_t buf_size, | |
226 | - MirrorSyncMode mode, BlockMirrorBackingMode backing_mode, | |
227 | + MirrorSyncMode mode, BdrvDirtyBitmap *bitmap, | |
228 | + BitmapSyncMode bitmap_mode, | |
229 | + BlockMirrorBackingMode backing_mode, | |
230 | bool zero_target, | |
231 | BlockdevOnError on_source_error, | |
232 | BlockdevOnError on_target_error, | |
233 | bool unmap, const char *filter_node_name, | |
234 | MirrorCopyMode copy_mode, Error **errp) | |
235 | { | |
236 | - bool is_none_mode; | |
237 | BlockDriverState *base; | |
238 | ||
239 | - if ((mode == MIRROR_SYNC_MODE_INCREMENTAL) || | |
240 | - (mode == MIRROR_SYNC_MODE_BITMAP)) { | |
241 | - error_setg(errp, "Sync mode '%s' not supported", | |
242 | - MirrorSyncMode_str(mode)); | |
243 | - return; | |
244 | - } | |
245 | - is_none_mode = mode == MIRROR_SYNC_MODE_NONE; | |
246 | base = mode == MIRROR_SYNC_MODE_TOP ? backing_bs(bs) : NULL; | |
247 | mirror_start_job(job_id, bs, creation_flags, target, replaces, | |
248 | speed, granularity, buf_size, backing_mode, zero_target, | |
249 | on_source_error, on_target_error, unmap, NULL, NULL, | |
250 | - &mirror_job_driver, is_none_mode, base, false, | |
251 | - filter_node_name, true, copy_mode, errp); | |
252 | + &mirror_job_driver, mode, bitmap, bitmap_mode, base, | |
253 | + false, filter_node_name, true, copy_mode, errp); | |
254 | } | |
255 | ||
256 | BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, | |
257 | @@ -1778,7 +1837,8 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, | |
258 | job_id, bs, creation_flags, base, NULL, speed, 0, 0, | |
259 | MIRROR_LEAVE_BACKING_CHAIN, false, | |
260 | on_error, on_error, true, cb, opaque, | |
261 | - &commit_active_job_driver, false, base, auto_complete, | |
262 | + &commit_active_job_driver, MIRROR_SYNC_MODE_FULL, | |
263 | + NULL, 0, base, auto_complete, | |
264 | filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND, | |
265 | &local_err); | |
266 | if (local_err) { | |
267 | diff --git a/blockdev.c b/blockdev.c | |
268 | index e5310cb939..08285b9e86 100644 | |
269 | --- a/blockdev.c | |
270 | +++ b/blockdev.c | |
271 | @@ -3763,6 +3763,10 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, | |
272 | BlockDriverState *target, | |
273 | bool has_replaces, const char *replaces, | |
274 | enum MirrorSyncMode sync, | |
275 | + bool has_bitmap, | |
276 | + const char *bitmap_name, | |
277 | + bool has_bitmap_mode, | |
278 | + BitmapSyncMode bitmap_mode, | |
279 | BlockMirrorBackingMode backing_mode, | |
280 | bool zero_target, | |
281 | bool has_speed, int64_t speed, | |
282 | @@ -3781,6 +3785,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, | |
283 | Error **errp) | |
284 | { | |
285 | int job_flags = JOB_DEFAULT; | |
286 | + BdrvDirtyBitmap *bitmap = NULL; | |
287 | ||
288 | if (!has_speed) { | |
289 | speed = 0; | |
290 | @@ -3835,6 +3840,29 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, | |
291 | sync = MIRROR_SYNC_MODE_FULL; | |
292 | } | |
293 | ||
294 | + if (has_bitmap) { | |
295 | + if (granularity) { | |
296 | + error_setg(errp, "Granularity and bitmap cannot both be set"); | |
297 | + return; | |
298 | + } | |
299 | + | |
300 | + if (!has_bitmap_mode) { | |
301 | + error_setg(errp, "bitmap-mode must be specified if" | |
302 | + " a bitmap is provided"); | |
303 | + return; | |
304 | + } | |
305 | + | |
306 | + bitmap = bdrv_find_dirty_bitmap(bs, bitmap_name); | |
307 | + if (!bitmap) { | |
308 | + error_setg(errp, "Dirty bitmap '%s' not found", bitmap_name); | |
309 | + return; | |
310 | + } | |
311 | + | |
312 | + if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_ALLOW_RO, errp)) { | |
313 | + return; | |
314 | + } | |
315 | + } | |
316 | + | |
317 | if (has_replaces) { | |
318 | BlockDriverState *to_replace_bs; | |
319 | AioContext *replace_aio_context; | |
320 | @@ -3872,8 +3900,8 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, | |
321 | * and will allow to check whether the node still exist at mirror completion | |
322 | */ | |
323 | mirror_start(job_id, bs, target, | |
324 | - has_replaces ? replaces : NULL, job_flags, | |
325 | - speed, granularity, buf_size, sync, backing_mode, zero_target, | |
326 | + has_replaces ? replaces : NULL, job_flags, speed, granularity, | |
327 | + buf_size, sync, bitmap, bitmap_mode, backing_mode, zero_target, | |
328 | on_source_error, on_target_error, unmap, filter_node_name, | |
329 | copy_mode, errp); | |
330 | } | |
331 | @@ -4003,6 +4031,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) | |
332 | ||
333 | blockdev_mirror_common(arg->has_job_id ? arg->job_id : NULL, bs, target_bs, | |
334 | arg->has_replaces, arg->replaces, arg->sync, | |
335 | + arg->has_bitmap, arg->bitmap, | |
336 | + arg->has_bitmap_mode, arg->bitmap_mode, | |
337 | backing_mode, zero_target, | |
338 | arg->has_speed, arg->speed, | |
339 | arg->has_granularity, arg->granularity, | |
340 | @@ -4025,6 +4055,8 @@ void qmp_blockdev_mirror(bool has_job_id, const char *job_id, | |
341 | const char *device, const char *target, | |
342 | bool has_replaces, const char *replaces, | |
343 | MirrorSyncMode sync, | |
344 | + bool has_bitmap, const char *bitmap, | |
345 | + bool has_bitmap_mode, BitmapSyncMode bitmap_mode, | |
346 | bool has_speed, int64_t speed, | |
347 | bool has_granularity, uint32_t granularity, | |
348 | bool has_buf_size, int64_t buf_size, | |
349 | @@ -4068,7 +4100,8 @@ void qmp_blockdev_mirror(bool has_job_id, const char *job_id, | |
350 | } | |
351 | ||
352 | blockdev_mirror_common(has_job_id ? job_id : NULL, bs, target_bs, | |
353 | - has_replaces, replaces, sync, backing_mode, | |
354 | + has_replaces, replaces, sync, has_bitmap, | |
355 | + bitmap, has_bitmap_mode, bitmap_mode, backing_mode, | |
356 | zero_target, has_speed, speed, | |
357 | has_granularity, granularity, | |
358 | has_buf_size, buf_size, | |
359 | diff --git a/tests/test-block-iothread.c b/tests/test-block-iothread.c | |
360 | index 0c861809f0..da87a67a57 100644 | |
361 | --- a/tests/test-block-iothread.c | |
362 | +++ b/tests/test-block-iothread.c | |
363 | @@ -611,8 +611,8 @@ static void test_propagate_mirror(void) | |
364 | ||
365 | /* Start a mirror job */ | |
366 | mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0, | |
367 | - MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false, | |
368 | - BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, | |
369 | + MIRROR_SYNC_MODE_NONE, NULL, 0, MIRROR_OPEN_BACKING_CHAIN, | |
370 | + false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, | |
371 | false, "filter_node", MIRROR_COPY_MODE_BACKGROUND, | |
372 | &error_abort); | |
373 | job = job_get("job0"); | |
374 | diff --git a/qapi/block-core.json b/qapi/block-core.json | |
375 | index 0b987ad6e3..e2050bab1d 100644 | |
376 | --- a/qapi/block-core.json | |
377 | +++ b/qapi/block-core.json | |
378 | @@ -2086,10 +2086,19 @@ | |
379 | # (all the disk, only the sectors allocated in the topmost image, or | |
380 | # only new I/O). | |
381 | # | |
382 | +# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must | |
383 | +# be present for bitmap mode and absent otherwise. The bitmap's | |
384 | +# granularity is used instead of @granularity (since 4.1). | |
385 | +# | |
386 | +# @bitmap-mode: Specifies the type of data the bitmap should contain after | |
387 | +# the operation concludes. Must be present if sync is "bitmap". | |
388 | +# Must NOT be present otherwise. (Since 4.1) | |
389 | +# | |
390 | # @granularity: granularity of the dirty bitmap, default is 64K | |
391 | # if the image format doesn't have clusters, 4K if the clusters | |
392 | # are smaller than that, else the cluster size. Must be a | |
393 | -# power of 2 between 512 and 64M (since 1.4). | |
394 | +# power of 2 between 512 and 64M. Must not be specified if | |
395 | +# @bitmap is present (since 1.4). | |
396 | # | |
397 | # @buf-size: maximum amount of data in flight from source to | |
398 | # target (since 1.4). | |
399 | @@ -2127,7 +2136,9 @@ | |
400 | { 'struct': 'DriveMirror', | |
401 | 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', | |
402 | '*format': 'str', '*node-name': 'str', '*replaces': 'str', | |
403 | - 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode', | |
404 | + 'sync': 'MirrorSyncMode', '*bitmap': 'str', | |
405 | + '*bitmap-mode': 'BitmapSyncMode', | |
406 | + '*mode': 'NewImageMode', | |
407 | '*speed': 'int', '*granularity': 'uint32', | |
408 | '*buf-size': 'int', '*on-source-error': 'BlockdevOnError', | |
409 | '*on-target-error': 'BlockdevOnError', | |
410 | @@ -2394,10 +2405,19 @@ | |
411 | # (all the disk, only the sectors allocated in the topmost image, or | |
412 | # only new I/O). | |
413 | # | |
414 | +# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must | |
415 | +# be present for bitmap mode and absent otherwise. The bitmap's | |
416 | +# granularity is used instead of @granularity (since 4.1). | |
417 | +# | |
418 | +# @bitmap-mode: Specifies the type of data the bitmap should contain after | |
419 | +# the operation concludes. Must be present if sync is "bitmap". | |
420 | +# Must NOT be present otherwise. (Since 4.1) | |
421 | +# | |
422 | # @granularity: granularity of the dirty bitmap, default is 64K | |
423 | # if the image format doesn't have clusters, 4K if the clusters | |
424 | # are smaller than that, else the cluster size. Must be a | |
425 | -# power of 2 between 512 and 64M | |
426 | +# power of 2 between 512 and 64M . Must not be specified if | |
427 | +# @bitmap is present. | |
428 | # | |
429 | # @buf-size: maximum amount of data in flight from source to | |
430 | # target | |
431 | @@ -2446,7 +2466,8 @@ | |
432 | { 'command': 'blockdev-mirror', | |
433 | 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', | |
434 | '*replaces': 'str', | |
435 | - 'sync': 'MirrorSyncMode', | |
436 | + 'sync': 'MirrorSyncMode', '*bitmap': 'str', | |
437 | + '*bitmap-mode': 'BitmapSyncMode', | |
438 | '*speed': 'int', '*granularity': 'uint32', | |
439 | '*buf-size': 'int', '*on-source-error': 'BlockdevOnError', | |
440 | '*on-target-error': 'BlockdevOnError', | |
441 | -- | |
442 | 2.20.1 | |
443 |