]>
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" |
98d2c6f2 | 25 | #include "qemu/ratelimit.h" |
f348b6d1 | 26 | #include "qemu/cutils.h" |
373340b2 | 27 | #include "sysemu/block-backend.h" |
b2f56462 | 28 | #include "qemu/bitmap.h" |
a410a7f1 | 29 | #include "qemu/error-report.h" |
98d2c6f2 | 30 | |
00e30f05 VSO |
31 | #include "block/backup-top.h" |
32 | ||
16096a4d | 33 | #define BACKUP_CLUSTER_SIZE_DEFAULT (1 << 16) |
98d2c6f2 | 34 | |
98d2c6f2 DM |
35 | typedef struct BackupBlockJob { |
36 | BlockJob common; | |
00e30f05 | 37 | BlockDriverState *backup_top; |
2c8074c4 | 38 | BlockDriverState *source_bs; |
62aa1fbe | 39 | |
d58d8453 | 40 | BdrvDirtyBitmap *sync_bitmap; |
62aa1fbe | 41 | |
fc5d3f84 | 42 | MirrorSyncMode sync_mode; |
c8b56501 | 43 | BitmapSyncMode bitmap_mode; |
98d2c6f2 DM |
44 | BlockdevOnError on_source_error; |
45 | BlockdevOnError on_target_error; | |
05df8a6a | 46 | uint64_t len; |
cf79cdf6 | 47 | uint64_t bytes_read; |
16096a4d | 48 | int64_t cluster_size; |
a193b0f0 | 49 | |
2c8074c4 | 50 | BlockCopyState *bcs; |
98d2c6f2 DM |
51 | } BackupBlockJob; |
52 | ||
bd21935b KW |
53 | static const BlockJobDriver backup_job_driver; |
54 | ||
2c8074c4 VSO |
55 | static void backup_progress_bytes_callback(int64_t bytes, void *opaque) |
56 | { | |
57 | BackupBlockJob *s = opaque; | |
58 | ||
59 | s->bytes_read += bytes; | |
2c8074c4 VSO |
60 | } |
61 | ||
0bd0c443 VSO |
62 | static int coroutine_fn backup_do_cow(BackupBlockJob *job, |
63 | int64_t offset, uint64_t bytes, | |
00e30f05 | 64 | bool *error_is_read) |
0bd0c443 | 65 | { |
0bd0c443 VSO |
66 | int ret = 0; |
67 | int64_t start, end; /* bytes */ | |
68 | ||
0bd0c443 VSO |
69 | start = QEMU_ALIGN_DOWN(offset, job->cluster_size); |
70 | end = QEMU_ALIGN_UP(bytes + offset, job->cluster_size); | |
71 | ||
72 | trace_backup_do_cow_enter(job, start, offset, bytes); | |
73 | ||
00e30f05 | 74 | ret = block_copy(job->bcs, start, end - start, error_is_read); |
0bd0c443 | 75 | |
03f5d60b | 76 | trace_backup_do_cow_return(job, offset, bytes, ret); |
98d2c6f2 | 77 | |
98d2c6f2 DM |
78 | return ret; |
79 | } | |
80 | ||
b976ea3c FZ |
81 | static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret) |
82 | { | |
83 | BdrvDirtyBitmap *bm; | |
c23909e5 JS |
84 | bool sync = (((ret == 0) || (job->bitmap_mode == BITMAP_SYNC_MODE_ALWAYS)) \ |
85 | && (job->bitmap_mode != BITMAP_SYNC_MODE_NEVER)); | |
b976ea3c | 86 | |
c23909e5 | 87 | if (sync) { |
cf0cd293 | 88 | /* |
c23909e5 JS |
89 | * We succeeded, or we always intended to sync the bitmap. |
90 | * Delete this bitmap and install the child. | |
cf0cd293 | 91 | */ |
5deb6cbd | 92 | bm = bdrv_dirty_bitmap_abdicate(job->sync_bitmap, NULL); |
c23909e5 JS |
93 | } else { |
94 | /* | |
95 | * We failed, or we never intended to sync the bitmap anyway. | |
96 | * Merge the successor back into the parent, keeping all data. | |
97 | */ | |
5deb6cbd | 98 | bm = bdrv_reclaim_dirty_bitmap(job->sync_bitmap, NULL); |
c23909e5 JS |
99 | } |
100 | ||
101 | assert(bm); | |
102 | ||
103 | if (ret < 0 && job->bitmap_mode == BITMAP_SYNC_MODE_ALWAYS) { | |
104 | /* If we failed and synced, merge in the bits we didn't copy: */ | |
397f4e9d | 105 | bdrv_dirty_bitmap_merge_internal(bm, block_copy_dirty_bitmap(job->bcs), |
c23909e5 | 106 | NULL, true); |
b976ea3c FZ |
107 | } |
108 | } | |
109 | ||
4ad35181 | 110 | static void backup_commit(Job *job) |
c347b2c6 | 111 | { |
4ad35181 | 112 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
c347b2c6 JS |
113 | if (s->sync_bitmap) { |
114 | backup_cleanup_sync_bitmap(s, 0); | |
115 | } | |
116 | } | |
117 | ||
4ad35181 | 118 | static void backup_abort(Job *job) |
c347b2c6 | 119 | { |
4ad35181 | 120 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
c347b2c6 JS |
121 | if (s->sync_bitmap) { |
122 | backup_cleanup_sync_bitmap(s, -1); | |
123 | } | |
124 | } | |
125 | ||
4ad35181 | 126 | static void backup_clean(Job *job) |
e8a40bf7 | 127 | { |
4ad35181 | 128 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
00e30f05 | 129 | bdrv_backup_top_drop(s->backup_top); |
e8a40bf7 JS |
130 | } |
131 | ||
49d3e828 WC |
132 | void backup_do_checkpoint(BlockJob *job, Error **errp) |
133 | { | |
134 | BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common); | |
49d3e828 | 135 | |
bd21935b | 136 | assert(block_job_driver(job) == &backup_job_driver); |
49d3e828 WC |
137 | |
138 | if (backup_job->sync_mode != MIRROR_SYNC_MODE_NONE) { | |
139 | error_setg(errp, "The backup job only supports block checkpoint in" | |
140 | " sync=none mode"); | |
141 | return; | |
142 | } | |
143 | ||
397f4e9d VSO |
144 | bdrv_set_dirty_bitmap(block_copy_dirty_bitmap(backup_job->bcs), 0, |
145 | backup_job->len); | |
49d3e828 WC |
146 | } |
147 | ||
98d2c6f2 DM |
148 | static BlockErrorAction backup_error_action(BackupBlockJob *job, |
149 | bool read, int error) | |
150 | { | |
151 | if (read) { | |
81e254dc KW |
152 | return block_job_error_action(&job->common, job->on_source_error, |
153 | true, error); | |
98d2c6f2 | 154 | } else { |
81e254dc KW |
155 | return block_job_error_action(&job->common, job->on_target_error, |
156 | false, error); | |
98d2c6f2 DM |
157 | } |
158 | } | |
159 | ||
d58d8453 JS |
160 | static bool coroutine_fn yield_and_check(BackupBlockJob *job) |
161 | { | |
dee81d51 KW |
162 | uint64_t delay_ns; |
163 | ||
daa7f2f9 | 164 | if (job_is_cancelled(&job->common.job)) { |
d58d8453 JS |
165 | return true; |
166 | } | |
167 | ||
0e23e382 VSO |
168 | /* |
169 | * We need to yield even for delay_ns = 0 so that bdrv_drain_all() can | |
170 | * return. Without a yield, the VM would not reboot. | |
171 | */ | |
dee81d51 KW |
172 | delay_ns = block_job_ratelimit_get_delay(&job->common, job->bytes_read); |
173 | job->bytes_read = 0; | |
5d43e86e | 174 | job_sleep_ns(&job->common.job, delay_ns); |
d58d8453 | 175 | |
daa7f2f9 | 176 | if (job_is_cancelled(&job->common.job)) { |
d58d8453 JS |
177 | return true; |
178 | } | |
179 | ||
180 | return false; | |
181 | } | |
182 | ||
c334e897 | 183 | static int coroutine_fn backup_loop(BackupBlockJob *job) |
d58d8453 JS |
184 | { |
185 | bool error_is_read; | |
a8389e31 | 186 | int64_t offset; |
62aa1fbe | 187 | BdrvDirtyBitmapIter *bdbi; |
62aa1fbe | 188 | int ret = 0; |
d58d8453 | 189 | |
397f4e9d | 190 | bdbi = bdrv_dirty_iter_new(block_copy_dirty_bitmap(job->bcs)); |
62aa1fbe | 191 | while ((offset = bdrv_dirty_iter_next(bdbi)) != -1) { |
53f1c879 VSO |
192 | do { |
193 | if (yield_and_check(job)) { | |
62aa1fbe | 194 | goto out; |
53f1c879 | 195 | } |
00e30f05 | 196 | ret = backup_do_cow(job, offset, job->cluster_size, &error_is_read); |
53f1c879 VSO |
197 | if (ret < 0 && backup_error_action(job, error_is_read, -ret) == |
198 | BLOCK_ERROR_ACTION_REPORT) | |
199 | { | |
62aa1fbe | 200 | goto out; |
53f1c879 VSO |
201 | } |
202 | } while (ret < 0); | |
d58d8453 JS |
203 | } |
204 | ||
62aa1fbe JS |
205 | out: |
206 | bdrv_dirty_iter_free(bdbi); | |
207 | return ret; | |
d58d8453 JS |
208 | } |
209 | ||
397f4e9d | 210 | static void backup_init_bcs_bitmap(BackupBlockJob *job) |
8cc6dc62 | 211 | { |
141cdcdf JS |
212 | bool ret; |
213 | uint64_t estimate; | |
397f4e9d | 214 | BdrvDirtyBitmap *bcs_bitmap = block_copy_dirty_bitmap(job->bcs); |
141cdcdf JS |
215 | |
216 | if (job->sync_mode == MIRROR_SYNC_MODE_BITMAP) { | |
397f4e9d | 217 | ret = bdrv_dirty_bitmap_merge_internal(bcs_bitmap, job->sync_bitmap, |
141cdcdf JS |
218 | NULL, true); |
219 | assert(ret); | |
220 | } else { | |
7e30dd61 JS |
221 | if (job->sync_mode == MIRROR_SYNC_MODE_TOP) { |
222 | /* | |
223 | * We can't hog the coroutine to initialize this thoroughly. | |
224 | * Set a flag and resume work when we are able to yield safely. | |
225 | */ | |
397f4e9d | 226 | block_copy_set_skip_unallocated(job->bcs, true); |
7e30dd61 | 227 | } |
397f4e9d | 228 | bdrv_set_dirty_bitmap(bcs_bitmap, 0, job->len); |
141cdcdf | 229 | } |
8cc6dc62 | 230 | |
397f4e9d | 231 | estimate = bdrv_get_dirty_count(bcs_bitmap); |
141cdcdf | 232 | job_progress_set_remaining(&job->common.job, estimate); |
8cc6dc62 VSO |
233 | } |
234 | ||
68702775 | 235 | static int coroutine_fn backup_run(Job *job, Error **errp) |
98d2c6f2 | 236 | { |
68702775 | 237 | BackupBlockJob *s = container_of(job, BackupBlockJob, common.job); |
98d2c6f2 DM |
238 | int ret = 0; |
239 | ||
397f4e9d | 240 | backup_init_bcs_bitmap(s); |
8cc6dc62 | 241 | |
7e30dd61 JS |
242 | if (s->sync_mode == MIRROR_SYNC_MODE_TOP) { |
243 | int64_t offset = 0; | |
244 | int64_t count; | |
245 | ||
246 | for (offset = 0; offset < s->len; ) { | |
247 | if (yield_and_check(s)) { | |
248 | ret = -ECANCELED; | |
249 | goto out; | |
250 | } | |
251 | ||
2c8074c4 | 252 | ret = block_copy_reset_unallocated(s->bcs, offset, &count); |
7e30dd61 JS |
253 | if (ret < 0) { |
254 | goto out; | |
255 | } | |
256 | ||
257 | offset += count; | |
258 | } | |
397f4e9d | 259 | block_copy_set_skip_unallocated(s->bcs, false); |
7e30dd61 JS |
260 | } |
261 | ||
68702775 | 262 | if (s->sync_mode == MIRROR_SYNC_MODE_NONE) { |
0e23e382 | 263 | /* |
397f4e9d | 264 | * All bits are set in bcs bitmap to allow any cluster to be copied. |
0e23e382 VSO |
265 | * This does not actually require them to be copied. |
266 | */ | |
68702775 | 267 | while (!job_is_cancelled(job)) { |
0e23e382 VSO |
268 | /* |
269 | * Yield until the job is cancelled. We just let our before_write | |
270 | * notify callback service CoW requests. | |
271 | */ | |
68702775 | 272 | job_yield(job); |
98d2c6f2 | 273 | } |
fc5d3f84 | 274 | } else { |
c334e897 | 275 | ret = backup_loop(s); |
98d2c6f2 DM |
276 | } |
277 | ||
7e30dd61 | 278 | out: |
f67432a2 | 279 | return ret; |
98d2c6f2 DM |
280 | } |
281 | ||
a7815a76 | 282 | static const BlockJobDriver backup_job_driver = { |
33e9e9bd KW |
283 | .job_driver = { |
284 | .instance_size = sizeof(BackupBlockJob), | |
252291ea | 285 | .job_type = JOB_TYPE_BACKUP, |
80fa2c75 | 286 | .free = block_job_free, |
b15de828 | 287 | .user_resume = block_job_user_resume, |
f67432a2 | 288 | .run = backup_run, |
4ad35181 KW |
289 | .commit = backup_commit, |
290 | .abort = backup_abort, | |
291 | .clean = backup_clean, | |
bb0c9409 | 292 | } |
a7815a76 JS |
293 | }; |
294 | ||
ae6b12fa VSO |
295 | static int64_t backup_calculate_cluster_size(BlockDriverState *target, |
296 | Error **errp) | |
297 | { | |
298 | int ret; | |
299 | BlockDriverInfo bdi; | |
300 | ||
301 | /* | |
302 | * If there is no backing file on the target, we cannot rely on COW if our | |
303 | * backup cluster size is smaller than the target cluster size. Even for | |
304 | * targets with a backing file, try to avoid COW if possible. | |
305 | */ | |
306 | ret = bdrv_get_info(target, &bdi); | |
307 | if (ret == -ENOTSUP && !target->backing) { | |
308 | /* Cluster size is not defined */ | |
309 | warn_report("The target block device doesn't provide " | |
310 | "information about the block size and it doesn't have a " | |
311 | "backing file. The default block size of %u bytes is " | |
312 | "used. If the actual block size of the target exceeds " | |
313 | "this default, the backup may be unusable", | |
314 | BACKUP_CLUSTER_SIZE_DEFAULT); | |
315 | return BACKUP_CLUSTER_SIZE_DEFAULT; | |
316 | } else if (ret < 0 && !target->backing) { | |
317 | error_setg_errno(errp, -ret, | |
318 | "Couldn't determine the cluster size of the target image, " | |
319 | "which has no backing file"); | |
320 | error_append_hint(errp, | |
321 | "Aborting, since this may create an unusable destination image\n"); | |
322 | return ret; | |
323 | } else if (ret < 0 && target->backing) { | |
324 | /* Not fatal; just trudge on ahead. */ | |
325 | return BACKUP_CLUSTER_SIZE_DEFAULT; | |
326 | } | |
327 | ||
328 | return MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size); | |
329 | } | |
330 | ||
111049a4 | 331 | BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, |
70559d49 AG |
332 | BlockDriverState *target, int64_t speed, |
333 | MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap, | |
c8b56501 | 334 | BitmapSyncMode bitmap_mode, |
13b9414b | 335 | bool compress, |
00e30f05 | 336 | const char *filter_node_name, |
98d2c6f2 DM |
337 | BlockdevOnError on_source_error, |
338 | BlockdevOnError on_target_error, | |
47970dfb | 339 | int creation_flags, |
097310b5 | 340 | BlockCompletionFunc *cb, void *opaque, |
62c9e416 | 341 | JobTxn *txn, Error **errp) |
98d2c6f2 | 342 | { |
958a04bd | 343 | int64_t len, target_len; |
91ab6883 | 344 | BackupBlockJob *job = NULL; |
ae6b12fa | 345 | int64_t cluster_size; |
2c8074c4 | 346 | BdrvRequestFlags write_flags; |
00e30f05 VSO |
347 | BlockDriverState *backup_top = NULL; |
348 | BlockCopyState *bcs = NULL; | |
98d2c6f2 DM |
349 | |
350 | assert(bs); | |
351 | assert(target); | |
98d2c6f2 | 352 | |
a6c9365a JS |
353 | /* QMP interface protects us from these cases */ |
354 | assert(sync_mode != MIRROR_SYNC_MODE_INCREMENTAL); | |
355 | assert(sync_bitmap || sync_mode != MIRROR_SYNC_MODE_BITMAP); | |
356 | ||
c29c1dd3 FZ |
357 | if (bs == target) { |
358 | error_setg(errp, "Source and target cannot be the same"); | |
111049a4 | 359 | return NULL; |
c29c1dd3 FZ |
360 | } |
361 | ||
c29c1dd3 FZ |
362 | if (!bdrv_is_inserted(bs)) { |
363 | error_setg(errp, "Device is not inserted: %s", | |
364 | bdrv_get_device_name(bs)); | |
111049a4 | 365 | return NULL; |
c29c1dd3 FZ |
366 | } |
367 | ||
368 | if (!bdrv_is_inserted(target)) { | |
369 | error_setg(errp, "Device is not inserted: %s", | |
370 | bdrv_get_device_name(target)); | |
111049a4 | 371 | return NULL; |
c29c1dd3 FZ |
372 | } |
373 | ||
ac850bf0 | 374 | if (compress && !block_driver_can_compress(target->drv)) { |
13b9414b PB |
375 | error_setg(errp, "Compression is not supported for this drive %s", |
376 | bdrv_get_device_name(target)); | |
111049a4 | 377 | return NULL; |
13b9414b PB |
378 | } |
379 | ||
c29c1dd3 | 380 | if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) { |
111049a4 | 381 | return NULL; |
c29c1dd3 FZ |
382 | } |
383 | ||
384 | if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) { | |
111049a4 | 385 | return NULL; |
c29c1dd3 FZ |
386 | } |
387 | ||
1a2b8b40 | 388 | if (sync_bitmap) { |
b30ffbef JS |
389 | /* If we need to write to this bitmap, check that we can: */ |
390 | if (bitmap_mode != BITMAP_SYNC_MODE_NEVER && | |
391 | bdrv_dirty_bitmap_check(sync_bitmap, BDRV_BITMAP_DEFAULT, errp)) { | |
392 | return NULL; | |
393 | } | |
394 | ||
d58d8453 | 395 | /* Create a new bitmap, and freeze/disable this one. */ |
5deb6cbd | 396 | if (bdrv_dirty_bitmap_create_successor(sync_bitmap, errp) < 0) { |
111049a4 | 397 | return NULL; |
d58d8453 | 398 | } |
d58d8453 JS |
399 | } |
400 | ||
98d2c6f2 DM |
401 | len = bdrv_getlength(bs); |
402 | if (len < 0) { | |
58226634 KW |
403 | error_setg_errno(errp, -len, "Unable to get length for '%s'", |
404 | bdrv_get_device_or_node_name(bs)); | |
d58d8453 | 405 | goto error; |
98d2c6f2 DM |
406 | } |
407 | ||
958a04bd KW |
408 | target_len = bdrv_getlength(target); |
409 | if (target_len < 0) { | |
410 | error_setg_errno(errp, -target_len, "Unable to get length for '%s'", | |
411 | bdrv_get_device_or_node_name(bs)); | |
412 | goto error; | |
413 | } | |
414 | ||
415 | if (target_len != len) { | |
416 | error_setg(errp, "Source and target image have different sizes"); | |
417 | goto error; | |
418 | } | |
419 | ||
ae6b12fa VSO |
420 | cluster_size = backup_calculate_cluster_size(target, errp); |
421 | if (cluster_size < 0) { | |
422 | goto error; | |
423 | } | |
424 | ||
a1ed82b4 | 425 | /* |
372c67ea VSO |
426 | * If source is in backing chain of target assume that target is going to be |
427 | * used for "image fleecing", i.e. it should represent a kind of snapshot of | |
428 | * source at backup-start point in time. And target is going to be read by | |
429 | * somebody (for example, used as NBD export) during backup job. | |
430 | * | |
431 | * In this case, we need to add BDRV_REQ_SERIALISING write flag to avoid | |
432 | * intersection of backup writes and third party reads from target, | |
433 | * otherwise reading from target we may occasionally read already updated by | |
434 | * guest data. | |
435 | * | |
436 | * For more information see commit f8d59dfb40bb and test | |
437 | * tests/qemu-iotests/222 | |
a1ed82b4 | 438 | */ |
2c8074c4 VSO |
439 | write_flags = (bdrv_chain_contains(target, bs) ? BDRV_REQ_SERIALISING : 0) | |
440 | (compress ? BDRV_REQ_WRITE_COMPRESSED : 0), | |
441 | ||
00e30f05 VSO |
442 | backup_top = bdrv_backup_top_append(bs, target, filter_node_name, |
443 | cluster_size, write_flags, &bcs, errp); | |
444 | if (!backup_top) { | |
445 | goto error; | |
446 | } | |
447 | ||
843670f3 | 448 | /* job->len is fixed, so we can't allow resize */ |
00e30f05 VSO |
449 | job = block_job_create(job_id, &backup_job_driver, txn, backup_top, |
450 | 0, BLK_PERM_ALL, | |
843670f3 VSO |
451 | speed, creation_flags, cb, opaque, errp); |
452 | if (!job) { | |
453 | goto error; | |
454 | } | |
455 | ||
00e30f05 | 456 | job->backup_top = backup_top; |
843670f3 VSO |
457 | job->source_bs = bs; |
458 | job->on_source_error = on_source_error; | |
459 | job->on_target_error = on_target_error; | |
460 | job->sync_mode = sync_mode; | |
461 | job->sync_bitmap = sync_bitmap; | |
462 | job->bitmap_mode = bitmap_mode; | |
00e30f05 | 463 | job->bcs = bcs; |
ae6b12fa | 464 | job->cluster_size = cluster_size; |
843670f3 | 465 | job->len = len; |
4c9bca7e | 466 | |
d0ebeca1 VSO |
467 | block_copy_set_progress_callback(bcs, backup_progress_bytes_callback, job); |
468 | block_copy_set_progress_meter(bcs, &job->common.job.progress); | |
0f4b02b7 | 469 | |
00e30f05 | 470 | /* Required permissions are already taken by backup-top target */ |
76d554e2 KW |
471 | block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL, |
472 | &error_abort); | |
111049a4 JS |
473 | |
474 | return &job->common; | |
d58d8453 JS |
475 | |
476 | error: | |
477 | if (sync_bitmap) { | |
5deb6cbd | 478 | bdrv_reclaim_dirty_bitmap(sync_bitmap, NULL); |
d58d8453 | 479 | } |
8ccf458a | 480 | if (backup_top) { |
00e30f05 | 481 | bdrv_backup_top_drop(backup_top); |
91ab6883 | 482 | } |
111049a4 JS |
483 | |
484 | return NULL; | |
98d2c6f2 | 485 | } |