]>
Commit | Line | Data |
---|---|---|
6402d961 TL |
1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
2 | From: Dietmar Maurer <dietmar@proxmox.com> | |
83faa3fe TL |
3 | Date: Mon, 6 Apr 2020 12:17:01 +0200 |
4 | Subject: [PATCH] PVE-Backup: pbs-restore - new command to restore from proxmox | |
5 | backup server | |
6402d961 | 6 | |
bce72611 | 7 | Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> |
db5d2a4b FE |
8 | [WB: add namespace support] |
9 | Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com> | |
6402d961 | 10 | --- |
817b7667 | 11 | meson.build | 4 + |
db5d2a4b FE |
12 | pbs-restore.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++ |
13 | 2 files changed, 240 insertions(+) | |
6402d961 TL |
14 | create mode 100644 pbs-restore.c |
15 | ||
817b7667 | 16 | diff --git a/meson.build b/meson.build |
4fbd50e2 | 17 | index d16b97cf3c..6de51c34cb 100644 |
817b7667 SR |
18 | --- a/meson.build |
19 | +++ b/meson.build | |
4fbd50e2 | 20 | @@ -4029,6 +4029,10 @@ if have_tools |
c5e8e7c9 | 21 | vma = executable('vma', files('vma.c', 'vma-reader.c') + genh, |
817b7667 | 22 | dependencies: [authz, block, crypto, io, qom], install: true) |
6402d961 | 23 | |
c5e8e7c9 | 24 | + pbs_restore = executable('pbs-restore', files('pbs-restore.c') + genh, |
817b7667 SR |
25 | + dependencies: [authz, block, crypto, io, qom, |
26 | + libproxmox_backup_qemu], install: true) | |
27 | + | |
28 | subdir('storage-daemon') | |
4fbd50e2 FE |
29 | |
30 | foreach exe: [ 'qemu-img', 'qemu-io', 'qemu-nbd', 'qemu-storage-daemon'] | |
6402d961 TL |
31 | diff --git a/pbs-restore.c b/pbs-restore.c |
32 | new file mode 100644 | |
db5d2a4b | 33 | index 0000000000..f03d9bab8d |
6402d961 TL |
34 | --- /dev/null |
35 | +++ b/pbs-restore.c | |
db5d2a4b | 36 | @@ -0,0 +1,236 @@ |
6402d961 TL |
37 | +/* |
38 | + * Qemu image restore helper for Proxmox Backup | |
39 | + * | |
40 | + * Copyright (C) 2019 Proxmox Server Solutions | |
41 | + * | |
42 | + * Authors: | |
43 | + * Dietmar Maurer (dietmar@proxmox.com) | |
44 | + * | |
45 | + * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
46 | + * See the COPYING file in the top-level directory. | |
47 | + * | |
48 | + */ | |
49 | + | |
50 | +#include "qemu/osdep.h" | |
51 | +#include <glib.h> | |
52 | +#include <getopt.h> | |
53 | +#include <string.h> | |
54 | + | |
6402d961 TL |
55 | +#include "qemu/module.h" |
56 | +#include "qemu/error-report.h" | |
57 | +#include "qemu/main-loop.h" | |
58 | +#include "qemu/cutils.h" | |
59 | +#include "qapi/error.h" | |
60 | +#include "qapi/qmp/qdict.h" | |
61 | +#include "sysemu/block-backend.h" | |
62 | + | |
63 | +#include <proxmox-backup-qemu.h> | |
64 | + | |
65 | +static void help(void) | |
66 | +{ | |
67 | + const char *help_msg = | |
db5d2a4b | 68 | + "usage: pbs-restore [--repository <repo>] [--ns namespace] snapshot archive-name target [command options]\n" |
6402d961 TL |
69 | + ; |
70 | + | |
71 | + printf("%s", help_msg); | |
72 | + exit(1); | |
73 | +} | |
74 | + | |
75 | +typedef struct CallbackData { | |
76 | + BlockBackend *target; | |
77 | + uint64_t last_offset; | |
78 | + bool skip_zero; | |
79 | +} CallbackData; | |
80 | + | |
81 | +static int write_callback( | |
82 | + void *callback_data_ptr, | |
83 | + uint64_t offset, | |
84 | + const unsigned char *data, | |
85 | + uint64_t data_len) | |
86 | +{ | |
87 | + int res = -1; | |
88 | + | |
89 | + CallbackData *callback_data = (CallbackData *)callback_data_ptr; | |
90 | + | |
91 | + uint64_t last_offset = callback_data->last_offset; | |
92 | + if (offset > last_offset) callback_data->last_offset = offset; | |
93 | + | |
94 | + if (data == NULL) { | |
95 | + if (callback_data->skip_zero && offset > last_offset) { | |
96 | + return 0; | |
97 | + } | |
98 | + res = blk_pwrite_zeroes(callback_data->target, offset, data_len, 0); | |
99 | + } else { | |
5b15e2ec | 100 | + res = blk_pwrite(callback_data->target, offset, data_len, data, 0); |
6402d961 TL |
101 | + } |
102 | + | |
103 | + if (res < 0) { | |
104 | + fprintf(stderr, "blk_pwrite failed at offset %ld length %ld (%d) - %s\n", offset, data_len, res, strerror(-res)); | |
105 | + return res; | |
106 | + } | |
107 | + | |
108 | + return 0; | |
109 | +} | |
110 | + | |
111 | +int main(int argc, char **argv) | |
112 | +{ | |
113 | + Error *main_loop_err = NULL; | |
114 | + const char *format = "raw"; | |
115 | + const char *repository = NULL; | |
db5d2a4b | 116 | + const char *backup_ns = NULL; |
6402d961 TL |
117 | + const char *keyfile = NULL; |
118 | + int verbose = false; | |
119 | + bool skip_zero = false; | |
120 | + | |
121 | + error_init(argv[0]); | |
122 | + | |
c6979241 | 123 | + for (;;) { |
6402d961 TL |
124 | + static const struct option long_options[] = { |
125 | + {"help", no_argument, 0, 'h'}, | |
126 | + {"skip-zero", no_argument, 0, 'S'}, | |
127 | + {"verbose", no_argument, 0, 'v'}, | |
128 | + {"format", required_argument, 0, 'f'}, | |
129 | + {"repository", required_argument, 0, 'r'}, | |
db5d2a4b | 130 | + {"ns", required_argument, 0, 'n'}, |
6402d961 TL |
131 | + {"keyfile", required_argument, 0, 'k'}, |
132 | + {0, 0, 0, 0} | |
133 | + }; | |
134 | + int c = getopt_long(argc, argv, "hvf:r:k:", long_options, NULL); | |
135 | + if (c == -1) { | |
136 | + break; | |
137 | + } | |
c6979241 TL |
138 | + switch (c) { |
139 | + case ':': | |
140 | + fprintf(stderr, "missing argument for option '%s'\n", argv[optind - 1]); | |
141 | + return -1; | |
142 | + case '?': | |
143 | + fprintf(stderr, "unrecognized option '%s'\n", argv[optind - 1]); | |
144 | + return -1; | |
145 | + case 'f': | |
146 | + format = g_strdup(argv[optind - 1]); | |
147 | + break; | |
148 | + case 'r': | |
149 | + repository = g_strdup(argv[optind - 1]); | |
150 | + break; | |
db5d2a4b FE |
151 | + case 'n': |
152 | + backup_ns = g_strdup(argv[optind - 1]); | |
153 | + break; | |
c6979241 TL |
154 | + case 'k': |
155 | + keyfile = g_strdup(argv[optind - 1]); | |
156 | + break; | |
157 | + case 'v': | |
158 | + verbose = true; | |
159 | + break; | |
160 | + case 'S': | |
161 | + skip_zero = true; | |
162 | + break; | |
163 | + case 'h': | |
164 | + help(); | |
165 | + return 0; | |
6402d961 TL |
166 | + } |
167 | + } | |
168 | + | |
169 | + if (optind >= argc - 2) { | |
170 | + fprintf(stderr, "missing arguments\n"); | |
171 | + help(); | |
172 | + return -1; | |
173 | + } | |
174 | + | |
175 | + if (repository == NULL) { | |
176 | + repository = getenv("PBS_REPOSITORY"); | |
177 | + } | |
178 | + | |
179 | + if (repository == NULL) { | |
180 | + fprintf(stderr, "no repository specified\n"); | |
181 | + help(); | |
182 | + return -1; | |
183 | + } | |
184 | + | |
185 | + char *snapshot = argv[optind++]; | |
186 | + char *archive_name = argv[optind++]; | |
187 | + char *target = argv[optind++]; | |
188 | + | |
189 | + const char *password = getenv("PBS_PASSWORD"); | |
190 | + const char *fingerprint = getenv("PBS_FINGERPRINT"); | |
191 | + const char *key_password = getenv("PBS_ENCRYPTION_PASSWORD"); | |
192 | + | |
193 | + if (qemu_init_main_loop(&main_loop_err)) { | |
194 | + g_error("%s", error_get_pretty(main_loop_err)); | |
195 | + } | |
196 | + | |
197 | + bdrv_init(); | |
198 | + module_call_init(MODULE_INIT_QOM); | |
199 | + | |
bce72611 TL |
200 | + if (verbose) { |
201 | + fprintf(stderr, "connecting to repository '%s'\n", repository); | |
202 | + } | |
6402d961 | 203 | + char *pbs_error = NULL; |
db5d2a4b FE |
204 | + ProxmoxRestoreHandle *conn = proxmox_restore_new_ns( |
205 | + repository, | |
206 | + snapshot, | |
207 | + backup_ns, | |
208 | + password, | |
209 | + keyfile, | |
210 | + key_password, | |
211 | + fingerprint, | |
212 | + &pbs_error | |
213 | + ); | |
6402d961 TL |
214 | + if (conn == NULL) { |
215 | + fprintf(stderr, "restore failed: %s\n", pbs_error); | |
216 | + return -1; | |
217 | + } | |
218 | + | |
0c893fd8 SR |
219 | + int res = proxmox_restore_connect(conn, &pbs_error); |
220 | + if (res < 0 || pbs_error) { | |
221 | + fprintf(stderr, "restore failed (connection error): %s\n", pbs_error); | |
222 | + return -1; | |
223 | + } | |
224 | + | |
6402d961 | 225 | + QDict *options = qdict_new(); |
6402d961 TL |
226 | + |
227 | + if (format) { | |
228 | + qdict_put_str(options, "driver", format); | |
229 | + } | |
230 | + | |
bce72611 TL |
231 | + |
232 | + if (verbose) { | |
233 | + fprintf(stderr, "open block backend for target '%s'\n", target); | |
234 | + } | |
6402d961 TL |
235 | + Error *local_err = NULL; |
236 | + int flags = BDRV_O_RDWR; | |
6402d961 TL |
237 | + BlockBackend *blk = blk_new_open(target, NULL, options, flags, &local_err); |
238 | + if (!blk) { | |
239 | + fprintf(stderr, "%s\n", error_get_pretty(local_err)); | |
240 | + return -1; | |
241 | + } | |
242 | + | |
c6979241 | 243 | + CallbackData *callback_data = calloc(sizeof(CallbackData), 1); |
6402d961 TL |
244 | + |
245 | + callback_data->target = blk; | |
246 | + callback_data->skip_zero = skip_zero; | |
247 | + callback_data->last_offset = 0; | |
248 | + | |
249 | + // blk_set_enable_write_cache(blk, !writethrough); | |
250 | + | |
bce72611 TL |
251 | + if (verbose) { |
252 | + fprintf(stderr, "starting to restore snapshot '%s'\n", snapshot); | |
fff7e250 | 253 | + fflush(stderr); // ensure we do not get printed after the progress log |
bce72611 | 254 | + } |
0c893fd8 | 255 | + res = proxmox_restore_image( |
6402d961 TL |
256 | + conn, |
257 | + archive_name, | |
258 | + write_callback, | |
259 | + callback_data, | |
260 | + &pbs_error, | |
261 | + verbose); | |
262 | + | |
263 | + proxmox_restore_disconnect(conn); | |
41941247 | 264 | + blk_unref(blk); |
6402d961 TL |
265 | + |
266 | + if (res < 0) { | |
267 | + fprintf(stderr, "restore failed: %s\n", pbs_error); | |
268 | + return -1; | |
269 | + } | |
270 | + | |
271 | + return 0; | |
272 | +} |