]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
new helper mountpoint_backup_enabled()
[pve-container.git] / src / PVE / LXC / Config.pm
CommitLineData
67afe46e
FG
1package PVE::LXC::Config;
2
3use strict;
4use warnings;
5
6use PVE::AbstractConfig;
7use PVE::Cluster qw(cfs_register_file);
8use PVE::INotify;
9use PVE::JSONSchema qw(get_standard_option);
10use PVE::Tools;
11
12use base qw(PVE::AbstractConfig);
13
14my $nodename = PVE::INotify::nodename();
15my $lock_handles = {};
16my $lockdir = "/run/lock/lxc";
17mkdir $lockdir;
18mkdir "/etc/pve/nodes/$nodename/lxc";
19my $MAX_MOUNT_POINTS = 10;
20my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
21
22# BEGIN implemented abstract methods from PVE::AbstractConfig
23
24sub guest_type {
25 return "CT";
26}
27
4518000b
FG
28sub __config_max_unused_disks {
29 my ($class) = @_;
30
31 return $MAX_UNUSED_DISKS;
32}
33
67afe46e
FG
34sub config_file_lock {
35 my ($class, $vmid) = @_;
36
37 return "$lockdir/pve-config-${vmid}.lock";
38}
39
40sub cfs_config_path {
41 my ($class, $vmid, $node) = @_;
42
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
45}
46
1a8269bc
DM
47sub mountpoint_backup_enabled {
48 my ($mp_key, $mountpoint) = @_;
49
50 return 1 if $mp_key eq 'rootfs';
51
52 return 1 if $mountpoint->{backup};
53
54 return 0;
55}
56
4518000b
FG
57sub has_feature {
58 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
59 my $err;
60
d250604f 61 $class->foreach_mountpoint($conf, sub {
4518000b
FG
62 my ($ms, $mountpoint) = @_;
63
64 return if $err; # skip further test
1a8269bc 65 return if $backup_only && !mountpoint_backup_enabled($ms, $mountpoint);
4518000b
FG
66
67 $err = 1
68 if !PVE::Storage::volume_has_feature($storecfg, $feature,
69 $mountpoint->{volume},
70 $snapname, $running);
71 });
72
73 return $err ? 0 : 1;
74}
75
76sub __snapshot_save_vmstate {
77 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
78 die "implement me - snapshot_save_vmstate\n";
79}
80
81sub __snapshot_check_running {
82 my ($class, $vmid) = @_;
83 return PVE::LXC::check_running($vmid);
84}
85
86sub __snapshot_check_freeze_needed {
87 my ($class, $vmid, $config, $save_vmstate) = @_;
88
89 my $ret = $class->__snapshot_check_running($vmid);
90 return ($ret, $ret);
91}
92
93sub __snapshot_freeze {
94 my ($class, $vmid, $unfreeze) = @_;
95
96 if ($unfreeze) {
97 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
98 warn $@ if $@;
99 } else {
100 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
101 PVE::LXC::sync_container_namespace($vmid);
102 }
103}
104
105sub __snapshot_create_vol_snapshot {
106 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
107
108 my $storecfg = PVE::Storage::config();
109
1a8269bc
DM
110 return if $snapname eq 'vzdump' &&
111 !mountpoint_backup_enabled($ms, $mountpoint);
112
4518000b
FG
113 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
114}
115
116sub __snapshot_delete_remove_drive {
117 my ($class, $snap, $remove_drive) = @_;
118
119 if ($remove_drive eq 'vmstate') {
120 die "implement me - saving vmstate\n";
121 } else {
122 my $value = $snap->{$remove_drive};
1b4cf758 123 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
4518000b
FG
124 delete $snap->{$remove_drive};
125 $class->add_unused_volume($snap, $mountpoint->{volume});
126 }
127}
128
129sub __snapshot_delete_vmstate_file {
130 my ($class, $snap, $force) = @_;
131
132 die "implement me - saving vmstate\n";
133}
134
135sub __snapshot_delete_vol_snapshot {
a8e9a4ea 136 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
4518000b
FG
137
138 my $storecfg = PVE::Storage::config();
139 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
a8e9a4ea 140 push @$unused, $mountpoint->{volume};
4518000b
FG
141}
142
143sub __snapshot_rollback_vol_possible {
144 my ($class, $mountpoint, $snapname) = @_;
145
146 my $storecfg = PVE::Storage::config();
147 PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
148}
149
150sub __snapshot_rollback_vol_rollback {
151 my ($class, $mountpoint, $snapname) = @_;
152
153 my $storecfg = PVE::Storage::config();
154 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
155}
156
157sub __snapshot_rollback_vm_stop {
158 my ($class, $vmid) = @_;
159
160 PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
161 if $class->__snapshot_check_running($vmid);
162}
163
164sub __snapshot_rollback_vm_start {
165 my ($class, $vmid, $vmstate, $forcemachine);
166
167 die "implement me - save vmstate\n";
168}
169
170sub __snapshot_foreach_volume {
171 my ($class, $conf, $func) = @_;
172
d250604f 173 $class->foreach_mountpoint($conf, $func);
4518000b
FG
174}
175
67afe46e
FG
176# END implemented abstract methods from PVE::AbstractConfig
177
1b4cf758
FG
178# BEGIN JSON config code
179
180cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
181
182my $rootfs_desc = {
183 volume => {
184 type => 'string',
185 default_key => 1,
186 format => 'pve-lxc-mp-string',
187 format_description => 'volume',
188 description => 'Volume, device or directory to mount into the container.',
189 },
190 backup => {
191 type => 'boolean',
1b4cf758
FG
192 description => 'Whether to include the mountpoint in backups.',
193 optional => 1,
194 },
195 size => {
196 type => 'string',
197 format => 'disk-size',
198 format_description => 'DiskSize',
199 description => 'Volume size (read only value).',
200 optional => 1,
201 },
202 acl => {
203 type => 'boolean',
1b4cf758
FG
204 description => 'Explicitly enable or disable ACL support.',
205 optional => 1,
206 },
207 ro => {
208 type => 'boolean',
1b4cf758
FG
209 description => 'Read-only mountpoint (not supported with bind mounts)',
210 optional => 1,
211 },
212 quota => {
213 type => 'boolean',
1b4cf758
FG
214 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
215 optional => 1,
216 },
217};
218
219PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
220 type => 'string', format => $rootfs_desc,
221 description => "Use volume as container root.",
222 optional => 1,
223});
224
225PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
226 description => "The name of the snapshot.",
227 type => 'string', format => 'pve-configid',
228 maxLength => 40,
229});
230
231my $confdesc = {
232 lock => {
233 optional => 1,
234 type => 'string',
235 description => "Lock/unlock the VM.",
236 enum => [qw(migrate backup snapshot rollback)],
237 },
238 onboot => {
239 optional => 1,
240 type => 'boolean',
241 description => "Specifies whether a VM will be started during system bootup.",
242 default => 0,
243 },
244 startup => get_standard_option('pve-startup-order'),
245 template => {
246 optional => 1,
247 type => 'boolean',
248 description => "Enable/disable Template.",
249 default => 0,
250 },
251 arch => {
252 optional => 1,
253 type => 'string',
254 enum => ['amd64', 'i386'],
255 description => "OS architecture type.",
256 default => 'amd64',
257 },
258 ostype => {
259 optional => 1,
260 type => 'string',
261 enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
262 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.",
263 },
264 console => {
265 optional => 1,
266 type => 'boolean',
267 description => "Attach a console device (/dev/console) to the container.",
268 default => 1,
269 },
270 tty => {
271 optional => 1,
272 type => 'integer',
273 description => "Specify the number of tty available to the container",
274 minimum => 0,
275 maximum => 6,
276 default => 2,
277 },
278 cpulimit => {
279 optional => 1,
280 type => 'number',
064529c3 281 description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
1b4cf758
FG
282 minimum => 0,
283 maximum => 128,
284 default => 0,
285 },
286 cpuunits => {
287 optional => 1,
288 type => 'integer',
289 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.",
290 minimum => 0,
291 maximum => 500000,
292 default => 1024,
293 },
294 memory => {
295 optional => 1,
296 type => 'integer',
297 description => "Amount of RAM for the VM in MB.",
298 minimum => 16,
299 default => 512,
300 },
301 swap => {
302 optional => 1,
303 type => 'integer',
304 description => "Amount of SWAP for the VM in MB.",
305 minimum => 0,
306 default => 512,
307 },
308 hostname => {
309 optional => 1,
310 description => "Set a host name for the container.",
311 type => 'string', format => 'dns-name',
312 maxLength => 255,
313 },
314 description => {
315 optional => 1,
316 type => 'string',
a069f163 317 description => "Container description. Only used on the configuration web interface.",
1b4cf758
FG
318 },
319 searchdomain => {
320 optional => 1,
321 type => 'string', format => 'dns-name-list',
322 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
323 },
324 nameserver => {
325 optional => 1,
326 type => 'string', format => 'address-list',
327 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.",
328 },
329 rootfs => get_standard_option('pve-ct-rootfs'),
330 parent => {
331 optional => 1,
332 type => 'string', format => 'pve-configid',
333 maxLength => 40,
334 description => "Parent snapshot name. This is used internally, and should not be modified.",
335 },
336 snaptime => {
337 optional => 1,
338 description => "Timestamp for snapshots.",
339 type => 'integer',
340 minimum => 0,
341 },
342 cmode => {
343 optional => 1,
344 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).",
345 type => 'string',
346 enum => ['shell', 'console', 'tty'],
347 default => 'tty',
348 },
349 protection => {
350 optional => 1,
351 type => 'boolean',
352 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
353 default => 0,
354 },
355 unprivileged => {
356 optional => 1,
357 type => 'boolean',
358 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
359 default => 0,
360 },
361};
362
363my $valid_lxc_conf_keys = {
364 'lxc.include' => 1,
365 'lxc.arch' => 1,
366 'lxc.utsname' => 1,
367 'lxc.haltsignal' => 1,
368 'lxc.rebootsignal' => 1,
369 'lxc.stopsignal' => 1,
370 'lxc.init_cmd' => 1,
371 'lxc.network.type' => 1,
372 'lxc.network.flags' => 1,
373 'lxc.network.link' => 1,
374 'lxc.network.mtu' => 1,
375 'lxc.network.name' => 1,
376 'lxc.network.hwaddr' => 1,
377 'lxc.network.ipv4' => 1,
378 'lxc.network.ipv4.gateway' => 1,
379 'lxc.network.ipv6' => 1,
380 'lxc.network.ipv6.gateway' => 1,
381 'lxc.network.script.up' => 1,
382 'lxc.network.script.down' => 1,
383 'lxc.pts' => 1,
384 'lxc.console.logfile' => 1,
385 'lxc.console' => 1,
386 'lxc.tty' => 1,
387 'lxc.devttydir' => 1,
388 'lxc.hook.autodev' => 1,
389 'lxc.autodev' => 1,
390 'lxc.kmsg' => 1,
391 'lxc.mount' => 1,
392 'lxc.mount.entry' => 1,
393 'lxc.mount.auto' => 1,
394 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
395 'lxc.rootfs.mount' => 1,
396 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
397 ', please use mountpoint options in the "rootfs" key',
398 # lxc.cgroup.*
399 'lxc.cap.drop' => 1,
400 'lxc.cap.keep' => 1,
401 'lxc.aa_profile' => 1,
402 'lxc.aa_allow_incomplete' => 1,
403 'lxc.se_context' => 1,
404 'lxc.seccomp' => 1,
405 'lxc.id_map' => 1,
406 'lxc.hook.pre-start' => 1,
407 'lxc.hook.pre-mount' => 1,
408 'lxc.hook.mount' => 1,
409 'lxc.hook.start' => 1,
410 'lxc.hook.stop' => 1,
411 'lxc.hook.post-stop' => 1,
412 'lxc.hook.clone' => 1,
413 'lxc.hook.destroy' => 1,
414 'lxc.loglevel' => 1,
415 'lxc.logfile' => 1,
416 'lxc.start.auto' => 1,
417 'lxc.start.delay' => 1,
418 'lxc.start.order' => 1,
419 'lxc.group' => 1,
420 'lxc.environment' => 1,
421};
422
5e5915c5 423our $netconf_desc = {
1b4cf758
FG
424 type => {
425 type => 'string',
426 optional => 1,
427 description => "Network interface type.",
428 enum => [qw(veth)],
429 },
430 name => {
431 type => 'string',
a069f163
DM
432 format_description => 'string',
433 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
434 pattern => '[-_.\w\d]+',
435 },
436 bridge => {
437 type => 'string',
a069f163 438 format_description => 'bridge',
1b4cf758
FG
439 description => 'Bridge to attach the network device to.',
440 pattern => '[-_.\w\d]+',
441 optional => 1,
442 },
443 hwaddr => {
444 type => 'string',
a069f163 445 format_description => "XX:XX:XX:XX:XX:XX",
e6f20294 446 description => 'The interface MAC address. This is dynamically allocated by default, but you can set that statically if needed, for example to always have the same link-local IPv6 address. (lxc.network.hwaddr)',
1b4cf758
FG
447 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
448 optional => 1,
449 },
450 mtu => {
451 type => 'integer',
1b4cf758
FG
452 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
453 minimum => 64, # minimum ethernet frame is 64 bytes
454 optional => 1,
455 },
456 ip => {
457 type => 'string',
458 format => 'pve-ipv4-config',
459 format_description => 'IPv4Format/CIDR',
460 description => 'IPv4 address in CIDR format.',
461 optional => 1,
462 },
463 gw => {
464 type => 'string',
465 format => 'ipv4',
466 format_description => 'GatewayIPv4',
467 description => 'Default gateway for IPv4 traffic.',
468 optional => 1,
469 },
470 ip6 => {
471 type => 'string',
472 format => 'pve-ipv6-config',
473 format_description => 'IPv6Format/CIDR',
474 description => 'IPv6 address in CIDR format.',
475 optional => 1,
476 },
477 gw6 => {
478 type => 'string',
479 format => 'ipv6',
480 format_description => 'GatewayIPv6',
481 description => 'Default gateway for IPv6 traffic.',
482 optional => 1,
483 },
484 firewall => {
485 type => 'boolean',
1b4cf758
FG
486 description => "Controls whether this interface's firewall rules should be used.",
487 optional => 1,
488 },
489 tag => {
490 type => 'integer',
1b4cf758
FG
491 minimum => '2',
492 maximum => '4094',
493 description => "VLAN tag for this interface.",
494 optional => 1,
495 },
496 trunks => {
497 type => 'string',
498 pattern => qr/\d+(?:;\d+)*/,
499 format_description => 'vlanid[;vlanid...]',
500 description => "VLAN ids to pass through the interface",
501 optional => 1,
502 },
380962c7
WB
503 rate => {
504 type => 'number',
505 format_description => 'mbps',
506 description => "Apply rate limiting to the interface",
507 optional => 1,
508 },
1b4cf758
FG
509};
510PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
511
512my $MAX_LXC_NETWORKS = 10;
513for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
514 $confdesc->{"net$i"} = {
515 optional => 1,
516 type => 'string', format => $netconf_desc,
517 description => "Specifies network interfaces for the container.",
518 };
519}
520
521PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
522sub verify_lxc_mp_string {
523 my ($mp, $noerr) = @_;
524
525 # do not allow:
526 # /./ or /../
527 # /. or /.. at the end
528 # ../ at the beginning
529
530 if($mp =~ m@/\.\.?/@ ||
531 $mp =~ m@/\.\.?$@ ||
532 $mp =~ m@^\.\./@) {
533 return undef if $noerr;
534 die "$mp contains illegal character sequences\n";
535 }
536 return $mp;
537}
538
539my $mp_desc = {
540 %$rootfs_desc,
541 mp => {
542 type => 'string',
543 format => 'pve-lxc-mp-string',
544 format_description => 'Path',
545 description => 'Path to the mountpoint as seen from inside the container.',
546 },
547};
548PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
549
550my $unuseddesc = {
551 optional => 1,
552 type => 'string', format => 'pve-volume-id',
553 description => "Reference to unused volumes.",
554};
555
556for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
557 $confdesc->{"mp$i"} = {
558 optional => 1,
559 type => 'string', format => $mp_desc,
560 description => "Use volume as container mount point (experimental feature).",
561 optional => 1,
562 };
563}
564
565for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
566 $confdesc->{"unused$i"} = $unuseddesc;
567}
568
569sub parse_pct_config {
570 my ($filename, $raw) = @_;
571
572 return undef if !defined($raw);
573
574 my $res = {
575 digest => Digest::SHA::sha1_hex($raw),
576 snapshots => {},
577 };
578
579 $filename =~ m|/lxc/(\d+).conf$|
580 || die "got strange filename '$filename'";
581
582 my $vmid = $1;
583
584 my $conf = $res;
585 my $descr = '';
586 my $section = '';
587
588 my @lines = split(/\n/, $raw);
589 foreach my $line (@lines) {
590 next if $line =~ m/^\s*$/;
591
592 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
593 $section = $1;
594 $conf->{description} = $descr if $descr;
595 $descr = '';
596 $conf = $res->{snapshots}->{$section} = {};
597 next;
598 }
599
600 if ($line =~ m/^\#(.*)\s*$/) {
601 $descr .= PVE::Tools::decode_text($1) . "\n";
602 next;
603 }
604
605 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
606 my $key = $1;
607 my $value = $3;
608 my $validity = $valid_lxc_conf_keys->{$key} || 0;
609 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
610 push @{$conf->{lxc}}, [$key, $value];
611 } elsif (my $errmsg = $validity) {
612 warn "vm $vmid - $key: $errmsg\n";
613 } else {
614 warn "vm $vmid - unable to parse config: $line\n";
615 }
616 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
617 $descr .= PVE::Tools::decode_text($2);
618 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
619 $conf->{snapstate} = $1;
620 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
621 my $key = $1;
622 my $value = $2;
623 eval { $value = PVE::LXC::Config->check_type($key, $value); };
624 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
625 $conf->{$key} = $value;
626 } else {
627 warn "vm $vmid - unable to parse config: $line\n";
628 }
629 }
630
631 $conf->{description} = $descr if $descr;
632
633 delete $res->{snapstate}; # just to be sure
634
635 return $res;
636}
637
638sub write_pct_config {
639 my ($filename, $conf) = @_;
640
641 delete $conf->{snapstate}; # just to be sure
642
643 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
644 my $used_volids = {};
645 foreach my $vid (@$volidlist) {
646 $used_volids->{$vid} = 1;
647 }
648
649 # remove 'unusedX' settings if the volume is still used
650 foreach my $key (keys %$conf) {
651 my $value = $conf->{$key};
652 if ($key =~ m/^unused/ && $used_volids->{$value}) {
653 delete $conf->{$key};
654 }
655 }
656
657 my $generate_raw_config = sub {
658 my ($conf) = @_;
659
660 my $raw = '';
661
662 # add description as comment to top of file
663 my $descr = $conf->{description} || '';
664 foreach my $cl (split(/\n/, $descr)) {
665 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
666 }
667
668 foreach my $key (sort keys %$conf) {
669 next if $key eq 'digest' || $key eq 'description' ||
670 $key eq 'pending' || $key eq 'snapshots' ||
671 $key eq 'snapname' || $key eq 'lxc';
672 my $value = $conf->{$key};
673 die "detected invalid newline inside property '$key'\n"
674 if $value =~ m/\n/;
675 $raw .= "$key: $value\n";
676 }
677
678 if (my $lxcconf = $conf->{lxc}) {
679 foreach my $entry (@$lxcconf) {
680 my ($k, $v) = @$entry;
681 $raw .= "$k: $v\n";
682 }
683 }
684
685 return $raw;
686 };
687
688 my $raw = &$generate_raw_config($conf);
689
690 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
691 $raw .= "\n[$snapname]\n";
692 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
693 }
694
695 return $raw;
696}
697
698sub update_pct_config {
699 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
700
701 my @nohotplug;
702
703 my $new_disks = 0;
704 my @deleted_volumes;
705
706 my $rootdir;
707 if ($running) {
708 my $pid = PVE::LXC::find_lxc_pid($vmid);
709 $rootdir = "/proc/$pid/root";
710 }
711
712 my $hotplug_error = sub {
713 if ($running) {
714 push @nohotplug, @_;
715 return 1;
716 } else {
717 return 0;
718 }
719 };
720
721 if (defined($delete)) {
722 foreach my $opt (@$delete) {
723 if (!exists($conf->{$opt})) {
724 warn "no such option: $opt\n";
725 next;
726 }
727
728 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
729 die "unable to delete required option '$opt'\n";
730 } elsif ($opt eq 'swap') {
731 delete $conf->{$opt};
732 PVE::LXC::write_cgroup_value("memory", $vmid,
733 "memory.memsw.limit_in_bytes", -1);
734 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
735 delete $conf->{$opt};
736 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
737 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
738 next if $hotplug_error->($opt);
739 delete $conf->{$opt};
d13b770f 740 } elsif ($opt eq 'cpulimit') {
213d70e6 741 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
d13b770f
WB
742 delete $conf->{$opt};
743 } elsif ($opt eq 'cpuunits') {
744 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
745 delete $conf->{$opt};
1b4cf758
FG
746 } elsif ($opt =~ m/^net(\d)$/) {
747 delete $conf->{$opt};
748 next if !$running;
749 my $netid = $1;
750 PVE::Network::veth_delete("veth${vmid}i$netid");
751 } elsif ($opt eq 'protection') {
752 delete $conf->{$opt};
753 } elsif ($opt =~ m/^unused(\d+)$/) {
754 next if $hotplug_error->($opt);
755 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
756 push @deleted_volumes, $conf->{$opt};
757 delete $conf->{$opt};
758 } elsif ($opt =~ m/^mp(\d+)$/) {
759 next if $hotplug_error->($opt);
760 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
761 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
762 delete $conf->{$opt};
763 if ($mp->{type} eq 'volume') {
764 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
765 }
766 } elsif ($opt eq 'unprivileged') {
767 die "unable to delete read-only option: '$opt'\n";
768 } else {
769 die "implement me (delete: $opt)"
770 }
771 PVE::LXC::Config->write_config($vmid, $conf) if $running;
772 }
773 }
774
775 # There's no separate swap size to configure, there's memory and "total"
776 # memory (iow. memory+swap). This means we have to change them together.
777 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
778 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
779 if (defined($wanted_memory) || defined($wanted_swap)) {
780
781 my $old_memory = ($conf->{memory} || 512);
782 my $old_swap = ($conf->{swap} || 0);
783
784 $wanted_memory //= $old_memory;
785 $wanted_swap //= $old_swap;
786
787 my $total = $wanted_memory + $wanted_swap;
788 if ($running) {
789 my $old_total = $old_memory + $old_swap;
790 if ($total > $old_total) {
791 PVE::LXC::write_cgroup_value("memory", $vmid,
792 "memory.memsw.limit_in_bytes",
793 int($total*1024*1024));
794 PVE::LXC::write_cgroup_value("memory", $vmid,
795 "memory.limit_in_bytes",
796 int($wanted_memory*1024*1024));
797 } else {
798 PVE::LXC::write_cgroup_value("memory", $vmid,
799 "memory.limit_in_bytes",
800 int($wanted_memory*1024*1024));
801 PVE::LXC::write_cgroup_value("memory", $vmid,
802 "memory.memsw.limit_in_bytes",
803 int($total*1024*1024));
804 }
805 }
806 $conf->{memory} = $wanted_memory;
807 $conf->{swap} = $wanted_swap;
808
809 PVE::LXC::Config->write_config($vmid, $conf) if $running;
810 }
811
812 my $used_volids = {};
1b3213ae
FG
813 my $check_content_type = sub {
814 my ($mp) = @_;
815 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
816 my $scfg = PVE::Storage::config();
817 my $storage_config = PVE::Storage::storage_config($scfg, $sid);
818 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
819 if !$storage_config->{content}->{rootdir};
820 };
1b4cf758
FG
821
822 foreach my $opt (keys %$param) {
823 my $value = $param->{$opt};
824 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
825 if ($opt eq 'hostname') {
826 $conf->{$opt} = $value;
827 } elsif ($opt eq 'onboot') {
828 $conf->{$opt} = $value ? 1 : 0;
829 } elsif ($opt eq 'startup') {
830 $conf->{$opt} = $value;
831 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
832 next if $hotplug_error->($opt);
833 $conf->{$opt} = $value;
834 } elsif ($opt eq 'nameserver') {
835 next if $hotplug_error->($opt);
836 my $list = PVE::LXC::verify_nameserver_list($value);
837 $conf->{$opt} = $list;
838 } elsif ($opt eq 'searchdomain') {
839 next if $hotplug_error->($opt);
840 my $list = PVE::LXC::verify_searchdomain_list($value);
841 $conf->{$opt} = $list;
842 } elsif ($opt eq 'cpulimit') {
213d70e6 843 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1b4cf758
FG
844 $conf->{$opt} = $value;
845 } elsif ($opt eq 'cpuunits') {
846 $conf->{$opt} = $value;
847 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
848 } elsif ($opt eq 'description') {
849 $conf->{$opt} = PVE::Tools::encode_text($value);
850 } elsif ($opt =~ m/^net(\d+)$/) {
851 my $netid = $1;
852 my $net = PVE::LXC::Config->parse_lxc_network($value);
853 if (!$running) {
854 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
855 } else {
856 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
857 }
858 } elsif ($opt eq 'protection') {
859 $conf->{$opt} = $value ? 1 : 0;
860 } elsif ($opt =~ m/^mp(\d+)$/) {
861 next if $hotplug_error->($opt);
862 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
863 my $old = $conf->{$opt};
56d7fc1b
FG
864 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
865 if ($mp->{type} eq 'volume') {
1b3213ae 866 &$check_content_type($mp);
56d7fc1b
FG
867 $used_volids->{$mp->{volume}} = 1;
868 }
1b4cf758
FG
869 $conf->{$opt} = $value;
870 if (defined($old)) {
871 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
872 if ($mp->{type} eq 'volume') {
873 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
874 }
875 }
876 $new_disks = 1;
1b4cf758
FG
877 } elsif ($opt eq 'rootfs') {
878 next if $hotplug_error->($opt);
879 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
880 my $old = $conf->{$opt};
881 $conf->{$opt} = $value;
56d7fc1b
FG
882 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
883 if ($mp->{type} eq 'volume') {
1b3213ae 884 &$check_content_type($mp);
56d7fc1b
FG
885 $used_volids->{$mp->{volume}} = 1;
886 }
1b4cf758
FG
887 if (defined($old)) {
888 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
889 if ($mp->{type} eq 'volume') {
890 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
891 }
892 }
1d9369fd 893 $new_disks = 1;
1b4cf758
FG
894 } elsif ($opt eq 'unprivileged') {
895 die "unable to modify read-only option: '$opt'\n";
896 } elsif ($opt eq 'ostype') {
897 next if $hotplug_error->($opt);
898 $conf->{$opt} = $value;
899 } else {
900 die "implement me: $opt";
901 }
902 PVE::LXC::Config->write_config($vmid, $conf) if $running;
903 }
904
905 # Apply deletions and creations of new volumes
906 if (@deleted_volumes) {
907 my $storage_cfg = PVE::Storage::config();
908 foreach my $volume (@deleted_volumes) {
909 next if $used_volids->{$volume}; # could have been re-added, too
910 # also check for references in snapshots
911 next if $class->is_volume_in_use($conf, $volume, 1);
912 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
913 }
914 }
915
916 if ($new_disks) {
917 my $storage_cfg = PVE::Storage::config();
918 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
919 }
920
921 # This should be the last thing we do here
922 if ($running && scalar(@nohotplug)) {
923 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
924 }
925}
926
927sub check_type {
928 my ($class, $key, $value) = @_;
929
930 die "unknown setting '$key'\n" if !$confdesc->{$key};
931
932 my $type = $confdesc->{$key}->{type};
933
934 if (!defined($value)) {
935 die "got undefined value\n";
936 }
937
938 if ($value =~ m/[\n\r]/) {
939 die "property contains a line feed\n";
940 }
941
942 if ($type eq 'boolean') {
943 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
944 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
945 die "type check ('boolean') failed - got '$value'\n";
946 } elsif ($type eq 'integer') {
947 return int($1) if $value =~ m/^(\d+)$/;
948 die "type check ('integer') failed - got '$value'\n";
949 } elsif ($type eq 'number') {
950 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
951 die "type check ('number') failed - got '$value'\n";
952 } elsif ($type eq 'string') {
953 if (my $fmt = $confdesc->{$key}->{format}) {
954 PVE::JSONSchema::check_format($fmt, $value);
955 return $value;
956 }
957 return $value;
958 } else {
959 die "internal error"
960 }
961}
962
963
964# add JSON properties for create and set function
965sub json_config_properties {
966 my ($class, $prop) = @_;
967
968 foreach my $opt (keys %$confdesc) {
969 next if $opt eq 'parent' || $opt eq 'snaptime';
970 next if $prop->{$opt};
971 $prop->{$opt} = $confdesc->{$opt};
972 }
973
974 return $prop;
975}
976
977sub __parse_ct_mountpoint_full {
978 my ($class, $desc, $data, $noerr) = @_;
979
980 $data //= '';
981
982 my $res;
983 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
984 if ($@) {
985 return undef if $noerr;
986 die $@;
987 }
988
989 if (defined(my $size = $res->{size})) {
990 $size = PVE::JSONSchema::parse_size($size);
991 if (!defined($size)) {
992 return undef if $noerr;
993 die "invalid size: $size\n";
994 }
995 $res->{size} = $size;
996 }
997
998 $res->{type} = $class->classify_mountpoint($res->{volume});
999
1000 return $res;
1001};
1002
1003sub parse_ct_rootfs {
1004 my ($class, $data, $noerr) = @_;
1005
1006 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1007
1008 $res->{mp} = '/' if defined($res);
1009
1010 return $res;
1011}
1012
1013sub parse_ct_mountpoint {
1014 my ($class, $data, $noerr) = @_;
1015
1016 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1017}
1018
1019sub print_ct_mountpoint {
1020 my ($class, $info, $nomp) = @_;
1021 my $skip = [ 'type' ];
1022 push @$skip, 'mp' if $nomp;
1023 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1024}
1025
1026sub print_lxc_network {
1027 my ($class, $net) = @_;
1028 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1029}
1030
1031sub parse_lxc_network {
1032 my ($class, $data) = @_;
1033
1034 my $res = {};
1035
1036 return $res if !$data;
1037
1038 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1039
1040 $res->{type} = 'veth';
1041 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
1042
1043 return $res;
1044}
1045
1046sub option_exists {
1047 my ($class, $name) = @_;
1048
1049 return defined($confdesc->{$name});
1050}
1051# END JSON config code
1052
d250604f
FG
1053sub classify_mountpoint {
1054 my ($class, $vol) = @_;
1055 if ($vol =~ m!^/!) {
1056 return 'device' if $vol =~ m!^/dev/!;
1057 return 'bind';
1058 }
1059 return 'volume';
1060}
1061
1062sub is_volume_in_use {
1063 my ($class, $config, $volid, $include_snapshots) = @_;
1064 my $used = 0;
1065
1066 $class->foreach_mountpoint($config, sub {
1067 my ($ms, $mountpoint) = @_;
1068 return if $used;
1069 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1070 });
1071
1072 my $snapshots = $config->{snapshots};
1073 if ($include_snapshots && $snapshots) {
1074 foreach my $snap (keys %$snapshots) {
1075 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1076 }
1077 }
1078
1079 return $used;
1080}
1081
1082sub has_dev_console {
1083 my ($class, $conf) = @_;
1084
1085 return !(defined($conf->{console}) && !$conf->{console});
1086}
1087
1b4cf758
FG
1088sub get_tty_count {
1089 my ($class, $conf) = @_;
1090
1091 return $conf->{tty} // $confdesc->{tty}->{default};
1092}
1093
1094sub get_cmode {
1095 my ($class, $conf) = @_;
1096
1097 return $conf->{cmode} // $confdesc->{cmode}->{default};
1098}
1099
d250604f
FG
1100sub mountpoint_names {
1101 my ($class, $reverse) = @_;
1102
1103 my @names = ('rootfs');
1104
1105 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1106 push @names, "mp$i";
1107 }
1108
1109 return $reverse ? reverse @names : @names;
1110}
1111
1112sub foreach_mountpoint_full {
1113 my ($class, $conf, $reverse, $func) = @_;
1114
1115 foreach my $key ($class->mountpoint_names($reverse)) {
1116 my $value = $conf->{$key};
1117 next if !defined($value);
1b4cf758 1118 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
d250604f
FG
1119 next if !defined($mountpoint);
1120
1121 &$func($key, $mountpoint);
1122 }
1123}
1124
1125sub foreach_mountpoint {
1126 my ($class, $conf, $func) = @_;
1127
1128 $class->foreach_mountpoint_full($conf, 0, $func);
1129}
1130
1131sub foreach_mountpoint_reverse {
1132 my ($class, $conf, $func) = @_;
1133
1134 $class->foreach_mountpoint_full($conf, 1, $func);
1135}
1136
1137sub get_vm_volumes {
1138 my ($class, $conf, $excludes) = @_;
1139
1140 my $vollist = [];
1141
1142 $class->foreach_mountpoint($conf, sub {
1143 my ($ms, $mountpoint) = @_;
1144
1145 return if $excludes && $ms eq $excludes;
1146
1147 my $volid = $mountpoint->{volume};
1148 return if !$volid || $mountpoint->{type} ne 'volume';
1149
1150 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1151 return if !$sid;
1152
1153 push @$vollist, $volid;
1154 });
1155
1156 return $vollist;
1157}
1158
67afe46e 1159return 1;