]>
Commit | Line | Data |
---|---|---|
5b4657d0 DM |
1 | package PVE::LXCCreate; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use File::Basename; | |
6 | use File::Path; | |
7 | use Data::Dumper; | |
8 | ||
9 | use PVE::Storage; | |
10 | use PVE::LXC; | |
11 | use PVE::LXCSetup; | |
f507c3a7 | 12 | use PVE::VZDump::ConvertOVZ; |
5b4657d0 | 13 | |
6ed8c6dd DM |
14 | sub next_free_nbd_dev { |
15 | ||
16 | for(my $i = 0;;$i++) { | |
17 | my $dev = "/dev/nbd$i"; | |
18 | last if ! -b $dev; | |
19 | next if -f "/sys/block/nbd$i/pid"; # busy | |
20 | return $dev; | |
21 | } | |
22 | die "unable to find free nbd device\n"; | |
23 | } | |
24 | ||
5b4657d0 DM |
25 | sub restore_archive { |
26 | my ($archive, $rootdir, $conf) = @_; | |
27 | ||
28 | # we always use the same mapping: 'b:0:100000:65536' | |
29 | my $userns_cmd; | |
30 | ||
31 | if ($conf->{'lxc.id_map'}) { | |
32 | $userns_cmd = ['lxc-usernsexec', '-m', 'b:0:100000:65536', '--']; | |
33 | } else { | |
34 | $userns_cmd = []; | |
35 | } | |
36 | ||
37 | my $cmd; | |
38 | ||
39 | if ($conf->{'lxc.id_map'}) { | |
40 | PVE::Tools::run_command(['chown', '-R', '100000:100000', $rootdir]); | |
41 | } | |
42 | ||
43 | $cmd = [@$userns_cmd, 'tar', 'xpf', $archive, '--numeric-owner', '--totals', | |
44 | '--sparse', '-C', $rootdir]; | |
45 | ||
46 | push @$cmd, '--anchored'; | |
47 | push @$cmd, '--exclude' , './dev/*'; | |
48 | ||
6034ae50 DM |
49 | if ($archive eq '-') { |
50 | print "extracting archive from STDIN\n"; | |
51 | PVE::Tools::run_command($cmd, input => "<&STDIN"); | |
52 | } else { | |
53 | print "extracting archive '$archive'\n"; | |
54 | PVE::Tools::run_command($cmd); | |
f507c3a7 | 55 | } |
6034ae50 | 56 | |
5b4657d0 DM |
57 | # is this really required? what for? |
58 | #$cmd = [@$userns_cmd, 'mkdir', '-p', "$rootdir/dev/pts"]; | |
59 | #PVE::Tools::run_command($cmd); | |
60 | ||
a9d131df TL |
61 | #determine file type of /usr/bin/file itself to get guests' architecture |
62 | $cmd = [@$userns_cmd, '/usr/bin/file', '-b', '-L', "$rootdir/usr/bin/file"]; | |
63 | PVE::Tools::run_command($cmd, outfunc => sub { | |
64 | shift =~ /^ELF (\d{2}-bit)/; # safely assumes x86 linux | |
65 | my $arch_str = $1; | |
66 | $conf->{'lxc.arch'} = 'amd64'; # defaults to 64bit | |
67 | if(defined($arch_str)) { | |
68 | $conf->{'lxc.arch'} = 'i386' if $arch_str =~ /32/; | |
69 | print "Detected container architecture: $conf->{'lxc.arch'}\n"; | |
70 | } else { | |
71 | print "CT architecture detection failed, falling back to amd64.\n". | |
72 | "Edit the config in /etc/pve/nodes/{node}/lxc/{vmid}/config". | |
73 | " to set another arch.\n"; | |
74 | } | |
75 | }); | |
5b4657d0 DM |
76 | } |
77 | ||
f507c3a7 WL |
78 | sub tar_archive_search_conf { |
79 | my ($archive) = @_; | |
80 | ||
81 | die "ERROR: file '$archive' does not exist\n" if ! -f $archive; | |
82 | ||
83 | my $pid = open(my $fh, '-|', 'tar', 'tf', $archive) || | |
84 | die "unable to open file '$archive'\n"; | |
85 | ||
86 | my $file; | |
effa4f43 DM |
87 | while (defined($file = <$fh>)) { |
88 | if ($file =~ m!^(\./etc/vzdump/(lxc|vps)\.conf)$!) { | |
89 | $file = $1; # untaint | |
90 | last; | |
91 | } | |
f507c3a7 WL |
92 | } |
93 | ||
94 | kill 15, $pid; | |
95 | waitpid $pid, 0; | |
96 | close $fh; | |
97 | ||
effa4f43 | 98 | die "ERROR: archive contains no configuration file\n" if !$file; |
f507c3a7 WL |
99 | chomp $file; |
100 | ||
101 | return $file; | |
102 | } | |
103 | ||
104 | sub recover_config { | |
effa4f43 | 105 | my ($archive) = @_; |
f507c3a7 WL |
106 | |
107 | my $conf_file = tar_archive_search_conf($archive); | |
108 | ||
6d098bf4 | 109 | my $ovs; |
f507c3a7 WL |
110 | my $raw = ''; |
111 | my $out = sub { | |
112 | my $output = shift; | |
113 | $raw .= "$output\n"; | |
114 | }; | |
115 | ||
116 | PVE::Tools::run_command(['tar', '-xpOf', $archive, $conf_file, '--occurrence'], outfunc => $out); | |
117 | ||
effa4f43 | 118 | my $conf; |
f507c3a7 | 119 | |
effa4f43 | 120 | if ($conf_file =~ m/lxc\.conf/) { |
f507c3a7 | 121 | |
effa4f43 | 122 | $conf = PVE::LXC::parse_lxc_config("/lxc/0/config" , $raw); |
f507c3a7 WL |
123 | |
124 | delete $conf->{'pve.volid'}; | |
125 | delete $conf->{'lxc.rootfs'}; | |
126 | delete $conf->{snapshots}; | |
127 | ||
effa4f43 | 128 | } elsif ($conf_file =~ m/vps\.conf/) { |
6d098bf4 | 129 | $ovs = 1; |
f507c3a7 WL |
130 | $conf = PVE::VZDump::ConvertOVZ::convert_ovz($raw); |
131 | ||
effa4f43 DM |
132 | } else { |
133 | ||
134 | die "internal error"; | |
f507c3a7 WL |
135 | } |
136 | ||
6d098bf4 | 137 | return $conf, $ovs; |
f507c3a7 WL |
138 | } |
139 | ||
5b4657d0 | 140 | sub restore_and_configure { |
f507c3a7 | 141 | my ($vmid, $archive, $rootdir, $conf, $password, $restore) = @_; |
5b4657d0 DM |
142 | |
143 | restore_archive($archive, $rootdir, $conf); | |
144 | ||
145 | PVE::LXC::write_config($vmid, $conf); | |
146 | ||
f507c3a7 WL |
147 | if (!$restore) { |
148 | my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir); # detect OS | |
5b4657d0 | 149 | |
f507c3a7 WL |
150 | PVE::LXC::write_config($vmid, $conf); # safe config (after OS detection) |
151 | $lxc_setup->post_create_hook($password); | |
152 | } | |
5b4657d0 DM |
153 | } |
154 | ||
155 | # directly use a storage directory | |
156 | sub create_rootfs_dir { | |
f507c3a7 | 157 | my ($cleanup, $storage_conf, $storage, $vmid, $conf, $archive, $password, $restore) = @_; |
5b4657d0 | 158 | |
10fc3ba5 | 159 | # note: there is no size limit |
148d1cb4 | 160 | $conf->{'pve.disksize'} = 0; |
5b4657d0 DM |
161 | |
162 | my $private = PVE::Storage::get_private_dir($storage_conf, $storage, $vmid); | |
163 | mkdir($private) || die "unable to create container private dir '$private' - $!\n"; | |
164 | ||
165 | push @{$cleanup->{files}}, $private; | |
166 | $conf->{'lxc.rootfs'} = $private; | |
167 | ||
f507c3a7 | 168 | restore_and_configure($vmid, $archive, $private, $conf, $password, $restore); |
5b4657d0 DM |
169 | } |
170 | ||
ad09aa15 DM |
171 | # use new subvolume API |
172 | sub create_rootfs_subvol { | |
f507c3a7 | 173 | my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore) = @_; |
ad09aa15 DM |
174 | |
175 | my $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'subvol', | |
176 | "subvol-$vmid-rootfs", $size); | |
177 | push @{$cleanup->{volids}}, $volid; | |
178 | ||
179 | my $private = PVE::Storage::path($storage_conf, $volid); | |
180 | (-d $private) || die "unable to get container private dir '$private' - $!\n"; | |
181 | ||
182 | $conf->{'lxc.rootfs'} = $private; | |
183 | $conf->{'pve.volid'} = $volid; | |
184 | ||
f507c3a7 | 185 | restore_and_configure($vmid, $archive, $private, $conf, $password, $restore); |
ad09aa15 DM |
186 | } |
187 | ||
5b4657d0 DM |
188 | # create a raw file, then loop mount |
189 | sub create_rootfs_dir_loop { | |
f507c3a7 | 190 | my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore) = @_; |
5b4657d0 DM |
191 | |
192 | my $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'raw', "vm-$vmid-rootfs.raw", $size); | |
148d1cb4 | 193 | $conf->{'pve.disksize'} = $size/(1024*1024); |
5b4657d0 DM |
194 | |
195 | push @{$cleanup->{volids}}, $volid; | |
196 | ||
197 | my $image_path = PVE::Storage::path($storage_conf, $volid); | |
198 | $conf->{'lxc.rootfs'} = "loop:${image_path}"; | |
199 | ||
200 | my $cmd = ['mkfs.ext4', $image_path]; | |
201 | PVE::Tools::run_command($cmd); | |
202 | ||
203 | print "allocated image: $image_path\n"; | |
204 | ||
205 | my $mountpoint; | |
206 | ||
207 | my $loopdev; | |
208 | eval { | |
209 | my $parser = sub { | |
210 | my $line = shift; | |
211 | $loopdev = $line if $line =~m|^/dev/loop\d+$|; | |
212 | }; | |
213 | PVE::Tools::run_command(['losetup', '--find', '--show', $image_path], outfunc => $parser); | |
214 | ||
215 | my $tmp = "/var/lib/lxc/$vmid/rootfs"; | |
216 | File::Path::mkpath($tmp); | |
217 | PVE::Tools::run_command(['mount', '-t', 'ext4', $loopdev, $tmp]); | |
218 | $mountpoint = $tmp; | |
219 | ||
611fe3aa | 220 | $conf->{'pve.volid'} = $volid; |
f507c3a7 | 221 | restore_and_configure($vmid, $archive, $mountpoint, $conf, $password, $restore); |
5b4657d0 DM |
222 | }; |
223 | if (my $err = $@) { | |
224 | if ($mountpoint) { | |
225 | eval { PVE::Tools::run_command(['umount', '-d', $mountpoint]) }; | |
226 | warn $@ if $@; | |
227 | } else { | |
228 | eval { PVE::Tools::run_command(['losetup', '-d', $loopdev]) if $loopdev; }; | |
229 | warn $@ if $@; | |
230 | } | |
231 | die $err; | |
232 | } | |
233 | ||
234 | PVE::Tools::run_command(['umount', '-l', '-d', $mountpoint]); | |
235 | } | |
236 | ||
6ed8c6dd DM |
237 | # create a file, then mount with qemu-nbd |
238 | sub create_rootfs_dir_qemu { | |
f507c3a7 | 239 | my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore) = @_; |
6ed8c6dd DM |
240 | |
241 | my $format = 'qcow2'; | |
242 | ||
243 | my $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, | |
244 | $format, "vm-$vmid-rootfs.$format", $size); | |
245 | ||
148d1cb4 DM |
246 | $conf->{'pve.disksize'} = $size/(1024*1024); |
247 | ||
6ed8c6dd DM |
248 | push @{$cleanup->{volids}}, $volid; |
249 | ||
250 | my $image_path = PVE::Storage::path($storage_conf, $volid); | |
251 | $conf->{'lxc.rootfs'} = "nbd:${image_path}"; | |
252 | ||
253 | print "allocated image: $image_path\n"; | |
254 | ||
255 | my $mountpoint; | |
256 | ||
257 | my $nbd_dev; | |
258 | eval { | |
259 | $nbd_dev = next_free_nbd_dev(); | |
260 | PVE::Tools::run_command(['qemu-nbd', '-c', $nbd_dev, $image_path]); | |
261 | ||
262 | my $cmd = ['mkfs.ext4', $nbd_dev]; | |
263 | PVE::Tools::run_command($cmd); | |
264 | ||
265 | ||
266 | my $tmp = "/var/lib/lxc/$vmid/rootfs"; | |
267 | File::Path::mkpath($tmp); | |
268 | PVE::Tools::run_command(['mount', '-t', 'ext4', $nbd_dev, $tmp]); | |
269 | $mountpoint = $tmp; | |
270 | ||
611fe3aa | 271 | $conf->{'pve.volid'} = $volid; |
f507c3a7 | 272 | restore_and_configure($vmid, $archive, $mountpoint, $conf, $password, $restore); |
6ed8c6dd DM |
273 | }; |
274 | if (my $err = $@) { | |
275 | if ($mountpoint) { | |
276 | eval { PVE::Tools::run_command(['umount', $mountpoint]); }; | |
277 | warn $@ if $@; | |
278 | } | |
279 | if ($nbd_dev) { | |
280 | eval { PVE::Tools::run_command(['qemu-nbd', '-d', $nbd_dev]); }; | |
281 | warn $@ if $@; | |
282 | } | |
283 | die $err; | |
284 | } | |
285 | ||
286 | PVE::Tools::run_command(['umount', $mountpoint]); | |
287 | PVE::Tools::run_command(['qemu-nbd', '-d', $nbd_dev]); | |
288 | } | |
289 | ||
5b4657d0 | 290 | sub create_rootfs { |
148d1cb4 DM |
291 | my ($storage_conf, $storage, $disk_size_gb, $vmid, $conf, $archive, $password, $restore) = @_; |
292 | ||
293 | my $config_fn = PVE::LXC::config_file($vmid); | |
294 | if (-f $config_fn) { | |
295 | die "container exists" if !$restore; # just to be sure | |
296 | ||
297 | my $old_conf = PVE::LXC::load_config($vmid); | |
298 | ||
299 | if (!defined($disk_size_gb) && defined($old_conf->{'pve.disksize'})) { | |
300 | $disk_size_gb = $old_conf->{'pve.disksize'}; | |
301 | } | |
f09ce711 DM |
302 | |
303 | # we only copy known settings to restored container | |
304 | my $pve_conf = PVE::LXC::lxc_conf_to_pve($vmid, $old_conf); | |
305 | foreach my $opt (qw(disk digest)) { | |
306 | delete $pve_conf->{$opt}; | |
307 | } | |
2a50f49a | 308 | PVE::LXC::update_lxc_config($vmid, $conf, 0, $pve_conf); |
f09ce711 | 309 | |
148d1cb4 DM |
310 | # destroy old container |
311 | PVE::LXC::destory_lxc_container($storage_conf, $vmid, $old_conf); | |
5b4657d0 | 312 | |
148d1cb4 | 313 | PVE::LXC::create_config($vmid, $conf); |
5b4657d0 | 314 | |
148d1cb4 DM |
315 | } else { |
316 | ||
317 | PVE::LXC::create_config($vmid, $conf); | |
318 | } | |
319 | ||
320 | my $size = 4*1024*1024; # defaults to 4G | |
321 | ||
322 | $size = int($disk_size_gb*1024) * 1024 if defined($disk_size_gb); | |
323 | ||
5b4657d0 DM |
324 | my $cleanup = { files => [], volids => [] }; |
325 | ||
326 | eval { | |
327 | my $scfg = PVE::Storage::storage_config($storage_conf, $storage); | |
328 | if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { | |
10fc3ba5 | 329 | if ($size > 0) { |
f507c3a7 | 330 | create_rootfs_dir_loop($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore); |
5b4657d0 | 331 | } else { |
f507c3a7 | 332 | create_rootfs_dir($cleanup, $storage_conf, $storage, $vmid, $conf, $archive, $password, $restore); |
5b4657d0 | 333 | } |
ad09aa15 DM |
334 | } elsif ($scfg->{type} eq 'zfspool') { |
335 | ||
336 | create_rootfs_subvol($cleanup, $storage_conf, $storage, $size, | |
f507c3a7 | 337 | $vmid, $conf, $archive, $password, $restore); |
ad09aa15 | 338 | |
5b4657d0 | 339 | } else { |
ad09aa15 | 340 | |
5b4657d0 DM |
341 | die "unable to create containers on storage type '$scfg->{type}'\n"; |
342 | } | |
343 | }; | |
344 | if (my $err = $@) { | |
345 | # cleanup | |
346 | File::Path::rmtree($cleanup->{files}); | |
347 | foreach my $volid (@{$cleanup->{volids}}) { | |
348 | eval { PVE::Storage::vdisk_free($storage_conf, $volid); }; | |
349 | warn $@ if $@; | |
350 | } | |
351 | ||
352 | PVE::LXC::destroy_config($vmid); | |
353 | ||
354 | die $err; | |
355 | } | |
356 | } | |
357 | ||
358 | 1; |