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