]>
Commit | Line | Data |
---|---|---|
98d2c6f2 DM |
1 | /* |
2 | * QEMU backup | |
3 | * | |
4 | * Copyright (C) 2013 Proxmox Server Solutions | |
00e30f05 | 5 | * Copyright (c) 2019 Virtuozzo International GmbH. |
98d2c6f2 DM |
6 | * |
7 | * Authors: | |
8 | * Dietmar Maurer ([email protected]) | |
9 | * | |
10 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
11 | * See the COPYING file in the top-level directory. | |
12 | * | |
13 | */ | |
14 | ||
80c71a24 | 15 | #include "qemu/osdep.h" |
98d2c6f2 DM |
16 | |
17 | #include "trace.h" | |
18 | #include "block/block.h" | |
19 | #include "block/block_int.h" | |
c87621ea | 20 | #include "block/blockjob_int.h" |
49d3e828 | 21 | #include "block/block_backup.h" |
beb5f545 | 22 | #include "block/block-copy.h" |
da34e65c | 23 | #include "qapi/error.h" |
cc7a8ea7 | 24 | #include "qapi/qmp/qerror.h" |
f348b6d1 | 25 | #include "qemu/cutils.h" |
373340b2 | 26 | #include "sysemu/block-backend.h" |
b2f56462 | 27 | #include "qemu/bitmap.h" |
a410a7f1 | 28 | #include "qemu/error-report.h" |
98d2c6f2 | 29 | |
d003e0ae | 30 | #include "block/copy-before-write.h" |
00e30f05 | 31 | |
98d2c6f2 DM |
32 | typedef struct BackupBlockJob { |
33 | BlockJob common; | |
d003e0ae | 34 | BlockDriverState *cbw; |
2c8074c4 | 35 | BlockDriverState *source_bs; |
ff789bf5 | 36 | BlockDriverState *target_bs; |
62aa1fbe | 37 | |
d58d8453 | 38 | BdrvDirtyBitmap *sync_bitmap; |
62aa1fbe | 39 | |
fc5d3f84 | 40 | MirrorSyncMode sync_mode; |
c8b56501 | 41 | BitmapSyncMode bitmap_mode; |
98d2c6f2 DM |
42 | BlockdevOnError on_source_error; |
43 | BlockdevOnError on_target_error; | |
05df8a6a | 44 | uint64_t len; |
16096a4d | 45 | int64_t cluster_size; |
86c6a3b6 | 46 | BackupPerf perf; |
a193b0f0 | 47 | |
2c8074c4 | 48 | BlockCopyState *bcs; |
71eed4ce VSO |
49 | |
50 | bool wait; | |
51 | BlockCopyCallState *bg_bcs_call; | |
98d2c6f2 DM |
52 | } BackupBlockJob; |
53 | ||
bd21935b KW |
54 | static const BlockJobDriver backup_job_driver; |
55 | ||
b976ea3c FZ |
56 | static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret) |
57 | { | |
58 | BdrvDirtyBitmap *bm; | |
c23909e5 JS |
59 | bool sync = (((ret == 0) || (job->bitmap_mode == BITMAP_SYNC_MODE_ALWAYS)) \ |
60 | && (job->bitmap_mode != BITMAP_SYNC_MODE_NEVER)); | |
b976ea3c | 61 | |
c23909e5 | 62 | if (sync) { |
cf0cd293 | 63 | /* |
c23909e5 JS |
64 | * We succeeded, or we always intended to sync the bitmap. |
65 | * Delete this bitmap and install the child. | |
cf0cd293 | 66 | */ |
5deb6cbd | 67 | bm = bdrv_dirty_bitmap_abdicate(job->sync_bitmap, NULL); |
c23909e5 JS |
68 | } else { |
69 | /* | |
70 | * We failed, or we never intended to sync the bitmap anyway. | |
71 | * Merge the successor back into the parent, keeping all data. | |
72 | */ | |
5deb6cbd | 73 | bm = bdrv_reclaim_dirty_bitmap(job->sync_bitmap, NULL); |
c23909e5 JS |
74 | } |
75 | ||
76 | assert(bm); | |
77 | ||
78 | if (ret < 0 && job->bitmap_mode == BITMAP_SYNC_MODE_ALWAYS) { | |
79 | /* If we failed and synced, merge in the bits we didn't copy: */ | |
397f4e9d | 80 | bdrv_dirty_bitmap_merge_internal(bm, block_copy_dirty_bitmap(job->bcs), |
c23909e5 | 81 | NULL, true); |
b976ea3c FZ |
82 | } |
83 | } | |
84 | ||
4ad35181 | 85 | static void backup_commit(Job *job) |
c347b2c6 | 86 | { |
4ad35181 | 87 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
c347b2c6 JS |
88 | if (s->sync_bitmap) { |
89 | backup_cleanup_sync_bitmap(s, 0); | |
90 | } | |
91 | } | |
92 | ||
4ad35181 | 93 | static void backup_abort(Job *job) |
c347b2c6 | 94 | { |
4ad35181 | 95 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
c347b2c6 JS |
96 | if (s->sync_bitmap) { |
97 | backup_cleanup_sync_bitmap(s, -1); | |
98 | } | |
99 | } | |
100 | ||
4ad35181 | 101 | static void backup_clean(Job *job) |
e8a40bf7 | 102 | { |
4ad35181 | 103 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
bdc4c4c5 | 104 | block_job_remove_all_bdrv(&s->common); |
d003e0ae | 105 | bdrv_cbw_drop(s->cbw); |
e8a40bf7 JS |
106 | } |
107 | ||
49d3e828 WC |
108 | void backup_do_checkpoint(BlockJob *job, Error **errp) |
109 | { | |
110 | BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common); | |
49d3e828 | 111 | |
bd21935b | 112 | assert(block_job_driver(job) == &backup_job_driver); |
49d3e828 WC |
113 | |
114 | if (backup_job->sync_mode != MIRROR_SYNC_MODE_NONE) { | |
115 | error_setg(errp, "The backup job only supports block checkpoint in" | |
116 | " sync=none mode"); | |
117 | return; | |
118 | } | |
119 | ||
397f4e9d VSO |
120 | bdrv_set_dirty_bitmap(block_copy_dirty_bitmap(backup_job->bcs), 0, |
121 | backup_job->len); | |
49d3e828 WC |
122 | } |
123 | ||
98d2c6f2 DM |
124 | static BlockErrorAction backup_error_action(BackupBlockJob *job, |
125 | bool read, int error) | |
126 | { | |
127 | if (read) { | |
81e254dc KW |
128 | return block_job_error_action(&job->common, job->on_source_error, |
129 | true, error); | |
98d2c6f2 | 130 | } else { |
81e254dc KW |
131 | return block_job_error_action(&job->common, job->on_target_error, |
132 | false, error); | |
98d2c6f2 DM |
133 | } |
134 | } | |
135 | ||
71eed4ce | 136 | static void coroutine_fn backup_block_copy_callback(void *opaque) |
d58d8453 | 137 | { |
71eed4ce | 138 | BackupBlockJob *s = opaque; |
d58d8453 | 139 | |
71eed4ce VSO |
140 | if (s->wait) { |
141 | s->wait = false; | |
142 | aio_co_wake(s->common.job.co); | |
143 | } else { | |
144 | job_enter(&s->common.job); | |
d58d8453 | 145 | } |
d58d8453 JS |
146 | } |
147 | ||
c334e897 | 148 | static int coroutine_fn backup_loop(BackupBlockJob *job) |
d58d8453 | 149 | { |
71eed4ce | 150 | BlockCopyCallState *s = NULL; |
62aa1fbe | 151 | int ret = 0; |
71eed4ce VSO |
152 | bool error_is_read; |
153 | BlockErrorAction act; | |
154 | ||
155 | while (true) { /* retry loop */ | |
156 | job->bg_bcs_call = s = block_copy_async(job->bcs, 0, | |
157 | QEMU_ALIGN_UP(job->len, job->cluster_size), | |
158 | job->perf.max_workers, job->perf.max_chunk, | |
159 | backup_block_copy_callback, job); | |
160 | ||
161 | while (!block_copy_call_finished(s) && | |
162 | !job_is_cancelled(&job->common.job)) | |
163 | { | |
164 | job_yield(&job->common.job); | |
165 | } | |
d58d8453 | 166 | |
71eed4ce VSO |
167 | if (!block_copy_call_finished(s)) { |
168 | assert(job_is_cancelled(&job->common.job)); | |
169 | /* | |
170 | * Note that we can't use job_yield() here, as it doesn't work for | |
171 | * cancelled job. | |
172 | */ | |
173 | block_copy_call_cancel(s); | |
174 | job->wait = true; | |
175 | qemu_coroutine_yield(); | |
176 | assert(block_copy_call_finished(s)); | |
177 | ret = 0; | |
178 | goto out; | |
179 | } | |
180 | ||
181 | if (job_is_cancelled(&job->common.job) || | |
182 | block_copy_call_succeeded(s)) | |
183 | { | |
184 | ret = 0; | |
185 | goto out; | |
186 | } | |
187 | ||
188 | if (block_copy_call_cancelled(s)) { | |
189 | /* | |
190 | * Job is not cancelled but only block-copy call. This is possible | |
191 | * after job pause. Now the pause is finished, start new block-copy | |
192 | * iteration. | |
193 | */ | |
194 | block_copy_call_free(s); | |
195 | continue; | |
196 | } | |
197 | ||
198 | /* The only remaining case is failed block-copy call. */ | |
199 | assert(block_copy_call_failed(s)); | |
200 | ||
201 | ret = block_copy_call_status(s, &error_is_read); | |
202 | act = backup_error_action(job, error_is_read, -ret); | |
203 | switch (act) { | |
204 | case BLOCK_ERROR_ACTION_REPORT: | |
205 | goto out; | |
206 | case BLOCK_ERROR_ACTION_STOP: | |
207 | /* | |
208 | * Go to pause prior to starting new block-copy call on the next | |
209 | * iteration. | |
210 | */ | |
211 | job_pause_point(&job->common.job); | |
212 | break; | |
213 | case BLOCK_ERROR_ACTION_IGNORE: | |
214 | /* Proceed to new block-copy call to retry. */ | |
215 | break; | |
216 | default: | |
217 | abort(); | |
218 | } | |
219 | ||
220 | block_copy_call_free(s); | |
d58d8453 JS |
221 | } |
222 | ||
71eed4ce VSO |
223 | out: |
224 | block_copy_call_free(s); | |
225 | job->bg_bcs_call = NULL; | |
62aa1fbe | 226 | return ret; |
d58d8453 JS |
227 | } |
228 | ||
397f4e9d | 229 | static void backup_init_bcs_bitmap(BackupBlockJob *job) |
8cc6dc62 | 230 | { |
141cdcdf JS |
231 | bool ret; |
232 | uint64_t estimate; | |
397f4e9d | 233 | BdrvDirtyBitmap *bcs_bitmap = block_copy_dirty_bitmap(job->bcs); |
141cdcdf JS |
234 | |
235 | if (job->sync_mode == MIRROR_SYNC_MODE_BITMAP) { | |
06e0a9c1 | 236 | bdrv_clear_dirty_bitmap(bcs_bitmap, NULL); |
397f4e9d | 237 | ret = bdrv_dirty_bitmap_merge_internal(bcs_bitmap, job->sync_bitmap, |
141cdcdf JS |
238 | NULL, true); |
239 | assert(ret); | |
06e0a9c1 VSO |
240 | } else if (job->sync_mode == MIRROR_SYNC_MODE_TOP) { |
241 | /* | |
242 | * We can't hog the coroutine to initialize this thoroughly. | |
243 | * Set a flag and resume work when we are able to yield safely. | |
244 | */ | |
245 | block_copy_set_skip_unallocated(job->bcs, true); | |
141cdcdf | 246 | } |
8cc6dc62 | 247 | |
397f4e9d | 248 | estimate = bdrv_get_dirty_count(bcs_bitmap); |
141cdcdf | 249 | job_progress_set_remaining(&job->common.job, estimate); |
8cc6dc62 VSO |
250 | } |
251 | ||
68702775 | 252 | static int coroutine_fn backup_run(Job *job, Error **errp) |
98d2c6f2 | 253 | { |
68702775 | 254 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
511e7d31 | 255 | int ret; |
98d2c6f2 | 256 | |
397f4e9d | 257 | backup_init_bcs_bitmap(s); |
8cc6dc62 | 258 | |
7e30dd61 JS |
259 | if (s->sync_mode == MIRROR_SYNC_MODE_TOP) { |
260 | int64_t offset = 0; | |
261 | int64_t count; | |
262 | ||
263 | for (offset = 0; offset < s->len; ) { | |
71eed4ce VSO |
264 | if (job_is_cancelled(job)) { |
265 | return -ECANCELED; | |
266 | } | |
267 | ||
268 | job_pause_point(job); | |
269 | ||
270 | if (job_is_cancelled(job)) { | |
511e7d31 | 271 | return -ECANCELED; |
7e30dd61 JS |
272 | } |
273 | ||
2c8074c4 | 274 | ret = block_copy_reset_unallocated(s->bcs, offset, &count); |
7e30dd61 | 275 | if (ret < 0) { |
511e7d31 | 276 | return ret; |
7e30dd61 JS |
277 | } |
278 | ||
279 | offset += count; | |
280 | } | |
397f4e9d | 281 | block_copy_set_skip_unallocated(s->bcs, false); |
7e30dd61 JS |
282 | } |
283 | ||
68702775 | 284 | if (s->sync_mode == MIRROR_SYNC_MODE_NONE) { |
0e23e382 | 285 | /* |
397f4e9d | 286 | * All bits are set in bcs bitmap to allow any cluster to be copied. |
0e23e382 VSO |
287 | * This does not actually require them to be copied. |
288 | */ | |
68702775 | 289 | while (!job_is_cancelled(job)) { |
0e23e382 VSO |
290 | /* |
291 | * Yield until the job is cancelled. We just let our before_write | |
292 | * notify callback service CoW requests. | |
293 | */ | |
68702775 | 294 | job_yield(job); |
98d2c6f2 | 295 | } |
fc5d3f84 | 296 | } else { |
511e7d31 | 297 | return backup_loop(s); |
98d2c6f2 DM |
298 | } |
299 | ||
511e7d31 | 300 | return 0; |
98d2c6f2 DM |
301 | } |
302 | ||
71eed4ce VSO |
303 | static void coroutine_fn backup_pause(Job *job) |
304 | { | |
305 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); | |
306 | ||
307 | if (s->bg_bcs_call && !block_copy_call_finished(s->bg_bcs_call)) { | |
308 | block_copy_call_cancel(s->bg_bcs_call); | |
309 | s->wait = true; | |
310 | qemu_coroutine_yield(); | |
311 | } | |
312 | } | |
313 | ||
314 | static void coroutine_fn backup_set_speed(BlockJob *job, int64_t speed) | |
315 | { | |
316 | BackupBlockJob *s = container_of(job, BackupBlockJob, common); | |
317 | ||
318 | /* | |
319 | * block_job_set_speed() is called first from block_job_create(), when we | |
320 | * don't yet have s->bcs. | |
321 | */ | |
322 | if (s->bcs) { | |
323 | block_copy_set_speed(s->bcs, speed); | |
324 | if (s->bg_bcs_call) { | |
325 | block_copy_kick(s->bg_bcs_call); | |
326 | } | |
327 | } | |
328 | } | |
329 | ||
73895f38 | 330 | static bool backup_cancel(Job *job, bool force) |
ff789bf5 VSO |
331 | { |
332 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); | |
333 | ||
334 | bdrv_cancel_in_flight(s->target_bs); | |
73895f38 | 335 | return true; |
ff789bf5 VSO |
336 | } |
337 | ||
a7815a76 | 338 | static const BlockJobDriver backup_job_driver = { |
33e9e9bd KW |
339 | .job_driver = { |
340 | .instance_size = sizeof(BackupBlockJob), | |
252291ea | 341 | .job_type = JOB_TYPE_BACKUP, |
80fa2c75 | 342 | .free = block_job_free, |
b15de828 | 343 | .user_resume = block_job_user_resume, |
f67432a2 | 344 | .run = backup_run, |
4ad35181 KW |
345 | .commit = backup_commit, |
346 | .abort = backup_abort, | |
347 | .clean = backup_clean, | |
71eed4ce | 348 | .pause = backup_pause, |
ff789bf5 | 349 | .cancel = backup_cancel, |
71eed4ce VSO |
350 | }, |
351 | .set_speed = backup_set_speed, | |
a7815a76 JS |
352 | }; |
353 | ||
111049a4 | 354 | BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, |
70559d49 AG |
355 | BlockDriverState *target, int64_t speed, |
356 | MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap, | |
c8b56501 | 357 | BitmapSyncMode bitmap_mode, |
13b9414b | 358 | bool compress, |
00e30f05 | 359 | const char *filter_node_name, |
86c6a3b6 | 360 | BackupPerf *perf, |
98d2c6f2 DM |
361 | BlockdevOnError on_source_error, |
362 | BlockdevOnError on_target_error, | |
47970dfb | 363 | int creation_flags, |
097310b5 | 364 | BlockCompletionFunc *cb, void *opaque, |
62c9e416 | 365 | JobTxn *txn, Error **errp) |
98d2c6f2 | 366 | { |
958a04bd | 367 | int64_t len, target_len; |
91ab6883 | 368 | BackupBlockJob *job = NULL; |
ae6b12fa | 369 | int64_t cluster_size; |
d003e0ae | 370 | BlockDriverState *cbw = NULL; |
00e30f05 | 371 | BlockCopyState *bcs = NULL; |
98d2c6f2 DM |
372 | |
373 | assert(bs); | |
374 | assert(target); | |
b4ad82aa | 375 | GLOBAL_STATE_CODE(); |
98d2c6f2 | 376 | |
a6c9365a JS |
377 | /* QMP interface protects us from these cases */ |
378 | assert(sync_mode != MIRROR_SYNC_MODE_INCREMENTAL); | |
379 | assert(sync_bitmap || sync_mode != MIRROR_SYNC_MODE_BITMAP); | |
380 | ||
c29c1dd3 FZ |
381 | if (bs == target) { |
382 | error_setg(errp, "Source and target cannot be the same"); | |
111049a4 | 383 | return NULL; |
c29c1dd3 FZ |
384 | } |
385 | ||
c29c1dd3 FZ |
386 | if (!bdrv_is_inserted(bs)) { |
387 | error_setg(errp, "Device is not inserted: %s", | |
388 | bdrv_get_device_name(bs)); | |
111049a4 | 389 | return NULL; |
c29c1dd3 FZ |
390 | } |
391 | ||
392 | if (!bdrv_is_inserted(target)) { | |
393 | error_setg(errp, "Device is not inserted: %s", | |
394 | bdrv_get_device_name(target)); | |
111049a4 | 395 | return NULL; |
c29c1dd3 FZ |
396 | } |
397 | ||
2b088c60 | 398 | if (compress && !bdrv_supports_compressed_writes(target)) { |
13b9414b PB |
399 | error_setg(errp, "Compression is not supported for this drive %s", |
400 | bdrv_get_device_name(target)); | |
111049a4 | 401 | return NULL; |
13b9414b PB |
402 | } |
403 | ||
c29c1dd3 | 404 | if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) { |
111049a4 | 405 | return NULL; |
c29c1dd3 FZ |
406 | } |
407 | ||
408 | if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) { | |
111049a4 | 409 | return NULL; |
c29c1dd3 FZ |
410 | } |
411 | ||
8fc898ce SG |
412 | if (perf->max_workers < 1 || perf->max_workers > INT_MAX) { |
413 | error_setg(errp, "max-workers must be between 1 and %d", INT_MAX); | |
2c59fd83 VSO |
414 | return NULL; |
415 | } | |
416 | ||
417 | if (perf->max_chunk < 0) { | |
418 | error_setg(errp, "max-chunk must be zero (which means no limit) or " | |
419 | "positive"); | |
420 | return NULL; | |
421 | } | |
422 | ||
1a2b8b40 | 423 | if (sync_bitmap) { |
b30ffbef JS |
424 | /* If we need to write to this bitmap, check that we can: */ |
425 | if (bitmap_mode != BITMAP_SYNC_MODE_NEVER && | |
426 | bdrv_dirty_bitmap_check(sync_bitmap, BDRV_BITMAP_DEFAULT, errp)) { | |
427 | return NULL; | |
428 | } | |
429 | ||
d58d8453 | 430 | /* Create a new bitmap, and freeze/disable this one. */ |
5deb6cbd | 431 | if (bdrv_dirty_bitmap_create_successor(sync_bitmap, errp) < 0) { |
111049a4 | 432 | return NULL; |
d58d8453 | 433 | } |
d58d8453 JS |
434 | } |
435 | ||
98d2c6f2 DM |
436 | len = bdrv_getlength(bs); |
437 | if (len < 0) { | |
58226634 KW |
438 | error_setg_errno(errp, -len, "Unable to get length for '%s'", |
439 | bdrv_get_device_or_node_name(bs)); | |
d58d8453 | 440 | goto error; |
98d2c6f2 DM |
441 | } |
442 | ||
958a04bd KW |
443 | target_len = bdrv_getlength(target); |
444 | if (target_len < 0) { | |
445 | error_setg_errno(errp, -target_len, "Unable to get length for '%s'", | |
446 | bdrv_get_device_or_node_name(bs)); | |
447 | goto error; | |
448 | } | |
449 | ||
450 | if (target_len != len) { | |
451 | error_setg(errp, "Source and target image have different sizes"); | |
452 | goto error; | |
453 | } | |
454 | ||
4c1e992b | 455 | cbw = bdrv_cbw_append(bs, target, filter_node_name, &bcs, errp); |
d003e0ae | 456 | if (!cbw) { |
00e30f05 VSO |
457 | goto error; |
458 | } | |
459 | ||
b518e9e9 VSO |
460 | cluster_size = block_copy_cluster_size(bcs); |
461 | ||
462 | if (perf->max_chunk && perf->max_chunk < cluster_size) { | |
463 | error_setg(errp, "Required max-chunk (%" PRIi64 ") is less than backup " | |
464 | "cluster size (%" PRIi64 ")", perf->max_chunk, cluster_size); | |
465 | goto error; | |
466 | } | |
467 | ||
843670f3 | 468 | /* job->len is fixed, so we can't allow resize */ |
d003e0ae | 469 | job = block_job_create(job_id, &backup_job_driver, txn, cbw, |
00e30f05 | 470 | 0, BLK_PERM_ALL, |
843670f3 VSO |
471 | speed, creation_flags, cb, opaque, errp); |
472 | if (!job) { | |
473 | goto error; | |
474 | } | |
475 | ||
d003e0ae | 476 | job->cbw = cbw; |
843670f3 | 477 | job->source_bs = bs; |
ff789bf5 | 478 | job->target_bs = target; |
843670f3 VSO |
479 | job->on_source_error = on_source_error; |
480 | job->on_target_error = on_target_error; | |
481 | job->sync_mode = sync_mode; | |
482 | job->sync_bitmap = sync_bitmap; | |
483 | job->bitmap_mode = bitmap_mode; | |
00e30f05 | 484 | job->bcs = bcs; |
ae6b12fa | 485 | job->cluster_size = cluster_size; |
843670f3 | 486 | job->len = len; |
86c6a3b6 | 487 | job->perf = *perf; |
4c9bca7e | 488 | |
2a6511df | 489 | block_copy_set_copy_opts(bcs, perf->use_copy_range, compress); |
d0ebeca1 | 490 | block_copy_set_progress_meter(bcs, &job->common.job.progress); |
71eed4ce | 491 | block_copy_set_speed(bcs, speed); |
0f4b02b7 | 492 | |
d003e0ae | 493 | /* Required permissions are taken by copy-before-write filter target */ |
76d554e2 KW |
494 | block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL, |
495 | &error_abort); | |
111049a4 JS |
496 | |
497 | return &job->common; | |
d58d8453 JS |
498 | |
499 | error: | |
500 | if (sync_bitmap) { | |
5deb6cbd | 501 | bdrv_reclaim_dirty_bitmap(sync_bitmap, NULL); |
d58d8453 | 502 | } |
d003e0ae VSO |
503 | if (cbw) { |
504 | bdrv_cbw_drop(cbw); | |
91ab6883 | 505 | } |
111049a4 JS |
506 | |
507 | return NULL; | |
98d2c6f2 | 508 | } |