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