]> git.proxmox.com Git - qemu-server.git/blob - qmrestore
Use 'dd' option ibs/obs instead of bs
[qemu-server.git] / qmrestore
1 #!/usr/bin/perl -w
2 #
3 # Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
4 #
5 # Copyright: vzdump is under GNU GPL, the GNU General Public License.
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 dated June, 1991.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the
18 # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 #
21 # Author: Dietmar Maurer <dietmar@proxmox.com>
22 #
23
24 use strict;
25 use Getopt::Long;
26 use Sys::Syslog;
27 use File::Path;
28 use PVE::VZDump;
29 use PVE::Storage;
30
31 $ENV{LANG} = "C"; # avoid locale related issues/warnings
32
33 openlog ('vzdump', 'cons,pid', 'daemon');
34
35 my @std_opts = ('extract', 'storage=s', 'info', 'prealloc', 'unique');
36
37 sub print_usage {
38 my $msg = shift;
39
40 print STDERR "ERROR: $msg\n\n" if $msg;
41
42 print STDERR "usage: $0 [OPTIONS] <ARCHIVE> <VMID>\n\n";
43 }
44
45 sub shellquote {
46 my $str = shift;
47
48 return "''" if !defined ($str) || ($str eq '');
49
50 die "unable to quote string containing null (\\000) bytes"
51 if $str =~ m/\x00/;
52
53 # from String::ShellQuote
54 if ($str =~ m|[^\w!%+,\-./:@^]|) {
55
56 # ' -> '\''
57 $str =~ s/'/'\\''/g;
58
59 $str = "'$str'";
60 $str =~ s/^''//;
61 $str =~ s/''$//;
62 }
63
64 return $str;
65 }
66
67 my $quoted_cmd = shellquote ($0);
68 foreach my $arg (@ARGV) {
69 $quoted_cmd .= " " . shellquote ($arg);
70 }
71
72 my $opts = {};
73 if (!GetOptions ($opts, @std_opts)) {
74 print_usage ();
75 exit (-1);
76 }
77
78 if ($#ARGV != 1) {
79 print_usage ();
80 exit (-1);
81 }
82
83 my $archive = shift;
84 my $vmid = PVE::VZDump::check_vmids ((shift))->[0];
85
86 $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
87 die "interrupted by signal\n";
88 };
89
90 sub debugmsg { PVE::VZDump::debugmsg (@_); } # just a shortcut
91
92 sub run_command { PVE::VZDump::run_command (undef, @_); } # just a shortcut
93
94 if ($opts->{extract}) {
95
96 # NOTE: this is run as tar subprocess (--to-command)
97
98 my $filename = $ENV{TAR_FILENAME};
99 die "got strange environment - no TAR_FILENAME\n" if !$filename;
100
101 my $filesize = $ENV{TAR_SIZE};
102 die "got strange file size '$filesize'\n" if !$filesize;
103
104 my $tmpdir = $ENV{VZDUMP_TMPDIR};
105 die "got strange environment - no VZDUMP_TMPDIR\n" if !$tmpdir;
106
107 my $filetype = $ENV{TAR_FILETYPE} || 'none';
108 die "got strange filetype '$filetype'\n" if $filetype ne 'f';
109
110 my $conffile = "$tmpdir/qemu-server.conf";
111 my $statfile = "$tmpdir/qmrestore.stat";
112
113 if ($opts->{info}) {
114 print STDERR "reading archive member '$filename'\n";
115 } else {
116 print STDERR "extracting '$filename' from archive\n";
117 }
118
119 if ($filename eq 'qemu-server.conf') {
120 my $outfd = IO::File->new ($conffile, "w") ||
121 die "unable to write file '$conffile'\n";
122
123 while (defined (my $line = <>)) {
124 print $outfd $line;
125 print STDERR "CONFIG: $line" if $opts->{info};
126 }
127
128 $outfd->close();
129
130 exit (0);
131 }
132
133 if ($opts->{info}) {
134 exec 'dd', 'bs=256K', "of=/dev/null";
135 die "couldn't exec dd: $!\n";
136 }
137
138 my $conffd = IO::File->new ($conffile, "r") ||
139 die "unable to read file '$conffile'\n";
140
141 my $map;
142 while (defined (my $line = <$conffd>)) {
143 if ($line =~ m/^\#vzdump\#map:(\S+):(\S+):(\d+):(\S*):$/) {
144 $map->{$2} = { virtdev => $1, size => $3, storeid => $4 };
145 }
146 }
147 close ($conffd);
148
149 my $statfd = IO::File->new ($statfile, "a") ||
150 die "unable to open file '$statfile'\n";
151
152 if ($filename !~ m/^.*\.([^\.]+)$/){
153 die "got strange filename '$filename'\n";
154 }
155 my $format = $1;
156
157 my $path;
158
159 if (!$map) {
160 print STDERR "restoring old style vzdump archive - " .
161 "no device map inside archive\n";
162 die "can't restore old style archive to storage '$opts->{storage}'\n"
163 if $opts->{storage} && $opts->{storage} ne 'local';
164
165 my $dir = "/var/lib/vz/images/$vmid";
166 mkpath $dir;
167
168 $path = "$dir/$filename";
169
170 print $statfd "vzdump::$path\n";
171 $statfd->close();
172
173 } else {
174
175 my $info = $map->{$filename};
176 die "no vzdump info for '$filename'\n" if !$info;
177
178 if ($filename !~ m/^vm-disk-$info->{virtdev}\.([^\.]+)$/){
179 die "got strange filename '$filename'\n";
180 }
181
182 if ($filesize != $info->{size}) {
183 die "detected size difference for '$filename' " .
184 "($filesize != $info->{size})\n";
185 }
186
187 my $storeid;
188 if ($opts->{storage}) {
189 $storeid = $opts->{storage};
190 } else {
191 $storeid = $info->{storeid} || 'local';
192 }
193
194 my $cfg = PVE::Storage::load_config();
195 my $scfg = PVE::Storage::storage_config ($cfg, $storeid);
196
197 my $alloc_size = ($filesize + 1024 - 1)/1024;
198 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
199 # hack: we just alloc a small file (32K) - we overwrite it anyways
200 $alloc_size = 32;
201 } else {
202 die "unable to restore '$filename' to storage '$storeid'\n" .
203 "storage type '$scfg->{type}' does not support format '$format\n"
204 if $format ne 'raw';
205 }
206
207 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $vmid,
208 $format, undef, $alloc_size);
209
210 print STDERR "new volume ID is '$volid'\n";
211
212 print $statfd "vzdump:$info->{virtdev}:$volid\n";
213 $statfd->close();
214
215 $path = PVE::Storage::path ($cfg, $volid);
216 }
217
218 print STDERR "restore data to '$path' ($filesize bytes)\n";
219
220 if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) {
221 exec 'dd', 'ibs=256K', 'obs=256K', "of=$path";
222 die "couldn't exec dd: $!\n";
223 } else {
224 exec '/usr/lib/qemu-server/sparsecp', $path;
225 die "couldn't exec sparsecp: $!\n";
226 }
227 }
228
229 sub restore_cleanup {
230 my $statfile = shift;
231
232 return if $opts->{info};
233
234 debugmsg ('info', "starting cleanup");
235 if (my $fd = IO::File->new ($statfile, "r")) {
236 while (defined (my $line = <$fd>)) {
237 if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
238 my $volid = $2;
239 eval {
240 if ($volid =~ m|^/|) {
241 unlink $volid || die 'unlink failed\n';
242 } else {
243 my $cfg = PVE::Storage::load_config();
244 PVE::Storage::vdisk_free ($cfg, $volid);
245 }
246 debugmsg ('info', "temporary volume '$volid' sucessfuly removed");
247 };
248 debugmsg ('err', "unable to cleanup '$volid' - $@") if $@;
249 } else {
250 debugmsg ('info', "unable to parse line in statfile - $line");
251 }
252 }
253 $fd->close();
254 }
255 }
256
257 sub restore_qemu {
258 my ($archive, $vmid, $tmpdir) = @_;
259
260 local $ENV{VZDUMP_TMPDIR} = $tmpdir;
261
262 my $subcmd = shellquote ("--to-command=${quoted_cmd}\ --extract");
263 my $cmd = "tar xf '$archive' $subcmd";
264 run_command ($cmd);
265
266 return if $opts->{info};
267
268 # reed new mapping
269 my $map = {};
270 my $statfile = "$tmpdir/qmrestore.stat";
271 if (my $fd = IO::File->new ($statfile, "r")) {
272 while (defined (my $line = <$fd>)) {
273 if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
274 $map->{$1} = $2 if $1;
275 } else {
276 debugmsg ('info', "unable to parse line in statfile - $line");
277 }
278 }
279 $fd->close();
280 }
281
282 my $confsrc = "$tmpdir/qemu-server.conf";
283
284 my $srcfd = new IO::File ($confsrc, "r") ||
285 die "unable to open file '$confsrc'\n";
286
287 my $conffile = PVE::QemuServer::config_file ($vmid);
288 my $tmpfn = "$conffile.$$.tmp";
289
290 my $outfd = new IO::File ($tmpfn, "w") ||
291 die "unable to write config for VM $vmid\n";
292
293 eval {
294 while (defined (my $line = <$srcfd>)) {
295 next if $line =~ m/^\#vzdump\#/;
296 next if $line =~ m/^lock:/;
297
298 if (($line =~ m/^((vlan)\d+):(.*)$/) && ($opts->{unique})) {
299 my ($id,$ethcfg) = ($1,$3);
300 $ethcfg =~ s/^\s+//;
301 my ($model, $mac) = split(/\=/,$ethcfg);
302 my $printvlan = PVE::QemuServer::print_vlan (PVE::QemuServer::parse_vlan ($model));
303 print $outfd "$id: $printvlan\n";
304 } elsif ($line =~ m/^((ide|scsi|virtio)\d+):(.*)$/) {
305 my $virtdev = $1;
306 my $value = $2;
307 if ($line =~ m/backup=no/) {
308 print $outfd "#$line";
309 } elsif ($virtdev && $map->{$virtdev}) {
310 my $di = PVE::QemuServer::parse_drive ($virtdev, $value);
311 $di->{file} = $map->{$virtdev};
312 $value = PVE::QemuServer::print_drive ($vmid, $di);
313 print $outfd "$virtdev: $value\n";
314 } else {
315 print $outfd $line;
316 }
317 } else {
318 print $outfd $line;
319 }
320 }
321 };
322 my $err = $@;
323
324 $outfd->close();
325
326 if ($err) {
327 unlink $tmpfn;
328 die $err;
329 } else {
330 rename $tmpfn, $conffile;
331 }
332 }
333
334 my $firstfile = PVE::VZDump::read_firstfile ($archive);
335 if ($firstfile ne 'qemu-server.conf') {
336 die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n";
337 }
338
339 my $tmpdir = "/var/tmp/vzdumptmp$$";
340
341 PVE::QemuServer::lock_config ($vmid, sub {
342
343 my $conffile = PVE::QemuServer::config_file ($vmid);
344
345 die "unable to restore VM '$vmid' - VM already exists\n"
346 if -f $conffile;
347
348 mkpath $tmpdir;
349
350 eval {
351 debugmsg ('info', "restore QemuServer backup '$archive' " .
352 "using ID $vmid", undef, 1) if !$opts->{info};
353
354 restore_qemu ($archive, $vmid, $tmpdir);
355
356 if ($opts->{info}) {
357 debugmsg ('info', "reading '$archive' successful");
358 } else {
359 debugmsg ('info', "restore QemuServer backup '$archive' successful",
360 undef, 1);
361 }
362 };
363 my $err = $@;
364
365 if ($err) {
366 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
367 debugmsg ('info', "got interrupt - ignored (cleanup phase)");
368 };
369
370 restore_cleanup ("$tmpdir/qmrestore.stat") if $err;
371 }
372
373 die $err if $err;
374 });
375
376 my $err = $@;
377
378 rmtree $tmpdir;
379
380 if ($err) {
381 if ($opts->{info}) {
382 debugmsg ('info', "reading '$archive' failed - $err");
383 } else {
384
385 debugmsg ('err', "restore QemuServer backup '$archive' failed - $err",
386 undef, 1);
387 }
388 exit (-1);
389 }
390
391 exit (0);
392
393 __END__
394
395 =head1 NAME
396
397 qmrestore - restore QemuServer vzdump backups
398
399 =head1 SYNOPSIS
400
401 qmrestore [OPTIONS] <archive> <VMID>
402
403 --info read/verify archive and print relevant
404 information (test run)
405
406 --unique assign a unique random ethernet address
407
408 --storage <STORAGE_ID> restore to storage <STORAGE_ID>
409
410 --prealloc never generate sparse files
411
412 =head1 DESCRIPTION
413
414 Restore the QemuServer vzdump backup <archive> to virtual machine
415 <VMID>. Volumes are allocated on the original storage if there is no
416 C<--storage> specified.
417
418 =head1 SEE ALSO
419
420 vzdump(1) vzrestore(1)