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