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