]> git.proxmox.com Git - qemu-server.git/blob - qmextract
d/control: bump version dependency on libpve-guest-common-perl
[qemu-server.git] / qmextract
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use Getopt::Long;
6 use File::Path;
7 use IO::File;
8 use PVE::INotify;
9 use PVE::JSONSchema;
10 use PVE::Tools;
11 use PVE::Cluster;
12 use PVE::RPCEnvironment;
13 use PVE::Storage;
14 use PVE::QemuServer;
15
16 $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
17
18 die "please run as root\n" if $> != 0;
19
20 my @std_opts = ('storage=s', 'pool=s', 'info', 'prealloc');
21
22 sub print_usage {
23 print STDERR "usage: $0 [--storage=<storeid>] [--pool=<poolid>] [--info] [--prealloc] <archive> <vmid>\n\n";
24 }
25
26 my $opts = {};
27 if (!GetOptions ($opts, @std_opts)) {
28 print_usage ();
29 exit (-1);
30 }
31
32 PVE::INotify::inotify_init();
33
34 my $rpcenv = PVE::RPCEnvironment->init('cli');
35
36 $rpcenv->init_request();
37 $rpcenv->set_language($ENV{LANG});
38 $rpcenv->set_user('root@pam');
39
40 sub extract_archive {
41 # NOTE: this is run as tar subprocess (--to-command)
42
43 $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
44 die "interrupted by signal\n";
45 };
46
47 my $filename = $ENV{TAR_FILENAME};
48 die "got strange environment - no TAR_FILENAME\n" if !$filename;
49
50 my $filesize = $ENV{TAR_SIZE};
51 die "got strange file size '$filesize'\n" if !$filesize;
52
53 my $tmpdir = $ENV{VZDUMP_TMPDIR};
54 die "got strange environment - no VZDUMP_TMPDIR\n" if !$tmpdir;
55
56 my $filetype = $ENV{TAR_FILETYPE} || 'none';
57 die "got strange filetype '$filetype'\n" if $filetype ne 'f';
58
59 my $vmid = $ENV{VZDUMP_VMID};
60 PVE::JSONSchema::pve_verify_vmid($vmid);
61
62 my $user = $ENV{VZDUMP_USER};
63 $rpcenv->check_user_enabled($user);
64
65 if ($opts->{info}) {
66 print STDERR "reading archive member '$filename'\n";
67 } else {
68 print STDERR "extracting '$filename' from archive\n";
69 }
70
71 my $conffile = "$tmpdir/qemu-server.conf";
72 my $statfile = "$tmpdir/qmrestore.stat";
73
74 if ($filename eq 'qemu-server.conf') {
75 my $outfd = IO::File->new($conffile, "w") ||
76 die "unable to write file '$conffile'\n";
77
78 while (defined(my $line = <>)) {
79 print $outfd $line;
80 print STDERR "CONFIG: $line" if $opts->{info};
81 }
82
83 $outfd->close();
84
85 exit(0);
86 }
87
88 if ($opts->{info}) {
89 exec 'dd', 'bs=256K', "of=/dev/null";
90 die "couldn't exec dd: $!\n";
91 }
92
93 my $conffd = IO::File->new($conffile, "r") ||
94 die "unable to read file '$conffile'\n";
95
96 my $map;
97 while (defined(my $line = <$conffd>)) {
98 if ($line =~ m/^\#vzdump\#map:(\S+):(\S+):(\d+):(\S*):$/) {
99 $map->{$2} = { virtdev => $1, size => $3, storeid => $4 };
100 }
101 }
102 close($conffd);
103
104 my $statfd = IO::File->new($statfile, "a") ||
105 die "unable to open file '$statfile'\n";
106
107 if ($filename !~ m/^.*\.([^\.]+)$/){
108 die "got strange filename '$filename'\n";
109 }
110 my $format = $1;
111
112 my $path;
113
114 if (!$map) {
115 print STDERR "restoring old style vzdump archive - " .
116 "no device map inside archive\n";
117 die "can't restore old style archive to storage '$opts->{storage}'\n"
118 if defined($opts->{storage}) && $opts->{storage} ne 'local';
119
120 my $dir = "/var/lib/vz/images/$vmid";
121 mkpath $dir;
122
123 $path = "$dir/$filename";
124
125 print $statfd "vzdump::$path\n";
126 $statfd->close();
127
128 } else {
129
130 my $info = $map->{$filename};
131 die "no vzdump info for '$filename'\n" if !$info;
132
133 if ($filename !~ m/^vm-disk-$info->{virtdev}\.([^\.]+)$/){
134 die "got strange filename '$filename'\n";
135 }
136
137 if ($filesize != $info->{size}) {
138 die "detected size difference for '$filename' " .
139 "($filesize != $info->{size})\n";
140 }
141
142 # check permission for all used storages
143 my $pool = $opts->{pool};
144 if ($user ne 'root@pam') {
145 if (defined($opts->{storage})) {
146 my $sid = $opts->{storage} || 'local';
147 $rpcenv->check($user, "/storage/$sid", ['Datastore.AllocateSpace']);
148 } else {
149 foreach my $fn (keys %$map) {
150 my $fi = $map->{$fn};
151 my $sid = $fi->{storeid} || 'local';
152 $rpcenv->check($user, "/storage/$sid", ['Datastore.AllocateSpace']);
153 }
154 }
155 }
156
157 my $storeid;
158 if (defined($opts->{storage})) {
159 $storeid = $opts->{storage} || 'local';
160 } else {
161 $storeid = $info->{storeid} || 'local';
162 }
163
164 my $cfg = PVE::Storage::config();
165 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
166
167 my $alloc_size = int(($filesize + 1024 - 1)/1024);
168 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
169 # hack: we just alloc a small file (32K) - we overwrite it anyways
170 $alloc_size = 32;
171 } else {
172 die "unable to restore '$filename' to storage '$storeid'\n" .
173 "storage type '$scfg->{type}' does not support format '$format\n"
174 if $format ne 'raw';
175 }
176
177 my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid,
178 $format, undef, $alloc_size);
179
180 print STDERR "new volume ID is '$volid'\n";
181
182 print $statfd "vzdump:$info->{virtdev}:$volid\n";
183 $statfd->close();
184
185 $path = PVE::Storage::path($cfg, $volid);
186 }
187
188 print STDERR "restore data to '$path' ($filesize bytes)\n";
189
190 if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) {
191 exec 'dd', 'ibs=256K', 'obs=256K', "of=$path";
192 die "couldn't exec dd: $!\n";
193 } else {
194 exec '/bin/cp', '--sparse=always', '/dev/stdin', $path;
195 die "couldn't exec cp: $!\n";
196 }
197 }
198
199
200 if (scalar(@ARGV) == 2) {
201 my $archive = shift;
202 my $vmid = shift;
203
204 # fixme: use API call
205 PVE::JSONSchema::pve_verify_vmid($vmid);
206
207 PVE::Cluster::check_cfs_quorum();
208
209 PVE::QemuServer::restore_archive($archive, $vmid, 'root@pam', $opts);
210
211 } elsif (scalar(@ARGV) == 0 && $ENV{TAR_FILENAME}) {
212 extract_archive();
213 } else {
214 print_usage ();
215 exit(-1);
216 }
217
218 exit(0);
219
220