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