]>
Commit | Line | Data |
---|---|---|
f76a2828 DM |
1 | package PVE::LXC; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
67afe46e | 5 | |
d14a9a1b | 6 | use POSIX qw(EINTR); |
f76a2828 | 7 | |
34fdb3d7 WB |
8 | use Socket; |
9 | ||
f76a2828 | 10 | use File::Path; |
2cfae16e WB |
11 | use File::Spec; |
12 | use Cwd qw(); | |
3cc56749 | 13 | use Fcntl qw(O_RDONLY); |
f76a2828 DM |
14 | |
15 | use PVE::Cluster qw(cfs_register_file cfs_read_file); | |
f1ba1a4b | 16 | use PVE::Exception qw(raise_perm_exc); |
c65e0a6d | 17 | use PVE::Storage; |
f76a2828 DM |
18 | use PVE::SafeSyslog; |
19 | use PVE::INotify; | |
a3249355 | 20 | use PVE::JSONSchema qw(get_standard_option); |
1bfe8728 | 21 | use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full); |
68fba17b | 22 | use PVE::Network; |
52389a07 | 23 | use PVE::AccessControl; |
228a5a1d | 24 | use PVE::ProcFSTools; |
67afe46e | 25 | use PVE::LXC::Config; |
688afc63 | 26 | use Time::HiRes qw (gettimeofday); |
f76a2828 DM |
27 | |
28 | use Data::Dumper; | |
29 | ||
27916659 DM |
30 | my $nodename = PVE::INotify::nodename(); |
31 | ||
688afc63 WL |
32 | my $cpuinfo= PVE::ProcFSTools::read_cpuinfo(); |
33 | ||
f9897acd | 34 | our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls', |
fc4e132e WB |
35 | '--xattrs', |
36 | '--xattrs-include=user.*', | |
4132377b WB |
37 | '--xattrs-include=security.capability', |
38 | '--warning=no-xattr-write' ]; | |
fc4e132e | 39 | |
27916659 | 40 | cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config); |
f76a2828 | 41 | |
769fbfab WB |
42 | my $rootfs_desc = { |
43 | volume => { | |
44 | type => 'string', | |
45 | default_key => 1, | |
da990295 | 46 | format => 'pve-lxc-mp-string', |
769fbfab WB |
47 | format_description => 'volume', |
48 | description => 'Volume, device or directory to mount into the container.', | |
49 | }, | |
50 | backup => { | |
51 | type => 'boolean', | |
52 | format_description => '[1|0]', | |
53 | description => 'Whether to include the mountpoint in backups.', | |
54 | optional => 1, | |
55 | }, | |
56 | size => { | |
72d583ff WB |
57 | type => 'string', |
58 | format => 'disk-size', | |
769fbfab | 59 | format_description => 'DiskSize', |
769fbfab WB |
60 | description => 'Volume size (read only value).', |
61 | optional => 1, | |
62 | }, | |
471dd315 WB |
63 | acl => { |
64 | type => 'boolean', | |
65 | format_description => 'acl', | |
66 | description => 'Explicitly enable or disable ACL support.', | |
67 | optional => 1, | |
68 | }, | |
69 | ro => { | |
70 | type => 'boolean', | |
71 | format_description => 'ro', | |
72 | description => 'Read-only mountpoint (not supported with bind mounts)', | |
73 | optional => 1, | |
74 | }, | |
50df544c WB |
75 | quota => { |
76 | type => 'boolean', | |
77 | format_description => '[0|1]', | |
78 | description => 'Enable user quotas inside the container (not supported with zfs subvolumes)', | |
79 | optional => 1, | |
80 | }, | |
769fbfab | 81 | }; |
822de0c3 | 82 | |
27916659 | 83 | PVE::JSONSchema::register_standard_option('pve-ct-rootfs', { |
769fbfab | 84 | type => 'string', format => $rootfs_desc, |
8fbd2935 | 85 | description => "Use volume as container root.", |
27916659 DM |
86 | optional => 1, |
87 | }); | |
88 | ||
52389a07 DM |
89 | PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', { |
90 | description => "The name of the snapshot.", | |
91 | type => 'string', format => 'pve-configid', | |
92 | maxLength => 40, | |
93 | }); | |
94 | ||
27916659 | 95 | my $confdesc = { |
09d3ec42 DM |
96 | lock => { |
97 | optional => 1, | |
98 | type => 'string', | |
99 | description => "Lock/unlock the VM.", | |
100 | enum => [qw(migrate backup snapshot rollback)], | |
101 | }, | |
27916659 DM |
102 | onboot => { |
103 | optional => 1, | |
104 | type => 'boolean', | |
105 | description => "Specifies whether a VM will be started during system bootup.", | |
106 | default => 0, | |
117636e5 | 107 | }, |
27916659 | 108 | startup => get_standard_option('pve-startup-order'), |
bb1ac2de DM |
109 | template => { |
110 | optional => 1, | |
111 | type => 'boolean', | |
112 | description => "Enable/disable Template.", | |
113 | default => 0, | |
114 | }, | |
27916659 DM |
115 | arch => { |
116 | optional => 1, | |
117 | type => 'string', | |
118 | enum => ['amd64', 'i386'], | |
119 | description => "OS architecture type.", | |
120 | default => 'amd64', | |
117636e5 | 121 | }, |
27916659 DM |
122 | ostype => { |
123 | optional => 1, | |
124 | type => 'string', | |
238b7e3e DM |
125 | enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'], |
126 | description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.", | |
a3249355 | 127 | }, |
4f958489 DM |
128 | console => { |
129 | optional => 1, | |
130 | type => 'boolean', | |
131 | description => "Attach a console device (/dev/console) to the container.", | |
132 | default => 1, | |
133 | }, | |
27916659 DM |
134 | tty => { |
135 | optional => 1, | |
136 | type => 'integer', | |
137 | description => "Specify the number of tty available to the container", | |
138 | minimum => 0, | |
139 | maximum => 6, | |
0d0ca400 | 140 | default => 2, |
611fe3aa | 141 | }, |
27916659 DM |
142 | cpulimit => { |
143 | optional => 1, | |
144 | type => 'number', | |
c31ad455 | 145 | description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.", |
27916659 DM |
146 | minimum => 0, |
147 | maximum => 128, | |
148 | default => 0, | |
149 | }, | |
150 | cpuunits => { | |
151 | optional => 1, | |
152 | type => 'integer', | |
c31ad455 | 153 | description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.", |
27916659 DM |
154 | minimum => 0, |
155 | maximum => 500000, | |
81bee809 | 156 | default => 1024, |
27916659 DM |
157 | }, |
158 | memory => { | |
159 | optional => 1, | |
160 | type => 'integer', | |
161 | description => "Amount of RAM for the VM in MB.", | |
162 | minimum => 16, | |
163 | default => 512, | |
164 | }, | |
165 | swap => { | |
166 | optional => 1, | |
167 | type => 'integer', | |
168 | description => "Amount of SWAP for the VM in MB.", | |
169 | minimum => 0, | |
170 | default => 512, | |
171 | }, | |
172 | hostname => { | |
173 | optional => 1, | |
174 | description => "Set a host name for the container.", | |
159aad3e | 175 | type => 'string', format => 'dns-name', |
27916659 DM |
176 | maxLength => 255, |
177 | }, | |
178 | description => { | |
179 | optional => 1, | |
180 | type => 'string', | |
181 | description => "Container description. Only used on the configuration web interface.", | |
182 | }, | |
183 | searchdomain => { | |
184 | optional => 1, | |
159aad3e | 185 | type => 'string', format => 'dns-name-list', |
c31ad455 | 186 | description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.", |
27916659 DM |
187 | }, |
188 | nameserver => { | |
189 | optional => 1, | |
159aad3e | 190 | type => 'string', format => 'address-list', |
c31ad455 | 191 | description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.", |
27916659 DM |
192 | }, |
193 | rootfs => get_standard_option('pve-ct-rootfs'), | |
09d3ec42 DM |
194 | parent => { |
195 | optional => 1, | |
196 | type => 'string', format => 'pve-configid', | |
197 | maxLength => 40, | |
198 | description => "Parent snapshot name. This is used internally, and should not be modified.", | |
199 | }, | |
200 | snaptime => { | |
201 | optional => 1, | |
202 | description => "Timestamp for snapshots.", | |
203 | type => 'integer', | |
204 | minimum => 0, | |
205 | }, | |
aca816ad DM |
206 | cmode => { |
207 | optional => 1, | |
208 | description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).", | |
209 | type => 'string', | |
210 | enum => ['shell', 'console', 'tty'], | |
211 | default => 'tty', | |
212 | }, | |
7e806596 AG |
213 | protection => { |
214 | optional => 1, | |
215 | type => 'boolean', | |
c31ad455 | 216 | description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.", |
7e806596 AG |
217 | default => 0, |
218 | }, | |
425b62cb WB |
219 | unprivileged => { |
220 | optional => 1, | |
221 | type => 'boolean', | |
222 | description => "Makes the container run as unprivileged user. (Should not be modified manually.)", | |
223 | default => 0, | |
224 | }, | |
f76a2828 DM |
225 | }; |
226 | ||
e576f689 DM |
227 | my $valid_lxc_conf_keys = { |
228 | 'lxc.include' => 1, | |
229 | 'lxc.arch' => 1, | |
230 | 'lxc.utsname' => 1, | |
231 | 'lxc.haltsignal' => 1, | |
232 | 'lxc.rebootsignal' => 1, | |
233 | 'lxc.stopsignal' => 1, | |
234 | 'lxc.init_cmd' => 1, | |
235 | 'lxc.network.type' => 1, | |
236 | 'lxc.network.flags' => 1, | |
237 | 'lxc.network.link' => 1, | |
238 | 'lxc.network.mtu' => 1, | |
239 | 'lxc.network.name' => 1, | |
240 | 'lxc.network.hwaddr' => 1, | |
241 | 'lxc.network.ipv4' => 1, | |
242 | 'lxc.network.ipv4.gateway' => 1, | |
243 | 'lxc.network.ipv6' => 1, | |
244 | 'lxc.network.ipv6.gateway' => 1, | |
245 | 'lxc.network.script.up' => 1, | |
246 | 'lxc.network.script.down' => 1, | |
247 | 'lxc.pts' => 1, | |
248 | 'lxc.console.logfile' => 1, | |
249 | 'lxc.console' => 1, | |
250 | 'lxc.tty' => 1, | |
251 | 'lxc.devttydir' => 1, | |
252 | 'lxc.hook.autodev' => 1, | |
253 | 'lxc.autodev' => 1, | |
254 | 'lxc.kmsg' => 1, | |
255 | 'lxc.mount' => 1, | |
256 | 'lxc.mount.entry' => 1, | |
257 | 'lxc.mount.auto' => 1, | |
312e9850 | 258 | 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs', |
e576f689 | 259 | 'lxc.rootfs.mount' => 1, |
312e9850 WB |
260 | 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' . |
261 | ', please use mountpoint options in the "rootfs" key', | |
e576f689 DM |
262 | # lxc.cgroup.* |
263 | 'lxc.cap.drop' => 1, | |
264 | 'lxc.cap.keep' => 1, | |
265 | 'lxc.aa_profile' => 1, | |
266 | 'lxc.aa_allow_incomplete' => 1, | |
267 | 'lxc.se_context' => 1, | |
268 | 'lxc.seccomp' => 1, | |
269 | 'lxc.id_map' => 1, | |
270 | 'lxc.hook.pre-start' => 1, | |
271 | 'lxc.hook.pre-mount' => 1, | |
272 | 'lxc.hook.mount' => 1, | |
273 | 'lxc.hook.start' => 1, | |
53775872 | 274 | 'lxc.hook.stop' => 1, |
e576f689 DM |
275 | 'lxc.hook.post-stop' => 1, |
276 | 'lxc.hook.clone' => 1, | |
277 | 'lxc.hook.destroy' => 1, | |
278 | 'lxc.loglevel' => 1, | |
279 | 'lxc.logfile' => 1, | |
280 | 'lxc.start.auto' => 1, | |
281 | 'lxc.start.delay' => 1, | |
282 | 'lxc.start.order' => 1, | |
283 | 'lxc.group' => 1, | |
284 | 'lxc.environment' => 1, | |
e576f689 DM |
285 | }; |
286 | ||
769fbfab WB |
287 | my $netconf_desc = { |
288 | type => { | |
289 | type => 'string', | |
290 | optional => 1, | |
291 | description => "Network interface type.", | |
292 | enum => [qw(veth)], | |
293 | }, | |
294 | name => { | |
295 | type => 'string', | |
296 | format_description => 'String', | |
297 | description => 'Name of the network device as seen from inside the container. (lxc.network.name)', | |
298 | pattern => '[-_.\w\d]+', | |
299 | }, | |
300 | bridge => { | |
301 | type => 'string', | |
302 | format_description => 'vmbr<Number>', | |
303 | description => 'Bridge to attach the network device to.', | |
304 | pattern => '[-_.\w\d]+', | |
a7c080a7 | 305 | optional => 1, |
769fbfab WB |
306 | }, |
307 | hwaddr => { | |
308 | type => 'string', | |
309 | format_description => 'MAC', | |
310 | description => 'Bridge to attach the network device to. (lxc.network.hwaddr)', | |
311 | pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i, | |
312 | optional => 1, | |
313 | }, | |
314 | mtu => { | |
315 | type => 'integer', | |
316 | format_description => 'Number', | |
317 | description => 'Maximum transfer unit of the interface. (lxc.network.mtu)', | |
07521af1 | 318 | minimum => 64, # minimum ethernet frame is 64 bytes |
769fbfab WB |
319 | optional => 1, |
320 | }, | |
321 | ip => { | |
322 | type => 'string', | |
323 | format => 'pve-ipv4-config', | |
324 | format_description => 'IPv4Format/CIDR', | |
325 | description => 'IPv4 address in CIDR format.', | |
326 | optional => 1, | |
327 | }, | |
328 | gw => { | |
329 | type => 'string', | |
330 | format => 'ipv4', | |
331 | format_description => 'GatewayIPv4', | |
332 | description => 'Default gateway for IPv4 traffic.', | |
333 | optional => 1, | |
334 | }, | |
335 | ip6 => { | |
336 | type => 'string', | |
337 | format => 'pve-ipv6-config', | |
338 | format_description => 'IPv6Format/CIDR', | |
339 | description => 'IPv6 address in CIDR format.', | |
340 | optional => 1, | |
341 | }, | |
342 | gw6 => { | |
343 | type => 'string', | |
344 | format => 'ipv6', | |
345 | format_description => 'GatewayIPv6', | |
346 | description => 'Default gateway for IPv6 traffic.', | |
347 | optional => 1, | |
348 | }, | |
349 | firewall => { | |
350 | type => 'boolean', | |
351 | format_description => '[1|0]', | |
352 | description => "Controls whether this interface's firewall rules should be used.", | |
353 | optional => 1, | |
354 | }, | |
355 | tag => { | |
356 | type => 'integer', | |
357 | format_description => 'VlanNo', | |
358 | minimum => '2', | |
359 | maximum => '4094', | |
23eb2244 WB |
360 | description => "VLAN tag for this interface.", |
361 | optional => 1, | |
362 | }, | |
363 | trunks => { | |
364 | type => 'string', | |
365 | pattern => qr/\d+(?:;\d+)*/, | |
366 | format_description => 'vlanid[;vlanid...]', | |
367 | description => "VLAN ids to pass through the interface", | |
769fbfab WB |
368 | optional => 1, |
369 | }, | |
370 | }; | |
371 | PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc); | |
372 | ||
27916659 DM |
373 | my $MAX_LXC_NETWORKS = 10; |
374 | for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) { | |
375 | $confdesc->{"net$i"} = { | |
376 | optional => 1, | |
1a0a239c | 377 | type => 'string', format => $netconf_desc, |
769fbfab | 378 | description => "Specifies network interfaces for the container.", |
27916659 | 379 | }; |
90bc31f7 DM |
380 | } |
381 | ||
da990295 DC |
382 | PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string); |
383 | sub verify_lxc_mp_string{ | |
384 | my ($mp, $noerr) = @_; | |
385 | ||
386 | # do not allow: | |
387 | # /./ or /../ | |
388 | # /. or /.. at the end | |
389 | # ../ at the beginning | |
390 | ||
391 | if($mp =~ m@/\.\.?/@ || | |
392 | $mp =~ m@/\.\.?$@ || | |
393 | $mp =~ m@^\.\./@){ | |
394 | return undef if $noerr; | |
395 | die "$mp contains illegal character sequences\n"; | |
396 | } | |
397 | return $mp; | |
398 | } | |
399 | ||
769fbfab WB |
400 | my $mp_desc = { |
401 | %$rootfs_desc, | |
402 | mp => { | |
403 | type => 'string', | |
da990295 | 404 | format => 'pve-lxc-mp-string', |
769fbfab WB |
405 | format_description => 'Path', |
406 | description => 'Path to the mountpoint as seen from inside the container.', | |
769fbfab WB |
407 | }, |
408 | }; | |
409 | PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc); | |
410 | ||
69202f71 WB |
411 | my $unuseddesc = { |
412 | optional => 1, | |
413 | type => 'string', format => 'pve-volume-id', | |
414 | description => "Reference to unused volumes.", | |
415 | }; | |
416 | ||
02c9d10c AD |
417 | my $MAX_MOUNT_POINTS = 10; |
418 | for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) { | |
419 | $confdesc->{"mp$i"} = { | |
420 | optional => 1, | |
769fbfab | 421 | type => 'string', format => $mp_desc, |
566d5f81 | 422 | description => "Use volume as container mount point (experimental feature).", |
02c9d10c AD |
423 | optional => 1, |
424 | }; | |
425 | } | |
426 | ||
69202f71 WB |
427 | my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS; |
428 | for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) { | |
429 | $confdesc->{"unused$i"} = $unuseddesc; | |
430 | } | |
431 | ||
27916659 DM |
432 | sub write_pct_config { |
433 | my ($filename, $conf) = @_; | |
f76a2828 | 434 | |
27916659 | 435 | delete $conf->{snapstate}; # just to be sure |
d250604f | 436 | my $volidlist = PVE::LXC::Config->get_vm_volumes($conf); |
5e93304e FG |
437 | my $used_volids = {}; |
438 | foreach my $vid (@$volidlist) { | |
439 | $used_volids->{$vid} = 1; | |
440 | } | |
441 | ||
442 | # remove 'unusedX' settings if the volume is still used | |
443 | foreach my $key (keys %$conf) { | |
444 | my $value = $conf->{$key}; | |
445 | if ($key =~ m/^unused/ && $used_volids->{$value}) { | |
446 | delete $conf->{$key}; | |
447 | } | |
448 | } | |
f76a2828 | 449 | |
27916659 DM |
450 | my $generate_raw_config = sub { |
451 | my ($conf) = @_; | |
f76a2828 | 452 | |
27916659 | 453 | my $raw = ''; |
cbb03fea | 454 | |
27916659 DM |
455 | # add description as comment to top of file |
456 | my $descr = $conf->{description} || ''; | |
457 | foreach my $cl (split(/\n/, $descr)) { | |
458 | $raw .= '#' . PVE::Tools::encode_text($cl) . "\n"; | |
a12a36e0 | 459 | } |
fff3a342 | 460 | |
27916659 | 461 | foreach my $key (sort keys %$conf) { |
09d3ec42 | 462 | next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || |
e576f689 | 463 | $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc'; |
545d10ba DM |
464 | my $value = $conf->{$key}; |
465 | die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/; | |
466 | $raw .= "$key: $value\n"; | |
a12a36e0 | 467 | } |
e576f689 DM |
468 | |
469 | if (my $lxcconf = $conf->{lxc}) { | |
470 | foreach my $entry (@$lxcconf) { | |
471 | my ($k, $v) = @$entry; | |
472 | $raw .= "$k: $v\n"; | |
473 | } | |
474 | } | |
475 | ||
27916659 | 476 | return $raw; |
a12a36e0 | 477 | }; |
160f0941 | 478 | |
27916659 | 479 | my $raw = &$generate_raw_config($conf); |
a12a36e0 | 480 | |
27916659 DM |
481 | foreach my $snapname (sort keys %{$conf->{snapshots}}) { |
482 | $raw .= "\n[$snapname]\n"; | |
483 | $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname}); | |
f76a2828 DM |
484 | } |
485 | ||
f76a2828 DM |
486 | return $raw; |
487 | } | |
488 | ||
27916659 DM |
489 | sub check_type { |
490 | my ($key, $value) = @_; | |
822de0c3 | 491 | |
27916659 | 492 | die "unknown setting '$key'\n" if !$confdesc->{$key}; |
822de0c3 | 493 | |
27916659 DM |
494 | my $type = $confdesc->{$key}->{type}; |
495 | ||
496 | if (!defined($value)) { | |
497 | die "got undefined value\n"; | |
498 | } | |
499 | ||
500 | if ($value =~ m/[\n\r]/) { | |
501 | die "property contains a line feed\n"; | |
502 | } | |
822de0c3 | 503 | |
27916659 DM |
504 | if ($type eq 'boolean') { |
505 | return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i); | |
506 | return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i); | |
507 | die "type check ('boolean') failed - got '$value'\n"; | |
508 | } elsif ($type eq 'integer') { | |
509 | return int($1) if $value =~ m/^(\d+)$/; | |
510 | die "type check ('integer') failed - got '$value'\n"; | |
511 | } elsif ($type eq 'number') { | |
512 | return $value if $value =~ m/^(\d+)(\.\d+)?$/; | |
513 | die "type check ('number') failed - got '$value'\n"; | |
514 | } elsif ($type eq 'string') { | |
515 | if (my $fmt = $confdesc->{$key}->{format}) { | |
516 | PVE::JSONSchema::check_format($fmt, $value); | |
517 | return $value; | |
518 | } | |
cbb03fea | 519 | return $value; |
822de0c3 | 520 | } else { |
27916659 | 521 | die "internal error" |
822de0c3 | 522 | } |
822de0c3 DM |
523 | } |
524 | ||
27916659 | 525 | sub parse_pct_config { |
f76a2828 DM |
526 | my ($filename, $raw) = @_; |
527 | ||
528 | return undef if !defined($raw); | |
529 | ||
27916659 | 530 | my $res = { |
f76a2828 | 531 | digest => Digest::SHA::sha1_hex($raw), |
27916659 | 532 | snapshots => {}, |
f76a2828 DM |
533 | }; |
534 | ||
27916659 | 535 | $filename =~ m|/lxc/(\d+).conf$| |
f76a2828 DM |
536 | || die "got strange filename '$filename'"; |
537 | ||
538 | my $vmid = $1; | |
539 | ||
27916659 DM |
540 | my $conf = $res; |
541 | my $descr = ''; | |
542 | my $section = ''; | |
543 | ||
544 | my @lines = split(/\n/, $raw); | |
545 | foreach my $line (@lines) { | |
546 | next if $line =~ m/^\s*$/; | |
547 | ||
548 | if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) { | |
549 | $section = $1; | |
550 | $conf->{description} = $descr if $descr; | |
551 | $descr = ''; | |
552 | $conf = $res->{snapshots}->{$section} = {}; | |
553 | next; | |
a12a36e0 | 554 | } |
a12a36e0 | 555 | |
27916659 DM |
556 | if ($line =~ m/^\#(.*)\s*$/) { |
557 | $descr .= PVE::Tools::decode_text($1) . "\n"; | |
558 | next; | |
f76a2828 | 559 | } |
5d186e16 | 560 | |
545d10ba | 561 | if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) { |
e576f689 DM |
562 | my $key = $1; |
563 | my $value = $3; | |
a23d627d WB |
564 | my $validity = $valid_lxc_conf_keys->{$key} || 0; |
565 | if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) { | |
e576f689 | 566 | push @{$conf->{lxc}}, [$key, $value]; |
a23d627d | 567 | } elsif (my $errmsg = $validity) { |
312e9850 | 568 | warn "vm $vmid - $key: $errmsg\n"; |
e576f689 DM |
569 | } else { |
570 | warn "vm $vmid - unable to parse config: $line\n"; | |
571 | } | |
572 | } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) { | |
27916659 DM |
573 | $descr .= PVE::Tools::decode_text($2); |
574 | } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) { | |
575 | $conf->{snapstate} = $1; | |
fe9a4ab3 | 576 | } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) { |
27916659 | 577 | my $key = $1; |
5d186e16 | 578 | my $value = $2; |
27916659 DM |
579 | eval { $value = check_type($key, $value); }; |
580 | warn "vm $vmid - unable to parse value of '$key' - $@" if $@; | |
581 | $conf->{$key} = $value; | |
5d186e16 | 582 | } else { |
27916659 | 583 | warn "vm $vmid - unable to parse config: $line\n"; |
5d186e16 | 584 | } |
7dfc49cc DM |
585 | } |
586 | ||
27916659 | 587 | $conf->{description} = $descr if $descr; |
5d186e16 | 588 | |
27916659 DM |
589 | delete $res->{snapstate}; # just to be sure |
590 | ||
591 | return $res; | |
f76a2828 DM |
592 | } |
593 | ||
594 | sub config_list { | |
595 | my $vmlist = PVE::Cluster::get_vmlist(); | |
596 | my $res = {}; | |
597 | return $res if !$vmlist || !$vmlist->{ids}; | |
598 | my $ids = $vmlist->{ids}; | |
599 | ||
600 | foreach my $vmid (keys %$ids) { | |
601 | next if !$vmid; # skip CT0 | |
602 | my $d = $ids->{$vmid}; | |
603 | next if !$d->{node} || $d->{node} ne $nodename; | |
604 | next if !$d->{type} || $d->{type} ne 'lxc'; | |
605 | $res->{$vmid}->{type} = 'lxc'; | |
606 | } | |
607 | return $res; | |
608 | } | |
609 | ||
5b4657d0 DM |
610 | sub destroy_config { |
611 | my ($vmid) = @_; | |
612 | ||
67afe46e | 613 | unlink PVE::LXC::Config->config_file($vmid, $nodename); |
3cc56749 FG |
614 | } |
615 | ||
ec52ac21 DM |
616 | sub option_exists { |
617 | my ($name) = @_; | |
618 | ||
619 | return defined($confdesc->{$name}); | |
620 | } | |
f76a2828 DM |
621 | |
622 | # add JSON properties for create and set function | |
623 | sub json_config_properties { | |
624 | my $prop = shift; | |
625 | ||
626 | foreach my $opt (keys %$confdesc) { | |
09d3ec42 | 627 | next if $opt eq 'parent' || $opt eq 'snaptime'; |
27916659 DM |
628 | next if $prop->{$opt}; |
629 | $prop->{$opt} = $confdesc->{$opt}; | |
630 | } | |
631 | ||
632 | return $prop; | |
633 | } | |
634 | ||
822de0c3 DM |
635 | # container status helpers |
636 | ||
637 | sub list_active_containers { | |
cbb03fea | 638 | |
822de0c3 DM |
639 | my $filename = "/proc/net/unix"; |
640 | ||
641 | # similar test is used by lcxcontainers.c: list_active_containers | |
642 | my $res = {}; | |
cbb03fea | 643 | |
822de0c3 DM |
644 | my $fh = IO::File->new ($filename, "r"); |
645 | return $res if !$fh; | |
646 | ||
647 | while (defined(my $line = <$fh>)) { | |
648 | if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) { | |
649 | my $path = $1; | |
27916659 | 650 | if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) { |
822de0c3 DM |
651 | $res->{$1} = 1; |
652 | } | |
653 | } | |
654 | } | |
655 | ||
656 | close($fh); | |
cbb03fea | 657 | |
822de0c3 DM |
658 | return $res; |
659 | } | |
f76a2828 | 660 | |
5c752bbf DM |
661 | # warning: this is slow |
662 | sub check_running { | |
663 | my ($vmid) = @_; | |
664 | ||
665 | my $active_hash = list_active_containers(); | |
666 | ||
667 | return 1 if defined($active_hash->{$vmid}); | |
cbb03fea | 668 | |
5c752bbf DM |
669 | return undef; |
670 | } | |
671 | ||
10fc3ba5 | 672 | sub get_container_disk_usage { |
73e03cb7 | 673 | my ($vmid, $pid) = @_; |
10fc3ba5 | 674 | |
73e03cb7 | 675 | return PVE::Tools::df("/proc/$pid/root/", 1); |
10fc3ba5 DM |
676 | } |
677 | ||
688afc63 WL |
678 | my $last_proc_vmid_stat; |
679 | ||
680 | my $parse_cpuacct_stat = sub { | |
681 | my ($vmid) = @_; | |
682 | ||
683 | my $raw = read_cgroup_value('cpuacct', $vmid, 'cpuacct.stat', 1); | |
684 | ||
685 | my $stat = {}; | |
686 | ||
687 | if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) { | |
688 | ||
689 | $stat->{utime} = $1; | |
690 | $stat->{stime} = $2; | |
691 | ||
692 | } | |
693 | ||
694 | return $stat; | |
695 | }; | |
696 | ||
f76a2828 DM |
697 | sub vmstatus { |
698 | my ($opt_vmid) = @_; | |
699 | ||
700 | my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list(); | |
701 | ||
822de0c3 | 702 | my $active_hash = list_active_containers(); |
cbb03fea | 703 | |
688afc63 WL |
704 | my $cpucount = $cpuinfo->{cpus} || 1; |
705 | ||
706 | my $cdtime = gettimeofday; | |
707 | ||
708 | my $uptime = (PVE::ProcFSTools::read_proc_uptime(1))[0]; | |
709 | ||
f76a2828 | 710 | foreach my $vmid (keys %$list) { |
f76a2828 | 711 | my $d = $list->{$vmid}; |
10fc3ba5 | 712 | |
d5588ee3 DM |
713 | eval { $d->{pid} = find_lxc_pid($vmid) if defined($active_hash->{$vmid}); }; |
714 | warn $@ if $@; # ignore errors (consider them stopped) | |
cbb03fea | 715 | |
d5588ee3 | 716 | $d->{status} = $d->{pid} ? 'running' : 'stopped'; |
f76a2828 | 717 | |
67afe46e | 718 | my $cfspath = PVE::LXC::Config->cfs_config_path($vmid); |
238a56cb | 719 | my $conf = PVE::Cluster::cfs_read_file($cfspath) || {}; |
cbb03fea | 720 | |
27916659 | 721 | $d->{name} = $conf->{'hostname'} || "CT$vmid"; |
238a56cb | 722 | $d->{name} =~ s/[\s]//g; |
cbb03fea | 723 | |
9db5687d | 724 | $d->{cpus} = $conf->{cpulimit} || $cpucount; |
44da0641 | 725 | |
d0226204 WB |
726 | $d->{lock} = $conf->{lock} || ''; |
727 | ||
d5588ee3 DM |
728 | if ($d->{pid}) { |
729 | my $res = get_container_disk_usage($vmid, $d->{pid}); | |
27916659 DM |
730 | $d->{disk} = $res->{used}; |
731 | $d->{maxdisk} = $res->{total}; | |
732 | } else { | |
733 | $d->{disk} = 0; | |
734 | # use 4GB by default ?? | |
735 | if (my $rootfs = $conf->{rootfs}) { | |
44a9face | 736 | my $rootinfo = parse_ct_rootfs($rootfs); |
27916659 DM |
737 | $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024; |
738 | } else { | |
739 | $d->{maxdisk} = 4*1024*1024*1024; | |
10fc3ba5 | 740 | } |
238a56cb | 741 | } |
cbb03fea | 742 | |
238a56cb DM |
743 | $d->{mem} = 0; |
744 | $d->{swap} = 0; | |
95df9a12 DM |
745 | $d->{maxmem} = ($conf->{memory}||512)*1024*1024; |
746 | $d->{maxswap} = ($conf->{swap}//0)*1024*1024; | |
e901d418 | 747 | |
238a56cb DM |
748 | $d->{uptime} = 0; |
749 | $d->{cpu} = 0; | |
e901d418 | 750 | |
238a56cb DM |
751 | $d->{netout} = 0; |
752 | $d->{netin} = 0; | |
f76a2828 | 753 | |
238a56cb DM |
754 | $d->{diskread} = 0; |
755 | $d->{diskwrite} = 0; | |
bb1ac2de | 756 | |
67afe46e | 757 | $d->{template} = PVE::LXC::Config->is_template($conf); |
f76a2828 | 758 | } |
cbb03fea | 759 | |
238a56cb DM |
760 | foreach my $vmid (keys %$list) { |
761 | my $d = $list->{$vmid}; | |
d5588ee3 DM |
762 | my $pid = $d->{pid}; |
763 | ||
764 | next if !$pid; # skip stopped CTs | |
f76a2828 | 765 | |
88a8696b TL |
766 | my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime |
767 | $d->{uptime} = time - $ctime; # the method lxcfs uses | |
22a77285 | 768 | |
238a56cb DM |
769 | $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes'); |
770 | $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem}; | |
b5289322 AD |
771 | |
772 | my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1); | |
1e647c7c | 773 | my @bytes = split(/\n/, $blkio_bytes); |
b5289322 | 774 | foreach my $byte (@bytes) { |
1e647c7c DM |
775 | if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) { |
776 | $d->{diskread} = $2 if $key eq 'Read'; | |
777 | $d->{diskwrite} = $2 if $key eq 'Write'; | |
778 | } | |
b5289322 | 779 | } |
688afc63 WL |
780 | |
781 | my $pstat = &$parse_cpuacct_stat($vmid); | |
782 | ||
783 | my $used = $pstat->{utime} + $pstat->{stime}; | |
784 | ||
785 | my $old = $last_proc_vmid_stat->{$vmid}; | |
786 | if (!$old) { | |
787 | $last_proc_vmid_stat->{$vmid} = { | |
788 | time => $cdtime, | |
789 | used => $used, | |
790 | cpu => 0, | |
791 | }; | |
792 | next; | |
793 | } | |
794 | ||
795 | my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz}; | |
796 | ||
797 | if ($dtime > 1000) { | |
798 | my $dutime = $used - $old->{used}; | |
799 | ||
800 | $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus}; | |
801 | $last_proc_vmid_stat->{$vmid} = { | |
802 | time => $cdtime, | |
803 | used => $used, | |
804 | cpu => $d->{cpu}, | |
805 | }; | |
806 | } else { | |
807 | $d->{cpu} = $old->{cpu}; | |
808 | } | |
238a56cb | 809 | } |
cbb03fea | 810 | |
68b8f4d1 WL |
811 | my $netdev = PVE::ProcFSTools::read_proc_net_dev(); |
812 | ||
813 | foreach my $dev (keys %$netdev) { | |
814 | next if $dev !~ m/^veth([1-9]\d*)i/; | |
815 | my $vmid = $1; | |
816 | my $d = $list->{$vmid}; | |
817 | ||
818 | next if !$d; | |
819 | ||
820 | $d->{netout} += $netdev->{$dev}->{receive}; | |
821 | $d->{netin} += $netdev->{$dev}->{transmit}; | |
822 | ||
823 | } | |
824 | ||
f76a2828 DM |
825 | return $list; |
826 | } | |
827 | ||
44a9face DM |
828 | my $parse_ct_mountpoint_full = sub { |
829 | my ($desc, $data, $noerr) = @_; | |
27916659 DM |
830 | |
831 | $data //= ''; | |
832 | ||
1b2c1e8c | 833 | my $res; |
44a9face | 834 | eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) }; |
1b2c1e8c | 835 | if ($@) { |
ca7feb1a WB |
836 | return undef if $noerr; |
837 | die $@; | |
27916659 DM |
838 | } |
839 | ||
bf4a209a | 840 | if (defined(my $size = $res->{size})) { |
ca7feb1a WB |
841 | $size = PVE::JSONSchema::parse_size($size); |
842 | if (!defined($size)) { | |
843 | return undef if $noerr; | |
844 | die "invalid size: $size\n"; | |
845 | } | |
846 | $res->{size} = $size; | |
27916659 DM |
847 | } |
848 | ||
d250604f | 849 | $res->{type} = PVE::LXC::Config->classify_mountpoint($res->{volume}); |
7c921c80 | 850 | |
27916659 | 851 | return $res; |
44a9face DM |
852 | }; |
853 | ||
854 | sub parse_ct_rootfs { | |
855 | my ($data, $noerr) = @_; | |
856 | ||
857 | my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr); | |
858 | ||
859 | $res->{mp} = '/' if defined($res); | |
860 | ||
861 | return $res; | |
862 | } | |
863 | ||
864 | sub parse_ct_mountpoint { | |
865 | my ($data, $noerr) = @_; | |
866 | ||
867 | return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr); | |
27916659 | 868 | } |
7dfc49cc | 869 | |
dde7b02b | 870 | sub print_ct_mountpoint { |
4fee75fd | 871 | my ($info, $nomp) = @_; |
7c921c80 WB |
872 | my $skip = [ 'type' ]; |
873 | push @$skip, 'mp' if $nomp; | |
6708ba93 | 874 | return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip); |
bb1ac2de DM |
875 | } |
876 | ||
7dfc49cc | 877 | sub print_lxc_network { |
f76a2828 | 878 | my $net = shift; |
6708ba93 | 879 | return PVE::JSONSchema::print_property_string($net, $netconf_desc); |
f76a2828 DM |
880 | } |
881 | ||
7dfc49cc DM |
882 | sub parse_lxc_network { |
883 | my ($data) = @_; | |
884 | ||
885 | my $res = {}; | |
886 | ||
887 | return $res if !$data; | |
888 | ||
ca7feb1a | 889 | $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data); |
7dfc49cc DM |
890 | |
891 | $res->{type} = 'veth'; | |
93cdbbfb | 892 | $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr}; |
cbb03fea | 893 | |
7dfc49cc DM |
894 | return $res; |
895 | } | |
f76a2828 | 896 | |
238a56cb DM |
897 | sub read_cgroup_value { |
898 | my ($group, $vmid, $name, $full) = @_; | |
899 | ||
900 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
901 | ||
902 | return PVE::Tools::file_get_contents($path) if $full; | |
903 | ||
904 | return PVE::Tools::file_read_firstline($path); | |
905 | } | |
906 | ||
bf0b8c43 AD |
907 | sub write_cgroup_value { |
908 | my ($group, $vmid, $name, $value) = @_; | |
909 | ||
910 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
911 | PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path; | |
912 | ||
913 | } | |
914 | ||
52f1d76b DM |
915 | sub find_lxc_console_pids { |
916 | ||
917 | my $res = {}; | |
918 | ||
919 | PVE::Tools::dir_glob_foreach('/proc', '\d+', sub { | |
920 | my ($pid) = @_; | |
921 | ||
922 | my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline"); | |
923 | return if !$cmdline; | |
924 | ||
925 | my @args = split(/\0/, $cmdline); | |
926 | ||
c31ad455 | 927 | # search for lxc-console -n <vmid> |
cbb03fea | 928 | return if scalar(@args) != 3; |
52f1d76b DM |
929 | return if $args[1] ne '-n'; |
930 | return if $args[2] !~ m/^\d+$/; | |
931 | return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|; | |
cbb03fea | 932 | |
52f1d76b | 933 | my $vmid = $args[2]; |
cbb03fea | 934 | |
52f1d76b DM |
935 | push @{$res->{$vmid}}, $pid; |
936 | }); | |
937 | ||
938 | return $res; | |
939 | } | |
940 | ||
bedeaaf1 AD |
941 | sub find_lxc_pid { |
942 | my ($vmid) = @_; | |
943 | ||
944 | my $pid = undef; | |
945 | my $parser = sub { | |
946 | my $line = shift; | |
8b25977f | 947 | $pid = $1 if $line =~ m/^PID:\s+(\d+)$/; |
bedeaaf1 | 948 | }; |
c39aa40a | 949 | PVE::Tools::run_command(['lxc-info', '-n', $vmid, '-p'], outfunc => $parser); |
bedeaaf1 | 950 | |
8b25977f | 951 | die "unable to get PID for CT $vmid (not running?)\n" if !$pid; |
cbb03fea | 952 | |
8b25977f | 953 | return $pid; |
bedeaaf1 AD |
954 | } |
955 | ||
cbb03fea | 956 | # Note: we cannot use Net:IP, because that only allows strict |
55fa4e09 DM |
957 | # CIDR networks |
958 | sub parse_ipv4_cidr { | |
959 | my ($cidr, $noerr) = @_; | |
960 | ||
f7a7b413 WB |
961 | if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) { |
962 | return { address => $1, netmask => $PVE::Network::ipv4_reverse_mask->[$2] }; | |
55fa4e09 | 963 | } |
cbb03fea | 964 | |
55fa4e09 | 965 | return undef if $noerr; |
cbb03fea | 966 | |
55fa4e09 DM |
967 | die "unable to parse ipv4 address/mask\n"; |
968 | } | |
93285df8 | 969 | |
e22af68f | 970 | |
27916659 | 971 | sub update_lxc_config { |
c628ffa1 | 972 | my ($storage_cfg, $vmid, $conf) = @_; |
b80dd50a | 973 | |
bb1ac2de DM |
974 | my $dir = "/var/lib/lxc/$vmid"; |
975 | ||
976 | if ($conf->{template}) { | |
977 | ||
978 | unlink "$dir/config"; | |
979 | ||
980 | return; | |
981 | } | |
982 | ||
27916659 | 983 | my $raw = ''; |
b80dd50a | 984 | |
27916659 DM |
985 | die "missing 'arch' - internal error" if !$conf->{arch}; |
986 | $raw .= "lxc.arch = $conf->{arch}\n"; | |
b80dd50a | 987 | |
425b62cb WB |
988 | my $unprivileged = $conf->{unprivileged}; |
989 | my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}}; | |
990 | ||
27916659 | 991 | my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error"; |
238b7e3e | 992 | if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine | unmanaged)$/x) { |
c34f7efe WB |
993 | my $inc ="/usr/share/lxc/config/$ostype.common.conf"; |
994 | $inc ="/usr/share/lxc/config/common.conf" if !-f $inc; | |
995 | $raw .= "lxc.include = $inc\n"; | |
425b62cb | 996 | if ($unprivileged || $custom_idmap) { |
c34f7efe WB |
997 | $inc = "/usr/share/lxc/config/$ostype.userns.conf"; |
998 | $inc = "/usr/share/lxc/config/userns.conf" if !-f $inc; | |
999 | $raw .= "lxc.include = $inc\n" | |
425b62cb | 1000 | } |
27916659 | 1001 | } else { |
9a7a910b | 1002 | die "implement me (ostype $ostype)"; |
27916659 | 1003 | } |
b80dd50a | 1004 | |
50df544c WB |
1005 | # WARNING: DO NOT REMOVE this without making sure that loop device nodes |
1006 | # cannot be exposed to the container with r/w access (cgroup perms). | |
1007 | # When this is enabled mounts will still remain in the monitor's namespace | |
1008 | # after the container unmounted them and thus will not detach from their | |
1009 | # files while the container is running! | |
c16b8890 | 1010 | $raw .= "lxc.monitor.unshare = 1\n"; |
58cc92a9 | 1011 | |
425b62cb WB |
1012 | # Should we read them from /etc/subuid? |
1013 | if ($unprivileged && !$custom_idmap) { | |
1014 | $raw .= "lxc.id_map = u 0 100000 65536\n"; | |
1015 | $raw .= "lxc.id_map = g 0 100000 65536\n"; | |
1016 | } | |
1017 | ||
d250604f | 1018 | if (!PVE::LXC::Config->has_dev_console($conf)) { |
eeaea429 DM |
1019 | $raw .= "lxc.console = none\n"; |
1020 | $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n"; | |
1021 | } | |
4f958489 | 1022 | |
0d0ca400 | 1023 | my $ttycount = get_tty_count($conf); |
27916659 | 1024 | $raw .= "lxc.tty = $ttycount\n"; |
cbb03fea | 1025 | |
c31ad455 | 1026 | # some init scripts expect a linux terminal (turnkey). |
a691a5a3 DM |
1027 | $raw .= "lxc.environment = TERM=linux\n"; |
1028 | ||
27916659 DM |
1029 | my $utsname = $conf->{hostname} || "CT$vmid"; |
1030 | $raw .= "lxc.utsname = $utsname\n"; | |
cbb03fea | 1031 | |
27916659 DM |
1032 | my $memory = $conf->{memory} || 512; |
1033 | my $swap = $conf->{swap} // 0; | |
1034 | ||
1035 | my $lxcmem = int($memory*1024*1024); | |
1036 | $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n"; | |
a12a36e0 | 1037 | |
27916659 DM |
1038 | my $lxcswap = int(($memory + $swap)*1024*1024); |
1039 | $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n"; | |
1040 | ||
1041 | if (my $cpulimit = $conf->{cpulimit}) { | |
1042 | $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n"; | |
1043 | my $value = int(100000*$cpulimit); | |
1044 | $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n"; | |
a12a36e0 WL |
1045 | } |
1046 | ||
27916659 DM |
1047 | my $shares = $conf->{cpuunits} || 1024; |
1048 | $raw .= "lxc.cgroup.cpu.shares = $shares\n"; | |
1049 | ||
44a9face | 1050 | my $mountpoint = parse_ct_rootfs($conf->{rootfs}); |
a3076d81 | 1051 | |
c9a5774b | 1052 | $raw .= "lxc.rootfs = $dir/rootfs\n"; |
27916659 DM |
1053 | |
1054 | my $netcount = 0; | |
1055 | foreach my $k (keys %$conf) { | |
1056 | next if $k !~ m/^net(\d+)$/; | |
1057 | my $ind = $1; | |
a16d94c8 | 1058 | my $d = parse_lxc_network($conf->{$k}); |
27916659 DM |
1059 | $netcount++; |
1060 | $raw .= "lxc.network.type = veth\n"; | |
18862537 | 1061 | $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n"; |
27916659 DM |
1062 | $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr}); |
1063 | $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name}); | |
1064 | $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu}); | |
a12a36e0 WL |
1065 | } |
1066 | ||
e576f689 DM |
1067 | if (my $lxcconf = $conf->{lxc}) { |
1068 | foreach my $entry (@$lxcconf) { | |
1069 | my ($k, $v) = @$entry; | |
1070 | $netcount++ if $k eq 'lxc.network.type'; | |
1071 | $raw .= "$k = $v\n"; | |
1072 | } | |
1073 | } | |
27916659 | 1074 | |
e576f689 DM |
1075 | $raw .= "lxc.network.type = empty\n" if !$netcount; |
1076 | ||
27916659 DM |
1077 | File::Path::mkpath("$dir/rootfs"); |
1078 | ||
1079 | PVE::Tools::file_set_contents("$dir/config", $raw); | |
b80dd50a DM |
1080 | } |
1081 | ||
117636e5 DM |
1082 | # verify and cleanup nameserver list (replace \0 with ' ') |
1083 | sub verify_nameserver_list { | |
1084 | my ($nameserver_list) = @_; | |
1085 | ||
1086 | my @list = (); | |
1087 | foreach my $server (PVE::Tools::split_list($nameserver_list)) { | |
1088 | PVE::JSONSchema::pve_verify_ip($server); | |
1089 | push @list, $server; | |
1090 | } | |
1091 | ||
1092 | return join(' ', @list); | |
1093 | } | |
1094 | ||
1095 | sub verify_searchdomain_list { | |
1096 | my ($searchdomain_list) = @_; | |
1097 | ||
1098 | my @list = (); | |
1099 | foreach my $server (PVE::Tools::split_list($searchdomain_list)) { | |
1100 | # todo: should we add checks for valid dns domains? | |
1101 | push @list, $server; | |
1102 | } | |
1103 | ||
1104 | return join(' ', @list); | |
1105 | } | |
1106 | ||
27916659 | 1107 | sub update_pct_config { |
93285df8 DM |
1108 | my ($vmid, $conf, $running, $param, $delete) = @_; |
1109 | ||
bf0b8c43 AD |
1110 | my @nohotplug; |
1111 | ||
7b49dfe0 | 1112 | my $new_disks = 0; |
69202f71 | 1113 | my @deleted_volumes; |
4fee75fd | 1114 | |
cbb03fea DM |
1115 | my $rootdir; |
1116 | if ($running) { | |
bedeaaf1 | 1117 | my $pid = find_lxc_pid($vmid); |
cbb03fea | 1118 | $rootdir = "/proc/$pid/root"; |
bedeaaf1 AD |
1119 | } |
1120 | ||
7a168607 DM |
1121 | my $hotplug_error = sub { |
1122 | if ($running) { | |
a6a77cfa WB |
1123 | push @nohotplug, @_; |
1124 | return 1; | |
7a168607 DM |
1125 | } else { |
1126 | return 0; | |
a6a77cfa | 1127 | } |
7a168607 | 1128 | }; |
a6a77cfa | 1129 | |
93285df8 DM |
1130 | if (defined($delete)) { |
1131 | foreach my $opt (@$delete) { | |
a61a5448 WB |
1132 | if (!exists($conf->{$opt})) { |
1133 | warn "no such option: $opt\n"; | |
1134 | next; | |
1135 | } | |
1136 | ||
27916659 | 1137 | if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') { |
93285df8 DM |
1138 | die "unable to delete required option '$opt'\n"; |
1139 | } elsif ($opt eq 'swap') { | |
27916659 | 1140 | delete $conf->{$opt}; |
bf0b8c43 | 1141 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1); |
40603eb3 | 1142 | } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') { |
27916659 | 1143 | delete $conf->{$opt}; |
4f958489 | 1144 | } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' || |
40603eb3 | 1145 | $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') { |
a6a77cfa | 1146 | next if $hotplug_error->($opt); |
27916659 | 1147 | delete $conf->{$opt}; |
68fba17b | 1148 | } elsif ($opt =~ m/^net(\d)$/) { |
93285df8 | 1149 | delete $conf->{$opt}; |
68fba17b AD |
1150 | next if !$running; |
1151 | my $netid = $1; | |
18862537 | 1152 | PVE::Network::veth_delete("veth${vmid}i$netid"); |
7e806596 AG |
1153 | } elsif ($opt eq 'protection') { |
1154 | delete $conf->{$opt}; | |
69202f71 | 1155 | } elsif ($opt =~ m/^unused(\d+)$/) { |
a6a77cfa | 1156 | next if $hotplug_error->($opt); |
69202f71 WB |
1157 | check_protection($conf, "can't remove CT $vmid drive '$opt'"); |
1158 | push @deleted_volumes, $conf->{$opt}; | |
1159 | delete $conf->{$opt}; | |
4fee75fd | 1160 | } elsif ($opt =~ m/^mp(\d+)$/) { |
a6a77cfa | 1161 | next if $hotplug_error->($opt); |
e22af68f | 1162 | check_protection($conf, "can't remove CT $vmid drive '$opt'"); |
12e95ae4 | 1163 | my $mp = parse_ct_mountpoint($conf->{$opt}); |
4fee75fd | 1164 | delete $conf->{$opt}; |
5e93304e | 1165 | if ($mp->{type} eq 'volume') { |
d250604f | 1166 | PVE::LXC::Config->add_unused_volume($conf, $mp->{volume}); |
12e95ae4 | 1167 | } |
425b62cb WB |
1168 | } elsif ($opt eq 'unprivileged') { |
1169 | die "unable to delete read-only option: '$opt'\n"; | |
93285df8 | 1170 | } else { |
9a7a910b | 1171 | die "implement me (delete: $opt)" |
93285df8 | 1172 | } |
67afe46e | 1173 | PVE::LXC::Config->write_config($vmid, $conf) if $running; |
93285df8 DM |
1174 | } |
1175 | } | |
1176 | ||
be6383d7 WB |
1177 | # There's no separate swap size to configure, there's memory and "total" |
1178 | # memory (iow. memory+swap). This means we have to change them together. | |
27916659 DM |
1179 | my $wanted_memory = PVE::Tools::extract_param($param, 'memory'); |
1180 | my $wanted_swap = PVE::Tools::extract_param($param, 'swap'); | |
be6383d7 | 1181 | if (defined($wanted_memory) || defined($wanted_swap)) { |
27916659 | 1182 | |
a2c57b0c WB |
1183 | my $old_memory = ($conf->{memory} || 512); |
1184 | my $old_swap = ($conf->{swap} || 0); | |
1185 | ||
1186 | $wanted_memory //= $old_memory; | |
1187 | $wanted_swap //= $old_swap; | |
27916659 DM |
1188 | |
1189 | my $total = $wanted_memory + $wanted_swap; | |
1190 | if ($running) { | |
a2c57b0c WB |
1191 | my $old_total = $old_memory + $old_swap; |
1192 | if ($total > $old_total) { | |
1193 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024)); | |
1194 | write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024)); | |
1195 | } else { | |
1196 | write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024)); | |
1197 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024)); | |
1198 | } | |
be6383d7 | 1199 | } |
27916659 DM |
1200 | $conf->{memory} = $wanted_memory; |
1201 | $conf->{swap} = $wanted_swap; | |
1202 | ||
67afe46e | 1203 | PVE::LXC::Config->write_config($vmid, $conf) if $running; |
be6383d7 WB |
1204 | } |
1205 | ||
dfab6edb WB |
1206 | my $used_volids = {}; |
1207 | ||
93285df8 DM |
1208 | foreach my $opt (keys %$param) { |
1209 | my $value = $param->{$opt}; | |
1210 | if ($opt eq 'hostname') { | |
27916659 | 1211 | $conf->{$opt} = $value; |
a99b3509 | 1212 | } elsif ($opt eq 'onboot') { |
27916659 | 1213 | $conf->{$opt} = $value ? 1 : 0; |
a3249355 | 1214 | } elsif ($opt eq 'startup') { |
27916659 | 1215 | $conf->{$opt} = $value; |
40603eb3 | 1216 | } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') { |
a6a77cfa | 1217 | next if $hotplug_error->($opt); |
e576f689 | 1218 | $conf->{$opt} = $value; |
ffa1d001 | 1219 | } elsif ($opt eq 'nameserver') { |
a6a77cfa | 1220 | next if $hotplug_error->($opt); |
117636e5 | 1221 | my $list = verify_nameserver_list($value); |
27916659 | 1222 | $conf->{$opt} = $list; |
ffa1d001 | 1223 | } elsif ($opt eq 'searchdomain') { |
a6a77cfa | 1224 | next if $hotplug_error->($opt); |
117636e5 | 1225 | my $list = verify_searchdomain_list($value); |
27916659 | 1226 | $conf->{$opt} = $list; |
45573f7c | 1227 | } elsif ($opt eq 'cpulimit') { |
a6a77cfa | 1228 | next if $hotplug_error->($opt); # FIXME: hotplug |
27916659 | 1229 | $conf->{$opt} = $value; |
b80dd50a | 1230 | } elsif ($opt eq 'cpuunits') { |
27916659 | 1231 | $conf->{$opt} = $value; |
bf0b8c43 | 1232 | write_cgroup_value("cpu", $vmid, "cpu.shares", $value); |
93285df8 | 1233 | } elsif ($opt eq 'description') { |
27916659 | 1234 | $conf->{$opt} = PVE::Tools::encode_text($value); |
93285df8 DM |
1235 | } elsif ($opt =~ m/^net(\d+)$/) { |
1236 | my $netid = $1; | |
a16d94c8 | 1237 | my $net = parse_lxc_network($value); |
27916659 DM |
1238 | if (!$running) { |
1239 | $conf->{$opt} = print_lxc_network($net); | |
cbb03fea | 1240 | } else { |
bedeaaf1 AD |
1241 | update_net($vmid, $conf, $opt, $net, $netid, $rootdir); |
1242 | } | |
7e806596 AG |
1243 | } elsif ($opt eq 'protection') { |
1244 | $conf->{$opt} = $value ? 1 : 0; | |
4fee75fd | 1245 | } elsif ($opt =~ m/^mp(\d+)$/) { |
a6a77cfa | 1246 | next if $hotplug_error->($opt); |
e22af68f | 1247 | check_protection($conf, "can't update CT $vmid drive '$opt'"); |
12e95ae4 | 1248 | my $old = $conf->{$opt}; |
4fee75fd | 1249 | $conf->{$opt} = $value; |
12e95ae4 FG |
1250 | if (defined($old)) { |
1251 | my $mp = parse_ct_mountpoint($old); | |
5e93304e | 1252 | if ($mp->{type} eq 'volume') { |
d250604f | 1253 | PVE::LXC::Config->add_unused_volume($conf, $mp->{volume}); |
12e95ae4 FG |
1254 | } |
1255 | } | |
7b49dfe0 | 1256 | $new_disks = 1; |
dfab6edb WB |
1257 | my $mp = parse_ct_mountpoint($value); |
1258 | $used_volids->{$mp->{volume}} = 1; | |
4fee75fd | 1259 | } elsif ($opt eq 'rootfs') { |
55ce8db8 | 1260 | next if $hotplug_error->($opt); |
e22af68f | 1261 | check_protection($conf, "can't update CT $vmid drive '$opt'"); |
12e95ae4 | 1262 | my $old = $conf->{$opt}; |
55ce8db8 | 1263 | $conf->{$opt} = $value; |
12e95ae4 FG |
1264 | if (defined($old)) { |
1265 | my $mp = parse_ct_rootfs($old); | |
5e93304e | 1266 | if ($mp->{type} eq 'volume') { |
d250604f | 1267 | PVE::LXC::Config->add_unused_volume($conf, $mp->{volume}); |
12e95ae4 FG |
1268 | } |
1269 | } | |
dfab6edb WB |
1270 | my $mp = parse_ct_rootfs($value); |
1271 | $used_volids->{$mp->{volume}} = 1; | |
425b62cb WB |
1272 | } elsif ($opt eq 'unprivileged') { |
1273 | die "unable to modify read-only option: '$opt'\n"; | |
238b7e3e DM |
1274 | } elsif ($opt eq 'ostype') { |
1275 | next if $hotplug_error->($opt); | |
1276 | $conf->{$opt} = $value; | |
93285df8 | 1277 | } else { |
a92f66c9 | 1278 | die "implement me: $opt"; |
93285df8 | 1279 | } |
67afe46e | 1280 | PVE::LXC::Config->write_config($vmid, $conf) if $running; |
93285df8 | 1281 | } |
bf0b8c43 | 1282 | |
dfab6edb | 1283 | # Apply deletions and creations of new volumes |
69202f71 WB |
1284 | if (@deleted_volumes) { |
1285 | my $storage_cfg = PVE::Storage::config(); | |
1286 | foreach my $volume (@deleted_volumes) { | |
dfab6edb | 1287 | next if $used_volids->{$volume}; # could have been re-added, too |
4defdb73 | 1288 | # also check for references in snapshots |
d250604f | 1289 | next if PVE::LXC::Config->is_volume_in_use($conf, $volume, 1); |
69202f71 WB |
1290 | delete_mountpoint_volume($storage_cfg, $vmid, $volume); |
1291 | } | |
1292 | } | |
1293 | ||
7b49dfe0 | 1294 | if ($new_disks) { |
4fee75fd | 1295 | my $storage_cfg = PVE::Storage::config(); |
6c871c36 | 1296 | create_disks($storage_cfg, $vmid, $conf, $conf); |
4fee75fd | 1297 | } |
694c25df WB |
1298 | |
1299 | # This should be the last thing we do here | |
1300 | if ($running && scalar(@nohotplug)) { | |
1301 | die "unable to modify " . join(',', @nohotplug) . " while container is running\n"; | |
1302 | } | |
93285df8 | 1303 | } |
c325b32f | 1304 | |
0d0ca400 DM |
1305 | sub get_tty_count { |
1306 | my ($conf) = @_; | |
1307 | ||
1308 | return $conf->{tty} // $confdesc->{tty}->{default}; | |
1309 | } | |
1310 | ||
aca816ad DM |
1311 | sub get_cmode { |
1312 | my ($conf) = @_; | |
1313 | ||
1314 | return $conf->{cmode} // $confdesc->{cmode}->{default}; | |
1315 | } | |
1316 | ||
1317 | sub get_console_command { | |
1318 | my ($vmid, $conf) = @_; | |
1319 | ||
1320 | my $cmode = get_cmode($conf); | |
1321 | ||
1322 | if ($cmode eq 'console') { | |
1323 | return ['lxc-console', '-n', $vmid, '-t', 0]; | |
1324 | } elsif ($cmode eq 'tty') { | |
1325 | return ['lxc-console', '-n', $vmid]; | |
1326 | } elsif ($cmode eq 'shell') { | |
1327 | return ['lxc-attach', '--clear-env', '-n', $vmid]; | |
1328 | } else { | |
1329 | die "internal error"; | |
1330 | } | |
1331 | } | |
1332 | ||
c325b32f DM |
1333 | sub get_primary_ips { |
1334 | my ($conf) = @_; | |
1335 | ||
1336 | # return data from net0 | |
cbb03fea | 1337 | |
27916659 | 1338 | return undef if !defined($conf->{net0}); |
a16d94c8 | 1339 | my $net = parse_lxc_network($conf->{net0}); |
c325b32f DM |
1340 | |
1341 | my $ipv4 = $net->{ip}; | |
db78a181 WB |
1342 | if ($ipv4) { |
1343 | if ($ipv4 =~ /^(dhcp|manual)$/) { | |
1344 | $ipv4 = undef | |
1345 | } else { | |
1346 | $ipv4 =~ s!/\d+$!!; | |
1347 | } | |
1348 | } | |
65e5eaa3 | 1349 | my $ipv6 = $net->{ip6}; |
db78a181 | 1350 | if ($ipv6) { |
5f291c7d | 1351 | if ($ipv6 =~ /^(auto|dhcp|manual)$/) { |
db78a181 WB |
1352 | $ipv6 = undef; |
1353 | } else { | |
1354 | $ipv6 =~ s!/\d+$!!; | |
1355 | } | |
1356 | } | |
cbb03fea | 1357 | |
c325b32f DM |
1358 | return ($ipv4, $ipv6); |
1359 | } | |
148d1cb4 | 1360 | |
b407293b WB |
1361 | sub delete_mountpoint_volume { |
1362 | my ($storage_cfg, $vmid, $volume) = @_; | |
1363 | ||
d250604f | 1364 | return if PVE::LXC::Config->classify_mountpoint($volume) ne 'volume'; |
b407293b WB |
1365 | |
1366 | my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume); | |
1367 | PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner; | |
1368 | } | |
ef241384 | 1369 | |
27916659 | 1370 | sub destroy_lxc_container { |
148d1cb4 DM |
1371 | my ($storage_cfg, $vmid, $conf) = @_; |
1372 | ||
d250604f | 1373 | PVE::LXC::Config->foreach_mountpoint($conf, sub { |
db8989e1 | 1374 | my ($ms, $mountpoint) = @_; |
b407293b | 1375 | delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume}); |
db8989e1 WB |
1376 | }); |
1377 | ||
27916659 DM |
1378 | rmdir "/var/lib/lxc/$vmid/rootfs"; |
1379 | unlink "/var/lib/lxc/$vmid/config"; | |
1380 | rmdir "/var/lib/lxc/$vmid"; | |
1381 | destroy_config($vmid); | |
1382 | ||
1383 | #my $cmd = ['lxc-destroy', '-n', $vmid ]; | |
1384 | #PVE::Tools::run_command($cmd); | |
148d1cb4 | 1385 | } |
68fba17b | 1386 | |
ef241384 | 1387 | sub vm_stop_cleanup { |
5fa890f0 | 1388 | my ($storage_cfg, $vmid, $conf, $keepActive) = @_; |
ef241384 DM |
1389 | |
1390 | eval { | |
1391 | if (!$keepActive) { | |
bf9d912c | 1392 | |
d250604f | 1393 | my $vollist = PVE::LXC::Config->get_vm_volumes($conf); |
a8b6b8a7 | 1394 | PVE::Storage::deactivate_volumes($storage_cfg, $vollist); |
ef241384 DM |
1395 | } |
1396 | }; | |
1397 | warn $@ if $@; # avoid errors - just warn | |
1398 | } | |
1399 | ||
93cdbbfb AD |
1400 | my $safe_num_ne = sub { |
1401 | my ($a, $b) = @_; | |
1402 | ||
1403 | return 0 if !defined($a) && !defined($b); | |
1404 | return 1 if !defined($a); | |
1405 | return 1 if !defined($b); | |
1406 | ||
1407 | return $a != $b; | |
1408 | }; | |
1409 | ||
1410 | my $safe_string_ne = sub { | |
1411 | my ($a, $b) = @_; | |
1412 | ||
1413 | return 0 if !defined($a) && !defined($b); | |
1414 | return 1 if !defined($a); | |
1415 | return 1 if !defined($b); | |
1416 | ||
1417 | return $a ne $b; | |
1418 | }; | |
1419 | ||
1420 | sub update_net { | |
bedeaaf1 | 1421 | my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_; |
93cdbbfb | 1422 | |
18862537 WB |
1423 | if ($newnet->{type} ne 'veth') { |
1424 | # for when there are physical interfaces | |
1425 | die "cannot update interface of type $newnet->{type}"; | |
1426 | } | |
1427 | ||
1428 | my $veth = "veth${vmid}i${netid}"; | |
93cdbbfb AD |
1429 | my $eth = $newnet->{name}; |
1430 | ||
18862537 WB |
1431 | if (my $oldnetcfg = $conf->{$opt}) { |
1432 | my $oldnet = parse_lxc_network($oldnetcfg); | |
1433 | ||
1434 | if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) || | |
1435 | &$safe_string_ne($oldnet->{name}, $newnet->{name})) { | |
93cdbbfb | 1436 | |
18862537 | 1437 | PVE::Network::veth_delete($veth); |
bedeaaf1 | 1438 | delete $conf->{$opt}; |
67afe46e | 1439 | PVE::LXC::Config->write_config($vmid, $conf); |
93cdbbfb | 1440 | |
18862537 | 1441 | hotplug_net($vmid, $conf, $opt, $newnet, $netid); |
bedeaaf1 | 1442 | |
18862537 WB |
1443 | } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) || |
1444 | &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) || | |
1445 | &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) { | |
bedeaaf1 | 1446 | |
18862537 | 1447 | if ($oldnet->{bridge}) { |
bedeaaf1 | 1448 | PVE::Network::tap_unplug($veth); |
18862537 WB |
1449 | foreach (qw(bridge tag firewall)) { |
1450 | delete $oldnet->{$_}; | |
1451 | } | |
1452 | $conf->{$opt} = print_lxc_network($oldnet); | |
67afe46e | 1453 | PVE::LXC::Config->write_config($vmid, $conf); |
bedeaaf1 | 1454 | } |
93cdbbfb | 1455 | |
23eb2244 | 1456 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}); |
18862537 WB |
1457 | foreach (qw(bridge tag firewall)) { |
1458 | $oldnet->{$_} = $newnet->{$_} if $newnet->{$_}; | |
1459 | } | |
1460 | $conf->{$opt} = print_lxc_network($oldnet); | |
67afe46e | 1461 | PVE::LXC::Config->write_config($vmid, $conf); |
93cdbbfb AD |
1462 | } |
1463 | } else { | |
18862537 | 1464 | hotplug_net($vmid, $conf, $opt, $newnet, $netid); |
93cdbbfb AD |
1465 | } |
1466 | ||
bedeaaf1 | 1467 | update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir); |
93cdbbfb AD |
1468 | } |
1469 | ||
1470 | sub hotplug_net { | |
18862537 | 1471 | my ($vmid, $conf, $opt, $newnet, $netid) = @_; |
93cdbbfb | 1472 | |
18862537 | 1473 | my $veth = "veth${vmid}i${netid}"; |
cbb03fea | 1474 | my $vethpeer = $veth . "p"; |
93cdbbfb AD |
1475 | my $eth = $newnet->{name}; |
1476 | ||
1477 | PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr}); | |
23eb2244 | 1478 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}); |
93cdbbfb | 1479 | |
cbb03fea | 1480 | # attach peer in container |
93cdbbfb AD |
1481 | my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ]; |
1482 | PVE::Tools::run_command($cmd); | |
1483 | ||
cbb03fea | 1484 | # link up peer in container |
93cdbbfb AD |
1485 | $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ]; |
1486 | PVE::Tools::run_command($cmd); | |
bedeaaf1 | 1487 | |
18862537 WB |
1488 | my $done = { type => 'veth' }; |
1489 | foreach (qw(bridge tag firewall hwaddr name)) { | |
1490 | $done->{$_} = $newnet->{$_} if $newnet->{$_}; | |
1491 | } | |
1492 | $conf->{$opt} = print_lxc_network($done); | |
bedeaaf1 | 1493 | |
67afe46e | 1494 | PVE::LXC::Config->write_config($vmid, $conf); |
93cdbbfb AD |
1495 | } |
1496 | ||
68a05bb3 | 1497 | sub update_ipconfig { |
bedeaaf1 AD |
1498 | my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_; |
1499 | ||
f2104b80 | 1500 | my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); |
bedeaaf1 | 1501 | |
18862537 | 1502 | my $optdata = parse_lxc_network($conf->{$opt}); |
84e0c123 WB |
1503 | my $deleted = []; |
1504 | my $added = []; | |
8d723477 WB |
1505 | my $nscmd = sub { |
1506 | my $cmdargs = shift; | |
1507 | PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs); | |
84e0c123 | 1508 | }; |
8d723477 | 1509 | my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) }; |
2bfd1615 | 1510 | |
84e0c123 | 1511 | my $change_ip_config = sub { |
f39002a6 DM |
1512 | my ($ipversion) = @_; |
1513 | ||
1514 | my $family_opt = "-$ipversion"; | |
1515 | my $suffix = $ipversion == 4 ? '' : $ipversion; | |
84e0c123 WB |
1516 | my $gw= "gw$suffix"; |
1517 | my $ip= "ip$suffix"; | |
bedeaaf1 | 1518 | |
6178b0dd WB |
1519 | my $newip = $newnet->{$ip}; |
1520 | my $newgw = $newnet->{$gw}; | |
1521 | my $oldip = $optdata->{$ip}; | |
1522 | ||
1523 | my $change_ip = &$safe_string_ne($oldip, $newip); | |
1524 | my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw); | |
bedeaaf1 | 1525 | |
84e0c123 | 1526 | return if !$change_ip && !$change_gw; |
68a05bb3 | 1527 | |
84e0c123 | 1528 | # step 1: add new IP, if this fails we cancel |
292aff54 WB |
1529 | my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/); |
1530 | if ($change_ip && $is_real_ip) { | |
8d723477 | 1531 | eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); }; |
84e0c123 WB |
1532 | if (my $err = $@) { |
1533 | warn $err; | |
1534 | return; | |
1535 | } | |
bedeaaf1 | 1536 | } |
bedeaaf1 | 1537 | |
84e0c123 WB |
1538 | # step 2: replace gateway |
1539 | # If this fails we delete the added IP and cancel. | |
1540 | # If it succeeds we save the config and delete the old IP, ignoring | |
1541 | # errors. The config is then saved. | |
1542 | # Note: 'ip route replace' can add | |
1543 | if ($change_gw) { | |
6178b0dd | 1544 | if ($newgw) { |
292aff54 WB |
1545 | eval { |
1546 | if ($is_real_ip && !PVE::Network::is_ip_in_cidr($newgw, $newip, $ipversion)) { | |
1547 | &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth); | |
1548 | } | |
1549 | &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); | |
1550 | }; | |
84e0c123 WB |
1551 | if (my $err = $@) { |
1552 | warn $err; | |
1553 | # the route was not replaced, the old IP is still available | |
1554 | # rollback (delete new IP) and cancel | |
1555 | if ($change_ip) { | |
8d723477 | 1556 | eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); }; |
84e0c123 WB |
1557 | warn $@ if $@; # no need to die here |
1558 | } | |
1559 | return; | |
1560 | } | |
1561 | } else { | |
8d723477 | 1562 | eval { &$ipcmd($family_opt, 'route', 'del', 'default'); }; |
84e0c123 WB |
1563 | # if the route was not deleted, the guest might have deleted it manually |
1564 | # warn and continue | |
1565 | warn $@ if $@; | |
1566 | } | |
2bfd1615 | 1567 | } |
2bfd1615 | 1568 | |
6178b0dd | 1569 | # from this point on we save the configuration |
84e0c123 | 1570 | # step 3: delete old IP ignoring errors |
6178b0dd | 1571 | if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) { |
8d723477 WB |
1572 | # We need to enable promote_secondaries, otherwise our newly added |
1573 | # address will be removed along with the old one. | |
1574 | my $promote = 0; | |
1575 | eval { | |
1576 | if ($ipversion == 4) { | |
1577 | &$nscmd({ outfunc => sub { $promote = int(shift) } }, | |
1578 | 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries"); | |
1579 | &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1"); | |
1580 | } | |
1581 | &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth); | |
1582 | }; | |
84e0c123 | 1583 | warn $@ if $@; # no need to die here |
8d723477 WB |
1584 | |
1585 | if ($ipversion == 4) { | |
1586 | &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote"); | |
1587 | } | |
bedeaaf1 AD |
1588 | } |
1589 | ||
84e0c123 WB |
1590 | foreach my $property ($ip, $gw) { |
1591 | if ($newnet->{$property}) { | |
1592 | $optdata->{$property} = $newnet->{$property}; | |
1593 | } else { | |
1594 | delete $optdata->{$property}; | |
1595 | } | |
bedeaaf1 | 1596 | } |
18862537 | 1597 | $conf->{$opt} = print_lxc_network($optdata); |
67afe46e | 1598 | PVE::LXC::Config->write_config($vmid, $conf); |
84e0c123 WB |
1599 | $lxc_setup->setup_network($conf); |
1600 | }; | |
bedeaaf1 | 1601 | |
f39002a6 DM |
1602 | &$change_ip_config(4); |
1603 | &$change_ip_config(6); | |
489e960d WL |
1604 | |
1605 | } | |
1606 | ||
34fdb3d7 WB |
1607 | my $enter_namespace = sub { |
1608 | my ($vmid, $pid, $which, $type) = @_; | |
1609 | sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY | |
1610 | or die "failed to open $which namespace of container $vmid: $!\n"; | |
1611 | PVE::Tools::setns(fileno($fd), $type) | |
1612 | or die "failed to enter $which namespace of container $vmid: $!\n"; | |
1613 | close $fd; | |
1614 | }; | |
1615 | ||
1616 | my $do_syncfs = sub { | |
1617 | my ($vmid, $pid, $socket) = @_; | |
1618 | ||
1619 | &$enter_namespace($vmid, $pid, 'mnt', PVE::Tools::CLONE_NEWNS); | |
1620 | ||
1621 | # Tell the parent process to start reading our /proc/mounts | |
1622 | print {$socket} "go\n"; | |
1623 | $socket->flush(); | |
1624 | ||
1625 | # Receive /proc/self/mounts | |
1626 | my $mountdata = do { local $/ = undef; <$socket> }; | |
1627 | close $socket; | |
1628 | ||
1629 | # Now sync all mountpoints... | |
1630 | my $mounts = PVE::ProcFSTools::parse_mounts($mountdata); | |
1631 | foreach my $mp (@$mounts) { | |
1632 | my ($what, $dir, $fs) = @$mp; | |
1633 | next if $fs eq 'fuse.lxcfs'; | |
1634 | eval { PVE::Tools::sync_mountpoint($dir); }; | |
1635 | warn $@ if $@; | |
1636 | } | |
1637 | }; | |
1638 | ||
1639 | sub sync_container_namespace { | |
1640 | my ($vmid) = @_; | |
1641 | my $pid = find_lxc_pid($vmid); | |
1642 | ||
1643 | # SOCK_DGRAM is nicer for barriers but cannot be slurped | |
1644 | socketpair my $pfd, my $cfd, AF_UNIX, SOCK_STREAM, PF_UNSPEC | |
1645 | or die "failed to create socketpair: $!\n"; | |
1646 | ||
1647 | my $child = fork(); | |
1648 | die "fork failed: $!\n" if !defined($child); | |
1649 | ||
1650 | if (!$child) { | |
1651 | eval { | |
1652 | close $pfd; | |
1653 | &$do_syncfs($vmid, $pid, $cfd); | |
1654 | }; | |
1655 | if (my $err = $@) { | |
1656 | warn $err; | |
1657 | POSIX::_exit(1); | |
1658 | } | |
1659 | POSIX::_exit(0); | |
1660 | } | |
1661 | close $cfd; | |
1662 | my $go = <$pfd>; | |
1663 | die "failed to enter container namespace\n" if $go ne "go\n"; | |
1664 | ||
1665 | open my $mounts, '<', "/proc/$child/mounts" | |
1666 | or die "failed to open container's /proc/mounts: $!\n"; | |
1667 | my $mountdata = do { local $/ = undef; <$mounts> }; | |
1668 | close $mounts; | |
1669 | print {$pfd} $mountdata; | |
1670 | close $pfd; | |
1671 | ||
1672 | while (waitpid($child, 0) != $child) {} | |
1673 | die "failed to sync container namespace\n" if $? != 0; | |
1674 | } | |
1675 | ||
bb1ac2de DM |
1676 | sub template_create { |
1677 | my ($vmid, $conf) = @_; | |
1678 | ||
1679 | my $storecfg = PVE::Storage::config(); | |
1680 | ||
44a9face | 1681 | my $rootinfo = parse_ct_rootfs($conf->{rootfs}); |
bb1ac2de DM |
1682 | my $volid = $rootinfo->{volume}; |
1683 | ||
1684 | die "Template feature is not available for '$volid'\n" | |
1685 | if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid); | |
1686 | ||
1687 | PVE::Storage::activate_volumes($storecfg, [$volid]); | |
1688 | ||
1689 | my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid); | |
1690 | $rootinfo->{volume} = $template_volid; | |
4fee75fd | 1691 | $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1); |
bb1ac2de | 1692 | |
67afe46e | 1693 | PVE::LXC::Config->write_config($vmid, $conf); |
bb1ac2de DM |
1694 | } |
1695 | ||
52389a07 | 1696 | sub check_ct_modify_config_perm { |
f1ba1a4b | 1697 | my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_; |
52389a07 | 1698 | |
c81f19d1 | 1699 | return 1 if $authuser eq 'root@pam'; |
52389a07 | 1700 | |
f1ba1a4b WB |
1701 | my $check = sub { |
1702 | my ($opt, $delete) = @_; | |
52389a07 DM |
1703 | if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') { |
1704 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']); | |
e59a61ed | 1705 | } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) { |
52389a07 | 1706 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); |
f1ba1a4b WB |
1707 | return if $delete; |
1708 | my $data = $opt eq 'rootfs' ? parse_ct_rootfs($newconf->{$opt}) | |
1709 | : parse_ct_mountpoint($newconf->{$opt}); | |
1710 | raise_perm_exc("mountpoint type $data->{type}") if $data->{type} ne 'volume'; | |
52389a07 DM |
1711 | } elsif ($opt eq 'memory' || $opt eq 'swap') { |
1712 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']); | |
1713 | } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' || | |
1714 | $opt eq 'searchdomain' || $opt eq 'hostname') { | |
1715 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']); | |
1716 | } else { | |
1717 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']); | |
1718 | } | |
f1ba1a4b WB |
1719 | }; |
1720 | ||
1721 | foreach my $opt (keys %$newconf) { | |
1722 | &$check($opt, 0); | |
1723 | } | |
1724 | foreach my $opt (@$delete) { | |
1725 | &$check($opt, 1); | |
52389a07 DM |
1726 | } |
1727 | ||
1728 | return 1; | |
1729 | } | |
1730 | ||
9622e848 | 1731 | sub umount_all { |
da629848 | 1732 | my ($vmid, $storage_cfg, $conf, $noerr) = @_; |
9622e848 DM |
1733 | |
1734 | my $rootdir = "/var/lib/lxc/$vmid/rootfs"; | |
d250604f | 1735 | my $volid_list = PVE::LXC::Config->get_vm_volumes($conf); |
9622e848 | 1736 | |
d250604f | 1737 | PVE::LXC::Config->foreach_mountpoint_reverse($conf, sub { |
9622e848 DM |
1738 | my ($ms, $mountpoint) = @_; |
1739 | ||
1740 | my $volid = $mountpoint->{volume}; | |
1741 | my $mount = $mountpoint->{mp}; | |
1742 | ||
1743 | return if !$volid || !$mount; | |
1744 | ||
d18f96b4 | 1745 | my $mount_path = "$rootdir/$mount"; |
f845a93d | 1746 | $mount_path =~ s!/+!/!g; |
9622e848 | 1747 | |
228a5a1d WL |
1748 | return if !PVE::ProcFSTools::is_mounted($mount_path); |
1749 | ||
9622e848 | 1750 | eval { |
d18f96b4 | 1751 | PVE::Tools::run_command(['umount', '-d', $mount_path]); |
9622e848 DM |
1752 | }; |
1753 | if (my $err = $@) { | |
1754 | if ($noerr) { | |
1755 | warn $err; | |
1756 | } else { | |
1757 | die $err; | |
1758 | } | |
1759 | } | |
1760 | }); | |
9622e848 DM |
1761 | } |
1762 | ||
1763 | sub mount_all { | |
7b49dfe0 | 1764 | my ($vmid, $storage_cfg, $conf) = @_; |
9622e848 DM |
1765 | |
1766 | my $rootdir = "/var/lib/lxc/$vmid/rootfs"; | |
1adc7e53 | 1767 | File::Path::make_path($rootdir); |
9622e848 | 1768 | |
d250604f | 1769 | my $volid_list = PVE::LXC::Config->get_vm_volumes($conf); |
9622e848 DM |
1770 | PVE::Storage::activate_volumes($storage_cfg, $volid_list); |
1771 | ||
1772 | eval { | |
d250604f | 1773 | PVE::LXC::Config->foreach_mountpoint($conf, sub { |
9622e848 DM |
1774 | my ($ms, $mountpoint) = @_; |
1775 | ||
da629848 | 1776 | mountpoint_mount($mountpoint, $rootdir, $storage_cfg); |
9622e848 DM |
1777 | }); |
1778 | }; | |
1779 | if (my $err = $@) { | |
e2007ac2 | 1780 | warn "mounting container failed\n"; |
9622e848 | 1781 | umount_all($vmid, $storage_cfg, $conf, 1); |
e2007ac2 | 1782 | die $err; |
9622e848 DM |
1783 | } |
1784 | ||
da629848 | 1785 | return $rootdir; |
9622e848 DM |
1786 | } |
1787 | ||
1788 | ||
b15c75fc | 1789 | sub mountpoint_mount_path { |
da629848 | 1790 | my ($mountpoint, $storage_cfg, $snapname) = @_; |
b15c75fc | 1791 | |
da629848 | 1792 | return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname); |
b15c75fc | 1793 | } |
cc6b0307 | 1794 | |
2cfae16e WB |
1795 | my $check_mount_path = sub { |
1796 | my ($path) = @_; | |
1797 | $path = File::Spec->canonpath($path); | |
1798 | my $real = Cwd::realpath($path); | |
1799 | if ($real ne $path) { | |
1800 | die "mount path modified by symlink: $path != $real"; | |
1801 | } | |
1802 | }; | |
1803 | ||
21f292ff WB |
1804 | sub query_loopdev { |
1805 | my ($path) = @_; | |
1806 | my $found; | |
1807 | my $parser = sub { | |
1808 | my $line = shift; | |
1809 | if ($line =~ m@^(/dev/loop\d+):@) { | |
1810 | $found = $1; | |
1811 | } | |
1812 | }; | |
1813 | my $cmd = ['losetup', '--associated', $path]; | |
1814 | PVE::Tools::run_command($cmd, outfunc => $parser); | |
1815 | return $found; | |
1816 | } | |
1817 | ||
50df544c WB |
1818 | # Run a function with a file attached to a loop device. |
1819 | # The loop device is always detached afterwards (or set to autoclear). | |
1820 | # Returns the loop device. | |
1821 | sub run_with_loopdev { | |
1822 | my ($func, $file) = @_; | |
54d11e5c WB |
1823 | my $device = query_loopdev($file); |
1824 | # Try to reuse an existing device | |
1825 | if ($device) { | |
1826 | # We assume that whoever setup the loop device is responsible for | |
1827 | # detaching it. | |
1828 | &$func($device); | |
1829 | return $device; | |
1830 | } | |
1831 | ||
50df544c WB |
1832 | my $parser = sub { |
1833 | my $line = shift; | |
1834 | if ($line =~ m@^(/dev/loop\d+)$@) { | |
1835 | $device = $1; | |
1836 | } | |
1837 | }; | |
1838 | PVE::Tools::run_command(['losetup', '--show', '-f', $file], outfunc => $parser); | |
1839 | die "failed to setup loop device for $file\n" if !$device; | |
1840 | eval { &$func($device); }; | |
1841 | my $err = $@; | |
1842 | PVE::Tools::run_command(['losetup', '-d', $device]); | |
1843 | die $err if $err; | |
1844 | return $device; | |
1845 | } | |
1846 | ||
c2744c97 WB |
1847 | sub bindmount { |
1848 | my ($dir, $dest, $ro, @extra_opts) = @_; | |
1849 | PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $dir, $dest]); | |
1850 | if ($ro) { | |
1851 | eval { PVE::Tools::run_command(['mount', '-o', 'bind,remount,ro', $dest]); }; | |
1852 | if (my $err = $@) { | |
1853 | warn "bindmount error\n"; | |
1854 | # don't leave writable bind-mounts behind... | |
1855 | PVE::Tools::run_command(['umount', $dest]); | |
1856 | die $err; | |
1857 | } | |
1858 | } | |
1859 | } | |
1860 | ||
b15c75fc | 1861 | # use $rootdir = undef to just return the corresponding mount path |
cc6b0307 | 1862 | sub mountpoint_mount { |
da629848 | 1863 | my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_; |
cc6b0307 AD |
1864 | |
1865 | my $volid = $mountpoint->{volume}; | |
1866 | my $mount = $mountpoint->{mp}; | |
7c921c80 | 1867 | my $type = $mountpoint->{type}; |
50df544c WB |
1868 | my $quota = !$snapname && !$mountpoint->{ro} && $mountpoint->{quota}; |
1869 | my $mounted_dev; | |
b15c75fc | 1870 | |
cc6b0307 AD |
1871 | return if !$volid || !$mount; |
1872 | ||
b15c75fc DM |
1873 | my $mount_path; |
1874 | ||
1875 | if (defined($rootdir)) { | |
1876 | $rootdir =~ s!/+$!!; | |
1877 | $mount_path = "$rootdir/$mount"; | |
f845a93d | 1878 | $mount_path =~ s!/+!/!g; |
2cfae16e | 1879 | &$check_mount_path($mount_path); |
b15c75fc | 1880 | File::Path::mkpath($mount_path); |
116ce06f | 1881 | } |
b15c75fc DM |
1882 | |
1883 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1); | |
cc6b0307 | 1884 | |
b15c75fc | 1885 | die "unknown snapshot path for '$volid'" if !$storage && defined($snapname); |
cc6b0307 | 1886 | |
471dd315 WB |
1887 | my $optstring = ''; |
1888 | if (defined($mountpoint->{acl})) { | |
1889 | $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl'); | |
1890 | } | |
c2744c97 | 1891 | my $readonly = $mountpoint->{ro}; |
471dd315 WB |
1892 | |
1893 | my @extra_opts = ('-o', $optstring); | |
1894 | ||
b15c75fc DM |
1895 | if ($storage) { |
1896 | ||
1897 | my $scfg = PVE::Storage::storage_config($storage_cfg, $storage); | |
1898 | my $path = PVE::Storage::path($storage_cfg, $volid, $snapname); | |
1899 | ||
1900 | my ($vtype, undef, undef, undef, undef, $isBase, $format) = | |
1901 | PVE::Storage::parse_volname($storage_cfg, $volid); | |
1902 | ||
c87b9dd8 DM |
1903 | $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files |
1904 | ||
b15c75fc | 1905 | if ($format eq 'subvol') { |
30de33be DM |
1906 | if ($mount_path) { |
1907 | if ($snapname) { | |
e84f7f5d DM |
1908 | if ($scfg->{type} eq 'zfspool') { |
1909 | my $path_arg = $path; | |
1910 | $path_arg =~ s!^/+!!; | |
471dd315 | 1911 | PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]); |
e84f7f5d | 1912 | } else { |
30de33be DM |
1913 | die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n"; |
1914 | } | |
e84f7f5d | 1915 | } else { |
c2744c97 | 1916 | bindmount($path, $mount_path, $readonly, @extra_opts); |
50df544c | 1917 | warn "cannot enable quota control for bind mounted subvolumes\n" if $quota; |
30de33be | 1918 | } |
b15c75fc | 1919 | } |
50df544c | 1920 | return wantarray ? ($path, 0, $mounted_dev) : $path; |
c87b9dd8 | 1921 | } elsif ($format eq 'raw' || $format eq 'iso') { |
50df544c WB |
1922 | my $domount = sub { |
1923 | my ($path) = @_; | |
1924 | if ($mount_path) { | |
1925 | if ($format eq 'iso') { | |
1926 | PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]); | |
1927 | } elsif ($isBase || defined($snapname)) { | |
1928 | PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]); | |
1929 | } else { | |
1930 | if ($quota) { | |
1931 | push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0'; | |
1932 | } | |
c2744c97 | 1933 | push @extra_opts, '-o', 'ro' if $readonly; |
50df544c WB |
1934 | PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]); |
1935 | } | |
1936 | } | |
1937 | }; | |
30de33be | 1938 | my $use_loopdev = 0; |
b15c75fc | 1939 | if ($scfg->{path}) { |
50df544c | 1940 | $mounted_dev = run_with_loopdev($domount, $path); |
30de33be | 1941 | $use_loopdev = 1; |
2e879877 DM |
1942 | } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || |
1943 | $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') { | |
50df544c WB |
1944 | $mounted_dev = $path; |
1945 | &$domount($path); | |
b15c75fc DM |
1946 | } else { |
1947 | die "unsupported storage type '$scfg->{type}'\n"; | |
1948 | } | |
50df544c | 1949 | return wantarray ? ($path, $use_loopdev, $mounted_dev) : $path; |
b15c75fc DM |
1950 | } else { |
1951 | die "unsupported image format '$format'\n"; | |
1952 | } | |
7c921c80 | 1953 | } elsif ($type eq 'device') { |
c2744c97 | 1954 | push @extra_opts, '-o', 'ro' if $readonly; |
471dd315 | 1955 | PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path; |
50df544c | 1956 | return wantarray ? ($volid, 0, $volid) : $volid; |
e2007ac2 DM |
1957 | } elsif ($type eq 'bind') { |
1958 | die "directory '$volid' does not exist\n" if ! -d $volid; | |
2cfae16e | 1959 | &$check_mount_path($volid); |
c2744c97 | 1960 | bindmount($volid, $mount_path, $readonly, @extra_opts) if $mount_path; |
50df544c WB |
1961 | warn "cannot enable quota control for bind mounts\n" if $quota; |
1962 | return wantarray ? ($volid, 0, undef) : $volid; | |
b15c75fc DM |
1963 | } |
1964 | ||
1965 | die "unsupported storage"; | |
cc6b0307 AD |
1966 | } |
1967 | ||
6c871c36 | 1968 | sub mkfs { |
d216e891 | 1969 | my ($dev, $rootuid, $rootgid) = @_; |
6c871c36 | 1970 | |
d216e891 WB |
1971 | PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', |
1972 | '-E', "root_owner=$rootuid:$rootgid", | |
1973 | $dev]); | |
6c871c36 DM |
1974 | } |
1975 | ||
1976 | sub format_disk { | |
d216e891 | 1977 | my ($storage_cfg, $volid, $rootuid, $rootgid) = @_; |
6c871c36 DM |
1978 | |
1979 | if ($volid =~ m!^/dev/.+!) { | |
1980 | mkfs($volid); | |
1981 | return; | |
1982 | } | |
1983 | ||
1984 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1); | |
1985 | ||
1986 | die "cannot format volume '$volid' with no storage\n" if !$storage; | |
1987 | ||
08ca136d DM |
1988 | PVE::Storage::activate_volumes($storage_cfg, [$volid]); |
1989 | ||
6c871c36 DM |
1990 | my $path = PVE::Storage::path($storage_cfg, $volid); |
1991 | ||
1992 | my ($vtype, undef, undef, undef, undef, $isBase, $format) = | |
1993 | PVE::Storage::parse_volname($storage_cfg, $volid); | |
1994 | ||
1995 | die "cannot format volume '$volid' (format == $format)\n" | |
1996 | if $format ne 'raw'; | |
1997 | ||
d216e891 | 1998 | mkfs($path, $rootuid, $rootgid); |
6c871c36 DM |
1999 | } |
2000 | ||
2001 | sub destroy_disks { | |
2002 | my ($storecfg, $vollist) = @_; | |
2003 | ||
2004 | foreach my $volid (@$vollist) { | |
2005 | eval { PVE::Storage::vdisk_free($storecfg, $volid); }; | |
2006 | warn $@ if $@; | |
2007 | } | |
2008 | } | |
2009 | ||
2010 | sub create_disks { | |
2011 | my ($storecfg, $vmid, $settings, $conf) = @_; | |
2012 | ||
2013 | my $vollist = []; | |
2014 | ||
2015 | eval { | |
d216e891 WB |
2016 | my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); |
2017 | my $chown_vollist = []; | |
2018 | ||
d250604f | 2019 | PVE::LXC::Config->foreach_mountpoint($settings, sub { |
6c871c36 DM |
2020 | my ($ms, $mountpoint) = @_; |
2021 | ||
2022 | my $volid = $mountpoint->{volume}; | |
2023 | my $mp = $mountpoint->{mp}; | |
2024 | ||
2025 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1); | |
2026 | ||
e2007ac2 | 2027 | if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) { |
8ed5ff9d | 2028 | my ($storeid, $size_gb) = ($1, $2); |
6c871c36 | 2029 | |
8ed5ff9d | 2030 | my $size_kb = int(${size_gb}*1024) * 1024; |
6c871c36 DM |
2031 | |
2032 | my $scfg = PVE::Storage::storage_config($storecfg, $storage); | |
2033 | # fixme: use better naming ct-$vmid-disk-X.raw? | |
2034 | ||
2035 | if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { | |
8ed5ff9d | 2036 | if ($size_kb > 0) { |
6c871c36 | 2037 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', |
8ed5ff9d | 2038 | undef, $size_kb); |
d216e891 | 2039 | format_disk($storecfg, $volid, $rootuid, $rootgid); |
6c871c36 DM |
2040 | } else { |
2041 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol', | |
2042 | undef, 0); | |
d216e891 | 2043 | push @$chown_vollist, $volid; |
6c871c36 DM |
2044 | } |
2045 | } elsif ($scfg->{type} eq 'zfspool') { | |
2046 | ||
2047 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol', | |
8ed5ff9d | 2048 | undef, $size_kb); |
d216e891 | 2049 | push @$chown_vollist, $volid; |
2e879877 | 2050 | } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'lvmthin') { |
6c871c36 | 2051 | |
8ed5ff9d | 2052 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb); |
d216e891 | 2053 | format_disk($storecfg, $volid, $rootuid, $rootgid); |
6c871c36 DM |
2054 | |
2055 | } elsif ($scfg->{type} eq 'rbd') { | |
2056 | ||
2057 | die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd}; | |
8ed5ff9d | 2058 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb); |
d216e891 | 2059 | format_disk($storecfg, $volid, $rootuid, $rootgid); |
6c871c36 DM |
2060 | } else { |
2061 | die "unable to create containers on storage type '$scfg->{type}'\n"; | |
2062 | } | |
2063 | push @$vollist, $volid; | |
71c780b9 WB |
2064 | $mountpoint->{volume} = $volid; |
2065 | $mountpoint->{size} = $size_kb * 1024; | |
2066 | $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs'); | |
6c871c36 | 2067 | } else { |
e2007ac2 DM |
2068 | # use specified/existing volid/dir/device |
2069 | $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs'); | |
6c871c36 DM |
2070 | } |
2071 | }); | |
d216e891 WB |
2072 | |
2073 | PVE::Storage::activate_volumes($storecfg, $chown_vollist, undef); | |
2074 | foreach my $volid (@$chown_vollist) { | |
2075 | my $path = PVE::Storage::path($storecfg, $volid, undef); | |
2076 | chown($rootuid, $rootgid, $path); | |
2077 | } | |
2078 | PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef); | |
6c871c36 DM |
2079 | }; |
2080 | # free allocated images on error | |
2081 | if (my $err = $@) { | |
2082 | destroy_disks($storecfg, $vollist); | |
2083 | die $err; | |
2084 | } | |
2085 | return $vollist; | |
2086 | } | |
2087 | ||
68e8f3c5 DM |
2088 | # bash completion helper |
2089 | ||
2090 | sub complete_os_templates { | |
2091 | my ($cmdname, $pname, $cvalue) = @_; | |
2092 | ||
2093 | my $cfg = PVE::Storage::config(); | |
2094 | ||
9e9bc3a6 | 2095 | my $storeid; |
68e8f3c5 DM |
2096 | |
2097 | if ($cvalue =~ m/^([^:]+):/) { | |
2098 | $storeid = $1; | |
2099 | } | |
2100 | ||
2101 | my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl'; | |
2102 | my $data = PVE::Storage::template_list($cfg, $storeid, $vtype); | |
2103 | ||
2104 | my $res = []; | |
2105 | foreach my $id (keys %$data) { | |
2106 | foreach my $item (@{$data->{$id}}) { | |
2107 | push @$res, $item->{volid} if defined($item->{volid}); | |
2108 | } | |
2109 | } | |
2110 | ||
2111 | return $res; | |
2112 | } | |
2113 | ||
68e8f3c5 DM |
2114 | my $complete_ctid_full = sub { |
2115 | my ($running) = @_; | |
2116 | ||
2117 | my $idlist = vmstatus(); | |
2118 | ||
2119 | my $active_hash = list_active_containers(); | |
2120 | ||
2121 | my $res = []; | |
2122 | ||
2123 | foreach my $id (keys %$idlist) { | |
2124 | my $d = $idlist->{$id}; | |
2125 | if (defined($running)) { | |
2126 | next if $d->{template}; | |
2127 | next if $running && !$active_hash->{$id}; | |
2128 | next if !$running && $active_hash->{$id}; | |
2129 | } | |
2130 | push @$res, $id; | |
2131 | ||
2132 | } | |
2133 | return $res; | |
2134 | }; | |
2135 | ||
2136 | sub complete_ctid { | |
2137 | return &$complete_ctid_full(); | |
2138 | } | |
2139 | ||
2140 | sub complete_ctid_stopped { | |
2141 | return &$complete_ctid_full(0); | |
2142 | } | |
2143 | ||
2144 | sub complete_ctid_running { | |
2145 | return &$complete_ctid_full(1); | |
2146 | } | |
2147 | ||
c6a605f9 WB |
2148 | sub parse_id_maps { |
2149 | my ($conf) = @_; | |
2150 | ||
2151 | my $id_map = []; | |
2152 | my $rootuid = 0; | |
2153 | my $rootgid = 0; | |
2154 | ||
2155 | my $lxc = $conf->{lxc}; | |
2156 | foreach my $entry (@$lxc) { | |
2157 | my ($key, $value) = @$entry; | |
2158 | next if $key ne 'lxc.id_map'; | |
2159 | if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) { | |
2160 | my ($type, $ct, $host, $length) = ($1, $2, $3, $4); | |
2161 | push @$id_map, [$type, $ct, $host, $length]; | |
2162 | if ($ct == 0) { | |
2163 | $rootuid = $host if $type eq 'u'; | |
2164 | $rootgid = $host if $type eq 'g'; | |
2165 | } | |
2166 | } else { | |
2167 | die "failed to parse id_map: $value\n"; | |
2168 | } | |
2169 | } | |
2170 | ||
2171 | if (!@$id_map && $conf->{unprivileged}) { | |
2172 | # Should we read them from /etc/subuid? | |
2173 | $id_map = [ ['u', '0', '100000', '65536'], | |
2174 | ['g', '0', '100000', '65536'] ]; | |
2175 | $rootuid = $rootgid = 100000; | |
2176 | } | |
2177 | ||
2178 | return ($id_map, $rootuid, $rootgid); | |
2179 | } | |
2180 | ||
01dce99b WB |
2181 | sub userns_command { |
2182 | my ($id_map) = @_; | |
2183 | if (@$id_map) { | |
2184 | return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--']; | |
2185 | } | |
2186 | return []; | |
2187 | } | |
2188 | ||
846a66b0 | 2189 | |
f76a2828 | 2190 | 1; |