]> git.proxmox.com Git - pve-qemu.git/blame - debian/patches/pve/0048-block-add-alloc-track-driver.patch
PVE backup: don't call no_co_wrapper function from coroutine
[pve-qemu.git] / debian / patches / pve / 0048-block-add-alloc-track-driver.patch
CommitLineData
677d0d16
SR
1From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2From: Stefan Reiter <s.reiter@proxmox.com>
3Date: Mon, 7 Dec 2020 15:21:03 +0100
4Subject: [PATCH] block: add alloc-track driver
5
6Add a new filter node 'alloc-track', which seperates reads and writes to
7different children, thus allowing to put a backing image behind any
8blockdev (regardless of driver support). Since we can't detect any
9pre-allocated blocks, we can only track new writes, hence the write
10target ('file') for this node must always be empty.
11
12Intended use case is for live restoring, i.e. add a backup image as a
13block device into a VM, then put an alloc-track on the restore target
14and set the backup as backing. With this, one can use a regular
15'block-stream' to restore the image, while the VM can already run in the
16background. Copy-on-read will help make progress as the VM reads as
17well.
18
19This only worked if the target supports backing images, so up until now
20only for qcow2, with alloc-track any driver for the target can be used.
21
22If 'auto-remove' is set, alloc-track will automatically detach itself
23once the backing image is removed. It will be replaced by 'file'.
24
25Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
ddbf7a87 26Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
4e1935c2
FE
27[FE: adapt to changed function signatures
28 make error return value consistent with QEMU]
29Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
677d0d16 30---
bf251437 31 block/alloc-track.c | 351 ++++++++++++++++++++++++++++++++++++++++++++
677d0d16 32 block/meson.build | 1 +
bf251437 33 2 files changed, 352 insertions(+)
677d0d16
SR
34 create mode 100644 block/alloc-track.c
35
36diff --git a/block/alloc-track.c b/block/alloc-track.c
37new file mode 100644
bf251437 38index 0000000000..113bbd7058
677d0d16
SR
39--- /dev/null
40+++ b/block/alloc-track.c
bf251437 41@@ -0,0 +1,351 @@
677d0d16
SR
42+/*
43+ * Node to allow backing images to be applied to any node. Assumes a blank
44+ * image to begin with, only new writes are tracked as allocated, thus this
45+ * must never be put on a node that already contains data.
46+ *
47+ * Copyright (c) 2020 Proxmox Server Solutions GmbH
48+ * Copyright (c) 2020 Stefan Reiter <s.reiter@proxmox.com>
49+ *
50+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
51+ * See the COPYING file in the top-level directory.
52+ */
53+
54+#include "qemu/osdep.h"
55+#include "qapi/error.h"
56+#include "block/block_int.h"
bf251437 57+#include "block/dirty-bitmap.h"
677d0d16
SR
58+#include "qapi/qmp/qdict.h"
59+#include "qapi/qmp/qstring.h"
60+#include "qemu/cutils.h"
61+#include "qemu/option.h"
62+#include "qemu/module.h"
63+#include "sysemu/block-backend.h"
64+
65+#define TRACK_OPT_AUTO_REMOVE "auto-remove"
66+
67+typedef enum DropState {
68+ DropNone,
69+ DropRequested,
70+ DropInProgress,
71+} DropState;
72+
73+typedef struct {
74+ BdrvDirtyBitmap *bitmap;
75+ DropState drop_state;
76+ bool auto_remove;
77+} BDRVAllocTrackState;
78+
79+static QemuOptsList runtime_opts = {
80+ .name = "alloc-track",
81+ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
82+ .desc = {
83+ {
84+ .name = TRACK_OPT_AUTO_REMOVE,
85+ .type = QEMU_OPT_BOOL,
86+ .help = "automatically replace this node with 'file' when 'backing'"
87+ "is detached",
88+ },
89+ { /* end of list */ }
90+ },
91+};
92+
93+static void track_refresh_limits(BlockDriverState *bs, Error **errp)
94+{
95+ BlockDriverInfo bdi;
96+
97+ if (!bs->file) {
98+ return;
99+ }
100+
101+ /* always use alignment from underlying write device so RMW cycle for
102+ * bdrv_pwritev reads data from our backing via track_co_preadv (no partial
103+ * cluster allocation in 'file') */
104+ bdrv_get_info(bs->file->bs, &bdi);
105+ bs->bl.request_alignment = MAX(bs->file->bs->bl.request_alignment,
106+ MAX(bdi.cluster_size, BDRV_SECTOR_SIZE));
107+}
108+
109+static int track_open(BlockDriverState *bs, QDict *options, int flags,
110+ Error **errp)
111+{
112+ BDRVAllocTrackState *s = bs->opaque;
113+ QemuOpts *opts;
114+ Error *local_err = NULL;
115+ int ret = 0;
116+
117+ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
118+ qemu_opts_absorb_qdict(opts, options, &local_err);
119+ if (local_err) {
120+ error_propagate(errp, local_err);
121+ ret = -EINVAL;
122+ goto fail;
123+ }
124+
125+ s->auto_remove = qemu_opt_get_bool(opts, TRACK_OPT_AUTO_REMOVE, false);
126+
127+ /* open the target (write) node, backing will be attached by block layer */
128+ bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
129+ BDRV_CHILD_DATA | BDRV_CHILD_METADATA, false,
130+ &local_err);
131+ if (local_err) {
132+ ret = -EINVAL;
133+ error_propagate(errp, local_err);
134+ goto fail;
135+ }
136+
137+ track_refresh_limits(bs, errp);
138+ uint64_t gran = bs->bl.request_alignment;
139+ s->bitmap = bdrv_create_dirty_bitmap(bs->file->bs, gran, NULL, &local_err);
140+ if (local_err) {
141+ ret = -EIO;
142+ error_propagate(errp, local_err);
143+ goto fail;
144+ }
145+
146+ s->drop_state = DropNone;
147+
148+fail:
149+ if (ret < 0) {
150+ bdrv_unref_child(bs, bs->file);
151+ if (s->bitmap) {
152+ bdrv_release_dirty_bitmap(s->bitmap);
153+ }
154+ }
155+ qemu_opts_del(opts);
156+ return ret;
157+}
158+
159+static void track_close(BlockDriverState *bs)
160+{
161+ BDRVAllocTrackState *s = bs->opaque;
162+ if (s->bitmap) {
163+ bdrv_release_dirty_bitmap(s->bitmap);
164+ }
165+}
166+
bf251437 167+static coroutine_fn int64_t track_co_getlength(BlockDriverState *bs)
677d0d16 168+{
bf251437 169+ return bdrv_co_getlength(bs->file->bs);
677d0d16
SR
170+}
171+
172+static int coroutine_fn track_co_preadv(BlockDriverState *bs,
4567474e 173+ int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
677d0d16
SR
174+{
175+ BDRVAllocTrackState *s = bs->opaque;
176+ QEMUIOVector local_qiov;
177+ int ret;
178+
179+ /* 'cur_offset' is relative to 'offset', 'local_offset' to image start */
180+ uint64_t cur_offset, local_offset;
181+ int64_t local_bytes;
182+ bool alloc;
183+
4567474e
FE
184+ if (offset < 0 || bytes < 0) {
185+ fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
4e1935c2 186+ return -EIO;
4567474e
FE
187+ }
188+
677d0d16
SR
189+ /* a read request can span multiple granularity-sized chunks, and can thus
190+ * contain blocks with different allocation status - we could just iterate
191+ * granularity-wise, but for better performance use bdrv_dirty_bitmap_next_X
192+ * to find the next flip and consider everything up to that in one go */
193+ for (cur_offset = 0; cur_offset < bytes; cur_offset += local_bytes) {
194+ local_offset = offset + cur_offset;
195+ alloc = bdrv_dirty_bitmap_get(s->bitmap, local_offset);
196+ if (alloc) {
197+ local_bytes = bdrv_dirty_bitmap_next_zero(s->bitmap, local_offset,
198+ bytes - cur_offset);
199+ } else {
200+ local_bytes = bdrv_dirty_bitmap_next_dirty(s->bitmap, local_offset,
201+ bytes - cur_offset);
202+ }
203+
204+ /* _bitmap_next_X return is -1 if no end found within limit, otherwise
205+ * offset of next flip (to start of image) */
206+ local_bytes = local_bytes < 0 ?
207+ bytes - cur_offset :
208+ local_bytes - local_offset;
209+
210+ qemu_iovec_init_slice(&local_qiov, qiov, cur_offset, local_bytes);
211+
212+ if (alloc) {
213+ ret = bdrv_co_preadv(bs->file, local_offset, local_bytes,
214+ &local_qiov, flags);
215+ } else if (bs->backing) {
216+ ret = bdrv_co_preadv(bs->backing, local_offset, local_bytes,
217+ &local_qiov, flags);
218+ } else {
219+ ret = qemu_iovec_memset(&local_qiov, cur_offset, 0, local_bytes);
220+ }
221+
222+ if (ret != 0) {
223+ break;
224+ }
225+ }
226+
227+ return ret;
228+}
229+
230+static int coroutine_fn track_co_pwritev(BlockDriverState *bs,
4567474e 231+ int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
677d0d16
SR
232+{
233+ return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
234+}
235+
236+static int coroutine_fn track_co_pwrite_zeroes(BlockDriverState *bs,
4567474e 237+ int64_t offset, int64_t bytes, BdrvRequestFlags flags)
677d0d16 238+{
4567474e 239+ return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
677d0d16
SR
240+}
241+
242+static int coroutine_fn track_co_pdiscard(BlockDriverState *bs,
4567474e 243+ int64_t offset, int64_t bytes)
677d0d16 244+{
4567474e 245+ return bdrv_co_pdiscard(bs->file, offset, bytes);
677d0d16
SR
246+}
247+
248+static coroutine_fn int track_co_flush(BlockDriverState *bs)
249+{
250+ return bdrv_co_flush(bs->file->bs);
251+}
252+
253+static int coroutine_fn track_co_block_status(BlockDriverState *bs,
254+ bool want_zero,
255+ int64_t offset,
256+ int64_t bytes,
257+ int64_t *pnum,
258+ int64_t *map,
259+ BlockDriverState **file)
260+{
261+ BDRVAllocTrackState *s = bs->opaque;
262+
263+ bool alloc = bdrv_dirty_bitmap_get(s->bitmap, offset);
264+ int64_t next_flipped;
265+ if (alloc) {
266+ next_flipped = bdrv_dirty_bitmap_next_zero(s->bitmap, offset, bytes);
267+ } else {
268+ next_flipped = bdrv_dirty_bitmap_next_dirty(s->bitmap, offset, bytes);
269+ }
270+
271+ /* in case not the entire region has the same state, we need to set pnum to
272+ * indicate for how many bytes our result is valid */
273+ *pnum = next_flipped == -1 ? bytes : next_flipped - offset;
274+ *map = offset;
275+
276+ if (alloc) {
277+ *file = bs->file->bs;
278+ return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID;
279+ } else if (bs->backing) {
280+ *file = bs->backing->bs;
281+ }
282+ return 0;
283+}
284+
285+static void track_child_perm(BlockDriverState *bs, BdrvChild *c,
286+ BdrvChildRole role, BlockReopenQueue *reopen_queue,
287+ uint64_t perm, uint64_t shared,
288+ uint64_t *nperm, uint64_t *nshared)
289+{
290+ BDRVAllocTrackState *s = bs->opaque;
291+
292+ *nshared = BLK_PERM_ALL;
293+
294+ /* in case we're currently dropping ourselves, claim to not use any
295+ * permissions at all - which is fine, since from this point on we will
296+ * never issue a read or write anymore */
297+ if (s->drop_state == DropInProgress) {
298+ *nperm = 0;
299+ return;
300+ }
301+
302+ if (role & BDRV_CHILD_DATA) {
303+ *nperm = perm & DEFAULT_PERM_PASSTHROUGH;
304+ } else {
305+ /* 'backing' is also a child of our BDS, but we don't expect it to be
306+ * writeable, so we only forward 'consistent read' */
307+ *nperm = perm & BLK_PERM_CONSISTENT_READ;
308+ }
309+}
310+
311+static void track_drop(void *opaque)
312+{
313+ BlockDriverState *bs = (BlockDriverState*)opaque;
314+ BlockDriverState *file = bs->file->bs;
315+ BDRVAllocTrackState *s = bs->opaque;
316+
317+ assert(file);
318+
319+ /* we rely on the fact that we're not used anywhere else, so let's wait
320+ * until we're only used once - in the drive connected to the guest (and one
321+ * ref is held by bdrv_ref in track_change_backing_file) */
322+ if (bs->refcnt > 2) {
323+ aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, opaque);
324+ return;
325+ }
b36e8acc
TL
326+ AioContext *aio_context = bdrv_get_aio_context(bs);
327+ aio_context_acquire(aio_context);
677d0d16 328+
677d0d16
SR
329+ bdrv_drained_begin(bs);
330+
331+ /* now that we're drained, we can safely set 'DropInProgress' */
332+ s->drop_state = DropInProgress;
333+ bdrv_child_refresh_perms(bs, bs->file, &error_abort);
334+
335+ bdrv_replace_node(bs, file, &error_abort);
aa42ea26
TL
336+ bdrv_set_backing_hd(bs, NULL, &error_abort);
337+ bdrv_drained_end(bs);
677d0d16 338+ bdrv_unref(bs);
b36e8acc 339+ aio_context_release(aio_context);
677d0d16
SR
340+}
341+
342+static int track_change_backing_file(BlockDriverState *bs,
343+ const char *backing_file,
344+ const char *backing_fmt)
345+{
346+ BDRVAllocTrackState *s = bs->opaque;
347+ if (s->auto_remove && s->drop_state == DropNone &&
348+ backing_file == NULL && backing_fmt == NULL)
349+ {
350+ /* backing file has been disconnected, there's no longer any use for
351+ * this node, so let's remove ourselves from the block graph - we need
352+ * to schedule this for later however, since when this function is
353+ * called, the blockjob modifying us is probably not done yet and has a
354+ * blocker on 'bs' */
355+ s->drop_state = DropRequested;
356+ bdrv_ref(bs);
357+ aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, (void*)bs);
358+ }
359+
360+ return 0;
361+}
362+
363+static BlockDriver bdrv_alloc_track = {
364+ .format_name = "alloc-track",
365+ .instance_size = sizeof(BDRVAllocTrackState),
366+
367+ .bdrv_file_open = track_open,
368+ .bdrv_close = track_close,
bf251437 369+ .bdrv_co_getlength = track_co_getlength,
677d0d16
SR
370+ .bdrv_child_perm = track_child_perm,
371+ .bdrv_refresh_limits = track_refresh_limits,
372+
373+ .bdrv_co_pwrite_zeroes = track_co_pwrite_zeroes,
374+ .bdrv_co_pwritev = track_co_pwritev,
375+ .bdrv_co_preadv = track_co_preadv,
376+ .bdrv_co_pdiscard = track_co_pdiscard,
377+
378+ .bdrv_co_flush = track_co_flush,
379+ .bdrv_co_flush_to_disk = track_co_flush,
380+
381+ .supports_backing = true,
382+
383+ .bdrv_co_block_status = track_co_block_status,
384+ .bdrv_change_backing_file = track_change_backing_file,
385+};
386+
387+static void bdrv_alloc_track_init(void)
388+{
389+ bdrv_register(&bdrv_alloc_track);
390+}
391+
392+block_init(bdrv_alloc_track_init);
393diff --git a/block/meson.build b/block/meson.build
bf251437 394index eece0d5743..8a68162cc0 100644
677d0d16
SR
395--- a/block/meson.build
396+++ b/block/meson.build
397@@ -2,6 +2,7 @@ block_ss.add(genh)
398 block_ss.add(files(
399 'accounting.c',
400 'aio_task.c',
401+ 'alloc-track.c',
402 'amend.c',
403 'backup.c',
404 'backup-dump.c',