]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
allow VLAN 1 tag in containers
[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 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
57 sub has_feature {
58 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
59 my $err;
60
61 $class->foreach_mountpoint($conf, sub {
62 my ($ms, $mountpoint) = @_;
63
64 return if $err; # skip further test
65 return if $backup_only && !mountpoint_backup_enabled($ms, $mountpoint);
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
76 sub __snapshot_save_vmstate {
77 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
78 die "implement me - snapshot_save_vmstate\n";
79 }
80
81 sub __snapshot_check_running {
82 my ($class, $vmid) = @_;
83 return PVE::LXC::check_running($vmid);
84 }
85
86 sub __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
93 sub __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
105 sub __snapshot_create_vol_snapshot {
106 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
107
108 my $storecfg = PVE::Storage::config();
109
110 return if $snapname eq 'vzdump' &&
111 !mountpoint_backup_enabled($ms, $mountpoint);
112
113 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
114 }
115
116 sub __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};
123 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
124 delete $snap->{$remove_drive};
125 $class->add_unused_volume($snap, $mountpoint->{volume});
126 }
127 }
128
129 sub __snapshot_delete_vmstate_file {
130 my ($class, $snap, $force) = @_;
131
132 die "implement me - saving vmstate\n";
133 }
134
135 sub __snapshot_delete_vol_snapshot {
136 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
137
138 my $storecfg = PVE::Storage::config();
139 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
140 push @$unused, $mountpoint->{volume};
141 }
142
143 sub __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
150 sub __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
157 sub __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
164 sub __snapshot_rollback_vm_start {
165 my ($class, $vmid, $vmstate, $forcemachine);
166
167 die "implement me - save vmstate\n";
168 }
169
170 sub __snapshot_foreach_volume {
171 my ($class, $conf, $func) = @_;
172
173 $class->foreach_mountpoint($conf, $func);
174 }
175
176 # END implemented abstract methods from PVE::AbstractConfig
177
178 # BEGIN JSON config code
179
180 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
181
182 my $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 size => {
191 type => 'string',
192 format => 'disk-size',
193 format_description => 'DiskSize',
194 description => 'Volume size (read only value).',
195 optional => 1,
196 },
197 acl => {
198 type => 'boolean',
199 description => 'Explicitly enable or disable ACL support.',
200 optional => 1,
201 },
202 ro => {
203 type => 'boolean',
204 description => 'Read-only mountpoint (not supported with bind mounts)',
205 optional => 1,
206 },
207 quota => {
208 type => 'boolean',
209 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
210 optional => 1,
211 },
212 };
213
214 PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
215 type => 'string', format => $rootfs_desc,
216 description => "Use volume as container root.",
217 optional => 1,
218 });
219
220 PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
221 description => "The name of the snapshot.",
222 type => 'string', format => 'pve-configid',
223 maxLength => 40,
224 });
225
226 my $confdesc = {
227 lock => {
228 optional => 1,
229 type => 'string',
230 description => "Lock/unlock the VM.",
231 enum => [qw(migrate backup snapshot rollback)],
232 },
233 onboot => {
234 optional => 1,
235 type => 'boolean',
236 description => "Specifies whether a VM will be started during system bootup.",
237 default => 0,
238 },
239 startup => get_standard_option('pve-startup-order'),
240 template => {
241 optional => 1,
242 type => 'boolean',
243 description => "Enable/disable Template.",
244 default => 0,
245 },
246 arch => {
247 optional => 1,
248 type => 'string',
249 enum => ['amd64', 'i386'],
250 description => "OS architecture type.",
251 default => 'amd64',
252 },
253 ostype => {
254 optional => 1,
255 type => 'string',
256 enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
257 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.",
258 },
259 console => {
260 optional => 1,
261 type => 'boolean',
262 description => "Attach a console device (/dev/console) to the container.",
263 default => 1,
264 },
265 tty => {
266 optional => 1,
267 type => 'integer',
268 description => "Specify the number of tty available to the container",
269 minimum => 0,
270 maximum => 6,
271 default => 2,
272 },
273 cpulimit => {
274 optional => 1,
275 type => 'number',
276 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.",
277 minimum => 0,
278 maximum => 128,
279 default => 0,
280 },
281 cpuunits => {
282 optional => 1,
283 type => 'integer',
284 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.",
285 minimum => 0,
286 maximum => 500000,
287 default => 1024,
288 },
289 memory => {
290 optional => 1,
291 type => 'integer',
292 description => "Amount of RAM for the VM in MB.",
293 minimum => 16,
294 default => 512,
295 },
296 swap => {
297 optional => 1,
298 type => 'integer',
299 description => "Amount of SWAP for the VM in MB.",
300 minimum => 0,
301 default => 512,
302 },
303 hostname => {
304 optional => 1,
305 description => "Set a host name for the container.",
306 type => 'string', format => 'dns-name',
307 maxLength => 255,
308 },
309 description => {
310 optional => 1,
311 type => 'string',
312 description => "Container description. Only used on the configuration web interface.",
313 },
314 searchdomain => {
315 optional => 1,
316 type => 'string', format => 'dns-name-list',
317 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
318 },
319 nameserver => {
320 optional => 1,
321 type => 'string', format => 'address-list',
322 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.",
323 },
324 rootfs => get_standard_option('pve-ct-rootfs'),
325 parent => {
326 optional => 1,
327 type => 'string', format => 'pve-configid',
328 maxLength => 40,
329 description => "Parent snapshot name. This is used internally, and should not be modified.",
330 },
331 snaptime => {
332 optional => 1,
333 description => "Timestamp for snapshots.",
334 type => 'integer',
335 minimum => 0,
336 },
337 cmode => {
338 optional => 1,
339 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).",
340 type => 'string',
341 enum => ['shell', 'console', 'tty'],
342 default => 'tty',
343 },
344 protection => {
345 optional => 1,
346 type => 'boolean',
347 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
348 default => 0,
349 },
350 unprivileged => {
351 optional => 1,
352 type => 'boolean',
353 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
354 default => 0,
355 },
356 };
357
358 my $valid_lxc_conf_keys = {
359 'lxc.include' => 1,
360 'lxc.arch' => 1,
361 'lxc.utsname' => 1,
362 'lxc.haltsignal' => 1,
363 'lxc.rebootsignal' => 1,
364 'lxc.stopsignal' => 1,
365 'lxc.init_cmd' => 1,
366 'lxc.network.type' => 1,
367 'lxc.network.flags' => 1,
368 'lxc.network.link' => 1,
369 'lxc.network.mtu' => 1,
370 'lxc.network.name' => 1,
371 'lxc.network.hwaddr' => 1,
372 'lxc.network.ipv4' => 1,
373 'lxc.network.ipv4.gateway' => 1,
374 'lxc.network.ipv6' => 1,
375 'lxc.network.ipv6.gateway' => 1,
376 'lxc.network.script.up' => 1,
377 'lxc.network.script.down' => 1,
378 'lxc.pts' => 1,
379 'lxc.console.logfile' => 1,
380 'lxc.console' => 1,
381 'lxc.tty' => 1,
382 'lxc.devttydir' => 1,
383 'lxc.hook.autodev' => 1,
384 'lxc.autodev' => 1,
385 'lxc.kmsg' => 1,
386 'lxc.mount' => 1,
387 'lxc.mount.entry' => 1,
388 'lxc.mount.auto' => 1,
389 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
390 'lxc.rootfs.mount' => 1,
391 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
392 ', please use mountpoint options in the "rootfs" key',
393 # lxc.cgroup.*
394 'lxc.cap.drop' => 1,
395 'lxc.cap.keep' => 1,
396 'lxc.aa_profile' => 1,
397 'lxc.aa_allow_incomplete' => 1,
398 'lxc.se_context' => 1,
399 'lxc.seccomp' => 1,
400 'lxc.id_map' => 1,
401 'lxc.hook.pre-start' => 1,
402 'lxc.hook.pre-mount' => 1,
403 'lxc.hook.mount' => 1,
404 'lxc.hook.start' => 1,
405 'lxc.hook.stop' => 1,
406 'lxc.hook.post-stop' => 1,
407 'lxc.hook.clone' => 1,
408 'lxc.hook.destroy' => 1,
409 'lxc.loglevel' => 1,
410 'lxc.logfile' => 1,
411 'lxc.start.auto' => 1,
412 'lxc.start.delay' => 1,
413 'lxc.start.order' => 1,
414 'lxc.group' => 1,
415 'lxc.environment' => 1,
416 };
417
418 our $netconf_desc = {
419 type => {
420 type => 'string',
421 optional => 1,
422 description => "Network interface type.",
423 enum => [qw(veth)],
424 },
425 name => {
426 type => 'string',
427 format_description => 'string',
428 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
429 pattern => '[-_.\w\d]+',
430 },
431 bridge => {
432 type => 'string',
433 format_description => 'bridge',
434 description => 'Bridge to attach the network device to.',
435 pattern => '[-_.\w\d]+',
436 optional => 1,
437 },
438 hwaddr => {
439 type => 'string',
440 format_description => "XX:XX:XX:XX:XX:XX",
441 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)',
442 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
443 optional => 1,
444 },
445 mtu => {
446 type => 'integer',
447 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
448 minimum => 64, # minimum ethernet frame is 64 bytes
449 optional => 1,
450 },
451 ip => {
452 type => 'string',
453 format => 'pve-ipv4-config',
454 format_description => 'IPv4Format/CIDR',
455 description => 'IPv4 address in CIDR format.',
456 optional => 1,
457 },
458 gw => {
459 type => 'string',
460 format => 'ipv4',
461 format_description => 'GatewayIPv4',
462 description => 'Default gateway for IPv4 traffic.',
463 optional => 1,
464 },
465 ip6 => {
466 type => 'string',
467 format => 'pve-ipv6-config',
468 format_description => 'IPv6Format/CIDR',
469 description => 'IPv6 address in CIDR format.',
470 optional => 1,
471 },
472 gw6 => {
473 type => 'string',
474 format => 'ipv6',
475 format_description => 'GatewayIPv6',
476 description => 'Default gateway for IPv6 traffic.',
477 optional => 1,
478 },
479 firewall => {
480 type => 'boolean',
481 description => "Controls whether this interface's firewall rules should be used.",
482 optional => 1,
483 },
484 tag => {
485 type => 'integer',
486 minimum => '1',
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 backup => {
537 type => 'boolean',
538 description => 'Whether to include the mountpoint in backups.',
539 optional => 1,
540 },
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 };
548 PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
549
550 my $unuseddesc = {
551 optional => 1,
552 type => 'string', format => 'pve-volume-id',
553 description => "Reference to unused volumes.",
554 };
555
556 for (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
565 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
566 $confdesc->{"unused$i"} = $unuseddesc;
567 }
568
569 sub 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
638 sub 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
698 sub 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};
740 } elsif ($opt eq 'cpulimit') {
741 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
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};
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 = {};
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 };
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') {
843 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
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};
864 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
865 if ($mp->{type} eq 'volume') {
866 &$check_content_type($mp);
867 $used_volids->{$mp->{volume}} = 1;
868 }
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;
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;
882 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
883 if ($mp->{type} eq 'volume') {
884 &$check_content_type($mp);
885 $used_volids->{$mp->{volume}} = 1;
886 }
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 }
893 $new_disks = 1;
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
927 sub 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
965 sub 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
977 sub __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
1003 sub 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
1013 sub parse_ct_mountpoint {
1014 my ($class, $data, $noerr) = @_;
1015
1016 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1017 }
1018
1019 sub 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
1026 sub print_lxc_network {
1027 my ($class, $net) = @_;
1028 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1029 }
1030
1031 sub 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
1046 sub option_exists {
1047 my ($class, $name) = @_;
1048
1049 return defined($confdesc->{$name});
1050 }
1051 # END JSON config code
1052
1053 sub 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
1062 sub 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
1082 sub has_dev_console {
1083 my ($class, $conf) = @_;
1084
1085 return !(defined($conf->{console}) && !$conf->{console});
1086 }
1087
1088 sub get_tty_count {
1089 my ($class, $conf) = @_;
1090
1091 return $conf->{tty} // $confdesc->{tty}->{default};
1092 }
1093
1094 sub get_cmode {
1095 my ($class, $conf) = @_;
1096
1097 return $conf->{cmode} // $confdesc->{cmode}->{default};
1098 }
1099
1100 sub 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
1112 sub 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);
1118 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1119 next if !defined($mountpoint);
1120
1121 &$func($key, $mountpoint);
1122 }
1123 }
1124
1125 sub foreach_mountpoint {
1126 my ($class, $conf, $func) = @_;
1127
1128 $class->foreach_mountpoint_full($conf, 0, $func);
1129 }
1130
1131 sub foreach_mountpoint_reverse {
1132 my ($class, $conf, $func) = @_;
1133
1134 $class->foreach_mountpoint_full($conf, 1, $func);
1135 }
1136
1137 sub 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
1159 return 1;