]>
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 | ||
f507c3a7 WL |
109 | my $raw = ''; |
110 | my $out = sub { | |
111 | my $output = shift; | |
112 | $raw .= "$output\n"; | |
113 | }; | |
114 | ||
115 | PVE::Tools::run_command(['tar', '-xpOf', $archive, $conf_file, '--occurrence'], outfunc => $out); | |
116 | ||
effa4f43 | 117 | my $conf; |
f507c3a7 | 118 | |
effa4f43 | 119 | if ($conf_file =~ m/lxc\.conf/) { |
f507c3a7 | 120 | |
effa4f43 | 121 | $conf = PVE::LXC::parse_lxc_config("/lxc/0/config" , $raw); |
f507c3a7 WL |
122 | |
123 | delete $conf->{'pve.volid'}; | |
124 | delete $conf->{'lxc.rootfs'}; | |
125 | delete $conf->{snapshots}; | |
126 | ||
effa4f43 | 127 | } elsif ($conf_file =~ m/vps\.conf/) { |
f507c3a7 WL |
128 | |
129 | $conf = PVE::VZDump::ConvertOVZ::convert_ovz($raw); | |
130 | ||
effa4f43 DM |
131 | } else { |
132 | ||
133 | die "internal error"; | |
f507c3a7 WL |
134 | } |
135 | ||
136 | return $conf; | |
137 | } | |
138 | ||
5b4657d0 | 139 | sub restore_and_configure { |
f507c3a7 | 140 | my ($vmid, $archive, $rootdir, $conf, $password, $restore) = @_; |
5b4657d0 DM |
141 | |
142 | restore_archive($archive, $rootdir, $conf); | |
143 | ||
144 | PVE::LXC::write_config($vmid, $conf); | |
145 | ||
f507c3a7 WL |
146 | if (!$restore) { |
147 | my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir); # detect OS | |
5b4657d0 | 148 | |
f507c3a7 WL |
149 | PVE::LXC::write_config($vmid, $conf); # safe config (after OS detection) |
150 | $lxc_setup->post_create_hook($password); | |
151 | } | |
5b4657d0 DM |
152 | } |
153 | ||
154 | # directly use a storage directory | |
155 | sub create_rootfs_dir { | |
f507c3a7 | 156 | my ($cleanup, $storage_conf, $storage, $vmid, $conf, $archive, $password, $restore) = @_; |
5b4657d0 | 157 | |
10fc3ba5 | 158 | # note: there is no size limit |
148d1cb4 | 159 | $conf->{'pve.disksize'} = 0; |
5b4657d0 DM |
160 | |
161 | my $private = PVE::Storage::get_private_dir($storage_conf, $storage, $vmid); | |
162 | mkdir($private) || die "unable to create container private dir '$private' - $!\n"; | |
163 | ||
164 | push @{$cleanup->{files}}, $private; | |
165 | $conf->{'lxc.rootfs'} = $private; | |
166 | ||
f507c3a7 | 167 | restore_and_configure($vmid, $archive, $private, $conf, $password, $restore); |
5b4657d0 DM |
168 | } |
169 | ||
ad09aa15 DM |
170 | # use new subvolume API |
171 | sub create_rootfs_subvol { | |
f507c3a7 | 172 | my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore) = @_; |
ad09aa15 DM |
173 | |
174 | my $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'subvol', | |
175 | "subvol-$vmid-rootfs", $size); | |
176 | push @{$cleanup->{volids}}, $volid; | |
177 | ||
178 | my $private = PVE::Storage::path($storage_conf, $volid); | |
179 | (-d $private) || die "unable to get container private dir '$private' - $!\n"; | |
180 | ||
181 | $conf->{'lxc.rootfs'} = $private; | |
182 | $conf->{'pve.volid'} = $volid; | |
183 | ||
f507c3a7 | 184 | restore_and_configure($vmid, $archive, $private, $conf, $password, $restore); |
ad09aa15 DM |
185 | } |
186 | ||
5b4657d0 DM |
187 | # create a raw file, then loop mount |
188 | sub create_rootfs_dir_loop { | |
f507c3a7 | 189 | my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore) = @_; |
5b4657d0 DM |
190 | |
191 | my $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'raw', "vm-$vmid-rootfs.raw", $size); | |
148d1cb4 | 192 | $conf->{'pve.disksize'} = $size/(1024*1024); |
5b4657d0 DM |
193 | |
194 | push @{$cleanup->{volids}}, $volid; | |
195 | ||
196 | my $image_path = PVE::Storage::path($storage_conf, $volid); | |
197 | $conf->{'lxc.rootfs'} = "loop:${image_path}"; | |
198 | ||
199 | my $cmd = ['mkfs.ext4', $image_path]; | |
200 | PVE::Tools::run_command($cmd); | |
201 | ||
202 | print "allocated image: $image_path\n"; | |
203 | ||
204 | my $mountpoint; | |
205 | ||
206 | my $loopdev; | |
207 | eval { | |
208 | my $parser = sub { | |
209 | my $line = shift; | |
210 | $loopdev = $line if $line =~m|^/dev/loop\d+$|; | |
211 | }; | |
212 | PVE::Tools::run_command(['losetup', '--find', '--show', $image_path], outfunc => $parser); | |
213 | ||
214 | my $tmp = "/var/lib/lxc/$vmid/rootfs"; | |
215 | File::Path::mkpath($tmp); | |
216 | PVE::Tools::run_command(['mount', '-t', 'ext4', $loopdev, $tmp]); | |
217 | $mountpoint = $tmp; | |
218 | ||
611fe3aa | 219 | $conf->{'pve.volid'} = $volid; |
f507c3a7 | 220 | restore_and_configure($vmid, $archive, $mountpoint, $conf, $password, $restore); |
5b4657d0 DM |
221 | }; |
222 | if (my $err = $@) { | |
223 | if ($mountpoint) { | |
224 | eval { PVE::Tools::run_command(['umount', '-d', $mountpoint]) }; | |
225 | warn $@ if $@; | |
226 | } else { | |
227 | eval { PVE::Tools::run_command(['losetup', '-d', $loopdev]) if $loopdev; }; | |
228 | warn $@ if $@; | |
229 | } | |
230 | die $err; | |
231 | } | |
232 | ||
233 | PVE::Tools::run_command(['umount', '-l', '-d', $mountpoint]); | |
234 | } | |
235 | ||
6ed8c6dd DM |
236 | # create a file, then mount with qemu-nbd |
237 | sub create_rootfs_dir_qemu { | |
f507c3a7 | 238 | my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore) = @_; |
6ed8c6dd DM |
239 | |
240 | my $format = 'qcow2'; | |
241 | ||
242 | my $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, | |
243 | $format, "vm-$vmid-rootfs.$format", $size); | |
244 | ||
148d1cb4 DM |
245 | $conf->{'pve.disksize'} = $size/(1024*1024); |
246 | ||
6ed8c6dd DM |
247 | push @{$cleanup->{volids}}, $volid; |
248 | ||
249 | my $image_path = PVE::Storage::path($storage_conf, $volid); | |
250 | $conf->{'lxc.rootfs'} = "nbd:${image_path}"; | |
251 | ||
252 | print "allocated image: $image_path\n"; | |
253 | ||
254 | my $mountpoint; | |
255 | ||
256 | my $nbd_dev; | |
257 | eval { | |
258 | $nbd_dev = next_free_nbd_dev(); | |
259 | PVE::Tools::run_command(['qemu-nbd', '-c', $nbd_dev, $image_path]); | |
260 | ||
261 | my $cmd = ['mkfs.ext4', $nbd_dev]; | |
262 | PVE::Tools::run_command($cmd); | |
263 | ||
264 | ||
265 | my $tmp = "/var/lib/lxc/$vmid/rootfs"; | |
266 | File::Path::mkpath($tmp); | |
267 | PVE::Tools::run_command(['mount', '-t', 'ext4', $nbd_dev, $tmp]); | |
268 | $mountpoint = $tmp; | |
269 | ||
611fe3aa | 270 | $conf->{'pve.volid'} = $volid; |
f507c3a7 | 271 | restore_and_configure($vmid, $archive, $mountpoint, $conf, $password, $restore); |
6ed8c6dd DM |
272 | }; |
273 | if (my $err = $@) { | |
274 | if ($mountpoint) { | |
275 | eval { PVE::Tools::run_command(['umount', $mountpoint]); }; | |
276 | warn $@ if $@; | |
277 | } | |
278 | if ($nbd_dev) { | |
279 | eval { PVE::Tools::run_command(['qemu-nbd', '-d', $nbd_dev]); }; | |
280 | warn $@ if $@; | |
281 | } | |
282 | die $err; | |
283 | } | |
284 | ||
285 | PVE::Tools::run_command(['umount', $mountpoint]); | |
286 | PVE::Tools::run_command(['qemu-nbd', '-d', $nbd_dev]); | |
287 | } | |
288 | ||
5b4657d0 | 289 | sub create_rootfs { |
148d1cb4 DM |
290 | my ($storage_conf, $storage, $disk_size_gb, $vmid, $conf, $archive, $password, $restore) = @_; |
291 | ||
292 | my $config_fn = PVE::LXC::config_file($vmid); | |
293 | if (-f $config_fn) { | |
294 | die "container exists" if !$restore; # just to be sure | |
295 | ||
296 | my $old_conf = PVE::LXC::load_config($vmid); | |
297 | ||
298 | if (!defined($disk_size_gb) && defined($old_conf->{'pve.disksize'})) { | |
299 | $disk_size_gb = $old_conf->{'pve.disksize'}; | |
300 | } | |
f09ce711 DM |
301 | |
302 | # we only copy known settings to restored container | |
303 | my $pve_conf = PVE::LXC::lxc_conf_to_pve($vmid, $old_conf); | |
304 | foreach my $opt (qw(disk digest)) { | |
305 | delete $pve_conf->{$opt}; | |
306 | } | |
2a50f49a | 307 | PVE::LXC::update_lxc_config($vmid, $conf, 0, $pve_conf); |
f09ce711 | 308 | |
148d1cb4 DM |
309 | # destroy old container |
310 | PVE::LXC::destory_lxc_container($storage_conf, $vmid, $old_conf); | |
5b4657d0 | 311 | |
148d1cb4 | 312 | PVE::LXC::create_config($vmid, $conf); |
5b4657d0 | 313 | |
148d1cb4 DM |
314 | } else { |
315 | ||
316 | PVE::LXC::create_config($vmid, $conf); | |
317 | } | |
318 | ||
319 | my $size = 4*1024*1024; # defaults to 4G | |
320 | ||
321 | $size = int($disk_size_gb*1024) * 1024 if defined($disk_size_gb); | |
322 | ||
5b4657d0 DM |
323 | my $cleanup = { files => [], volids => [] }; |
324 | ||
325 | eval { | |
326 | my $scfg = PVE::Storage::storage_config($storage_conf, $storage); | |
327 | if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { | |
10fc3ba5 | 328 | if ($size > 0) { |
f507c3a7 | 329 | create_rootfs_dir_loop($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password, $restore); |
5b4657d0 | 330 | } else { |
f507c3a7 | 331 | create_rootfs_dir($cleanup, $storage_conf, $storage, $vmid, $conf, $archive, $password, $restore); |
5b4657d0 | 332 | } |
ad09aa15 DM |
333 | } elsif ($scfg->{type} eq 'zfspool') { |
334 | ||
335 | create_rootfs_subvol($cleanup, $storage_conf, $storage, $size, | |
f507c3a7 | 336 | $vmid, $conf, $archive, $password, $restore); |
ad09aa15 | 337 | |
5b4657d0 | 338 | } else { |
ad09aa15 | 339 | |
5b4657d0 DM |
340 | die "unable to create containers on storage type '$scfg->{type}'\n"; |
341 | } | |
342 | }; | |
343 | if (my $err = $@) { | |
344 | # cleanup | |
345 | File::Path::rmtree($cleanup->{files}); | |
346 | foreach my $volid (@{$cleanup->{volids}}) { | |
347 | eval { PVE::Storage::vdisk_free($storage_conf, $volid); }; | |
348 | warn $@ if $@; | |
349 | } | |
350 | ||
351 | PVE::LXC::destroy_config($vmid); | |
352 | ||
353 | die $err; | |
354 | } | |
355 | } | |
356 | ||
357 | 1; |