]>
Commit | Line | Data |
---|---|---|
e9ee6d7c | 1 | From d48092bb9901112b3356aa8d461c45ffb4ec2b9a Mon Sep 17 00:00:00 2001 |
ca0fe5f5 WB |
2 | From: Wolfgang Bumiller <w.bumiller@proxmox.com> |
3 | Date: Wed, 9 Dec 2015 15:20:56 +0100 | |
adeb0c7a | 4 | Subject: [PATCH 15/48] backup: add pve monitor commands |
ca0fe5f5 WB |
5 | |
6 | --- | |
1a91ab45 WB |
7 | blockdev.c | 465 ++++++++++++++++++++++++++++++++++++++++++++++ |
8 | blockjob.c | 11 +- | |
68a30562 WB |
9 | hmp-commands-info.hx | 13 ++ |
10 | hmp-commands.hx | 29 +++ | |
1a91ab45 | 11 | hmp.c | 61 ++++++ |
68a30562 WB |
12 | hmp.h | 3 + |
13 | include/block/block_int.h | 2 +- | |
1a91ab45 WB |
14 | qapi-schema.json | 90 +++++++++ |
15 | 8 files changed, 668 insertions(+), 6 deletions(-) | |
ca0fe5f5 WB |
16 | |
17 | diff --git a/blockdev.c b/blockdev.c | |
1a91ab45 | 18 | index bb3fc5b..3e5c9ce 100644 |
ca0fe5f5 WB |
19 | --- a/blockdev.c |
20 | +++ b/blockdev.c | |
1a91ab45 WB |
21 | @@ -35,6 +35,7 @@ |
22 | #include "sysemu/blockdev.h" | |
23 | #include "hw/block/block.h" | |
24 | #include "block/blockjob.h" | |
25 | +#include "block/blockjob_int.h" | |
26 | #include "block/throttle-groups.h" | |
27 | #include "monitor/monitor.h" | |
28 | #include "qemu/error-report.h" | |
29 | @@ -53,6 +54,7 @@ | |
b07d35a5 TL |
30 | #include "qemu/cutils.h" |
31 | #include "qemu/help_option.h" | |
1a91ab45 | 32 | #include "qemu/throttle-options.h" |
ca0fe5f5 WB |
33 | +#include "vma.h" |
34 | ||
b07d35a5 TL |
35 | static QTAILQ_HEAD(, BlockDriverState) monitor_bdrv_states = |
36 | QTAILQ_HEAD_INITIALIZER(monitor_bdrv_states); | |
1a91ab45 WB |
37 | @@ -2956,6 +2958,469 @@ out: |
38 | aio_context_release(aio_context); | |
ca0fe5f5 WB |
39 | } |
40 | ||
1a91ab45 WB |
41 | +void block_job_event_cancelled(BlockJob *job); |
42 | +void block_job_event_completed(BlockJob *job, const char *msg); | |
43 | +static void block_job_cb(void *opaque, int ret) | |
44 | +{ | |
45 | + /* Note that this function may be executed from another AioContext besides | |
46 | + * the QEMU main loop. If you need to access anything that assumes the | |
47 | + * QEMU global mutex, use a BH or introduce a mutex. | |
48 | + */ | |
49 | + | |
50 | + BlockDriverState *bs = opaque; | |
51 | + const char *msg = NULL; | |
52 | + | |
53 | + assert(bs->job); | |
54 | + | |
55 | + if (ret < 0) { | |
56 | + msg = strerror(-ret); | |
57 | + } | |
58 | + | |
59 | + if (block_job_is_cancelled(bs->job)) { | |
60 | + block_job_event_cancelled(bs->job); | |
61 | + } else { | |
62 | + block_job_event_completed(bs->job, msg); | |
63 | + } | |
64 | +} | |
65 | + | |
ca0fe5f5 WB |
66 | +/* PVE backup related function */ |
67 | + | |
68 | +static struct PVEBackupState { | |
69 | + Error *error; | |
70 | + bool cancel; | |
71 | + uuid_t uuid; | |
72 | + char uuid_str[37]; | |
73 | + int64_t speed; | |
74 | + time_t start_time; | |
75 | + time_t end_time; | |
76 | + char *backup_file; | |
77 | + VmaWriter *vmaw; | |
78 | + GList *di_list; | |
79 | + size_t total; | |
80 | + size_t transferred; | |
81 | + size_t zero_bytes; | |
82 | +} backup_state; | |
83 | + | |
84 | +typedef struct PVEBackupDevInfo { | |
85 | + BlockDriverState *bs; | |
86 | + size_t size; | |
87 | + uint8_t dev_id; | |
88 | + //bool started; | |
89 | + bool completed; | |
90 | +} PVEBackupDevInfo; | |
91 | + | |
92 | +static void pvebackup_run_next_job(void); | |
93 | + | |
68a30562 | 94 | +static int pvebackup_dump_cb(void *opaque, BlockBackend *target, |
ca0fe5f5 WB |
95 | + int64_t sector_num, int n_sectors, |
96 | + unsigned char *buf) | |
97 | +{ | |
98 | + PVEBackupDevInfo *di = opaque; | |
99 | + | |
100 | + if (sector_num & 0x7f) { | |
101 | + if (!backup_state.error) { | |
102 | + error_setg(&backup_state.error, | |
103 | + "got unaligned write inside backup dump " | |
104 | + "callback (sector %ld)", sector_num); | |
105 | + } | |
106 | + return -1; // not aligned to cluster size | |
107 | + } | |
108 | + | |
109 | + int64_t cluster_num = sector_num >> 7; | |
110 | + int size = n_sectors * BDRV_SECTOR_SIZE; | |
111 | + | |
112 | + int ret = -1; | |
113 | + | |
114 | + if (backup_state.vmaw) { | |
115 | + size_t zero_bytes = 0; | |
116 | + ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num, | |
117 | + buf, &zero_bytes); | |
118 | + backup_state.zero_bytes += zero_bytes; | |
119 | + } else { | |
120 | + ret = size; | |
121 | + if (!buf) { | |
122 | + backup_state.zero_bytes += size; | |
123 | + } | |
124 | + } | |
125 | + | |
126 | + backup_state.transferred += size; | |
127 | + | |
128 | + return ret; | |
129 | +} | |
130 | + | |
131 | +static void pvebackup_cleanup(void) | |
132 | +{ | |
133 | + backup_state.end_time = time(NULL); | |
134 | + | |
135 | + if (backup_state.vmaw) { | |
136 | + Error *local_err = NULL; | |
137 | + vma_writer_close(backup_state.vmaw, &local_err); | |
138 | + error_propagate(&backup_state.error, local_err); | |
139 | + backup_state.vmaw = NULL; | |
140 | + } | |
141 | + | |
142 | + if (backup_state.di_list) { | |
143 | + GList *l = backup_state.di_list; | |
144 | + while (l) { | |
145 | + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; | |
146 | + l = g_list_next(l); | |
147 | + g_free(di); | |
148 | + } | |
149 | + g_list_free(backup_state.di_list); | |
150 | + backup_state.di_list = NULL; | |
151 | + } | |
152 | +} | |
153 | + | |
154 | +static void pvebackup_complete_cb(void *opaque, int ret) | |
155 | +{ | |
156 | + PVEBackupDevInfo *di = opaque; | |
157 | + | |
158 | + assert(backup_state.vmaw); | |
159 | + | |
160 | + di->completed = true; | |
161 | + | |
162 | + if (ret < 0 && !backup_state.error) { | |
163 | + error_setg(&backup_state.error, "job failed with err %d - %s", | |
164 | + ret, strerror(-ret)); | |
165 | + } | |
166 | + | |
167 | + BlockDriverState *bs = di->bs; | |
168 | + | |
169 | + di->bs = NULL; | |
170 | + | |
171 | + vma_writer_close_stream(backup_state.vmaw, di->dev_id); | |
172 | + | |
173 | + block_job_cb(bs, ret); | |
174 | + | |
175 | + if (!backup_state.cancel) { | |
176 | + pvebackup_run_next_job(); | |
177 | + } | |
178 | +} | |
179 | + | |
180 | +static void pvebackup_cancel(void *opaque) | |
181 | +{ | |
182 | + backup_state.cancel = true; | |
183 | + | |
184 | + if (!backup_state.error) { | |
185 | + error_setg(&backup_state.error, "backup cancelled"); | |
186 | + } | |
187 | + | |
188 | + /* drain all i/o (awake jobs waiting for aio) */ | |
189 | + bdrv_drain_all(); | |
190 | + | |
191 | + GList *l = backup_state.di_list; | |
192 | + while (l) { | |
193 | + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; | |
194 | + l = g_list_next(l); | |
195 | + if (!di->completed && di->bs) { | |
196 | + BlockJob *job = di->bs->job; | |
197 | + if (job) { | |
198 | + if (!di->completed) { | |
199 | + block_job_cancel_sync(job); | |
200 | + } | |
201 | + } | |
202 | + } | |
203 | + } | |
204 | + | |
205 | + pvebackup_cleanup(); | |
206 | +} | |
207 | + | |
208 | +void qmp_backup_cancel(Error **errp) | |
209 | +{ | |
68a30562 WB |
210 | + Coroutine *co = qemu_coroutine_create(pvebackup_cancel, NULL); |
211 | + qemu_coroutine_enter(co); | |
ca0fe5f5 WB |
212 | + |
213 | + while (backup_state.vmaw) { | |
214 | + /* vma writer use main aio context */ | |
215 | + aio_poll(qemu_get_aio_context(), true); | |
216 | + } | |
217 | +} | |
218 | + | |
68a30562 | 219 | +bool block_job_should_pause(BlockJob *job); |
ca0fe5f5 WB |
220 | +static void pvebackup_run_next_job(void) |
221 | +{ | |
222 | + GList *l = backup_state.di_list; | |
223 | + while (l) { | |
224 | + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; | |
225 | + l = g_list_next(l); | |
226 | + if (!di->completed && di->bs && di->bs->job) { | |
227 | + BlockJob *job = di->bs->job; | |
68a30562 | 228 | + if (block_job_should_pause(job)) { |
ca0fe5f5 WB |
229 | + bool cancel = backup_state.error || backup_state.cancel; |
230 | + if (cancel) { | |
231 | + block_job_cancel(job); | |
232 | + } else { | |
233 | + block_job_resume(job); | |
234 | + } | |
235 | + } | |
236 | + return; | |
237 | + } | |
238 | + } | |
239 | + | |
240 | + pvebackup_cleanup(); | |
241 | +} | |
242 | + | |
243 | +UuidInfo *qmp_backup(const char *backup_file, bool has_format, | |
244 | + BackupFormat format, | |
245 | + bool has_config_file, const char *config_file, | |
246 | + bool has_devlist, const char *devlist, | |
247 | + bool has_speed, int64_t speed, Error **errp) | |
248 | +{ | |
249 | + BlockBackend *blk; | |
250 | + BlockDriverState *bs = NULL; | |
251 | + Error *local_err = NULL; | |
252 | + uuid_t uuid; | |
253 | + VmaWriter *vmaw = NULL; | |
254 | + gchar **devs = NULL; | |
255 | + GList *di_list = NULL; | |
256 | + GList *l; | |
257 | + UuidInfo *uuid_info; | |
258 | + | |
259 | + if (backup_state.di_list) { | |
260 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, | |
261 | + "previous backup not finished"); | |
262 | + return NULL; | |
263 | + } | |
264 | + | |
265 | + /* Todo: try to auto-detect format based on file name */ | |
266 | + format = has_format ? format : BACKUP_FORMAT_VMA; | |
267 | + | |
268 | + if (format != BACKUP_FORMAT_VMA) { | |
269 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format"); | |
270 | + return NULL; | |
271 | + } | |
272 | + | |
273 | + if (has_devlist) { | |
274 | + devs = g_strsplit_set(devlist, ",;:", -1); | |
275 | + | |
276 | + gchar **d = devs; | |
277 | + while (d && *d) { | |
278 | + blk = blk_by_name(*d); | |
279 | + if (blk) { | |
280 | + bs = blk_bs(blk); | |
281 | + if (bdrv_is_read_only(bs)) { | |
282 | + error_setg(errp, "Node '%s' is read only", *d); | |
283 | + goto err; | |
284 | + } | |
285 | + if (!bdrv_is_inserted(bs)) { | |
286 | + error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d); | |
287 | + goto err; | |
288 | + } | |
289 | + PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1); | |
290 | + di->bs = bs; | |
291 | + di_list = g_list_append(di_list, di); | |
292 | + } else { | |
293 | + error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, | |
294 | + "Device '%s' not found", *d); | |
295 | + goto err; | |
296 | + } | |
297 | + d++; | |
298 | + } | |
299 | + | |
300 | + } else { | |
68a30562 | 301 | + BdrvNextIterator it; |
ca0fe5f5 WB |
302 | + |
303 | + bs = NULL; | |
68a30562 | 304 | + for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) { |
ca0fe5f5 WB |
305 | + if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) { |
306 | + continue; | |
307 | + } | |
308 | + | |
309 | + PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1); | |
310 | + di->bs = bs; | |
311 | + di_list = g_list_append(di_list, di); | |
312 | + } | |
313 | + } | |
314 | + | |
315 | + if (!di_list) { | |
316 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list"); | |
317 | + goto err; | |
318 | + } | |
319 | + | |
320 | + size_t total = 0; | |
321 | + | |
322 | + l = di_list; | |
323 | + while (l) { | |
324 | + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; | |
325 | + l = g_list_next(l); | |
326 | + if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) { | |
327 | + goto err; | |
328 | + } | |
329 | + | |
330 | + ssize_t size = bdrv_getlength(di->bs); | |
331 | + if (size < 0) { | |
332 | + error_setg_errno(errp, -di->size, "bdrv_getlength failed"); | |
333 | + goto err; | |
334 | + } | |
335 | + di->size = size; | |
336 | + total += size; | |
337 | + } | |
338 | + | |
339 | + uuid_generate(uuid); | |
340 | + | |
341 | + vmaw = vma_writer_create(backup_file, uuid, &local_err); | |
342 | + if (!vmaw) { | |
343 | + if (local_err) { | |
344 | + error_propagate(errp, local_err); | |
345 | + } | |
346 | + goto err; | |
347 | + } | |
348 | + | |
349 | + /* register all devices for vma writer */ | |
350 | + l = di_list; | |
351 | + while (l) { | |
352 | + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; | |
353 | + l = g_list_next(l); | |
354 | + | |
355 | + const char *devname = bdrv_get_device_name(di->bs); | |
356 | + di->dev_id = vma_writer_register_stream(vmaw, devname, di->size); | |
357 | + if (di->dev_id <= 0) { | |
358 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, | |
359 | + "register_stream failed"); | |
360 | + goto err; | |
361 | + } | |
362 | + } | |
363 | + | |
364 | + /* add configuration file to archive */ | |
365 | + if (has_config_file) { | |
366 | + char *cdata = NULL; | |
367 | + gsize clen = 0; | |
368 | + GError *err = NULL; | |
369 | + if (!g_file_get_contents(config_file, &cdata, &clen, &err)) { | |
370 | + error_setg(errp, "unable to read file '%s'", config_file); | |
371 | + goto err; | |
372 | + } | |
373 | + | |
374 | + const char *basename = g_path_get_basename(config_file); | |
375 | + if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) { | |
376 | + error_setg(errp, "unable to add config data to vma archive"); | |
377 | + g_free(cdata); | |
378 | + goto err; | |
379 | + } | |
380 | + g_free(cdata); | |
381 | + } | |
382 | + | |
383 | + /* initialize global backup_state now */ | |
384 | + | |
385 | + backup_state.cancel = false; | |
386 | + | |
387 | + if (backup_state.error) { | |
388 | + error_free(backup_state.error); | |
389 | + backup_state.error = NULL; | |
390 | + } | |
391 | + | |
392 | + backup_state.speed = (has_speed && speed > 0) ? speed : 0; | |
393 | + | |
394 | + backup_state.start_time = time(NULL); | |
395 | + backup_state.end_time = 0; | |
396 | + | |
397 | + if (backup_state.backup_file) { | |
398 | + g_free(backup_state.backup_file); | |
399 | + } | |
400 | + backup_state.backup_file = g_strdup(backup_file); | |
401 | + | |
402 | + backup_state.vmaw = vmaw; | |
403 | + | |
404 | + uuid_copy(backup_state.uuid, uuid); | |
405 | + uuid_unparse_lower(uuid, backup_state.uuid_str); | |
406 | + | |
407 | + backup_state.di_list = di_list; | |
408 | + | |
409 | + backup_state.total = total; | |
410 | + backup_state.transferred = 0; | |
411 | + backup_state.zero_bytes = 0; | |
412 | + | |
413 | + /* start all jobs (paused state) */ | |
414 | + l = di_list; | |
415 | + while (l) { | |
416 | + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; | |
417 | + l = g_list_next(l); | |
418 | + | |
1a91ab45 WB |
419 | + backup_job_create(NULL, di->bs, NULL, speed, MIRROR_SYNC_MODE_FULL, NULL, |
420 | + BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, | |
421 | + pvebackup_dump_cb, pvebackup_complete_cb, di, | |
422 | + 1, NULL, &local_err); | |
ca0fe5f5 WB |
423 | + if (local_err != NULL) { |
424 | + error_setg(&backup_state.error, "backup_job_create failed"); | |
425 | + pvebackup_cancel(NULL); | |
426 | + } | |
427 | + } | |
428 | + | |
429 | + if (!backup_state.error) { | |
430 | + pvebackup_run_next_job(); // run one job | |
431 | + } | |
432 | + | |
433 | + uuid_info = g_malloc0(sizeof(*uuid_info)); | |
434 | + uuid_info->UUID = g_strdup(backup_state.uuid_str); | |
435 | + return uuid_info; | |
436 | + | |
437 | +err: | |
438 | + | |
439 | + l = di_list; | |
440 | + while (l) { | |
441 | + g_free(l->data); | |
442 | + l = g_list_next(l); | |
443 | + } | |
444 | + g_list_free(di_list); | |
445 | + | |
446 | + if (devs) { | |
447 | + g_strfreev(devs); | |
448 | + } | |
449 | + | |
450 | + if (vmaw) { | |
451 | + Error *err = NULL; | |
452 | + vma_writer_close(vmaw, &err); | |
453 | + unlink(backup_file); | |
454 | + } | |
455 | + | |
456 | + return NULL; | |
457 | +} | |
458 | + | |
459 | +BackupStatus *qmp_query_backup(Error **errp) | |
460 | +{ | |
461 | + BackupStatus *info = g_malloc0(sizeof(*info)); | |
462 | + | |
463 | + if (!backup_state.start_time) { | |
464 | + /* not started, return {} */ | |
465 | + return info; | |
466 | + } | |
467 | + | |
468 | + info->has_status = true; | |
469 | + info->has_start_time = true; | |
470 | + info->start_time = backup_state.start_time; | |
471 | + | |
472 | + if (backup_state.backup_file) { | |
473 | + info->has_backup_file = true; | |
474 | + info->backup_file = g_strdup(backup_state.backup_file); | |
475 | + } | |
476 | + | |
477 | + info->has_uuid = true; | |
478 | + info->uuid = g_strdup(backup_state.uuid_str); | |
479 | + | |
480 | + if (backup_state.end_time) { | |
481 | + if (backup_state.error) { | |
482 | + info->status = g_strdup("error"); | |
483 | + info->has_errmsg = true; | |
484 | + info->errmsg = g_strdup(error_get_pretty(backup_state.error)); | |
485 | + } else { | |
486 | + info->status = g_strdup("done"); | |
487 | + } | |
488 | + info->has_end_time = true; | |
489 | + info->end_time = backup_state.end_time; | |
490 | + } else { | |
491 | + info->status = g_strdup("active"); | |
492 | + } | |
493 | + | |
494 | + info->has_total = true; | |
495 | + info->total = backup_state.total; | |
496 | + info->has_zero_bytes = true; | |
497 | + info->zero_bytes = backup_state.zero_bytes; | |
498 | + info->has_transferred = true; | |
499 | + info->transferred = backup_state.transferred; | |
500 | + | |
501 | + return info; | |
502 | +} | |
503 | + | |
68a30562 | 504 | void qmp_block_stream(bool has_job_id, const char *job_id, const char *device, |
ca0fe5f5 | 505 | bool has_base, const char *base, |
1a91ab45 | 506 | bool has_base_node, const char *base_node, |
68a30562 | 507 | diff --git a/blockjob.c b/blockjob.c |
1a91ab45 | 508 | index 9b619f385..54bd34a 100644 |
68a30562 WB |
509 | --- a/blockjob.c |
510 | +++ b/blockjob.c | |
1a91ab45 WB |
511 | @@ -37,8 +37,8 @@ |
512 | #include "qemu/timer.h" | |
513 | #include "qapi-event.h" | |
514 | ||
515 | -static void block_job_event_cancelled(BlockJob *job); | |
516 | -static void block_job_event_completed(BlockJob *job, const char *msg); | |
517 | +void block_job_event_cancelled(BlockJob *job); | |
518 | +void block_job_event_completed(BlockJob *job, const char *msg); | |
519 | ||
520 | /* Transactional group of block jobs */ | |
521 | struct BlockJobTxn { | |
522 | @@ -473,7 +473,8 @@ void block_job_user_pause(BlockJob *job) | |
523 | block_job_pause(job); | |
68a30562 WB |
524 | } |
525 | ||
526 | -static bool block_job_should_pause(BlockJob *job) | |
527 | +bool block_job_should_pause(BlockJob *job); | |
528 | +bool block_job_should_pause(BlockJob *job) | |
529 | { | |
530 | return job->pause_count > 0; | |
531 | } | |
1a91ab45 WB |
532 | @@ -687,7 +688,7 @@ static void block_job_iostatus_set_err(BlockJob *job, int error) |
533 | } | |
534 | } | |
535 | ||
536 | -static void block_job_event_cancelled(BlockJob *job) | |
537 | +void block_job_event_cancelled(BlockJob *job) | |
538 | { | |
539 | if (block_job_is_internal(job)) { | |
540 | return; | |
541 | @@ -701,7 +702,7 @@ static void block_job_event_cancelled(BlockJob *job) | |
542 | &error_abort); | |
543 | } | |
544 | ||
545 | -static void block_job_event_completed(BlockJob *job, const char *msg) | |
546 | +void block_job_event_completed(BlockJob *job, const char *msg) | |
547 | { | |
548 | if (block_job_is_internal(job)) { | |
549 | return; | |
ca0fe5f5 | 550 | diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx |
1a91ab45 | 551 | index a53f105..1a18380 100644 |
ca0fe5f5 WB |
552 | --- a/hmp-commands-info.hx |
553 | +++ b/hmp-commands-info.hx | |
1a91ab45 | 554 | @@ -487,6 +487,19 @@ STEXI |
ca0fe5f5 WB |
555 | Show CPU statistics. |
556 | ETEXI | |
557 | ||
558 | + { | |
559 | + .name = "backup", | |
560 | + .args_type = "", | |
561 | + .params = "", | |
562 | + .help = "show backup status", | |
1a91ab45 | 563 | + .cmd = hmp_info_backup, |
ca0fe5f5 WB |
564 | + }, |
565 | + | |
566 | +STEXI | |
567 | +@item info backup | |
568 | +show backup status | |
569 | +ETEXI | |
570 | + | |
571 | #if defined(CONFIG_SLIRP) | |
572 | { | |
573 | .name = "usernet", | |
574 | diff --git a/hmp-commands.hx b/hmp-commands.hx | |
1a91ab45 | 575 | index 8819281..aea39d0 100644 |
ca0fe5f5 WB |
576 | --- a/hmp-commands.hx |
577 | +++ b/hmp-commands.hx | |
578 | @@ -87,6 +87,35 @@ STEXI | |
579 | Copy data from a backing file into a block device. | |
580 | ETEXI | |
581 | ||
582 | + { | |
583 | + .name = "backup", | |
584 | + .args_type = "backupfile:s,speed:o?,devlist:s?", | |
585 | + .params = "backupfile [speed [devlist]]", | |
586 | + .help = "create a VM Backup.", | |
1a91ab45 | 587 | + .cmd = hmp_backup, |
ca0fe5f5 WB |
588 | + }, |
589 | + | |
590 | +STEXI | |
591 | +@item backup | |
592 | +@findex backup | |
593 | +Create a VM backup. | |
594 | +ETEXI | |
595 | + | |
596 | + { | |
597 | + .name = "backup_cancel", | |
598 | + .args_type = "", | |
599 | + .params = "", | |
600 | + .help = "cancel the current VM backup", | |
1a91ab45 | 601 | + .cmd = hmp_backup_cancel, |
ca0fe5f5 WB |
602 | + }, |
603 | + | |
604 | +STEXI | |
605 | +@item backup_cancel | |
606 | +@findex backup_cancel | |
607 | +Cancel the current VM backup. | |
608 | + | |
609 | +ETEXI | |
610 | + | |
611 | { | |
612 | .name = "block_job_set_speed", | |
613 | .args_type = "device:B,speed:o", | |
614 | diff --git a/hmp.c b/hmp.c | |
1a91ab45 | 615 | index 904542d..c685ba5 100644 |
ca0fe5f5 WB |
616 | --- a/hmp.c |
617 | +++ b/hmp.c | |
1a91ab45 | 618 | @@ -151,6 +151,44 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict) |
ca0fe5f5 WB |
619 | qapi_free_MouseInfoList(mice_list); |
620 | } | |
621 | ||
622 | +void hmp_info_backup(Monitor *mon, const QDict *qdict) | |
623 | +{ | |
624 | + BackupStatus *info; | |
625 | + | |
626 | + info = qmp_query_backup(NULL); | |
627 | + if (info->has_status) { | |
628 | + if (info->has_errmsg) { | |
629 | + monitor_printf(mon, "Backup status: %s - %s\n", | |
630 | + info->status, info->errmsg); | |
631 | + } else { | |
632 | + monitor_printf(mon, "Backup status: %s\n", info->status); | |
633 | + } | |
634 | + } | |
635 | + | |
636 | + if (info->has_backup_file) { | |
637 | + monitor_printf(mon, "Start time: %s", ctime(&info->start_time)); | |
638 | + if (info->end_time) { | |
639 | + monitor_printf(mon, "End time: %s", ctime(&info->end_time)); | |
640 | + } | |
641 | + | |
642 | + int per = (info->has_total && info->total && | |
643 | + info->has_transferred && info->transferred) ? | |
644 | + (info->transferred * 100)/info->total : 0; | |
645 | + int zero_per = (info->has_total && info->total && | |
646 | + info->has_zero_bytes && info->zero_bytes) ? | |
647 | + (info->zero_bytes * 100)/info->total : 0; | |
648 | + monitor_printf(mon, "Backup file: %s\n", info->backup_file); | |
649 | + monitor_printf(mon, "Backup uuid: %s\n", info->uuid); | |
650 | + monitor_printf(mon, "Total size: %zd\n", info->total); | |
651 | + monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n", | |
652 | + info->transferred, per); | |
653 | + monitor_printf(mon, "Zero bytes: %zd (%d%%)\n", | |
654 | + info->zero_bytes, zero_per); | |
655 | + } | |
656 | + | |
657 | + qapi_free_BackupStatus(info); | |
658 | +} | |
659 | + | |
660 | void hmp_info_migrate(Monitor *mon, const QDict *qdict) | |
661 | { | |
662 | MigrationInfo *info; | |
1a91ab45 | 663 | @@ -1613,6 +1651,29 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict) |
ca0fe5f5 WB |
664 | hmp_handle_error(mon, &error); |
665 | } | |
666 | ||
667 | +void hmp_backup_cancel(Monitor *mon, const QDict *qdict) | |
668 | +{ | |
669 | + Error *error = NULL; | |
670 | + | |
671 | + qmp_backup_cancel(&error); | |
672 | + | |
673 | + hmp_handle_error(mon, &error); | |
674 | +} | |
675 | + | |
676 | +void hmp_backup(Monitor *mon, const QDict *qdict) | |
677 | +{ | |
678 | + Error *error = NULL; | |
679 | + | |
680 | + const char *backup_file = qdict_get_str(qdict, "backupfile"); | |
681 | + const char *devlist = qdict_get_try_str(qdict, "devlist"); | |
682 | + int64_t speed = qdict_get_try_int(qdict, "speed", 0); | |
683 | + | |
684 | + qmp_backup(backup_file, true, BACKUP_FORMAT_VMA, false, NULL, !!devlist, | |
685 | + devlist, qdict_haskey(qdict, "speed"), speed, &error); | |
686 | + | |
687 | + hmp_handle_error(mon, &error); | |
688 | +} | |
689 | + | |
690 | void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict) | |
691 | { | |
692 | Error *error = NULL; | |
693 | diff --git a/hmp.h b/hmp.h | |
1a91ab45 | 694 | index 799fd37..17a65b2 100644 |
ca0fe5f5 WB |
695 | --- a/hmp.h |
696 | +++ b/hmp.h | |
697 | @@ -30,6 +30,7 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict); | |
698 | void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict); | |
699 | void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict); | |
700 | void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict); | |
701 | +void hmp_info_backup(Monitor *mon, const QDict *qdict); | |
702 | void hmp_info_cpus(Monitor *mon, const QDict *qdict); | |
703 | void hmp_info_block(Monitor *mon, const QDict *qdict); | |
704 | void hmp_info_blockstats(Monitor *mon, const QDict *qdict); | |
1a91ab45 | 705 | @@ -79,6 +80,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict); |
ca0fe5f5 WB |
706 | void hmp_change(Monitor *mon, const QDict *qdict); |
707 | void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict); | |
708 | void hmp_block_stream(Monitor *mon, const QDict *qdict); | |
709 | +void hmp_backup(Monitor *mon, const QDict *qdict); | |
710 | +void hmp_backup_cancel(Monitor *mon, const QDict *qdict); | |
711 | void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict); | |
712 | void hmp_block_job_cancel(Monitor *mon, const QDict *qdict); | |
713 | void hmp_block_job_pause(Monitor *mon, const QDict *qdict); | |
68a30562 | 714 | diff --git a/include/block/block_int.h b/include/block/block_int.h |
1a91ab45 | 715 | index ec65581..278da16 100644 |
68a30562 WB |
716 | --- a/include/block/block_int.h |
717 | +++ b/include/block/block_int.h | |
718 | @@ -59,7 +59,7 @@ | |
719 | ||
720 | #define BLOCK_PROBE_BUF_SIZE 512 | |
721 | ||
722 | -typedef int BackupDumpFunc(void *opaque, BlockDriverState *bs, | |
723 | +typedef int BackupDumpFunc(void *opaque, BlockBackend *be, | |
724 | int64_t sector_num, int n_sectors, unsigned char *buf); | |
725 | ||
726 | enum BdrvTrackedRequestType { | |
ca0fe5f5 | 727 | diff --git a/qapi-schema.json b/qapi-schema.json |
1a91ab45 | 728 | index ca534cc..059cbfc 100644 |
ca0fe5f5 WB |
729 | --- a/qapi-schema.json |
730 | +++ b/qapi-schema.json | |
1a91ab45 | 731 | @@ -570,6 +570,96 @@ |
ca0fe5f5 WB |
732 | { 'command': 'query-events', 'returns': ['EventInfo'] } |
733 | ||
1a91ab45 | 734 | ## |
ca0fe5f5 WB |
735 | +# @BackupStatus: |
736 | +# | |
737 | +# Detailed backup status. | |
738 | +# | |
1a91ab45 | 739 | +# @status: string describing the current backup status. |
ca0fe5f5 WB |
740 | +# This can be 'active', 'done', 'error'. If this field is not |
741 | +# returned, no backup process has been initiated | |
742 | +# | |
1a91ab45 | 743 | +# @errmsg: error message (only returned if status is 'error') |
ca0fe5f5 | 744 | +# |
1a91ab45 | 745 | +# @total: total amount of bytes involved in the backup process |
ca0fe5f5 | 746 | +# |
1a91ab45 | 747 | +# @transferred: amount of bytes already backed up. |
ca0fe5f5 | 748 | +# |
1a91ab45 | 749 | +# @zero-bytes: amount of 'zero' bytes detected. |
ca0fe5f5 | 750 | +# |
1a91ab45 | 751 | +# @start-time: time (epoch) when backup job started. |
ca0fe5f5 | 752 | +# |
1a91ab45 | 753 | +# @end-time: time (epoch) when backup job finished. |
ca0fe5f5 | 754 | +# |
1a91ab45 | 755 | +# @backup-file: backup file name |
ca0fe5f5 | 756 | +# |
1a91ab45 | 757 | +# @uuid: uuid for this backup job |
ca0fe5f5 WB |
758 | +# |
759 | +## | |
760 | +{ 'struct': 'BackupStatus', | |
761 | + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', | |
762 | + '*transferred': 'int', '*zero-bytes': 'int', | |
763 | + '*start-time': 'int', '*end-time': 'int', | |
764 | + '*backup-file': 'str', '*uuid': 'str' } } | |
765 | + | |
766 | +## | |
1a91ab45 | 767 | +# @BackupFormat: |
ca0fe5f5 WB |
768 | +# |
769 | +# An enumeration of supported backup formats. | |
770 | +# | |
771 | +# @vma: Proxmox vma backup format | |
772 | +## | |
773 | +{ 'enum': 'BackupFormat', | |
774 | + 'data': [ 'vma' ] } | |
775 | + | |
776 | +## | |
777 | +# @backup: | |
778 | +# | |
779 | +# Starts a VM backup. | |
780 | +# | |
781 | +# @backup-file: the backup file name | |
782 | +# | |
783 | +# @format: format of the backup file | |
784 | +# | |
1a91ab45 | 785 | +# @config-file: a configuration file to include into |
ca0fe5f5 WB |
786 | +# the backup archive. |
787 | +# | |
1a91ab45 | 788 | +# @speed: the maximum speed, in bytes per second |
ca0fe5f5 | 789 | +# |
1a91ab45 | 790 | +# @devlist: list of block device names (separated by ',', ';' |
ca0fe5f5 WB |
791 | +# or ':'). By default the backup includes all writable block devices. |
792 | +# | |
793 | +# Returns: the uuid of the backup job | |
794 | +# | |
795 | +## | |
796 | +{ 'command': 'backup', 'data': { 'backup-file': 'str', | |
797 | + '*format': 'BackupFormat', | |
798 | + '*config-file': 'str', | |
799 | + '*devlist': 'str', '*speed': 'int' }, | |
800 | + 'returns': 'UuidInfo' } | |
801 | + | |
802 | +## | |
1a91ab45 | 803 | +# @query-backup: |
ca0fe5f5 WB |
804 | +# |
805 | +# Returns information about current/last backup task. | |
806 | +# | |
807 | +# Returns: @BackupStatus | |
808 | +# | |
809 | +## | |
810 | +{ 'command': 'query-backup', 'returns': 'BackupStatus' } | |
811 | + | |
812 | +## | |
1a91ab45 | 813 | +# @backup-cancel: |
ca0fe5f5 WB |
814 | +# |
815 | +# Cancel the current executing backup process. | |
816 | +# | |
817 | +# Returns: nothing on success | |
818 | +# | |
819 | +# Notes: This command succeeds even if there is no backup process running. | |
820 | +# | |
821 | +## | |
822 | +{ 'command': 'backup-cancel' } | |
823 | + | |
1a91ab45 WB |
824 | +## |
825 | # @MigrationStats: | |
ca0fe5f5 | 826 | # |
1a91ab45 | 827 | # Detailed migration status. |
ca0fe5f5 WB |
828 | -- |
829 | 2.1.4 | |
830 |