mirror of
https://git.proxmox.com/git/qemu-server
synced 2025-06-14 15:54:08 +00:00
implement qmrestore
Restore is a special case of create_vm.
This commit is contained in:
parent
c339b1967d
commit
3e16d5fc60
9
Makefile
9
Makefile
@ -54,10 +54,13 @@ sparsecp: sparsecp.c utils.c
|
|||||||
qm.1.pod: qm PVE/QemuServer.pm
|
qm.1.pod: qm PVE/QemuServer.pm
|
||||||
perl -I. ./qm printmanpod >$@
|
perl -I. ./qm printmanpod >$@
|
||||||
|
|
||||||
|
qmrestore.1.pod: qmrestore
|
||||||
|
perl -I. ./qmrestore printmanpod >$@
|
||||||
|
|
||||||
vm.conf.5.pod: gen-vmconf-pod.pl PVE/QemuServer.pm
|
vm.conf.5.pod: gen-vmconf-pod.pl PVE/QemuServer.pm
|
||||||
perl -I. ./gen-vmconf-pod.pl >$@
|
perl -I. ./gen-vmconf-pod.pl >$@
|
||||||
|
|
||||||
PKGSOURCES=qm qm.1.gz qm.1.pod qmrestore qmrestore.1.gz sparsecp vmtar qemu.init.d qmupdate control vm.conf.5.pod vm.conf.5.gz
|
PKGSOURCES=qm qm.1.gz qm.1.pod qmrestore qmrestore.1.pod qmrestore.1.gz qmextract sparsecp vmtar qemu.init.d qmupdate control vm.conf.5.pod vm.conf.5.gz
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install: ${PKGSOURCES}
|
install: ${PKGSOURCES}
|
||||||
@ -78,9 +81,11 @@ install: ${PKGSOURCES}
|
|||||||
install -m 0755 pve-bridge ${DESTDIR}${VARLIBDIR}/pve-bridge
|
install -m 0755 pve-bridge ${DESTDIR}${VARLIBDIR}/pve-bridge
|
||||||
install -s -m 0755 vmtar ${DESTDIR}${LIBDIR}
|
install -s -m 0755 vmtar ${DESTDIR}${LIBDIR}
|
||||||
install -s -m 0755 sparsecp ${DESTDIR}${LIBDIR}
|
install -s -m 0755 sparsecp ${DESTDIR}${LIBDIR}
|
||||||
|
install -m 0755 qmextract ${DESTDIR}${LIBDIR}
|
||||||
install -m 0644 qm.1.gz ${DESTDIR}/usr/share/man/man1/
|
install -m 0644 qm.1.gz ${DESTDIR}/usr/share/man/man1/
|
||||||
install -m 0644 qm.1.pod ${DESTDIR}/${PODDIR}
|
install -m 0644 qm.1.pod ${DESTDIR}/${PODDIR}
|
||||||
install -m 0644 qmrestore.1.gz ${DESTDIR}/usr/share/man/man1/
|
install -m 0644 qmrestore.1.gz ${DESTDIR}/usr/share/man/man1/
|
||||||
|
install -m 0644 qmrestore.1.pod ${DESTDIR}/${PODDIR}
|
||||||
install -m 0644 vm.conf.5.pod ${DESTDIR}/${PODDIR}
|
install -m 0644 vm.conf.5.pod ${DESTDIR}/${PODDIR}
|
||||||
install -m 0644 vm.conf.5.gz ${DESTDIR}/usr/share/man/man5/
|
install -m 0644 vm.conf.5.gz ${DESTDIR}/usr/share/man/man5/
|
||||||
|
|
||||||
@ -114,7 +119,7 @@ upload:
|
|||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf build *.deb qm.1.gz control vzsyscalls.ph _h2ph_pre.ph ${PACKAGE}-*.tar.gz dist *.1,gz *.pod vmtar sparsecp
|
rm -rf build *.deb control vzsyscalls.ph _h2ph_pre.ph ${PACKAGE}-*.tar.gz dist *.1.gz *.pod vmtar sparsecp
|
||||||
find . -name '*~' -exec rm {} ';'
|
find . -name '*~' -exec rm {} ';'
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ __PACKAGE__->register_method({
|
|||||||
name => 'create_vm',
|
name => 'create_vm',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'POST',
|
method => 'POST',
|
||||||
description => "Create new virtual machine.",
|
description => "Create or restore a virtual machine.",
|
||||||
protected => 1,
|
protected => 1,
|
||||||
proxyto => 'node',
|
proxyto => 'node',
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -75,6 +75,21 @@ __PACKAGE__->register_method({
|
|||||||
{
|
{
|
||||||
node => get_standard_option('pve-node'),
|
node => get_standard_option('pve-node'),
|
||||||
vmid => get_standard_option('pve-vmid'),
|
vmid => get_standard_option('pve-vmid'),
|
||||||
|
archive => {
|
||||||
|
description => "The backup file.",
|
||||||
|
type => 'string',
|
||||||
|
optional => 1,
|
||||||
|
maxLength => 255,
|
||||||
|
},
|
||||||
|
storage => get_standard_option('pve-storage-id', {
|
||||||
|
description => "Default storage.",
|
||||||
|
optional => 1,
|
||||||
|
}),
|
||||||
|
force => {
|
||||||
|
optional => 1,
|
||||||
|
type => 'boolean',
|
||||||
|
description => "Allow to overwrite existing VM.",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
@ -91,26 +106,50 @@ __PACKAGE__->register_method({
|
|||||||
|
|
||||||
my $vmid = extract_param($param, 'vmid');
|
my $vmid = extract_param($param, 'vmid');
|
||||||
|
|
||||||
|
my $archive = extract_param($param, 'archive');
|
||||||
|
|
||||||
|
my $storage = extract_param($param, 'storage');
|
||||||
|
|
||||||
my $filename = PVE::QemuServer::config_file($vmid);
|
my $filename = PVE::QemuServer::config_file($vmid);
|
||||||
# first test (befor locking)
|
|
||||||
die "unable to create vm $vmid: config file already exists\n"
|
|
||||||
if -f $filename;
|
|
||||||
|
|
||||||
my $storecfg = PVE::Storage::config();
|
my $storecfg = PVE::Storage::config();
|
||||||
|
|
||||||
&$resolve_cdrom_alias($param);
|
PVE::Cluster::check_cfs_quorum();
|
||||||
|
|
||||||
foreach my $opt (keys %$param) {
|
if (!$archive) {
|
||||||
if (PVE::QemuServer::valid_drivename($opt)) {
|
&$resolve_cdrom_alias($param);
|
||||||
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
|
|
||||||
raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
|
|
||||||
|
|
||||||
PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
|
foreach my $opt (keys %$param) {
|
||||||
$param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
|
if (PVE::QemuServer::valid_drivename($opt)) {
|
||||||
|
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
|
||||||
|
raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
|
||||||
|
|
||||||
|
PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
|
||||||
|
$param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PVE::QemuServer::add_random_macs($param);
|
||||||
}
|
}
|
||||||
|
|
||||||
PVE::QemuServer::add_random_macs($param);
|
# fixme: archive eq '-' (read from stdin)
|
||||||
|
|
||||||
|
my $restorefn = sub {
|
||||||
|
|
||||||
|
if (-f $filename) {
|
||||||
|
die "unable to restore vm $vmid: config file already exists\n"
|
||||||
|
if !$param->{force};
|
||||||
|
|
||||||
|
die "unable to restore vm $vmid: vm is running\n"
|
||||||
|
if PVE::QemuServer::check_running($vmid);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $realcmd = sub {
|
||||||
|
PVE::QemuServer::restore_archive($archive, $vmid, { storage => $storage});
|
||||||
|
};
|
||||||
|
|
||||||
|
return $rpcenv->fork_worker('qmrestore', $vmid, $user, $realcmd);
|
||||||
|
};
|
||||||
|
|
||||||
my $createfn = sub {
|
my $createfn = sub {
|
||||||
|
|
||||||
@ -123,7 +162,7 @@ __PACKAGE__->register_method({
|
|||||||
my $vollist = [];
|
my $vollist = [];
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
$vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param);
|
$vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param, $storage);
|
||||||
|
|
||||||
# try to be smart about bootdisk
|
# try to be smart about bootdisk
|
||||||
my @disks = PVE::QemuServer::disknames();
|
my @disks = PVE::QemuServer::disknames();
|
||||||
@ -155,7 +194,7 @@ __PACKAGE__->register_method({
|
|||||||
return $rpcenv->fork_worker('qmcreate', $vmid, $user, $realcmd);
|
return $rpcenv->fork_worker('qmcreate', $vmid, $user, $realcmd);
|
||||||
};
|
};
|
||||||
|
|
||||||
return PVE::QemuServer::lock_config($vmid, $createfn);
|
return PVE::QemuServer::lock_config($vmid, $archive ? $restorefn : $createfn);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
|
@ -1265,7 +1265,7 @@ sub touch_config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub create_disks {
|
sub create_disks {
|
||||||
my ($storecfg, $vmid, $settings, $conf) = @_;
|
my ($storecfg, $vmid, $settings, $conf, $default_storage) = @_;
|
||||||
|
|
||||||
my $vollist = [];
|
my $vollist = [];
|
||||||
|
|
||||||
@ -1278,7 +1278,7 @@ sub create_disks {
|
|||||||
my $file = $disk->{file};
|
my $file = $disk->{file};
|
||||||
|
|
||||||
if ($file =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
|
if ($file =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
|
||||||
my $storeid = $2 || 'local';
|
my $storeid = $2 || $default_storage;
|
||||||
my $size = $3;
|
my $size = $3;
|
||||||
my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
|
my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
|
||||||
my $fmt = $disk->{format} || $defformat;
|
my $fmt = $disk->{format} || $defformat;
|
||||||
@ -2918,4 +2918,161 @@ sub vm_balloonset {
|
|||||||
vm_monitor_command($vmid, "balloon $value", 1);
|
vm_monitor_command($vmid, "balloon $value", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# vzdump restore implementaion
|
||||||
|
|
||||||
|
sub archive_read_firstfile {
|
||||||
|
my $archive = shift;
|
||||||
|
|
||||||
|
die "ERROR: file '$archive' does not exist\n" if ! -f $archive;
|
||||||
|
|
||||||
|
# try to detect archive type first
|
||||||
|
my $pid = open (TMP, "tar tf '$archive'|") ||
|
||||||
|
die "unable to open file '$archive'\n";
|
||||||
|
my $firstfile = <TMP>;
|
||||||
|
kill 15, $pid;
|
||||||
|
close TMP;
|
||||||
|
|
||||||
|
die "ERROR: archive contaions no data\n" if !$firstfile;
|
||||||
|
chomp $firstfile;
|
||||||
|
|
||||||
|
return $firstfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub restore_cleanup {
|
||||||
|
my $statfile = shift;
|
||||||
|
|
||||||
|
print STDERR "starting cleanup\n";
|
||||||
|
|
||||||
|
if (my $fd = IO::File->new($statfile, "r")) {
|
||||||
|
while (defined(my $line = <$fd>)) {
|
||||||
|
if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
|
||||||
|
my $volid = $2;
|
||||||
|
eval {
|
||||||
|
if ($volid =~ m|^/|) {
|
||||||
|
unlink $volid || die 'unlink failed\n';
|
||||||
|
} else {
|
||||||
|
my $cfg = cfs_read_file('storage.cfg');
|
||||||
|
PVE::Storage::vdisk_free($cfg, $volid);
|
||||||
|
}
|
||||||
|
print STDERR "temporary volume '$volid' sucessfuly removed\n";
|
||||||
|
};
|
||||||
|
print STDERR "unable to cleanup '$volid' - $@" if $@;
|
||||||
|
} else {
|
||||||
|
print STDERR "unable to parse line in statfile - $line";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fd->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub restore_archive {
|
||||||
|
my ($archive, $vmid, $opts) = @_;
|
||||||
|
|
||||||
|
my $firstfile = archive_read_firstfile($archive);
|
||||||
|
die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n"
|
||||||
|
if $firstfile ne 'qemu-server.conf';
|
||||||
|
|
||||||
|
my $tocmd = "/usr/lib/qemu-server/qmextract";
|
||||||
|
|
||||||
|
$tocmd .= " --storage $opts->{storage}" if $opts->{storage};
|
||||||
|
$tocmd .= ' --prealloc' if $opts->{prealloc};
|
||||||
|
$tocmd .= ' --info' if $opts->{info};
|
||||||
|
|
||||||
|
my $cmd = ['tar', 'xf', $archive, '--to-command', $tocmd];
|
||||||
|
|
||||||
|
my $tmpdir = "/var/tmp/vzdumptmp$$";
|
||||||
|
mkpath $tmpdir;
|
||||||
|
|
||||||
|
local $ENV{VZDUMP_TMPDIR} = $tmpdir;
|
||||||
|
local $ENV{VZDUMP_VMID} = $vmid;
|
||||||
|
|
||||||
|
my $conffile = PVE::QemuServer::config_file($vmid);
|
||||||
|
my $tmpfn = "$conffile.$$.tmp";
|
||||||
|
|
||||||
|
# disable interrupts (always do cleanups)
|
||||||
|
local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
|
||||||
|
print STDERR "got interrupt - ignored\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
eval {
|
||||||
|
# enable interrupts
|
||||||
|
local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
|
||||||
|
die "interrupted by signal\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
PVE::Tools::run_command($cmd);
|
||||||
|
|
||||||
|
return if $opts->{info};
|
||||||
|
|
||||||
|
# read new mapping
|
||||||
|
my $map = {};
|
||||||
|
my $statfile = "$tmpdir/qmrestore.stat";
|
||||||
|
if (my $fd = IO::File->new($statfile, "r")) {
|
||||||
|
while (defined (my $line = <$fd>)) {
|
||||||
|
if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
|
||||||
|
$map->{$1} = $2 if $1;
|
||||||
|
} else {
|
||||||
|
print STDERR "unable to parse line in statfile - $line\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fd->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
my $confsrc = "$tmpdir/qemu-server.conf";
|
||||||
|
|
||||||
|
my $srcfd = new IO::File($confsrc, "r") ||
|
||||||
|
die "unable to open file '$confsrc'\n";
|
||||||
|
|
||||||
|
my $outfd = new IO::File ($tmpfn, "w") ||
|
||||||
|
die "unable to write config for VM $vmid\n";
|
||||||
|
|
||||||
|
while (defined (my $line = <$srcfd>)) {
|
||||||
|
next if $line =~ m/^\#vzdump\#/;
|
||||||
|
next if $line =~ m/^lock:/;
|
||||||
|
next if $line =~ m/^unused\d+:/;
|
||||||
|
|
||||||
|
if (($line =~ m/^((vlan)\d+):(.*)$/) && ($opts->{unique})) {
|
||||||
|
my ($id,$ethcfg) = ($1,$3);
|
||||||
|
$ethcfg =~ s/^\s+//;
|
||||||
|
my ($model, $mac) = split(/\=/,$ethcfg);
|
||||||
|
my $printvlan = PVE::QemuServer::print_vlan(PVE::QemuServer::parse_vlan($model));
|
||||||
|
print $outfd "$id: $printvlan\n";
|
||||||
|
} elsif ($line =~ m/^((ide|scsi|virtio)\d+):(.*)$/) {
|
||||||
|
my $virtdev = $1;
|
||||||
|
my $value = $2;
|
||||||
|
if ($line =~ m/backup=no/) {
|
||||||
|
print $outfd "#$line";
|
||||||
|
} elsif ($virtdev && $map->{$virtdev}) {
|
||||||
|
my $di = PVE::QemuServer::parse_drive($virtdev, $value);
|
||||||
|
$di->{file} = $map->{$virtdev};
|
||||||
|
$value = PVE::QemuServer::print_drive($vmid, $di);
|
||||||
|
print $outfd "$virtdev: $value\n";
|
||||||
|
} else {
|
||||||
|
print $outfd $line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print $outfd $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$srcfd->close();
|
||||||
|
$outfd->close();
|
||||||
|
};
|
||||||
|
my $err = $@;
|
||||||
|
|
||||||
|
if ($err) {
|
||||||
|
|
||||||
|
unlink $tmpfn;
|
||||||
|
|
||||||
|
restore_cleanup("$tmpdir/qmrestore.stat") if !$opts->{info};
|
||||||
|
|
||||||
|
die $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
rmtree $tmpdir;
|
||||||
|
|
||||||
|
rename $tmpfn, $conffile ||
|
||||||
|
die "unable to commit configuration file '$conffile'\n";
|
||||||
|
};
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -6,7 +6,8 @@ use File::Path;
|
|||||||
use File::Basename;
|
use File::Basename;
|
||||||
use PVE::INotify;
|
use PVE::INotify;
|
||||||
use PVE::VZDump;
|
use PVE::VZDump;
|
||||||
use PVE::Cluster;
|
use PVE::Cluster qw(cfs_read_file);
|
||||||
|
use PVE::Tools;
|
||||||
use PVE::Storage;
|
use PVE::Storage;
|
||||||
use PVE::QemuServer;
|
use PVE::QemuServer;
|
||||||
use IO::File;
|
use IO::File;
|
||||||
|
186
qmextract
Executable file
186
qmextract
Executable file
@ -0,0 +1,186 @@
|
|||||||
|
#!/usr/bin/perl -w
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use Getopt::Long;
|
||||||
|
use File::Path;
|
||||||
|
use IO::File;
|
||||||
|
use PVE::JSONSchema;
|
||||||
|
use PVE::Tools;
|
||||||
|
use PVE::Cluster qw(cfs_read_file);
|
||||||
|
use PVE::QemuServer;
|
||||||
|
|
||||||
|
my @std_opts = ('storage=s', 'info', 'prealloc');
|
||||||
|
|
||||||
|
sub print_usage {
|
||||||
|
print STDERR "usage: $0 [--storage=<storeid>] [--info] [--prealloc] <archive> <vmid>\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $opts = {};
|
||||||
|
if (!GetOptions ($opts, @std_opts)) {
|
||||||
|
print_usage ();
|
||||||
|
exit (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub extract_archive {
|
||||||
|
# NOTE: this is run as tar subprocess (--to-command)
|
||||||
|
|
||||||
|
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
|
||||||
|
die "interrupted by signal\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
my $filename = $ENV{TAR_FILENAME};
|
||||||
|
die "got strange environment - no TAR_FILENAME\n" if !$filename;
|
||||||
|
|
||||||
|
my $filesize = $ENV{TAR_SIZE};
|
||||||
|
die "got strange file size '$filesize'\n" if !$filesize;
|
||||||
|
|
||||||
|
my $tmpdir = $ENV{VZDUMP_TMPDIR};
|
||||||
|
die "got strange environment - no VZDUMP_TMPDIR\n" if !$tmpdir;
|
||||||
|
|
||||||
|
my $filetype = $ENV{TAR_FILETYPE} || 'none';
|
||||||
|
die "got strange filetype '$filetype'\n" if $filetype ne 'f';
|
||||||
|
|
||||||
|
my $vmid = $ENV{VZDUMP_VMID};
|
||||||
|
PVE::JSONSchema::pve_verify_vmid($vmid);
|
||||||
|
|
||||||
|
if ($opts->{info}) {
|
||||||
|
print STDERR "reading archive member '$filename'\n";
|
||||||
|
} else {
|
||||||
|
print STDERR "extracting '$filename' from archive\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $conffile = "$tmpdir/qemu-server.conf";
|
||||||
|
my $statfile = "$tmpdir/qmrestore.stat";
|
||||||
|
|
||||||
|
if ($filename eq 'qemu-server.conf') {
|
||||||
|
my $outfd = IO::File->new($conffile, "w") ||
|
||||||
|
die "unable to write file '$conffile'\n";
|
||||||
|
|
||||||
|
while (defined(my $line = <>)) {
|
||||||
|
print $outfd $line;
|
||||||
|
print STDERR "CONFIG: $line" if $opts->{info};
|
||||||
|
}
|
||||||
|
|
||||||
|
$outfd->close();
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($opts->{info}) {
|
||||||
|
exec 'dd', 'bs=256K', "of=/dev/null";
|
||||||
|
die "couldn't exec dd: $!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $conffd = IO::File->new($conffile, "r") ||
|
||||||
|
die "unable to read file '$conffile'\n";
|
||||||
|
|
||||||
|
my $map;
|
||||||
|
while (defined(my $line = <$conffd>)) {
|
||||||
|
if ($line =~ m/^\#vzdump\#map:(\S+):(\S+):(\d+):(\S*):$/) {
|
||||||
|
$map->{$2} = { virtdev => $1, size => $3, storeid => $4 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close($conffd);
|
||||||
|
|
||||||
|
my $statfd = IO::File->new($statfile, "a") ||
|
||||||
|
die "unable to open file '$statfile'\n";
|
||||||
|
|
||||||
|
if ($filename !~ m/^.*\.([^\.]+)$/){
|
||||||
|
die "got strange filename '$filename'\n";
|
||||||
|
}
|
||||||
|
my $format = $1;
|
||||||
|
|
||||||
|
my $path;
|
||||||
|
|
||||||
|
if (!$map) {
|
||||||
|
print STDERR "restoring old style vzdump archive - " .
|
||||||
|
"no device map inside archive\n";
|
||||||
|
die "can't restore old style archive to storage '$opts->{storage}'\n"
|
||||||
|
if $opts->{storage} && $opts->{storage} ne 'local';
|
||||||
|
|
||||||
|
my $dir = "/var/lib/vz/images/$vmid";
|
||||||
|
mkpath $dir;
|
||||||
|
|
||||||
|
$path = "$dir/$filename";
|
||||||
|
|
||||||
|
print $statfd "vzdump::$path\n";
|
||||||
|
$statfd->close();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
my $info = $map->{$filename};
|
||||||
|
die "no vzdump info for '$filename'\n" if !$info;
|
||||||
|
|
||||||
|
if ($filename !~ m/^vm-disk-$info->{virtdev}\.([^\.]+)$/){
|
||||||
|
die "got strange filename '$filename'\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filesize != $info->{size}) {
|
||||||
|
die "detected size difference for '$filename' " .
|
||||||
|
"($filesize != $info->{size})\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $storeid;
|
||||||
|
if ($opts->{storage}) {
|
||||||
|
$storeid = $opts->{storage};
|
||||||
|
} else {
|
||||||
|
$storeid = $info->{storeid} || 'local';
|
||||||
|
}
|
||||||
|
|
||||||
|
my $cfg = cfs_read_file('storage.cfg');
|
||||||
|
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
|
||||||
|
|
||||||
|
my $alloc_size = ($filesize + 1024 - 1)/1024;
|
||||||
|
if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
|
||||||
|
# hack: we just alloc a small file (32K) - we overwrite it anyways
|
||||||
|
$alloc_size = 32;
|
||||||
|
} else {
|
||||||
|
die "unable to restore '$filename' to storage '$storeid'\n" .
|
||||||
|
"storage type '$scfg->{type}' does not support format '$format\n"
|
||||||
|
if $format ne 'raw';
|
||||||
|
}
|
||||||
|
|
||||||
|
my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid,
|
||||||
|
$format, undef, $alloc_size);
|
||||||
|
|
||||||
|
print STDERR "new volume ID is '$volid'\n";
|
||||||
|
|
||||||
|
print $statfd "vzdump:$info->{virtdev}:$volid\n";
|
||||||
|
$statfd->close();
|
||||||
|
|
||||||
|
$path = PVE::Storage::path($cfg, $volid);
|
||||||
|
}
|
||||||
|
|
||||||
|
print STDERR "restore data to '$path' ($filesize bytes)\n";
|
||||||
|
|
||||||
|
if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) {
|
||||||
|
exec 'dd', 'ibs=256K', 'obs=256K', "of=$path";
|
||||||
|
die "couldn't exec dd: $!\n";
|
||||||
|
} else {
|
||||||
|
exec '/usr/lib/qemu-server/sparsecp', $path;
|
||||||
|
die "couldn't exec sparsecp: $!\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (scalar(@ARGV) == 2) {
|
||||||
|
my $archive = shift;
|
||||||
|
my $vmid = shift;
|
||||||
|
|
||||||
|
# fixme: use API call
|
||||||
|
PVE::JSONSchema::pve_verify_vmid($vmid);
|
||||||
|
|
||||||
|
PVE::Cluster::check_cfs_quorum();
|
||||||
|
|
||||||
|
PVE::QemuServer::restore_archive($archive, $vmid, $opts);
|
||||||
|
|
||||||
|
} elsif (scalar(@ARGV) == 0 && $ENV{TAR_FILENAME}) {
|
||||||
|
extract_archive();
|
||||||
|
} else {
|
||||||
|
print_usage ();
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
|
483
qmrestore
483
qmrestore
@ -1,394 +1,80 @@
|
|||||||
#!/usr/bin/perl -w
|
#!/usr/bin/perl -w
|
||||||
#
|
|
||||||
# Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
|
|
||||||
#
|
|
||||||
# Copyright: vzdump is under GNU GPL, the GNU General Public License.
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; version 2 dated June, 1991.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the
|
|
||||||
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
|
|
||||||
# MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
# Author: Dietmar Maurer <dietmar@proxmox.com>
|
|
||||||
#
|
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use Getopt::Long;
|
use PVE::SafeSyslog;
|
||||||
use Sys::Syslog;
|
use PVE::Tools qw(extract_param);
|
||||||
use File::Path;
|
use PVE::INotify;
|
||||||
use PVE::VZDump;
|
use PVE::RPCEnvironment;
|
||||||
use PVE::Storage;
|
use PVE::CLIHandler;
|
||||||
|
use PVE::JSONSchema qw(get_standard_option);
|
||||||
$ENV{LANG} = "C"; # avoid locale related issues/warnings
|
use PVE::API2::Qemu;
|
||||||
|
|
||||||
openlog ('vzdump', 'cons,pid', 'daemon');
|
use Data::Dumper; # fixme: remove
|
||||||
|
|
||||||
my @std_opts = ('extract', 'storage=s', 'info', 'prealloc', 'unique');
|
use base qw(PVE::CLIHandler);
|
||||||
|
|
||||||
sub print_usage {
|
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
|
||||||
my $msg = shift;
|
|
||||||
|
initlog('qmrestore');
|
||||||
print STDERR "ERROR: $msg\n\n" if $msg;
|
|
||||||
|
die "please run as root\n" if $> != 0;
|
||||||
print STDERR "usage: $0 [OPTIONS] <ARCHIVE> <VMID>\n\n";
|
|
||||||
}
|
PVE::INotify::inotify_init();
|
||||||
|
|
||||||
sub shellquote {
|
my $rpcenv = PVE::RPCEnvironment->init('cli');
|
||||||
my $str = shift;
|
|
||||||
|
$rpcenv->init_request();
|
||||||
return "''" if !defined ($str) || ($str eq '');
|
$rpcenv->set_language($ENV{LANG});
|
||||||
|
$rpcenv->set_user('root@pam');
|
||||||
die "unable to quote string containing null (\\000) bytes"
|
|
||||||
if $str =~ m/\x00/;
|
__PACKAGE__->register_method({
|
||||||
|
name => 'qmrestore',
|
||||||
# from String::ShellQuote
|
path => 'qmrestore',
|
||||||
if ($str =~ m|[^\w!%+,\-./:@^]|) {
|
method => 'POST',
|
||||||
|
description => "Restore QemuServer vzdump backups.",
|
||||||
# ' -> '\''
|
parameters => {
|
||||||
$str =~ s/'/'\\''/g;
|
additionalProperties => 0,
|
||||||
|
properties => {
|
||||||
$str = "'$str'";
|
vmid => get_standard_option('pve-vmid'),
|
||||||
$str =~ s/^''//;
|
archive => {
|
||||||
$str =~ s/''$//;
|
description => "The backup file.",
|
||||||
}
|
type => 'string',
|
||||||
|
maxLength => 255,
|
||||||
return $str;
|
},
|
||||||
}
|
storage => get_standard_option('pve-storage-id', {
|
||||||
|
description => "Default storage.",
|
||||||
my $quoted_cmd = shellquote ($0);
|
optional => 1,
|
||||||
foreach my $arg (@ARGV) {
|
}),
|
||||||
$quoted_cmd .= " " . shellquote ($arg);
|
force => {
|
||||||
}
|
optional => 1,
|
||||||
|
type => 'boolean',
|
||||||
my $opts = {};
|
description => "Allow to overwrite existing VM.",
|
||||||
if (!GetOptions ($opts, @std_opts)) {
|
},
|
||||||
print_usage ();
|
},
|
||||||
exit (-1);
|
},
|
||||||
}
|
returns => {
|
||||||
|
type => 'string',
|
||||||
if ($#ARGV != 1) {
|
},
|
||||||
print_usage ();
|
code => sub {
|
||||||
exit (-1);
|
my ($param) = @_;
|
||||||
}
|
|
||||||
|
$param->{node} = PVE::INotify::nodename();
|
||||||
my $archive = shift;
|
|
||||||
my $vmid = PVE::VZDump::check_vmids ((shift))->[0];
|
return PVE::API2::Qemu->create_vm($param);
|
||||||
|
}});
|
||||||
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
|
|
||||||
die "interrupted by signal\n";
|
my $cmddef = [ __PACKAGE__, 'qmrestore', ['archive', 'vmid'], undef,
|
||||||
};
|
sub {
|
||||||
|
my $upid = shift;
|
||||||
sub debugmsg { PVE::VZDump::debugmsg (@_); } # just a shortcut
|
my $status = PVE::Tools::upid_read_status($upid);
|
||||||
|
exit($status eq 'OK' ? 0 : -1);
|
||||||
sub run_command { PVE::VZDump::run_command (undef, @_); } # just a shortcut
|
}];
|
||||||
|
|
||||||
if ($opts->{extract}) {
|
push @ARGV, 'help' if !scalar(@ARGV);
|
||||||
|
|
||||||
# NOTE: this is run as tar subprocess (--to-command)
|
PVE::CLIHandler::handle_simple_cmd($cmddef, \@ARGV, undef, $0);
|
||||||
|
|
||||||
my $filename = $ENV{TAR_FILENAME};
|
exit 0;
|
||||||
die "got strange environment - no TAR_FILENAME\n" if !$filename;
|
|
||||||
|
|
||||||
my $filesize = $ENV{TAR_SIZE};
|
|
||||||
die "got strange file size '$filesize'\n" if !$filesize;
|
|
||||||
|
|
||||||
my $tmpdir = $ENV{VZDUMP_TMPDIR};
|
|
||||||
die "got strange environment - no VZDUMP_TMPDIR\n" if !$tmpdir;
|
|
||||||
|
|
||||||
my $filetype = $ENV{TAR_FILETYPE} || 'none';
|
|
||||||
die "got strange filetype '$filetype'\n" if $filetype ne 'f';
|
|
||||||
|
|
||||||
my $conffile = "$tmpdir/qemu-server.conf";
|
|
||||||
my $statfile = "$tmpdir/qmrestore.stat";
|
|
||||||
|
|
||||||
if ($opts->{info}) {
|
|
||||||
print STDERR "reading archive member '$filename'\n";
|
|
||||||
} else {
|
|
||||||
print STDERR "extracting '$filename' from archive\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($filename eq 'qemu-server.conf') {
|
|
||||||
my $outfd = IO::File->new ($conffile, "w") ||
|
|
||||||
die "unable to write file '$conffile'\n";
|
|
||||||
|
|
||||||
while (defined (my $line = <>)) {
|
|
||||||
print $outfd $line;
|
|
||||||
print STDERR "CONFIG: $line" if $opts->{info};
|
|
||||||
}
|
|
||||||
|
|
||||||
$outfd->close();
|
|
||||||
|
|
||||||
exit (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($opts->{info}) {
|
|
||||||
exec 'dd', 'bs=256K', "of=/dev/null";
|
|
||||||
die "couldn't exec dd: $!\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
my $conffd = IO::File->new ($conffile, "r") ||
|
|
||||||
die "unable to read file '$conffile'\n";
|
|
||||||
|
|
||||||
my $map;
|
|
||||||
while (defined (my $line = <$conffd>)) {
|
|
||||||
if ($line =~ m/^\#vzdump\#map:(\S+):(\S+):(\d+):(\S*):$/) {
|
|
||||||
$map->{$2} = { virtdev => $1, size => $3, storeid => $4 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close ($conffd);
|
|
||||||
|
|
||||||
my $statfd = IO::File->new ($statfile, "a") ||
|
|
||||||
die "unable to open file '$statfile'\n";
|
|
||||||
|
|
||||||
if ($filename !~ m/^.*\.([^\.]+)$/){
|
|
||||||
die "got strange filename '$filename'\n";
|
|
||||||
}
|
|
||||||
my $format = $1;
|
|
||||||
|
|
||||||
my $path;
|
|
||||||
|
|
||||||
if (!$map) {
|
|
||||||
print STDERR "restoring old style vzdump archive - " .
|
|
||||||
"no device map inside archive\n";
|
|
||||||
die "can't restore old style archive to storage '$opts->{storage}'\n"
|
|
||||||
if $opts->{storage} && $opts->{storage} ne 'local';
|
|
||||||
|
|
||||||
my $dir = "/var/lib/vz/images/$vmid";
|
|
||||||
mkpath $dir;
|
|
||||||
|
|
||||||
$path = "$dir/$filename";
|
|
||||||
|
|
||||||
print $statfd "vzdump::$path\n";
|
|
||||||
$statfd->close();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
my $info = $map->{$filename};
|
|
||||||
die "no vzdump info for '$filename'\n" if !$info;
|
|
||||||
|
|
||||||
if ($filename !~ m/^vm-disk-$info->{virtdev}\.([^\.]+)$/){
|
|
||||||
die "got strange filename '$filename'\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($filesize != $info->{size}) {
|
|
||||||
die "detected size difference for '$filename' " .
|
|
||||||
"($filesize != $info->{size})\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
my $storeid;
|
|
||||||
if ($opts->{storage}) {
|
|
||||||
$storeid = $opts->{storage};
|
|
||||||
} else {
|
|
||||||
$storeid = $info->{storeid} || 'local';
|
|
||||||
}
|
|
||||||
|
|
||||||
my $cfg = PVE::Storage::load_config();
|
|
||||||
my $scfg = PVE::Storage::storage_config ($cfg, $storeid);
|
|
||||||
|
|
||||||
my $alloc_size = ($filesize + 1024 - 1)/1024;
|
|
||||||
if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
|
|
||||||
# hack: we just alloc a small file (32K) - we overwrite it anyways
|
|
||||||
$alloc_size = 32;
|
|
||||||
} else {
|
|
||||||
die "unable to restore '$filename' to storage '$storeid'\n" .
|
|
||||||
"storage type '$scfg->{type}' does not support format '$format\n"
|
|
||||||
if $format ne 'raw';
|
|
||||||
}
|
|
||||||
|
|
||||||
my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $vmid,
|
|
||||||
$format, undef, $alloc_size);
|
|
||||||
|
|
||||||
print STDERR "new volume ID is '$volid'\n";
|
|
||||||
|
|
||||||
print $statfd "vzdump:$info->{virtdev}:$volid\n";
|
|
||||||
$statfd->close();
|
|
||||||
|
|
||||||
$path = PVE::Storage::path ($cfg, $volid);
|
|
||||||
}
|
|
||||||
|
|
||||||
print STDERR "restore data to '$path' ($filesize bytes)\n";
|
|
||||||
|
|
||||||
if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) {
|
|
||||||
exec 'dd', 'ibs=256K', 'obs=256K', "of=$path";
|
|
||||||
die "couldn't exec dd: $!\n";
|
|
||||||
} else {
|
|
||||||
exec '/usr/lib/qemu-server/sparsecp', $path;
|
|
||||||
die "couldn't exec sparsecp: $!\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub restore_cleanup {
|
|
||||||
my $statfile = shift;
|
|
||||||
|
|
||||||
return if $opts->{info};
|
|
||||||
|
|
||||||
debugmsg ('info', "starting cleanup");
|
|
||||||
if (my $fd = IO::File->new ($statfile, "r")) {
|
|
||||||
while (defined (my $line = <$fd>)) {
|
|
||||||
if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
|
|
||||||
my $volid = $2;
|
|
||||||
eval {
|
|
||||||
if ($volid =~ m|^/|) {
|
|
||||||
unlink $volid || die 'unlink failed\n';
|
|
||||||
} else {
|
|
||||||
my $cfg = PVE::Storage::load_config();
|
|
||||||
PVE::Storage::vdisk_free ($cfg, $volid);
|
|
||||||
}
|
|
||||||
debugmsg ('info', "temporary volume '$volid' sucessfuly removed");
|
|
||||||
};
|
|
||||||
debugmsg ('err', "unable to cleanup '$volid' - $@") if $@;
|
|
||||||
} else {
|
|
||||||
debugmsg ('info', "unable to parse line in statfile - $line");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$fd->close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub restore_qemu {
|
|
||||||
my ($archive, $vmid, $tmpdir) = @_;
|
|
||||||
|
|
||||||
local $ENV{VZDUMP_TMPDIR} = $tmpdir;
|
|
||||||
|
|
||||||
my $subcmd = shellquote ("--to-command=${quoted_cmd}\ --extract");
|
|
||||||
my $cmd = "tar xf '$archive' $subcmd";
|
|
||||||
run_command ($cmd);
|
|
||||||
|
|
||||||
return if $opts->{info};
|
|
||||||
|
|
||||||
# reed new mapping
|
|
||||||
my $map = {};
|
|
||||||
my $statfile = "$tmpdir/qmrestore.stat";
|
|
||||||
if (my $fd = IO::File->new ($statfile, "r")) {
|
|
||||||
while (defined (my $line = <$fd>)) {
|
|
||||||
if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
|
|
||||||
$map->{$1} = $2 if $1;
|
|
||||||
} else {
|
|
||||||
debugmsg ('info', "unable to parse line in statfile - $line");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$fd->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
my $confsrc = "$tmpdir/qemu-server.conf";
|
|
||||||
|
|
||||||
my $srcfd = new IO::File ($confsrc, "r") ||
|
|
||||||
die "unable to open file '$confsrc'\n";
|
|
||||||
|
|
||||||
my $conffile = PVE::QemuServer::config_file ($vmid);
|
|
||||||
my $tmpfn = "$conffile.$$.tmp";
|
|
||||||
|
|
||||||
my $outfd = new IO::File ($tmpfn, "w") ||
|
|
||||||
die "unable to write config for VM $vmid\n";
|
|
||||||
|
|
||||||
eval {
|
|
||||||
while (defined (my $line = <$srcfd>)) {
|
|
||||||
next if $line =~ m/^\#vzdump\#/;
|
|
||||||
next if $line =~ m/^lock:/;
|
|
||||||
|
|
||||||
if (($line =~ m/^((vlan)\d+):(.*)$/) && ($opts->{unique})) {
|
|
||||||
my ($id,$ethcfg) = ($1,$3);
|
|
||||||
$ethcfg =~ s/^\s+//;
|
|
||||||
my ($model, $mac) = split(/\=/,$ethcfg);
|
|
||||||
my $printvlan = PVE::QemuServer::print_vlan (PVE::QemuServer::parse_vlan ($model));
|
|
||||||
print $outfd "$id: $printvlan\n";
|
|
||||||
} elsif ($line =~ m/^((ide|scsi|virtio)\d+):(.*)$/) {
|
|
||||||
my $virtdev = $1;
|
|
||||||
my $value = $2;
|
|
||||||
if ($line =~ m/backup=no/) {
|
|
||||||
print $outfd "#$line";
|
|
||||||
} elsif ($virtdev && $map->{$virtdev}) {
|
|
||||||
my $di = PVE::QemuServer::parse_drive ($virtdev, $value);
|
|
||||||
$di->{file} = $map->{$virtdev};
|
|
||||||
$value = PVE::QemuServer::print_drive ($vmid, $di);
|
|
||||||
print $outfd "$virtdev: $value\n";
|
|
||||||
} else {
|
|
||||||
print $outfd $line;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print $outfd $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
my $err = $@;
|
|
||||||
|
|
||||||
$outfd->close();
|
|
||||||
|
|
||||||
if ($err) {
|
|
||||||
unlink $tmpfn;
|
|
||||||
die $err;
|
|
||||||
} else {
|
|
||||||
rename $tmpfn, $conffile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
my $firstfile = PVE::VZDump::read_firstfile ($archive);
|
|
||||||
if ($firstfile ne 'qemu-server.conf') {
|
|
||||||
die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
my $tmpdir = "/var/tmp/vzdumptmp$$";
|
|
||||||
|
|
||||||
PVE::QemuServer::lock_config ($vmid, sub {
|
|
||||||
|
|
||||||
my $conffile = PVE::QemuServer::config_file ($vmid);
|
|
||||||
|
|
||||||
die "unable to restore VM '$vmid' - VM already exists\n"
|
|
||||||
if -f $conffile;
|
|
||||||
|
|
||||||
mkpath $tmpdir;
|
|
||||||
|
|
||||||
eval {
|
|
||||||
debugmsg ('info', "restore QemuServer backup '$archive' " .
|
|
||||||
"using ID $vmid", undef, 1) if !$opts->{info};
|
|
||||||
|
|
||||||
restore_qemu ($archive, $vmid, $tmpdir);
|
|
||||||
|
|
||||||
if ($opts->{info}) {
|
|
||||||
debugmsg ('info', "reading '$archive' successful");
|
|
||||||
} else {
|
|
||||||
debugmsg ('info', "restore QemuServer backup '$archive' successful",
|
|
||||||
undef, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
my $err = $@;
|
|
||||||
|
|
||||||
if ($err) {
|
|
||||||
local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
|
|
||||||
debugmsg ('info', "got interrupt - ignored (cleanup phase)");
|
|
||||||
};
|
|
||||||
|
|
||||||
restore_cleanup ("$tmpdir/qmrestore.stat") if $err;
|
|
||||||
}
|
|
||||||
|
|
||||||
die $err if $err;
|
|
||||||
});
|
|
||||||
|
|
||||||
my $err = $@;
|
|
||||||
|
|
||||||
rmtree $tmpdir;
|
|
||||||
|
|
||||||
if ($err) {
|
|
||||||
if ($opts->{info}) {
|
|
||||||
debugmsg ('info', "reading '$archive' failed - $err");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
debugmsg ('err', "restore QemuServer backup '$archive' failed - $err",
|
|
||||||
undef, 1);
|
|
||||||
}
|
|
||||||
exit (-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
exit (0);
|
|
||||||
|
|
||||||
__END__
|
__END__
|
||||||
|
|
||||||
@ -398,23 +84,16 @@ qmrestore - restore QemuServer vzdump backups
|
|||||||
|
|
||||||
=head1 SYNOPSIS
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
qmrestore [OPTIONS] <archive> <VMID>
|
=include synopsis
|
||||||
|
|
||||||
--info read/verify archive and print relevant
|
|
||||||
information (test run)
|
|
||||||
|
|
||||||
--unique assign a unique random ethernet address
|
|
||||||
|
|
||||||
--storage <STORAGE_ID> restore to storage <STORAGE_ID>
|
|
||||||
|
|
||||||
--prealloc never generate sparse files
|
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
Restore the QemuServer vzdump backup <archive> to virtual machine
|
Restore the QemuServer vzdump backup C<archive> to virtual machine
|
||||||
<VMID>. Volumes are allocated on the original storage if there is no
|
C<vmid>. Volumes are allocated on the original storage if there is no
|
||||||
C<--storage> specified.
|
C<storage> specified.
|
||||||
|
|
||||||
=head1 SEE ALSO
|
=head1 SEE ALSO
|
||||||
|
|
||||||
vzdump(1) vzrestore(1)
|
vzdump(1) vzrestore(1)
|
||||||
|
|
||||||
|
=include pve_copyright
|
||||||
|
Loading…
Reference in New Issue
Block a user