]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
allow deleting of container hostname
[pve-container.git] / src / PVE / LXC / Config.pm
CommitLineData
67afe46e
FG
1package PVE::LXC::Config;
2
3use strict;
4use warnings;
5
6use PVE::AbstractConfig;
7use PVE::Cluster qw(cfs_register_file);
8use PVE::INotify;
9use PVE::JSONSchema qw(get_standard_option);
10use PVE::Tools;
11
12use base qw(PVE::AbstractConfig);
13
14my $nodename = PVE::INotify::nodename();
15my $lock_handles = {};
16my $lockdir = "/run/lock/lxc";
17mkdir $lockdir;
18mkdir "/etc/pve/nodes/$nodename/lxc";
19my $MAX_MOUNT_POINTS = 10;
20my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
21
22# BEGIN implemented abstract methods from PVE::AbstractConfig
23
24sub guest_type {
25 return "CT";
26}
27
4518000b
FG
28sub __config_max_unused_disks {
29 my ($class) = @_;
30
31 return $MAX_UNUSED_DISKS;
32}
33
67afe46e
FG
34sub config_file_lock {
35 my ($class, $vmid) = @_;
36
37 return "$lockdir/pve-config-${vmid}.lock";
38}
39
40sub cfs_config_path {
41 my ($class, $vmid, $node) = @_;
42
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
45}
46
1a8269bc 47sub mountpoint_backup_enabled {
ec7f0f09 48 my ($class, $mp_key, $mountpoint) = @_;
1a8269bc
DM
49
50 return 1 if $mp_key eq 'rootfs';
51
f59c9670
FG
52 return 0 if $mountpoint->{type} ne 'volume';
53
1a8269bc
DM
54 return 1 if $mountpoint->{backup};
55
56 return 0;
57}
58
4518000b
FG
59sub has_feature {
60 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
61 my $err;
62
d250604f 63 $class->foreach_mountpoint($conf, sub {
4518000b
FG
64 my ($ms, $mountpoint) = @_;
65
66 return if $err; # skip further test
ec7f0f09 67 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
4518000b
FG
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
78sub __snapshot_save_vmstate {
79 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
80 die "implement me - snapshot_save_vmstate\n";
81}
82
83sub __snapshot_check_running {
84 my ($class, $vmid) = @_;
85 return PVE::LXC::check_running($vmid);
86}
87
88sub __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
95sub __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
107sub __snapshot_create_vol_snapshot {
108 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
109
110 my $storecfg = PVE::Storage::config();
111
1a8269bc 112 return if $snapname eq 'vzdump' &&
ec7f0f09 113 !$class->mountpoint_backup_enabled($ms, $mountpoint);
1a8269bc 114
4518000b
FG
115 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
116}
117
118sub __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};
1b4cf758 125 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
4518000b 126 delete $snap->{$remove_drive};
d103721f
FG
127
128 $class->add_unused_volume($snap, $mountpoint->{volume})
129 if ($mountpoint->{type} eq 'volume');
4518000b
FG
130 }
131}
132
133sub __snapshot_delete_vmstate_file {
134 my ($class, $snap, $force) = @_;
135
136 die "implement me - saving vmstate\n";
137}
138
139sub __snapshot_delete_vol_snapshot {
a8e9a4ea 140 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
4518000b 141
d103721f
FG
142 return if $snapname eq 'vzdump' &&
143 !$class->mountpoint_backup_enabled($ms, $mountpoint);
144
4518000b
FG
145 my $storecfg = PVE::Storage::config();
146 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
a8e9a4ea 147 push @$unused, $mountpoint->{volume};
4518000b
FG
148}
149
150sub __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
157sub __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
164sub __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
171sub __snapshot_rollback_vm_start {
172 my ($class, $vmid, $vmstate, $forcemachine);
173
174 die "implement me - save vmstate\n";
175}
176
a8656869
FG
177sub __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
4518000b
FG
206sub __snapshot_foreach_volume {
207 my ($class, $conf, $func) = @_;
208
d250604f 209 $class->foreach_mountpoint($conf, $func);
4518000b
FG
210}
211
67afe46e
FG
212# END implemented abstract methods from PVE::AbstractConfig
213
1b4cf758
FG
214# BEGIN JSON config code
215
216cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
217
218my $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 },
1b4cf758
FG
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',
1b4cf758
FG
235 description => 'Explicitly enable or disable ACL support.',
236 optional => 1,
237 },
238 ro => {
239 type => 'boolean',
fd0e8880 240 description => 'Read-only mountpoint',
1b4cf758
FG
241 optional => 1,
242 },
243 quota => {
244 type => 'boolean',
1b4cf758
FG
245 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
246 optional => 1,
247 },
248};
249
250PVE::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
256PVE::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
262my $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',
ed027b58 292 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
1b4cf758
FG
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',
064529c3 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.",
1b4cf758
FG
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',
a069f163 348 description => "Container description. Only used on the configuration web interface.",
1b4cf758
FG
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
394my $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
5e5915c5 454our $netconf_desc = {
1b4cf758
FG
455 type => {
456 type => 'string',
457 optional => 1,
458 description => "Network interface type.",
459 enum => [qw(veth)],
460 },
461 name => {
462 type => 'string',
a069f163
DM
463 format_description => 'string',
464 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
465 pattern => '[-_.\w\d]+',
466 },
467 bridge => {
468 type => 'string',
a069f163 469 format_description => 'bridge',
1b4cf758
FG
470 description => 'Bridge to attach the network device to.',
471 pattern => '[-_.\w\d]+',
472 optional => 1,
473 },
474 hwaddr => {
475 type => 'string',
a069f163 476 format_description => "XX:XX:XX:XX:XX:XX",
e6f20294 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)',
1b4cf758
FG
478 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
479 optional => 1,
480 },
481 mtu => {
482 type => 'integer',
1b4cf758
FG
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',
1b4cf758
FG
517 description => "Controls whether this interface's firewall rules should be used.",
518 optional => 1,
519 },
520 tag => {
521 type => 'integer',
9c2cc8fc 522 minimum => '1',
1b4cf758
FG
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 },
380962c7
WB
534 rate => {
535 type => 'number',
536 format_description => 'mbps',
537 description => "Apply rate limiting to the interface",
538 optional => 1,
539 },
1b4cf758
FG
540};
541PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
542
543my $MAX_LXC_NETWORKS = 10;
544for (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
552PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
553sub 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
570my $mp_desc = {
571 %$rootfs_desc,
84820d40
DM
572 backup => {
573 type => 'boolean',
574 description => 'Whether to include the mountpoint in backups.',
fd0e8880
FG
575 verbose_description => 'Whether to include the mountpoint in backups '.
576 '(only used for volume mountpoints).',
84820d40
DM
577 optional => 1,
578 },
1b4cf758
FG
579 mp => {
580 type => 'string',
581 format => 'pve-lxc-mp-string',
582 format_description => 'Path',
52b6f941
FG
583 description => 'Path to the mountpoint as seen from inside the container '.
584 '(must not contain symlinks).',
fd0e8880 585 verbose_description => "Path to the mountpoint as seen from inside the container.\n\n".
52b6f941 586 "NOTE: Must not contain any symlinks for security reasons."
1b4cf758
FG
587 },
588};
589PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
590
591my $unuseddesc = {
592 optional => 1,
593 type => 'string', format => 'pve-volume-id',
2928e616 594 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
1b4cf758
FG
595};
596
597for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
598 $confdesc->{"mp$i"} = {
599 optional => 1,
600 type => 'string', format => $mp_desc,
2928e616 601 description => "Use volume as container mount point.",
1b4cf758
FG
602 optional => 1,
603 };
604}
605
606for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
607 $confdesc->{"unused$i"} = $unuseddesc;
608}
609
610sub 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
679sub 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
739sub 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
e96a0ceb 769 if ($opt eq 'memory' || $opt eq 'rootfs') {
1b4cf758 770 die "unable to delete required option '$opt'\n";
e96a0ceb
DC
771 } elsif ($opt eq 'hostname') {
772 delete $conf->{$opt};
1b4cf758
FG
773 } elsif ($opt eq 'swap') {
774 delete $conf->{$opt};
775 PVE::LXC::write_cgroup_value("memory", $vmid,
776 "memory.memsw.limit_in_bytes", -1);
777 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
778 delete $conf->{$opt};
779 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
780 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
781 next if $hotplug_error->($opt);
782 delete $conf->{$opt};
d13b770f 783 } elsif ($opt eq 'cpulimit') {
213d70e6 784 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
d13b770f
WB
785 delete $conf->{$opt};
786 } elsif ($opt eq 'cpuunits') {
787 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
788 delete $conf->{$opt};
1b4cf758
FG
789 } elsif ($opt =~ m/^net(\d)$/) {
790 delete $conf->{$opt};
791 next if !$running;
792 my $netid = $1;
793 PVE::Network::veth_delete("veth${vmid}i$netid");
794 } elsif ($opt eq 'protection') {
795 delete $conf->{$opt};
796 } elsif ($opt =~ m/^unused(\d+)$/) {
797 next if $hotplug_error->($opt);
798 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
799 push @deleted_volumes, $conf->{$opt};
800 delete $conf->{$opt};
801 } elsif ($opt =~ m/^mp(\d+)$/) {
802 next if $hotplug_error->($opt);
803 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
804 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
805 delete $conf->{$opt};
806 if ($mp->{type} eq 'volume') {
807 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
808 }
809 } elsif ($opt eq 'unprivileged') {
810 die "unable to delete read-only option: '$opt'\n";
811 } else {
812 die "implement me (delete: $opt)"
813 }
814 PVE::LXC::Config->write_config($vmid, $conf) if $running;
815 }
816 }
817
818 # There's no separate swap size to configure, there's memory and "total"
819 # memory (iow. memory+swap). This means we have to change them together.
820 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
821 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
822 if (defined($wanted_memory) || defined($wanted_swap)) {
823
824 my $old_memory = ($conf->{memory} || 512);
825 my $old_swap = ($conf->{swap} || 0);
826
827 $wanted_memory //= $old_memory;
828 $wanted_swap //= $old_swap;
829
830 my $total = $wanted_memory + $wanted_swap;
831 if ($running) {
832 my $old_total = $old_memory + $old_swap;
833 if ($total > $old_total) {
834 PVE::LXC::write_cgroup_value("memory", $vmid,
835 "memory.memsw.limit_in_bytes",
836 int($total*1024*1024));
837 PVE::LXC::write_cgroup_value("memory", $vmid,
838 "memory.limit_in_bytes",
839 int($wanted_memory*1024*1024));
840 } else {
841 PVE::LXC::write_cgroup_value("memory", $vmid,
842 "memory.limit_in_bytes",
843 int($wanted_memory*1024*1024));
844 PVE::LXC::write_cgroup_value("memory", $vmid,
845 "memory.memsw.limit_in_bytes",
846 int($total*1024*1024));
847 }
848 }
849 $conf->{memory} = $wanted_memory;
850 $conf->{swap} = $wanted_swap;
851
852 PVE::LXC::Config->write_config($vmid, $conf) if $running;
853 }
854
855 my $used_volids = {};
1b3213ae
FG
856 my $check_content_type = sub {
857 my ($mp) = @_;
858 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
859 my $scfg = PVE::Storage::config();
860 my $storage_config = PVE::Storage::storage_config($scfg, $sid);
861 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
862 if !$storage_config->{content}->{rootdir};
863 };
1b4cf758
FG
864
865 foreach my $opt (keys %$param) {
866 my $value = $param->{$opt};
867 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
868 if ($opt eq 'hostname') {
869 $conf->{$opt} = $value;
870 } elsif ($opt eq 'onboot') {
871 $conf->{$opt} = $value ? 1 : 0;
872 } elsif ($opt eq 'startup') {
873 $conf->{$opt} = $value;
874 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
875 next if $hotplug_error->($opt);
876 $conf->{$opt} = $value;
877 } elsif ($opt eq 'nameserver') {
878 next if $hotplug_error->($opt);
879 my $list = PVE::LXC::verify_nameserver_list($value);
880 $conf->{$opt} = $list;
881 } elsif ($opt eq 'searchdomain') {
882 next if $hotplug_error->($opt);
883 my $list = PVE::LXC::verify_searchdomain_list($value);
884 $conf->{$opt} = $list;
885 } elsif ($opt eq 'cpulimit') {
213d70e6 886 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1b4cf758
FG
887 $conf->{$opt} = $value;
888 } elsif ($opt eq 'cpuunits') {
889 $conf->{$opt} = $value;
890 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
891 } elsif ($opt eq 'description') {
892 $conf->{$opt} = PVE::Tools::encode_text($value);
893 } elsif ($opt =~ m/^net(\d+)$/) {
894 my $netid = $1;
895 my $net = PVE::LXC::Config->parse_lxc_network($value);
896 if (!$running) {
897 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
898 } else {
899 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
900 }
901 } elsif ($opt eq 'protection') {
902 $conf->{$opt} = $value ? 1 : 0;
903 } elsif ($opt =~ m/^mp(\d+)$/) {
904 next if $hotplug_error->($opt);
905 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
906 my $old = $conf->{$opt};
56d7fc1b
FG
907 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
908 if ($mp->{type} eq 'volume') {
1b3213ae 909 &$check_content_type($mp);
56d7fc1b
FG
910 $used_volids->{$mp->{volume}} = 1;
911 }
1b4cf758
FG
912 $conf->{$opt} = $value;
913 if (defined($old)) {
914 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
915 if ($mp->{type} eq 'volume') {
916 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
917 }
918 }
919 $new_disks = 1;
1b4cf758
FG
920 } elsif ($opt eq 'rootfs') {
921 next if $hotplug_error->($opt);
922 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
923 my $old = $conf->{$opt};
924 $conf->{$opt} = $value;
56d7fc1b
FG
925 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
926 if ($mp->{type} eq 'volume') {
1b3213ae 927 &$check_content_type($mp);
56d7fc1b
FG
928 $used_volids->{$mp->{volume}} = 1;
929 }
1b4cf758
FG
930 if (defined($old)) {
931 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
932 if ($mp->{type} eq 'volume') {
933 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
934 }
935 }
1d9369fd 936 $new_disks = 1;
1b4cf758
FG
937 } elsif ($opt eq 'unprivileged') {
938 die "unable to modify read-only option: '$opt'\n";
939 } elsif ($opt eq 'ostype') {
940 next if $hotplug_error->($opt);
941 $conf->{$opt} = $value;
942 } else {
943 die "implement me: $opt";
944 }
945 PVE::LXC::Config->write_config($vmid, $conf) if $running;
946 }
947
948 # Apply deletions and creations of new volumes
949 if (@deleted_volumes) {
950 my $storage_cfg = PVE::Storage::config();
951 foreach my $volume (@deleted_volumes) {
952 next if $used_volids->{$volume}; # could have been re-added, too
953 # also check for references in snapshots
954 next if $class->is_volume_in_use($conf, $volume, 1);
955 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
956 }
957 }
958
959 if ($new_disks) {
960 my $storage_cfg = PVE::Storage::config();
961 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
962 }
963
964 # This should be the last thing we do here
965 if ($running && scalar(@nohotplug)) {
966 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
967 }
968}
969
970sub check_type {
971 my ($class, $key, $value) = @_;
972
973 die "unknown setting '$key'\n" if !$confdesc->{$key};
974
975 my $type = $confdesc->{$key}->{type};
976
977 if (!defined($value)) {
978 die "got undefined value\n";
979 }
980
981 if ($value =~ m/[\n\r]/) {
982 die "property contains a line feed\n";
983 }
984
985 if ($type eq 'boolean') {
986 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
987 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
988 die "type check ('boolean') failed - got '$value'\n";
989 } elsif ($type eq 'integer') {
990 return int($1) if $value =~ m/^(\d+)$/;
991 die "type check ('integer') failed - got '$value'\n";
992 } elsif ($type eq 'number') {
993 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
994 die "type check ('number') failed - got '$value'\n";
995 } elsif ($type eq 'string') {
996 if (my $fmt = $confdesc->{$key}->{format}) {
997 PVE::JSONSchema::check_format($fmt, $value);
998 return $value;
999 }
1000 return $value;
1001 } else {
1002 die "internal error"
1003 }
1004}
1005
1006
1007# add JSON properties for create and set function
1008sub json_config_properties {
1009 my ($class, $prop) = @_;
1010
1011 foreach my $opt (keys %$confdesc) {
1012 next if $opt eq 'parent' || $opt eq 'snaptime';
1013 next if $prop->{$opt};
1014 $prop->{$opt} = $confdesc->{$opt};
1015 }
1016
1017 return $prop;
1018}
1019
1020sub __parse_ct_mountpoint_full {
1021 my ($class, $desc, $data, $noerr) = @_;
1022
1023 $data //= '';
1024
1025 my $res;
1026 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1027 if ($@) {
1028 return undef if $noerr;
1029 die $@;
1030 }
1031
1032 if (defined(my $size = $res->{size})) {
1033 $size = PVE::JSONSchema::parse_size($size);
1034 if (!defined($size)) {
1035 return undef if $noerr;
1036 die "invalid size: $size\n";
1037 }
1038 $res->{size} = $size;
1039 }
1040
1041 $res->{type} = $class->classify_mountpoint($res->{volume});
1042
1043 return $res;
1044};
1045
1046sub parse_ct_rootfs {
1047 my ($class, $data, $noerr) = @_;
1048
1049 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1050
1051 $res->{mp} = '/' if defined($res);
1052
1053 return $res;
1054}
1055
1056sub parse_ct_mountpoint {
1057 my ($class, $data, $noerr) = @_;
1058
1059 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1060}
1061
1062sub print_ct_mountpoint {
1063 my ($class, $info, $nomp) = @_;
1064 my $skip = [ 'type' ];
1065 push @$skip, 'mp' if $nomp;
1066 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1067}
1068
1069sub print_lxc_network {
1070 my ($class, $net) = @_;
1071 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1072}
1073
1074sub parse_lxc_network {
1075 my ($class, $data) = @_;
1076
1077 my $res = {};
1078
1079 return $res if !$data;
1080
1081 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1082
1083 $res->{type} = 'veth';
2f19133b
WB
1084 if (!$res->{hwaddr}) {
1085 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1086 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1087 }
1b4cf758
FG
1088
1089 return $res;
1090}
1091
1092sub option_exists {
1093 my ($class, $name) = @_;
1094
1095 return defined($confdesc->{$name});
1096}
1097# END JSON config code
1098
d250604f
FG
1099sub classify_mountpoint {
1100 my ($class, $vol) = @_;
1101 if ($vol =~ m!^/!) {
1102 return 'device' if $vol =~ m!^/dev/!;
1103 return 'bind';
1104 }
1105 return 'volume';
1106}
1107
1108sub is_volume_in_use {
1109 my ($class, $config, $volid, $include_snapshots) = @_;
1110 my $used = 0;
1111
1112 $class->foreach_mountpoint($config, sub {
1113 my ($ms, $mountpoint) = @_;
1114 return if $used;
1115 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1116 });
1117
1118 my $snapshots = $config->{snapshots};
1119 if ($include_snapshots && $snapshots) {
1120 foreach my $snap (keys %$snapshots) {
1121 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1122 }
1123 }
1124
1125 return $used;
1126}
1127
1128sub has_dev_console {
1129 my ($class, $conf) = @_;
1130
1131 return !(defined($conf->{console}) && !$conf->{console});
1132}
1133
1b4cf758
FG
1134sub get_tty_count {
1135 my ($class, $conf) = @_;
1136
1137 return $conf->{tty} // $confdesc->{tty}->{default};
1138}
1139
1140sub get_cmode {
1141 my ($class, $conf) = @_;
1142
1143 return $conf->{cmode} // $confdesc->{cmode}->{default};
1144}
1145
d250604f
FG
1146sub mountpoint_names {
1147 my ($class, $reverse) = @_;
1148
1149 my @names = ('rootfs');
1150
1151 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1152 push @names, "mp$i";
1153 }
1154
1155 return $reverse ? reverse @names : @names;
1156}
1157
1158sub foreach_mountpoint_full {
8915b450 1159 my ($class, $conf, $reverse, $func, @param) = @_;
d250604f
FG
1160
1161 foreach my $key ($class->mountpoint_names($reverse)) {
1162 my $value = $conf->{$key};
1163 next if !defined($value);
1b4cf758 1164 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
d250604f
FG
1165 next if !defined($mountpoint);
1166
8915b450 1167 &$func($key, $mountpoint, @param);
d250604f
FG
1168 }
1169}
1170
1171sub foreach_mountpoint {
8915b450 1172 my ($class, $conf, $func, @param) = @_;
d250604f 1173
8915b450 1174 $class->foreach_mountpoint_full($conf, 0, $func, @param);
d250604f
FG
1175}
1176
1177sub foreach_mountpoint_reverse {
8915b450 1178 my ($class, $conf, $func, @param) = @_;
d250604f 1179
8915b450 1180 $class->foreach_mountpoint_full($conf, 1, $func, @param);
d250604f
FG
1181}
1182
1183sub get_vm_volumes {
1184 my ($class, $conf, $excludes) = @_;
1185
1186 my $vollist = [];
1187
1188 $class->foreach_mountpoint($conf, sub {
1189 my ($ms, $mountpoint) = @_;
1190
1191 return if $excludes && $ms eq $excludes;
1192
1193 my $volid = $mountpoint->{volume};
1194 return if !$volid || $mountpoint->{type} ne 'volume';
1195
1196 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1197 return if !$sid;
1198
1199 push @$vollist, $volid;
1200 });
1201
1202 return $vollist;
1203}
1204
67afe46e 1205return 1;