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