#!/usr/bin/perl use strict; use warnings; use Getopt::Long; use File::Path; use IO::File; use PVE::INotify; use PVE::JSONSchema; use PVE::Tools; use PVE::Cluster; use PVE::RPCEnvironment; use PVE::Storage; use PVE::QemuServer; $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin'; die "please run as root\n" if $> != 0; my @std_opts = ('storage=s', 'pool=s', 'info', 'prealloc'); sub print_usage { print STDERR "usage: $0 [--storage=] [--pool=] [--info] [--prealloc] \n\n"; } my $opts = {}; if (!GetOptions ($opts, @std_opts)) { print_usage (); exit (-1); } PVE::INotify::inotify_init(); my $rpcenv = PVE::RPCEnvironment->init('cli'); $rpcenv->init_request(); $rpcenv->set_language($ENV{LANG}); $rpcenv->set_user('root@pam'); 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); my $user = $ENV{VZDUMP_USER}; $rpcenv->check_user_enabled($user); 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 defined($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"; } # check permission for all used storages my $pool = $opts->{pool}; if ($user ne 'root@pam') { if (defined($opts->{storage})) { my $sid = $opts->{storage} || 'local'; $rpcenv->check($user, "/storage/$sid", ['Datastore.AllocateSpace']); } else { foreach my $fn (keys %$map) { my $fi = $map->{$fn}; my $sid = $fi->{storeid} || 'local'; $rpcenv->check($user, "/storage/$sid", ['Datastore.AllocateSpace']); } } } my $storeid; if (defined($opts->{storage})) { $storeid = $opts->{storage} || 'local'; } else { $storeid = $info->{storeid} || 'local'; } my $cfg = PVE::Storage::config(); my $scfg = PVE::Storage::storage_config($cfg, $storeid); my $alloc_size = int(($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 '/bin/cp', '--sparse=always', '/dev/stdin', $path; die "couldn't exec cp: $!\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, 'root@pam', $opts); } elsif (scalar(@ARGV) == 0 && $ENV{TAR_FILENAME}) { extract_archive(); } else { print_usage (); exit(-1); } exit(0);