]>
Commit | Line | Data |
---|---|---|
3499c5b4 TL |
1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
2 | From: Stefan Reiter <s.reiter@proxmox.com> | |
3 | Date: Wed, 8 Jul 2020 09:50:54 +0200 | |
4 | Subject: [PATCH] PVE: Add PBS block driver to map backup archives into VMs | |
5 | ||
6 | Signed-off-by: Stefan Reiter <s.reiter@proxmox.com> | |
7 | [error cleanups, file_open implementation] | |
8 | Signed-off-by: Dietmar Maurer <dietmar@proxmox.com> | |
ddbf7a87 | 9 | Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> |
db5d2a4b FE |
10 | [WB: add namespace support] |
11 | Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com> | |
4e1935c2 | 12 | [FE: adapt to changed function signatures |
bf251437 FE |
13 | make pbs_co_preadv return values consistent with QEMU |
14 | getlength is now a coroutine function] | |
4e1935c2 | 15 | Signed-off-by: Fiona Ebner <f.ebner@proxmox.com> |
3499c5b4 | 16 | --- |
817b7667 | 17 | block/meson.build | 3 + |
db5d2a4b | 18 | block/pbs.c | 305 +++++++++++++++++++++++++++++++++++++++++++ |
817b7667 | 19 | configure | 9 ++ |
5b15e2ec | 20 | meson.build | 2 +- |
4567474e | 21 | qapi/block-core.json | 13 ++ |
271ac0a8 | 22 | qapi/pragma.json | 1 + |
db5d2a4b | 23 | 6 files changed, 332 insertions(+), 1 deletion(-) |
3499c5b4 TL |
24 | create mode 100644 block/pbs.c |
25 | ||
817b7667 | 26 | diff --git a/block/meson.build b/block/meson.build |
bf251437 | 27 | index 5bcebb934b..eece0d5743 100644 |
817b7667 SR |
28 | --- a/block/meson.build |
29 | +++ b/block/meson.build | |
bf251437 | 30 | @@ -54,6 +54,9 @@ block_ss.add(files( |
817b7667 SR |
31 | '../pve-backup.c', |
32 | ), libproxmox_backup_qemu) | |
33 | ||
34 | +block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: files('pbs.c')) | |
35 | +block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: libproxmox_backup_qemu) | |
36 | + | |
37 | ||
38 | softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) | |
d03e1b3c | 39 | softmmu_ss.add(files('block-ram-registrar.c')) |
3499c5b4 TL |
40 | diff --git a/block/pbs.c b/block/pbs.c |
41 | new file mode 100644 | |
db5d2a4b | 42 | index 0000000000..a2211e0f3b |
3499c5b4 TL |
43 | --- /dev/null |
44 | +++ b/block/pbs.c | |
db5d2a4b | 45 | @@ -0,0 +1,305 @@ |
3499c5b4 TL |
46 | +/* |
47 | + * Proxmox Backup Server read-only block driver | |
48 | + */ | |
49 | + | |
50 | +#include "qemu/osdep.h" | |
51 | +#include "qapi/error.h" | |
52 | +#include "qapi/qmp/qdict.h" | |
53 | +#include "qapi/qmp/qstring.h" | |
54 | +#include "qemu/module.h" | |
55 | +#include "qemu/option.h" | |
56 | +#include "qemu/cutils.h" | |
57 | +#include "block/block_int.h" | |
bf251437 | 58 | +#include "block/block-io.h" |
3499c5b4 TL |
59 | + |
60 | +#include <proxmox-backup-qemu.h> | |
61 | + | |
62 | +#define PBS_OPT_REPOSITORY "repository" | |
db5d2a4b | 63 | +#define PBS_OPT_NAMESPACE "namespace" |
3499c5b4 TL |
64 | +#define PBS_OPT_SNAPSHOT "snapshot" |
65 | +#define PBS_OPT_ARCHIVE "archive" | |
66 | +#define PBS_OPT_KEYFILE "keyfile" | |
67 | +#define PBS_OPT_PASSWORD "password" | |
68 | +#define PBS_OPT_FINGERPRINT "fingerprint" | |
69 | +#define PBS_OPT_ENCRYPTION_PASSWORD "key_password" | |
70 | + | |
71 | +typedef struct { | |
72 | + ProxmoxRestoreHandle *conn; | |
73 | + char aid; | |
74 | + int64_t length; | |
75 | + | |
76 | + char *repository; | |
db5d2a4b | 77 | + char *namespace; |
3499c5b4 TL |
78 | + char *snapshot; |
79 | + char *archive; | |
80 | +} BDRVPBSState; | |
81 | + | |
82 | +static QemuOptsList runtime_opts = { | |
83 | + .name = "pbs", | |
84 | + .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), | |
85 | + .desc = { | |
86 | + { | |
87 | + .name = PBS_OPT_REPOSITORY, | |
88 | + .type = QEMU_OPT_STRING, | |
89 | + .help = "The server address and repository to connect to.", | |
90 | + }, | |
91 | + { | |
db5d2a4b FE |
92 | + .name = PBS_OPT_NAMESPACE, |
93 | + .type = QEMU_OPT_STRING, | |
94 | + .help = "Optional: The snapshot's namespace.", | |
95 | + }, | |
96 | + { | |
3499c5b4 TL |
97 | + .name = PBS_OPT_SNAPSHOT, |
98 | + .type = QEMU_OPT_STRING, | |
99 | + .help = "The snapshot to read.", | |
100 | + }, | |
101 | + { | |
102 | + .name = PBS_OPT_ARCHIVE, | |
103 | + .type = QEMU_OPT_STRING, | |
104 | + .help = "Which archive within the snapshot should be accessed.", | |
105 | + }, | |
106 | + { | |
107 | + .name = PBS_OPT_PASSWORD, | |
108 | + .type = QEMU_OPT_STRING, | |
109 | + .help = "Server password. Can be passed as env var 'PBS_PASSWORD'.", | |
110 | + }, | |
111 | + { | |
112 | + .name = PBS_OPT_FINGERPRINT, | |
113 | + .type = QEMU_OPT_STRING, | |
114 | + .help = "Server fingerprint. Can be passed as env var 'PBS_FINGERPRINT'.", | |
115 | + }, | |
116 | + { | |
117 | + .name = PBS_OPT_ENCRYPTION_PASSWORD, | |
118 | + .type = QEMU_OPT_STRING, | |
119 | + .help = "Optional: Key password. Can be passed as env var 'PBS_ENCRYPTION_PASSWORD'.", | |
120 | + }, | |
121 | + { | |
122 | + .name = PBS_OPT_KEYFILE, | |
123 | + .type = QEMU_OPT_STRING, | |
124 | + .help = "Optional: The path to the keyfile to use.", | |
125 | + }, | |
126 | + { /* end of list */ } | |
127 | + }, | |
128 | +}; | |
129 | + | |
130 | + | |
131 | +// filename format: | |
db5d2a4b | 132 | +// pbs:repository=<repo>,namespace=<ns>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive> |
3499c5b4 TL |
133 | +static void pbs_parse_filename(const char *filename, QDict *options, |
134 | + Error **errp) | |
135 | +{ | |
136 | + | |
137 | + if (!strstart(filename, "pbs:", &filename)) { | |
138 | + if (errp) error_setg(errp, "pbs_parse_filename failed - missing 'pbs:' prefix"); | |
139 | + } | |
140 | + | |
141 | + | |
142 | + QemuOpts *opts = qemu_opts_parse_noisily(&runtime_opts, filename, false); | |
143 | + if (!opts) { | |
144 | + if (errp) error_setg(errp, "pbs_parse_filename failed"); | |
145 | + return; | |
146 | + } | |
147 | + | |
148 | + qemu_opts_to_qdict(opts, options); | |
149 | + | |
150 | + qemu_opts_del(opts); | |
151 | +} | |
152 | + | |
153 | +static int pbs_open(BlockDriverState *bs, QDict *options, int flags, | |
154 | + Error **errp) | |
155 | +{ | |
156 | + QemuOpts *opts; | |
157 | + BDRVPBSState *s = bs->opaque; | |
158 | + char *pbs_error = NULL; | |
159 | + | |
160 | + opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); | |
161 | + qemu_opts_absorb_qdict(opts, options, &error_abort); | |
162 | + | |
163 | + s->repository = g_strdup(qemu_opt_get(opts, PBS_OPT_REPOSITORY)); | |
164 | + s->snapshot = g_strdup(qemu_opt_get(opts, PBS_OPT_SNAPSHOT)); | |
165 | + s->archive = g_strdup(qemu_opt_get(opts, PBS_OPT_ARCHIVE)); | |
166 | + const char *keyfile = qemu_opt_get(opts, PBS_OPT_KEYFILE); | |
167 | + const char *password = qemu_opt_get(opts, PBS_OPT_PASSWORD); | |
db5d2a4b | 168 | + const char *namespace = qemu_opt_get(opts, PBS_OPT_NAMESPACE); |
3499c5b4 TL |
169 | + const char *fingerprint = qemu_opt_get(opts, PBS_OPT_FINGERPRINT); |
170 | + const char *key_password = qemu_opt_get(opts, PBS_OPT_ENCRYPTION_PASSWORD); | |
171 | + | |
172 | + if (!password) { | |
173 | + password = getenv("PBS_PASSWORD"); | |
174 | + } | |
175 | + if (!fingerprint) { | |
176 | + fingerprint = getenv("PBS_FINGERPRINT"); | |
177 | + } | |
178 | + if (!key_password) { | |
179 | + key_password = getenv("PBS_ENCRYPTION_PASSWORD"); | |
180 | + } | |
db5d2a4b FE |
181 | + if (namespace) { |
182 | + s->namespace = g_strdup(namespace); | |
183 | + } | |
3499c5b4 TL |
184 | + |
185 | + /* connect to PBS server in read mode */ | |
db5d2a4b | 186 | + s->conn = proxmox_restore_new_ns(s->repository, s->snapshot, s->namespace, password, |
3499c5b4 TL |
187 | + keyfile, key_password, fingerprint, &pbs_error); |
188 | + | |
189 | + /* invalidates qemu_opt_get char pointers from above */ | |
190 | + qemu_opts_del(opts); | |
191 | + | |
192 | + if (!s->conn) { | |
193 | + if (pbs_error && errp) error_setg(errp, "PBS restore_new failed: %s", pbs_error); | |
194 | + if (pbs_error) proxmox_backup_free_error(pbs_error); | |
195 | + return -ENOMEM; | |
196 | + } | |
197 | + | |
198 | + int ret = proxmox_restore_connect(s->conn, &pbs_error); | |
199 | + if (ret < 0) { | |
200 | + if (pbs_error && errp) error_setg(errp, "PBS connect failed: %s", pbs_error); | |
201 | + if (pbs_error) proxmox_backup_free_error(pbs_error); | |
202 | + return -ECONNREFUSED; | |
203 | + } | |
204 | + | |
205 | + /* acquire handle and length */ | |
206 | + s->aid = proxmox_restore_open_image(s->conn, s->archive, &pbs_error); | |
207 | + if (s->aid < 0) { | |
208 | + if (pbs_error && errp) error_setg(errp, "PBS open_image failed: %s", pbs_error); | |
209 | + if (pbs_error) proxmox_backup_free_error(pbs_error); | |
210 | + return -ENODEV; | |
211 | + } | |
212 | + s->length = proxmox_restore_get_image_length(s->conn, s->aid, &pbs_error); | |
213 | + if (s->length < 0) { | |
214 | + if (pbs_error && errp) error_setg(errp, "PBS get_image_length failed: %s", pbs_error); | |
215 | + if (pbs_error) proxmox_backup_free_error(pbs_error); | |
216 | + return -EINVAL; | |
217 | + } | |
218 | + | |
219 | + return 0; | |
220 | +} | |
221 | + | |
222 | +static int pbs_file_open(BlockDriverState *bs, QDict *options, int flags, | |
223 | + Error **errp) | |
224 | +{ | |
225 | + return pbs_open(bs, options, flags, errp); | |
226 | +} | |
227 | + | |
228 | +static void pbs_close(BlockDriverState *bs) { | |
229 | + BDRVPBSState *s = bs->opaque; | |
230 | + g_free(s->repository); | |
db5d2a4b | 231 | + g_free(s->namespace); |
3499c5b4 TL |
232 | + g_free(s->snapshot); |
233 | + g_free(s->archive); | |
234 | + proxmox_restore_disconnect(s->conn); | |
235 | +} | |
236 | + | |
bf251437 | 237 | +static coroutine_fn int64_t pbs_co_getlength(BlockDriverState *bs) |
3499c5b4 TL |
238 | +{ |
239 | + BDRVPBSState *s = bs->opaque; | |
240 | + return s->length; | |
241 | +} | |
242 | + | |
243 | +typedef struct ReadCallbackData { | |
244 | + Coroutine *co; | |
245 | + AioContext *ctx; | |
246 | +} ReadCallbackData; | |
247 | + | |
248 | +static void read_callback(void *callback_data) | |
249 | +{ | |
250 | + ReadCallbackData *rcb = callback_data; | |
251 | + aio_co_schedule(rcb->ctx, rcb->co); | |
252 | +} | |
253 | + | |
254 | +static coroutine_fn int pbs_co_preadv(BlockDriverState *bs, | |
4567474e FE |
255 | + int64_t offset, int64_t bytes, |
256 | + QEMUIOVector *qiov, BdrvRequestFlags flags) | |
3499c5b4 TL |
257 | +{ |
258 | + BDRVPBSState *s = bs->opaque; | |
259 | + int ret; | |
260 | + char *pbs_error = NULL; | |
db5d2a4b FE |
261 | + uint8_t *buf; |
262 | + bool inline_buf = true; | |
263 | + | |
264 | + /* for single-buffer IO vectors we can fast-path the write directly to it */ | |
265 | + if (qiov->niov == 1 && qiov->iov->iov_len >= bytes) { | |
266 | + buf = qiov->iov->iov_base; | |
267 | + } else { | |
268 | + inline_buf = false; | |
269 | + buf = g_malloc(bytes); | |
270 | + } | |
3499c5b4 | 271 | + |
4567474e FE |
272 | + if (offset < 0 || bytes < 0) { |
273 | + fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n"); | |
4e1935c2 | 274 | + return -EIO; |
4567474e FE |
275 | + } |
276 | + | |
3499c5b4 TL |
277 | + ReadCallbackData rcb = { |
278 | + .co = qemu_coroutine_self(), | |
76e46478 | 279 | + .ctx = bdrv_get_aio_context(bs), |
3499c5b4 TL |
280 | + }; |
281 | + | |
4567474e | 282 | + proxmox_restore_read_image_at_async(s->conn, s->aid, buf, (uint64_t)offset, (uint64_t)bytes, |
3499c5b4 TL |
283 | + read_callback, (void *) &rcb, &ret, &pbs_error); |
284 | + | |
285 | + qemu_coroutine_yield(); | |
286 | + | |
287 | + if (ret < 0) { | |
288 | + fprintf(stderr, "error during PBS read: %s\n", pbs_error ? pbs_error : "unknown error"); | |
289 | + if (pbs_error) proxmox_backup_free_error(pbs_error); | |
290 | + return -EIO; | |
291 | + } | |
292 | + | |
db5d2a4b FE |
293 | + if (!inline_buf) { |
294 | + qemu_iovec_from_buf(qiov, 0, buf, bytes); | |
295 | + g_free(buf); | |
296 | + } | |
3499c5b4 | 297 | + |
4e1935c2 | 298 | + return 0; |
3499c5b4 TL |
299 | +} |
300 | + | |
301 | +static coroutine_fn int pbs_co_pwritev(BlockDriverState *bs, | |
4567474e FE |
302 | + int64_t offset, int64_t bytes, |
303 | + QEMUIOVector *qiov, BdrvRequestFlags flags) | |
3499c5b4 TL |
304 | +{ |
305 | + fprintf(stderr, "pbs-bdrv: cannot write to backup file, make sure " | |
306 | + "any attached disk devices are set to read-only!\n"); | |
307 | + return -EPERM; | |
308 | +} | |
309 | + | |
310 | +static void pbs_refresh_filename(BlockDriverState *bs) | |
311 | +{ | |
312 | + BDRVPBSState *s = bs->opaque; | |
db5d2a4b FE |
313 | + if (s->namespace) { |
314 | + snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s/%s:%s(%s)", | |
315 | + s->repository, s->namespace, s->snapshot, s->archive); | |
316 | + } else { | |
317 | + snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s/%s(%s)", | |
318 | + s->repository, s->snapshot, s->archive); | |
319 | + } | |
3499c5b4 TL |
320 | +} |
321 | + | |
322 | +static const char *const pbs_strong_runtime_opts[] = { | |
323 | + NULL | |
324 | +}; | |
325 | + | |
326 | +static BlockDriver bdrv_pbs_co = { | |
327 | + .format_name = "pbs", | |
328 | + .protocol_name = "pbs", | |
329 | + .instance_size = sizeof(BDRVPBSState), | |
330 | + | |
331 | + .bdrv_parse_filename = pbs_parse_filename, | |
332 | + | |
333 | + .bdrv_file_open = pbs_file_open, | |
334 | + .bdrv_open = pbs_open, | |
335 | + .bdrv_close = pbs_close, | |
bf251437 | 336 | + .bdrv_co_getlength = pbs_co_getlength, |
3499c5b4 TL |
337 | + |
338 | + .bdrv_co_preadv = pbs_co_preadv, | |
339 | + .bdrv_co_pwritev = pbs_co_pwritev, | |
340 | + | |
341 | + .bdrv_refresh_filename = pbs_refresh_filename, | |
342 | + .strong_runtime_opts = pbs_strong_runtime_opts, | |
343 | +}; | |
344 | + | |
345 | +static void bdrv_pbs_init(void) | |
346 | +{ | |
347 | + bdrv_register(&bdrv_pbs_co); | |
348 | +} | |
349 | + | |
350 | +block_init(bdrv_pbs_init); | |
351 | diff --git a/configure b/configure | |
53b56ca7 | 352 | index a62a3e6be9..1ac0feb46b 100755 |
3499c5b4 TL |
353 | --- a/configure |
354 | +++ b/configure | |
bf251437 | 355 | @@ -288,6 +288,7 @@ linux_user="" |
5b15e2ec FE |
356 | bsd_user="" |
357 | pie="" | |
dc9827a6 | 358 | coroutine="" |
3499c5b4 | 359 | +pbs_bdrv="yes" |
4567474e | 360 | plugins="$default_feature" |
dc9827a6 | 361 | meson="" |
d03e1b3c | 362 | ninja="" |
53b56ca7 | 363 | @@ -873,6 +874,10 @@ for opt do |
bf251437 FE |
364 | ;; |
365 | --with-coroutine=*) coroutine="$optarg" | |
3499c5b4 TL |
366 | ;; |
367 | + --disable-pbs-bdrv) pbs_bdrv="no" | |
368 | + ;; | |
369 | + --enable-pbs-bdrv) pbs_bdrv="yes" | |
370 | + ;; | |
5b15e2ec | 371 | --with-git=*) git="$optarg" |
3499c5b4 | 372 | ;; |
5b15e2ec | 373 | --with-git-submodules=*) |
53b56ca7 | 374 | @@ -1049,6 +1054,7 @@ cat << EOF |
5b15e2ec FE |
375 | debug-info debugging information |
376 | safe-stack SafeStack Stack Smash Protection. Depends on | |
bf251437 | 377 | clang/llvm and requires coroutine backend ucontext. |
3499c5b4 | 378 | + pbs-bdrv Proxmox backup server read-only block driver support |
dc9827a6 FE |
379 | |
380 | NOTE: The object files are built at the place where configure is launched | |
5b15e2ec | 381 | EOF |
53b56ca7 | 382 | @@ -2386,6 +2392,9 @@ echo "TARGET_DIRS=$target_list" >> $config_host_mak |
5b15e2ec FE |
383 | if test "$modules" = "yes"; then |
384 | echo "CONFIG_MODULES=y" >> $config_host_mak | |
3499c5b4 TL |
385 | fi |
386 | +if test "$pbs_bdrv" = "yes" ; then | |
387 | + echo "CONFIG_PBS_BDRV=y" >> $config_host_mak | |
388 | +fi | |
5b15e2ec FE |
389 | |
390 | # XXX: suppress that | |
391 | if [ "$bsd" = "yes" ] ; then | |
817b7667 | 392 | diff --git a/meson.build b/meson.build |
d8474461 | 393 | index 32ab849ce6..69afe3441b 100644 |
817b7667 SR |
394 | --- a/meson.build |
395 | +++ b/meson.build | |
d8474461 | 396 | @@ -4041,7 +4041,7 @@ summary_info += {'bzip2 support': libbzip2} |
dc9827a6 | 397 | summary_info += {'lzfse support': liblzfse} |
4567474e | 398 | summary_info += {'zstd support': zstd} |
dc9827a6 | 399 | summary_info += {'NUMA host support': numa} |
5b15e2ec | 400 | -summary_info += {'capstone': capstone} |
817b7667 | 401 | +summary_info += {'PBS bdrv support': config_host.has_key('CONFIG_PBS_BDRV')} |
4567474e FE |
402 | summary_info += {'libpmem support': libpmem} |
403 | summary_info += {'libdaxctl support': libdaxctl} | |
5b15e2ec | 404 | summary_info += {'libudev': libudev} |
3499c5b4 | 405 | diff --git a/qapi/block-core.json b/qapi/block-core.json |
9e0186f2 | 406 | index 985859ddee..d601fb4ab2 100644 |
3499c5b4 TL |
407 | --- a/qapi/block-core.json |
408 | +++ b/qapi/block-core.json | |
9e0186f2 | 409 | @@ -3304,6 +3304,7 @@ |
d03e1b3c FE |
410 | 'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum', |
411 | 'raw', 'rbd', | |
4567474e | 412 | { 'name': 'replication', 'if': 'CONFIG_REPLICATION' }, |
f376b2b9 | 413 | + 'pbs', |
d03e1b3c FE |
414 | 'ssh', 'throttle', 'vdi', 'vhdx', |
415 | { 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' }, | |
416 | { 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' }, | |
9e0186f2 | 417 | @@ -3380,6 +3381,17 @@ |
3499c5b4 TL |
418 | { 'struct': 'BlockdevOptionsNull', |
419 | 'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } } | |
420 | ||
421 | +## | |
422 | +# @BlockdevOptionsPbs: | |
423 | +# | |
424 | +# Driver specific block device options for the PBS backend. | |
425 | +# | |
426 | +## | |
427 | +{ 'struct': 'BlockdevOptionsPbs', | |
428 | + 'data': { 'repository': 'str', 'snapshot': 'str', 'archive': 'str', | |
429 | + '*keyfile': 'str', '*password': 'str', '*fingerprint': 'str', | |
db5d2a4b | 430 | + '*key_password': 'str', '*namespace': 'str' } } |
3499c5b4 TL |
431 | + |
432 | ## | |
433 | # @BlockdevOptionsNVMe: | |
434 | # | |
9e0186f2 | 435 | @@ -4753,6 +4765,7 @@ |
3499c5b4 TL |
436 | 'nfs': 'BlockdevOptionsNfs', |
437 | 'null-aio': 'BlockdevOptionsNull', | |
438 | 'null-co': 'BlockdevOptionsNull', | |
439 | + 'pbs': 'BlockdevOptionsPbs', | |
440 | 'nvme': 'BlockdevOptionsNVMe', | |
d03e1b3c FE |
441 | 'nvme-io_uring': { 'type': 'BlockdevOptionsNvmeIoUring', |
442 | 'if': 'CONFIG_BLKIO' }, | |
271ac0a8 | 443 | diff --git a/qapi/pragma.json b/qapi/pragma.json |
a816d296 | 444 | index 325e684411..b6079f6a0e 100644 |
271ac0a8 FE |
445 | --- a/qapi/pragma.json |
446 | +++ b/qapi/pragma.json | |
a816d296 | 447 | @@ -45,6 +45,7 @@ |
271ac0a8 FE |
448 | 'BlockInfo', # query-block |
449 | 'BlockdevAioOptions', # blockdev-add, -blockdev | |
450 | 'BlockdevDriver', # blockdev-add, query-blockstats, ... | |
451 | + 'BlockdevOptionsPbs', # for PBS backwards compat | |
452 | 'BlockdevVmdkAdapterType', # blockdev-create (to match VMDK spec) | |
453 | 'BlockdevVmdkSubformat', # blockdev-create (to match VMDK spec) | |
454 | 'ColoCompareProperties', # object_add, -object |