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