]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
pass datacenter.cfg's mac_prefix to random_ether_addr
[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 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
770 die "unable to delete required option '$opt'\n";
771 } elsif ($opt eq 'swap') {
772 delete $conf->{$opt};
773 PVE::LXC::write_cgroup_value("memory", $vmid,
774 "memory.memsw.limit_in_bytes", -1);
775 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
776 delete $conf->{$opt};
777 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
778 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
779 next if $hotplug_error->($opt);
780 delete $conf->{$opt};
781 } elsif ($opt eq 'cpulimit') {
782 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
783 delete $conf->{$opt};
784 } elsif ($opt eq 'cpuunits') {
785 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
786 delete $conf->{$opt};
787 } elsif ($opt =~ m/^net(\d)$/) {
788 delete $conf->{$opt};
789 next if !$running;
790 my $netid = $1;
791 PVE::Network::veth_delete("veth${vmid}i$netid");
792 } elsif ($opt eq 'protection') {
793 delete $conf->{$opt};
794 } elsif ($opt =~ m/^unused(\d+)$/) {
795 next if $hotplug_error->($opt);
796 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
797 push @deleted_volumes, $conf->{$opt};
798 delete $conf->{$opt};
799 } elsif ($opt =~ m/^mp(\d+)$/) {
800 next if $hotplug_error->($opt);
801 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
802 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
803 delete $conf->{$opt};
804 if ($mp->{type} eq 'volume') {
805 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
806 }
807 } elsif ($opt eq 'unprivileged') {
808 die "unable to delete read-only option: '$opt'\n";
809 } else {
810 die "implement me (delete: $opt)"
811 }
812 PVE::LXC::Config->write_config($vmid, $conf) if $running;
813 }
814 }
815
816 # There's no separate swap size to configure, there's memory and "total"
817 # memory (iow. memory+swap). This means we have to change them together.
818 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
819 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
820 if (defined($wanted_memory) || defined($wanted_swap)) {
821
822 my $old_memory = ($conf->{memory} || 512);
823 my $old_swap = ($conf->{swap} || 0);
824
825 $wanted_memory //= $old_memory;
826 $wanted_swap //= $old_swap;
827
828 my $total = $wanted_memory + $wanted_swap;
829 if ($running) {
830 my $old_total = $old_memory + $old_swap;
831 if ($total > $old_total) {
832 PVE::LXC::write_cgroup_value("memory", $vmid,
833 "memory.memsw.limit_in_bytes",
834 int($total*1024*1024));
835 PVE::LXC::write_cgroup_value("memory", $vmid,
836 "memory.limit_in_bytes",
837 int($wanted_memory*1024*1024));
838 } else {
839 PVE::LXC::write_cgroup_value("memory", $vmid,
840 "memory.limit_in_bytes",
841 int($wanted_memory*1024*1024));
842 PVE::LXC::write_cgroup_value("memory", $vmid,
843 "memory.memsw.limit_in_bytes",
844 int($total*1024*1024));
845 }
846 }
847 $conf->{memory} = $wanted_memory;
848 $conf->{swap} = $wanted_swap;
849
850 PVE::LXC::Config->write_config($vmid, $conf) if $running;
851 }
852
853 my $used_volids = {};
854 my $check_content_type = sub {
855 my ($mp) = @_;
856 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
857 my $scfg = PVE::Storage::config();
858 my $storage_config = PVE::Storage::storage_config($scfg, $sid);
859 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
860 if !$storage_config->{content}->{rootdir};
861 };
862
863 foreach my $opt (keys %$param) {
864 my $value = $param->{$opt};
865 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
866 if ($opt eq 'hostname') {
867 $conf->{$opt} = $value;
868 } elsif ($opt eq 'onboot') {
869 $conf->{$opt} = $value ? 1 : 0;
870 } elsif ($opt eq 'startup') {
871 $conf->{$opt} = $value;
872 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
873 next if $hotplug_error->($opt);
874 $conf->{$opt} = $value;
875 } elsif ($opt eq 'nameserver') {
876 next if $hotplug_error->($opt);
877 my $list = PVE::LXC::verify_nameserver_list($value);
878 $conf->{$opt} = $list;
879 } elsif ($opt eq 'searchdomain') {
880 next if $hotplug_error->($opt);
881 my $list = PVE::LXC::verify_searchdomain_list($value);
882 $conf->{$opt} = $list;
883 } elsif ($opt eq 'cpulimit') {
884 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
885 $conf->{$opt} = $value;
886 } elsif ($opt eq 'cpuunits') {
887 $conf->{$opt} = $value;
888 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
889 } elsif ($opt eq 'description') {
890 $conf->{$opt} = PVE::Tools::encode_text($value);
891 } elsif ($opt =~ m/^net(\d+)$/) {
892 my $netid = $1;
893 my $net = PVE::LXC::Config->parse_lxc_network($value);
894 if (!$running) {
895 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
896 } else {
897 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
898 }
899 } elsif ($opt eq 'protection') {
900 $conf->{$opt} = $value ? 1 : 0;
901 } elsif ($opt =~ m/^mp(\d+)$/) {
902 next if $hotplug_error->($opt);
903 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
904 my $old = $conf->{$opt};
905 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
906 if ($mp->{type} eq 'volume') {
907 &$check_content_type($mp);
908 $used_volids->{$mp->{volume}} = 1;
909 }
910 $conf->{$opt} = $value;
911 if (defined($old)) {
912 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
913 if ($mp->{type} eq 'volume') {
914 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
915 }
916 }
917 $new_disks = 1;
918 } elsif ($opt eq 'rootfs') {
919 next if $hotplug_error->($opt);
920 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
921 my $old = $conf->{$opt};
922 $conf->{$opt} = $value;
923 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
924 if ($mp->{type} eq 'volume') {
925 &$check_content_type($mp);
926 $used_volids->{$mp->{volume}} = 1;
927 }
928 if (defined($old)) {
929 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
930 if ($mp->{type} eq 'volume') {
931 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
932 }
933 }
934 $new_disks = 1;
935 } elsif ($opt eq 'unprivileged') {
936 die "unable to modify read-only option: '$opt'\n";
937 } elsif ($opt eq 'ostype') {
938 next if $hotplug_error->($opt);
939 $conf->{$opt} = $value;
940 } else {
941 die "implement me: $opt";
942 }
943 PVE::LXC::Config->write_config($vmid, $conf) if $running;
944 }
945
946 # Apply deletions and creations of new volumes
947 if (@deleted_volumes) {
948 my $storage_cfg = PVE::Storage::config();
949 foreach my $volume (@deleted_volumes) {
950 next if $used_volids->{$volume}; # could have been re-added, too
951 # also check for references in snapshots
952 next if $class->is_volume_in_use($conf, $volume, 1);
953 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
954 }
955 }
956
957 if ($new_disks) {
958 my $storage_cfg = PVE::Storage::config();
959 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
960 }
961
962 # This should be the last thing we do here
963 if ($running && scalar(@nohotplug)) {
964 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
965 }
966 }
967
968 sub check_type {
969 my ($class, $key, $value) = @_;
970
971 die "unknown setting '$key'\n" if !$confdesc->{$key};
972
973 my $type = $confdesc->{$key}->{type};
974
975 if (!defined($value)) {
976 die "got undefined value\n";
977 }
978
979 if ($value =~ m/[\n\r]/) {
980 die "property contains a line feed\n";
981 }
982
983 if ($type eq 'boolean') {
984 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
985 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
986 die "type check ('boolean') failed - got '$value'\n";
987 } elsif ($type eq 'integer') {
988 return int($1) if $value =~ m/^(\d+)$/;
989 die "type check ('integer') failed - got '$value'\n";
990 } elsif ($type eq 'number') {
991 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
992 die "type check ('number') failed - got '$value'\n";
993 } elsif ($type eq 'string') {
994 if (my $fmt = $confdesc->{$key}->{format}) {
995 PVE::JSONSchema::check_format($fmt, $value);
996 return $value;
997 }
998 return $value;
999 } else {
1000 die "internal error"
1001 }
1002 }
1003
1004
1005 # add JSON properties for create and set function
1006 sub json_config_properties {
1007 my ($class, $prop) = @_;
1008
1009 foreach my $opt (keys %$confdesc) {
1010 next if $opt eq 'parent' || $opt eq 'snaptime';
1011 next if $prop->{$opt};
1012 $prop->{$opt} = $confdesc->{$opt};
1013 }
1014
1015 return $prop;
1016 }
1017
1018 sub __parse_ct_mountpoint_full {
1019 my ($class, $desc, $data, $noerr) = @_;
1020
1021 $data //= '';
1022
1023 my $res;
1024 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1025 if ($@) {
1026 return undef if $noerr;
1027 die $@;
1028 }
1029
1030 if (defined(my $size = $res->{size})) {
1031 $size = PVE::JSONSchema::parse_size($size);
1032 if (!defined($size)) {
1033 return undef if $noerr;
1034 die "invalid size: $size\n";
1035 }
1036 $res->{size} = $size;
1037 }
1038
1039 $res->{type} = $class->classify_mountpoint($res->{volume});
1040
1041 return $res;
1042 };
1043
1044 sub parse_ct_rootfs {
1045 my ($class, $data, $noerr) = @_;
1046
1047 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1048
1049 $res->{mp} = '/' if defined($res);
1050
1051 return $res;
1052 }
1053
1054 sub parse_ct_mountpoint {
1055 my ($class, $data, $noerr) = @_;
1056
1057 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1058 }
1059
1060 sub print_ct_mountpoint {
1061 my ($class, $info, $nomp) = @_;
1062 my $skip = [ 'type' ];
1063 push @$skip, 'mp' if $nomp;
1064 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1065 }
1066
1067 sub print_lxc_network {
1068 my ($class, $net) = @_;
1069 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1070 }
1071
1072 sub parse_lxc_network {
1073 my ($class, $data) = @_;
1074
1075 my $res = {};
1076
1077 return $res if !$data;
1078
1079 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1080
1081 $res->{type} = 'veth';
1082 if (!$res->{hwaddr}) {
1083 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1084 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1085 }
1086
1087 return $res;
1088 }
1089
1090 sub option_exists {
1091 my ($class, $name) = @_;
1092
1093 return defined($confdesc->{$name});
1094 }
1095 # END JSON config code
1096
1097 sub classify_mountpoint {
1098 my ($class, $vol) = @_;
1099 if ($vol =~ m!^/!) {
1100 return 'device' if $vol =~ m!^/dev/!;
1101 return 'bind';
1102 }
1103 return 'volume';
1104 }
1105
1106 sub is_volume_in_use {
1107 my ($class, $config, $volid, $include_snapshots) = @_;
1108 my $used = 0;
1109
1110 $class->foreach_mountpoint($config, sub {
1111 my ($ms, $mountpoint) = @_;
1112 return if $used;
1113 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1114 });
1115
1116 my $snapshots = $config->{snapshots};
1117 if ($include_snapshots && $snapshots) {
1118 foreach my $snap (keys %$snapshots) {
1119 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1120 }
1121 }
1122
1123 return $used;
1124 }
1125
1126 sub has_dev_console {
1127 my ($class, $conf) = @_;
1128
1129 return !(defined($conf->{console}) && !$conf->{console});
1130 }
1131
1132 sub get_tty_count {
1133 my ($class, $conf) = @_;
1134
1135 return $conf->{tty} // $confdesc->{tty}->{default};
1136 }
1137
1138 sub get_cmode {
1139 my ($class, $conf) = @_;
1140
1141 return $conf->{cmode} // $confdesc->{cmode}->{default};
1142 }
1143
1144 sub mountpoint_names {
1145 my ($class, $reverse) = @_;
1146
1147 my @names = ('rootfs');
1148
1149 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1150 push @names, "mp$i";
1151 }
1152
1153 return $reverse ? reverse @names : @names;
1154 }
1155
1156 sub foreach_mountpoint_full {
1157 my ($class, $conf, $reverse, $func, @param) = @_;
1158
1159 foreach my $key ($class->mountpoint_names($reverse)) {
1160 my $value = $conf->{$key};
1161 next if !defined($value);
1162 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1163 next if !defined($mountpoint);
1164
1165 &$func($key, $mountpoint, @param);
1166 }
1167 }
1168
1169 sub foreach_mountpoint {
1170 my ($class, $conf, $func, @param) = @_;
1171
1172 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1173 }
1174
1175 sub foreach_mountpoint_reverse {
1176 my ($class, $conf, $func, @param) = @_;
1177
1178 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1179 }
1180
1181 sub get_vm_volumes {
1182 my ($class, $conf, $excludes) = @_;
1183
1184 my $vollist = [];
1185
1186 $class->foreach_mountpoint($conf, sub {
1187 my ($ms, $mountpoint) = @_;
1188
1189 return if $excludes && $ms eq $excludes;
1190
1191 my $volid = $mountpoint->{volume};
1192 return if !$volid || $mountpoint->{type} ne 'volume';
1193
1194 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1195 return if !$sid;
1196
1197 push @$vollist, $volid;
1198 });
1199
1200 return $vollist;
1201 }
1202
1203 return 1;