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