]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
Check content type when adding/updating volumes
[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 rate => {
499 type => 'number',
500 format_description => 'mbps',
501 description => "Apply rate limiting to the interface",
502 optional => 1,
503 },
504 };
505 PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
506
507 my $MAX_LXC_NETWORKS = 10;
508 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
509 $confdesc->{"net$i"} = {
510 optional => 1,
511 type => 'string', format => $netconf_desc,
512 description => "Specifies network interfaces for the container.",
513 };
514 }
515
516 PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
517 sub verify_lxc_mp_string {
518 my ($mp, $noerr) = @_;
519
520 # do not allow:
521 # /./ or /../
522 # /. or /.. at the end
523 # ../ at the beginning
524
525 if($mp =~ m@/\.\.?/@ ||
526 $mp =~ m@/\.\.?$@ ||
527 $mp =~ m@^\.\./@) {
528 return undef if $noerr;
529 die "$mp contains illegal character sequences\n";
530 }
531 return $mp;
532 }
533
534 my $mp_desc = {
535 %$rootfs_desc,
536 mp => {
537 type => 'string',
538 format => 'pve-lxc-mp-string',
539 format_description => 'Path',
540 description => 'Path to the mountpoint as seen from inside the container.',
541 },
542 };
543 PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
544
545 my $unuseddesc = {
546 optional => 1,
547 type => 'string', format => 'pve-volume-id',
548 description => "Reference to unused volumes.",
549 };
550
551 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
552 $confdesc->{"mp$i"} = {
553 optional => 1,
554 type => 'string', format => $mp_desc,
555 description => "Use volume as container mount point (experimental feature).",
556 optional => 1,
557 };
558 }
559
560 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
561 $confdesc->{"unused$i"} = $unuseddesc;
562 }
563
564 sub parse_pct_config {
565 my ($filename, $raw) = @_;
566
567 return undef if !defined($raw);
568
569 my $res = {
570 digest => Digest::SHA::sha1_hex($raw),
571 snapshots => {},
572 };
573
574 $filename =~ m|/lxc/(\d+).conf$|
575 || die "got strange filename '$filename'";
576
577 my $vmid = $1;
578
579 my $conf = $res;
580 my $descr = '';
581 my $section = '';
582
583 my @lines = split(/\n/, $raw);
584 foreach my $line (@lines) {
585 next if $line =~ m/^\s*$/;
586
587 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
588 $section = $1;
589 $conf->{description} = $descr if $descr;
590 $descr = '';
591 $conf = $res->{snapshots}->{$section} = {};
592 next;
593 }
594
595 if ($line =~ m/^\#(.*)\s*$/) {
596 $descr .= PVE::Tools::decode_text($1) . "\n";
597 next;
598 }
599
600 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
601 my $key = $1;
602 my $value = $3;
603 my $validity = $valid_lxc_conf_keys->{$key} || 0;
604 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
605 push @{$conf->{lxc}}, [$key, $value];
606 } elsif (my $errmsg = $validity) {
607 warn "vm $vmid - $key: $errmsg\n";
608 } else {
609 warn "vm $vmid - unable to parse config: $line\n";
610 }
611 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
612 $descr .= PVE::Tools::decode_text($2);
613 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
614 $conf->{snapstate} = $1;
615 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
616 my $key = $1;
617 my $value = $2;
618 eval { $value = PVE::LXC::Config->check_type($key, $value); };
619 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
620 $conf->{$key} = $value;
621 } else {
622 warn "vm $vmid - unable to parse config: $line\n";
623 }
624 }
625
626 $conf->{description} = $descr if $descr;
627
628 delete $res->{snapstate}; # just to be sure
629
630 return $res;
631 }
632
633 sub write_pct_config {
634 my ($filename, $conf) = @_;
635
636 delete $conf->{snapstate}; # just to be sure
637
638 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
639 my $used_volids = {};
640 foreach my $vid (@$volidlist) {
641 $used_volids->{$vid} = 1;
642 }
643
644 # remove 'unusedX' settings if the volume is still used
645 foreach my $key (keys %$conf) {
646 my $value = $conf->{$key};
647 if ($key =~ m/^unused/ && $used_volids->{$value}) {
648 delete $conf->{$key};
649 }
650 }
651
652 my $generate_raw_config = sub {
653 my ($conf) = @_;
654
655 my $raw = '';
656
657 # add description as comment to top of file
658 my $descr = $conf->{description} || '';
659 foreach my $cl (split(/\n/, $descr)) {
660 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
661 }
662
663 foreach my $key (sort keys %$conf) {
664 next if $key eq 'digest' || $key eq 'description' ||
665 $key eq 'pending' || $key eq 'snapshots' ||
666 $key eq 'snapname' || $key eq 'lxc';
667 my $value = $conf->{$key};
668 die "detected invalid newline inside property '$key'\n"
669 if $value =~ m/\n/;
670 $raw .= "$key: $value\n";
671 }
672
673 if (my $lxcconf = $conf->{lxc}) {
674 foreach my $entry (@$lxcconf) {
675 my ($k, $v) = @$entry;
676 $raw .= "$k: $v\n";
677 }
678 }
679
680 return $raw;
681 };
682
683 my $raw = &$generate_raw_config($conf);
684
685 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
686 $raw .= "\n[$snapname]\n";
687 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
688 }
689
690 return $raw;
691 }
692
693 sub update_pct_config {
694 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
695
696 my @nohotplug;
697
698 my $new_disks = 0;
699 my @deleted_volumes;
700
701 my $rootdir;
702 if ($running) {
703 my $pid = PVE::LXC::find_lxc_pid($vmid);
704 $rootdir = "/proc/$pid/root";
705 }
706
707 my $hotplug_error = sub {
708 if ($running) {
709 push @nohotplug, @_;
710 return 1;
711 } else {
712 return 0;
713 }
714 };
715
716 if (defined($delete)) {
717 foreach my $opt (@$delete) {
718 if (!exists($conf->{$opt})) {
719 warn "no such option: $opt\n";
720 next;
721 }
722
723 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
724 die "unable to delete required option '$opt'\n";
725 } elsif ($opt eq 'swap') {
726 delete $conf->{$opt};
727 PVE::LXC::write_cgroup_value("memory", $vmid,
728 "memory.memsw.limit_in_bytes", -1);
729 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
730 delete $conf->{$opt};
731 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
732 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
733 next if $hotplug_error->($opt);
734 delete $conf->{$opt};
735 } elsif ($opt eq 'cpulimit') {
736 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
737 delete $conf->{$opt};
738 } elsif ($opt eq 'cpuunits') {
739 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
740 delete $conf->{$opt};
741 } elsif ($opt =~ m/^net(\d)$/) {
742 delete $conf->{$opt};
743 next if !$running;
744 my $netid = $1;
745 PVE::Network::veth_delete("veth${vmid}i$netid");
746 } elsif ($opt eq 'protection') {
747 delete $conf->{$opt};
748 } elsif ($opt =~ m/^unused(\d+)$/) {
749 next if $hotplug_error->($opt);
750 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
751 push @deleted_volumes, $conf->{$opt};
752 delete $conf->{$opt};
753 } elsif ($opt =~ m/^mp(\d+)$/) {
754 next if $hotplug_error->($opt);
755 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
756 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
757 delete $conf->{$opt};
758 if ($mp->{type} eq 'volume') {
759 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
760 }
761 } elsif ($opt eq 'unprivileged') {
762 die "unable to delete read-only option: '$opt'\n";
763 } else {
764 die "implement me (delete: $opt)"
765 }
766 PVE::LXC::Config->write_config($vmid, $conf) if $running;
767 }
768 }
769
770 # There's no separate swap size to configure, there's memory and "total"
771 # memory (iow. memory+swap). This means we have to change them together.
772 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
773 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
774 if (defined($wanted_memory) || defined($wanted_swap)) {
775
776 my $old_memory = ($conf->{memory} || 512);
777 my $old_swap = ($conf->{swap} || 0);
778
779 $wanted_memory //= $old_memory;
780 $wanted_swap //= $old_swap;
781
782 my $total = $wanted_memory + $wanted_swap;
783 if ($running) {
784 my $old_total = $old_memory + $old_swap;
785 if ($total > $old_total) {
786 PVE::LXC::write_cgroup_value("memory", $vmid,
787 "memory.memsw.limit_in_bytes",
788 int($total*1024*1024));
789 PVE::LXC::write_cgroup_value("memory", $vmid,
790 "memory.limit_in_bytes",
791 int($wanted_memory*1024*1024));
792 } else {
793 PVE::LXC::write_cgroup_value("memory", $vmid,
794 "memory.limit_in_bytes",
795 int($wanted_memory*1024*1024));
796 PVE::LXC::write_cgroup_value("memory", $vmid,
797 "memory.memsw.limit_in_bytes",
798 int($total*1024*1024));
799 }
800 }
801 $conf->{memory} = $wanted_memory;
802 $conf->{swap} = $wanted_swap;
803
804 PVE::LXC::Config->write_config($vmid, $conf) if $running;
805 }
806
807 my $used_volids = {};
808
809 foreach my $opt (keys %$param) {
810 my $value = $param->{$opt};
811 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
812 if ($opt eq 'hostname') {
813 $conf->{$opt} = $value;
814 } elsif ($opt eq 'onboot') {
815 $conf->{$opt} = $value ? 1 : 0;
816 } elsif ($opt eq 'startup') {
817 $conf->{$opt} = $value;
818 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
819 next if $hotplug_error->($opt);
820 $conf->{$opt} = $value;
821 } elsif ($opt eq 'nameserver') {
822 next if $hotplug_error->($opt);
823 my $list = PVE::LXC::verify_nameserver_list($value);
824 $conf->{$opt} = $list;
825 } elsif ($opt eq 'searchdomain') {
826 next if $hotplug_error->($opt);
827 my $list = PVE::LXC::verify_searchdomain_list($value);
828 $conf->{$opt} = $list;
829 } elsif ($opt eq 'cpulimit') {
830 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
831 $conf->{$opt} = $value;
832 } elsif ($opt eq 'cpuunits') {
833 $conf->{$opt} = $value;
834 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
835 } elsif ($opt eq 'description') {
836 $conf->{$opt} = PVE::Tools::encode_text($value);
837 } elsif ($opt =~ m/^net(\d+)$/) {
838 my $netid = $1;
839 my $net = PVE::LXC::Config->parse_lxc_network($value);
840 if (!$running) {
841 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
842 } else {
843 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
844 }
845 } elsif ($opt eq 'protection') {
846 $conf->{$opt} = $value ? 1 : 0;
847 } elsif ($opt =~ m/^mp(\d+)$/) {
848 next if $hotplug_error->($opt);
849 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
850 my $old = $conf->{$opt};
851 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
852 if ($mp->{type} eq 'volume') {
853 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
854 my $scfg = PVE::Storage::config();
855 my $storage_config = PVE::Storage::storage_config($scfg, $sid);
856 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
857 if !$storage_config->{content}->{rootdir};
858 $used_volids->{$mp->{volume}} = 1;
859 }
860 $conf->{$opt} = $value;
861 if (defined($old)) {
862 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
863 if ($mp->{type} eq 'volume') {
864 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
865 }
866 }
867 $new_disks = 1;
868 } elsif ($opt eq 'rootfs') {
869 next if $hotplug_error->($opt);
870 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
871 my $old = $conf->{$opt};
872 $conf->{$opt} = $value;
873 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
874 if ($mp->{type} eq 'volume') {
875 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
876 my $scfg = PVE::Storage::config();
877 my $storage_config = PVE::Storage::storage_config($scfg, $sid);
878 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
879 if !$storage_config->{content}->{rootdir};
880 $used_volids->{$mp->{volume}} = 1;
881 }
882 if (defined($old)) {
883 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
884 if ($mp->{type} eq 'volume') {
885 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
886 }
887 }
888 } elsif ($opt eq 'unprivileged') {
889 die "unable to modify read-only option: '$opt'\n";
890 } elsif ($opt eq 'ostype') {
891 next if $hotplug_error->($opt);
892 $conf->{$opt} = $value;
893 } else {
894 die "implement me: $opt";
895 }
896 PVE::LXC::Config->write_config($vmid, $conf) if $running;
897 }
898
899 # Apply deletions and creations of new volumes
900 if (@deleted_volumes) {
901 my $storage_cfg = PVE::Storage::config();
902 foreach my $volume (@deleted_volumes) {
903 next if $used_volids->{$volume}; # could have been re-added, too
904 # also check for references in snapshots
905 next if $class->is_volume_in_use($conf, $volume, 1);
906 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
907 }
908 }
909
910 if ($new_disks) {
911 my $storage_cfg = PVE::Storage::config();
912 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
913 }
914
915 # This should be the last thing we do here
916 if ($running && scalar(@nohotplug)) {
917 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
918 }
919 }
920
921 sub check_type {
922 my ($class, $key, $value) = @_;
923
924 die "unknown setting '$key'\n" if !$confdesc->{$key};
925
926 my $type = $confdesc->{$key}->{type};
927
928 if (!defined($value)) {
929 die "got undefined value\n";
930 }
931
932 if ($value =~ m/[\n\r]/) {
933 die "property contains a line feed\n";
934 }
935
936 if ($type eq 'boolean') {
937 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
938 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
939 die "type check ('boolean') failed - got '$value'\n";
940 } elsif ($type eq 'integer') {
941 return int($1) if $value =~ m/^(\d+)$/;
942 die "type check ('integer') failed - got '$value'\n";
943 } elsif ($type eq 'number') {
944 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
945 die "type check ('number') failed - got '$value'\n";
946 } elsif ($type eq 'string') {
947 if (my $fmt = $confdesc->{$key}->{format}) {
948 PVE::JSONSchema::check_format($fmt, $value);
949 return $value;
950 }
951 return $value;
952 } else {
953 die "internal error"
954 }
955 }
956
957
958 # add JSON properties for create and set function
959 sub json_config_properties {
960 my ($class, $prop) = @_;
961
962 foreach my $opt (keys %$confdesc) {
963 next if $opt eq 'parent' || $opt eq 'snaptime';
964 next if $prop->{$opt};
965 $prop->{$opt} = $confdesc->{$opt};
966 }
967
968 return $prop;
969 }
970
971 sub __parse_ct_mountpoint_full {
972 my ($class, $desc, $data, $noerr) = @_;
973
974 $data //= '';
975
976 my $res;
977 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
978 if ($@) {
979 return undef if $noerr;
980 die $@;
981 }
982
983 if (defined(my $size = $res->{size})) {
984 $size = PVE::JSONSchema::parse_size($size);
985 if (!defined($size)) {
986 return undef if $noerr;
987 die "invalid size: $size\n";
988 }
989 $res->{size} = $size;
990 }
991
992 $res->{type} = $class->classify_mountpoint($res->{volume});
993
994 return $res;
995 };
996
997 sub parse_ct_rootfs {
998 my ($class, $data, $noerr) = @_;
999
1000 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1001
1002 $res->{mp} = '/' if defined($res);
1003
1004 return $res;
1005 }
1006
1007 sub parse_ct_mountpoint {
1008 my ($class, $data, $noerr) = @_;
1009
1010 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1011 }
1012
1013 sub print_ct_mountpoint {
1014 my ($class, $info, $nomp) = @_;
1015 my $skip = [ 'type' ];
1016 push @$skip, 'mp' if $nomp;
1017 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1018 }
1019
1020 sub print_lxc_network {
1021 my ($class, $net) = @_;
1022 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1023 }
1024
1025 sub parse_lxc_network {
1026 my ($class, $data) = @_;
1027
1028 my $res = {};
1029
1030 return $res if !$data;
1031
1032 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1033
1034 $res->{type} = 'veth';
1035 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
1036
1037 return $res;
1038 }
1039
1040 sub option_exists {
1041 my ($class, $name) = @_;
1042
1043 return defined($confdesc->{$name});
1044 }
1045 # END JSON config code
1046
1047 sub classify_mountpoint {
1048 my ($class, $vol) = @_;
1049 if ($vol =~ m!^/!) {
1050 return 'device' if $vol =~ m!^/dev/!;
1051 return 'bind';
1052 }
1053 return 'volume';
1054 }
1055
1056 sub is_volume_in_use {
1057 my ($class, $config, $volid, $include_snapshots) = @_;
1058 my $used = 0;
1059
1060 $class->foreach_mountpoint($config, sub {
1061 my ($ms, $mountpoint) = @_;
1062 return if $used;
1063 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1064 });
1065
1066 my $snapshots = $config->{snapshots};
1067 if ($include_snapshots && $snapshots) {
1068 foreach my $snap (keys %$snapshots) {
1069 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1070 }
1071 }
1072
1073 return $used;
1074 }
1075
1076 sub has_dev_console {
1077 my ($class, $conf) = @_;
1078
1079 return !(defined($conf->{console}) && !$conf->{console});
1080 }
1081
1082 sub get_tty_count {
1083 my ($class, $conf) = @_;
1084
1085 return $conf->{tty} // $confdesc->{tty}->{default};
1086 }
1087
1088 sub get_cmode {
1089 my ($class, $conf) = @_;
1090
1091 return $conf->{cmode} // $confdesc->{cmode}->{default};
1092 }
1093
1094 sub mountpoint_names {
1095 my ($class, $reverse) = @_;
1096
1097 my @names = ('rootfs');
1098
1099 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1100 push @names, "mp$i";
1101 }
1102
1103 return $reverse ? reverse @names : @names;
1104 }
1105
1106 sub foreach_mountpoint_full {
1107 my ($class, $conf, $reverse, $func) = @_;
1108
1109 foreach my $key ($class->mountpoint_names($reverse)) {
1110 my $value = $conf->{$key};
1111 next if !defined($value);
1112 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1113 next if !defined($mountpoint);
1114
1115 &$func($key, $mountpoint);
1116 }
1117 }
1118
1119 sub foreach_mountpoint {
1120 my ($class, $conf, $func) = @_;
1121
1122 $class->foreach_mountpoint_full($conf, 0, $func);
1123 }
1124
1125 sub foreach_mountpoint_reverse {
1126 my ($class, $conf, $func) = @_;
1127
1128 $class->foreach_mountpoint_full($conf, 1, $func);
1129 }
1130
1131 sub get_vm_volumes {
1132 my ($class, $conf, $excludes) = @_;
1133
1134 my $vollist = [];
1135
1136 $class->foreach_mountpoint($conf, sub {
1137 my ($ms, $mountpoint) = @_;
1138
1139 return if $excludes && $ms eq $excludes;
1140
1141 my $volid = $mountpoint->{volume};
1142 return if !$volid || $mountpoint->{type} ne 'volume';
1143
1144 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1145 return if !$sid;
1146
1147 push @$vollist, $volid;
1148 });
1149
1150 return $vollist;
1151 }
1152
1153 return 1;