]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
change cpu shares: hard-code cgroupv1 default parameter
[pve-container.git] / src / PVE / LXC / Config.pm
CommitLineData
67afe46e
FG
1package PVE::LXC::Config;
2
3use strict;
4use warnings;
f89af842 5
8463099d 6use Fcntl qw(O_RDONLY);
67afe46e
FG
7
8use PVE::AbstractConfig;
9use PVE::Cluster qw(cfs_register_file);
8b076579 10use PVE::DataCenterConfig;
1a416433 11use PVE::GuestHelpers;
67afe46e
FG
12use PVE::INotify;
13use PVE::JSONSchema qw(get_standard_option);
14use PVE::Tools;
15
11066f6b
TL
16use PVE::LXC;
17
67afe46e
FG
18use base qw(PVE::AbstractConfig);
19
f89af842
TL
20use constant {
21 FIFREEZE => 0xc0045877,
22 FITHAW => 0xc0045878,
23};
40cd2e89 24
67afe46e
FG
25my $nodename = PVE::INotify::nodename();
26my $lock_handles = {};
27my $lockdir = "/run/lock/lxc";
28mkdir $lockdir;
29mkdir "/etc/pve/nodes/$nodename/lxc";
717f70b7 30my $MAX_MOUNT_POINTS = 256;
67afe46e
FG
31my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
32
33# BEGIN implemented abstract methods from PVE::AbstractConfig
34
35sub guest_type {
36 return "CT";
37}
38
4518000b
FG
39sub __config_max_unused_disks {
40 my ($class) = @_;
41
42 return $MAX_UNUSED_DISKS;
43}
44
67afe46e
FG
45sub config_file_lock {
46 my ($class, $vmid) = @_;
47
48 return "$lockdir/pve-config-${vmid}.lock";
49}
50
51sub cfs_config_path {
52 my ($class, $vmid, $node) = @_;
53
54 $node = $nodename if !$node;
55 return "nodes/$node/lxc/$vmid.conf";
56}
57
1a8269bc 58sub mountpoint_backup_enabled {
ec7f0f09 59 my ($class, $mp_key, $mountpoint) = @_;
1a8269bc 60
dd4fa308
AL
61 my $enabled;
62 my $reason;
63
64 if ($mp_key eq 'rootfs') {
65 $enabled = 1;
66 $reason = 'rootfs';
67 } elsif ($mountpoint->{type} ne 'volume') {
68 $enabled = 0;
69 $reason = 'not a volume';
70 } elsif ($mountpoint->{backup}) {
71 $enabled = 1;
72 $reason = 'enabled';
73 } else {
74 $enabled = 0;
75 $reason = 'disabled';
76 }
77 return wantarray ? ($enabled, $reason) : $enabled;
1a8269bc
DM
78}
79
4518000b
FG
80sub has_feature {
81 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
82 my $err;
83
4a954457
FE
84 my $opts;
85 if ($feature eq 'copy' || $feature eq 'clone') {
86 $opts = {'valid_target_formats' => ['raw', 'subvol']};
87 }
88
015740e6 89 $class->foreach_volume($conf, sub {
4518000b
FG
90 my ($ms, $mountpoint) = @_;
91
92 return if $err; # skip further test
ec7f0f09 93 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
4518000b 94
a12caf82
TL
95 $err = 1 if !PVE::Storage::volume_has_feature(
96 $storecfg, $feature, $mountpoint->{volume}, $snapname, $running, $opts);
4518000b
FG
97 });
98
99 return $err ? 0 : 1;
100}
101
102sub __snapshot_save_vmstate {
103 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
104 die "implement me - snapshot_save_vmstate\n";
105}
106
7402b360
FE
107sub __snapshot_activate_storages {
108 my ($class, $conf, $include_vmstate) = @_;
109
110 my $storecfg = PVE::Storage::config();
111 my $opts = $include_vmstate ? { 'extra_keys' => ['vmstate'] } : {};
112 my $storage_hash = {};
113
114 $class->foreach_volume_full($conf, $opts, sub {
115 my ($vs, $mountpoint) = @_;
116
117 return if $mountpoint->{type} ne 'volume';
118
119 my ($storeid) = PVE::Storage::parse_volume_id($mountpoint->{volume});
120 $storage_hash->{$storeid} = 1;
121 });
122
123 PVE::Storage::activate_storage_list($storecfg, [ sort keys $storage_hash->%* ]);
124}
125
4518000b
FG
126sub __snapshot_check_running {
127 my ($class, $vmid) = @_;
128 return PVE::LXC::check_running($vmid);
129}
130
131sub __snapshot_check_freeze_needed {
132 my ($class, $vmid, $config, $save_vmstate) = @_;
133
134 my $ret = $class->__snapshot_check_running($vmid);
135 return ($ret, $ret);
136}
137
40cd2e89
SI
138# implements similar functionality to fsfreeze(8)
139sub fsfreeze_mountpoint {
140 my ($path, $thaw) = @_;
141
142 my $op = $thaw ? 'thaw' : 'freeze';
143 my $ioctl = $thaw ? FITHAW : FIFREEZE;
144
145 sysopen my $fd, $path, O_RDONLY or die "failed to open $path: $!\n";
146 my $ioctl_err;
147 if (!ioctl($fd, $ioctl, 0)) {
148 $ioctl_err = "$!";
149 }
150 close($fd);
151 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
152}
153
4518000b
FG
154sub __snapshot_freeze {
155 my ($class, $vmid, $unfreeze) = @_;
156
8463099d
SI
157 my $conf = $class->load_config($vmid);
158 my $storagecfg = PVE::Storage::config();
159
160 my $freeze_mps = [];
161 $class->foreach_volume($conf, sub {
162 my ($ms, $mountpoint) = @_;
163
7a8591e8
SI
164 return if $mountpoint->{type} ne 'volume';
165
8463099d
SI
166 if (PVE::Storage::volume_snapshot_needs_fsfreeze($storagecfg, $mountpoint->{volume})) {
167 push @$freeze_mps, $mountpoint->{mp};
168 }
169 });
170
171 my $freeze_mountpoints = sub {
172 my ($thaw) = @_;
173
174 return if scalar(@$freeze_mps) == 0;
175
176 my $pid = PVE::LXC::find_lxc_pid($vmid);
177
178 for my $mp (@$freeze_mps) {
179 eval{ fsfreeze_mountpoint("/proc/${pid}/root/${mp}", $thaw); };
180 warn $@ if $@;
181 }
182 };
183
4518000b 184 if ($unfreeze) {
89424a8b 185 eval { PVE::LXC::thaw($vmid); };
4518000b 186 warn $@ if $@;
8463099d 187 $freeze_mountpoints->(1);
4518000b 188 } else {
89424a8b 189 PVE::LXC::freeze($vmid);
4518000b 190 PVE::LXC::sync_container_namespace($vmid);
8463099d 191 $freeze_mountpoints->(0);
4518000b
FG
192 }
193}
194
195sub __snapshot_create_vol_snapshot {
196 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
197
198 my $storecfg = PVE::Storage::config();
199
1a8269bc 200 return if $snapname eq 'vzdump' &&
ec7f0f09 201 !$class->mountpoint_backup_enabled($ms, $mountpoint);
1a8269bc 202
4518000b
FG
203 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
204}
205
206sub __snapshot_delete_remove_drive {
207 my ($class, $snap, $remove_drive) = @_;
208
209 if ($remove_drive eq 'vmstate') {
210 die "implement me - saving vmstate\n";
211 } else {
212 my $value = $snap->{$remove_drive};
e4034859 213 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
4518000b 214 delete $snap->{$remove_drive};
d103721f
FG
215
216 $class->add_unused_volume($snap, $mountpoint->{volume})
db2b28c7 217 if $mountpoint && ($mountpoint->{type} eq 'volume');
4518000b
FG
218 }
219}
220
221sub __snapshot_delete_vmstate_file {
222 my ($class, $snap, $force) = @_;
223
224 die "implement me - saving vmstate\n";
225}
226
227sub __snapshot_delete_vol_snapshot {
a8e9a4ea 228 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
4518000b 229
d103721f
FG
230 return if $snapname eq 'vzdump' &&
231 !$class->mountpoint_backup_enabled($ms, $mountpoint);
232
4518000b
FG
233 my $storecfg = PVE::Storage::config();
234 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
a8e9a4ea 235 push @$unused, $mountpoint->{volume};
4518000b
FG
236}
237
238sub __snapshot_rollback_vol_possible {
199df356 239 my ($class, $mountpoint, $snapname, $blockers) = @_;
4518000b
FG
240
241 my $storecfg = PVE::Storage::config();
199df356
FE
242 PVE::Storage::volume_rollback_is_possible(
243 $storecfg,
244 $mountpoint->{volume},
245 $snapname,
246 $blockers,
247 );
4518000b
FG
248}
249
250sub __snapshot_rollback_vol_rollback {
251 my ($class, $mountpoint, $snapname) = @_;
252
253 my $storecfg = PVE::Storage::config();
254 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
255}
256
257sub __snapshot_rollback_vm_stop {
258 my ($class, $vmid) = @_;
259
b1bad293 260 PVE::LXC::vm_stop($vmid, 1)
4518000b
FG
261 if $class->__snapshot_check_running($vmid);
262}
263
264sub __snapshot_rollback_vm_start {
67779c3c 265 my ($class, $vmid, $vmstate, $data);
4518000b
FG
266
267 die "implement me - save vmstate\n";
268}
269
a8656869
FG
270sub __snapshot_rollback_get_unused {
271 my ($class, $conf, $snap) = @_;
272
273 my $unused = [];
274
5e5d76cf 275 $class->foreach_volume($conf, sub {
a8656869
FG
276 my ($vs, $volume) = @_;
277
278 return if $volume->{type} ne 'volume';
279
280 my $found = 0;
281 my $volid = $volume->{volume};
282
5e5d76cf 283 $class->foreach_volume($snap, sub {
a8656869
FG
284 my ($ms, $mountpoint) = @_;
285
286 return if $found;
287 return if ($mountpoint->{type} ne 'volume');
288
289 $found = 1
290 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
291 });
292
293 push @$unused, $volid if !$found;
294 });
295
296 return $unused;
297}
298
67afe46e
FG
299# END implemented abstract methods from PVE::AbstractConfig
300
1b4cf758
FG
301# BEGIN JSON config code
302
303cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
304
2bf24eb3 305
7a89429c 306my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
e80cb0cd
TL
307
308sub is_valid_mount_option {
309 my ($option) = @_;
310 return $option =~ $valid_mount_option_re;
2bf24eb3
OB
311}
312
1b4cf758
FG
313my $rootfs_desc = {
314 volume => {
315 type => 'string',
316 default_key => 1,
317 format => 'pve-lxc-mp-string',
318 format_description => 'volume',
319 description => 'Volume, device or directory to mount into the container.',
320 },
1b4cf758
FG
321 size => {
322 type => 'string',
323 format => 'disk-size',
324 format_description => 'DiskSize',
325 description => 'Volume size (read only value).',
326 optional => 1,
327 },
328 acl => {
329 type => 'boolean',
1b4cf758
FG
330 description => 'Explicitly enable or disable ACL support.',
331 optional => 1,
332 },
2bf24eb3
OB
333 mountoptions => {
334 optional => 1,
335 type => 'string',
336 description => 'Extra mount options for rootfs/mps.',
337 format_description => 'opt[;opt...]',
e80cb0cd 338 pattern => qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
2bf24eb3 339 },
1b4cf758
FG
340 ro => {
341 type => 'boolean',
235dbdf3 342 description => 'Read-only mount point',
1b4cf758
FG
343 optional => 1,
344 },
345 quota => {
346 type => 'boolean',
1b4cf758
FG
347 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
348 optional => 1,
349 },
76ec0820 350 replicate => {
f8aa3d35
WL
351 type => 'boolean',
352 description => 'Will include this volume to a storage replica job.',
353 optional => 1,
354 default => 1,
355 },
552e168f
FG
356 shared => {
357 type => 'boolean',
358 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
359 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!",
360 optional => 1,
361 default => 0,
362 },
1b4cf758
FG
363};
364
365PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
366 type => 'string', format => $rootfs_desc,
367 description => "Use volume as container root.",
368 optional => 1,
369});
370
08a58f12
WB
371# IP address with optional interface suffix for link local ipv6 addresses
372PVE::JSONSchema::register_format('lxc-ip-with-ll-iface', \&verify_ip_with_ll_iface);
373sub verify_ip_with_ll_iface {
374 my ($addr, $noerr) = @_;
375
376 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
377 if (PVE::JSONSchema::pve_verify_ip($addr, 1)
378 && PVE::JSONSchema::pve_verify_iface($iface, 1))
379 {
380 return $addr;
381 }
382 }
383
384 return PVE::JSONSchema::pve_verify_ip($addr, $noerr);
385}
386
387
5a63f1c5
WB
388my $features_desc = {
389 mount => {
390 optional => 1,
391 type => 'string',
392 description => "Allow mounting file systems of specific types."
393 ." This should be a list of file system types as used with the mount command."
394 ." Note that this can have negative effects on the container's security."
395 ." With access to a loop device, mounting a file can circumvent the mknod"
396 ." permission of the devices cgroup, mounting an NFS file system can"
397 ." block the host's I/O completely and prevent it from rebooting, etc.",
398 format_description => 'fstype;fstype;...',
e188f1bb 399 pattern => qr/[a-zA-Z0-9_; ]+/,
5a63f1c5
WB
400 },
401 nesting => {
402 optional => 1,
403 type => 'boolean',
404 default => 0,
405 description => "Allow nesting."
406 ." Best used with unprivileged containers with additional id mapping."
407 ." Note that this will expose procfs and sysfs contents of the host"
408 ." to the guest.",
409 },
410 keyctl => {
411 optional => 1,
412 type => 'boolean',
413 default => 0,
414 description => "For unprivileged containers only: Allow the use of the keyctl() system call."
415 ." This is required to use docker inside a container."
416 ." By default unprivileged containers will see this system call as non-existent."
417 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
418 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
419 ." Essentially, you can choose between running systemd-networkd or docker.",
420 },
96f8d2a2
WB
421 fuse => {
422 optional => 1,
423 type => 'boolean',
424 default => 0,
425 description => "Allow using 'fuse' file systems in a container."
426 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
427 },
2df08734
WB
428 mknod => {
429 optional => 1,
430 type => 'boolean',
431 default => 0,
432 description => "Allow unprivileged containers to use mknod() to add certain device nodes."
433 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
434 ." This is experimental.",
435 },
741b7737
TL
436 force_rw_sys => {
437 optional => 1,
438 type => 'boolean',
439 default => 0,
440 description => "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
441 ." This can break networking under newer (>= v245) systemd-network use."
442 },
5a63f1c5
WB
443};
444
1b4cf758
FG
445my $confdesc = {
446 lock => {
447 optional => 1,
448 type => 'string',
449 description => "Lock/unlock the VM.",
7fc1d9eb 450 enum => [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
1b4cf758
FG
451 },
452 onboot => {
453 optional => 1,
454 type => 'boolean',
455 description => "Specifies whether a VM will be started during system bootup.",
456 default => 0,
457 },
458 startup => get_standard_option('pve-startup-order'),
459 template => {
460 optional => 1,
461 type => 'boolean',
462 description => "Enable/disable Template.",
463 default => 0,
464 },
465 arch => {
466 optional => 1,
467 type => 'string',
e1d54a38 468 enum => ['amd64', 'i386', 'arm64', 'armhf'],
1b4cf758
FG
469 description => "OS architecture type.",
470 default => 'amd64',
471 },
472 ostype => {
473 optional => 1,
474 type => 'string',
6226d010 475 enum => [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
1b4cf758
FG
476 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.",
477 },
478 console => {
479 optional => 1,
480 type => 'boolean',
481 description => "Attach a console device (/dev/console) to the container.",
482 default => 1,
483 },
484 tty => {
485 optional => 1,
486 type => 'integer',
487 description => "Specify the number of tty available to the container",
488 minimum => 0,
489 maximum => 6,
490 default => 2,
491 },
f2357408
DM
492 cores => {
493 optional => 1,
494 type => 'integer',
495 description => "The number of cores assigned to the container. A container can use all available cores by default.",
496 minimum => 1,
a804f2d1 497 maximum => 8192,
f2357408 498 },
1b4cf758
FG
499 cpulimit => {
500 optional => 1,
501 type => 'number',
064529c3 502 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 503 minimum => 0,
a804f2d1 504 maximum => 8192,
1b4cf758
FG
505 default => 0,
506 },
507 cpuunits => {
508 optional => 1,
509 type => 'integer',
2dd0895a 510 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.",
1b4cf758
FG
511 minimum => 0,
512 maximum => 500000,
513 default => 1024,
514 },
515 memory => {
516 optional => 1,
517 type => 'integer',
518 description => "Amount of RAM for the VM in MB.",
519 minimum => 16,
520 default => 512,
521 },
522 swap => {
523 optional => 1,
524 type => 'integer',
525 description => "Amount of SWAP for the VM in MB.",
526 minimum => 0,
527 default => 512,
528 },
529 hostname => {
530 optional => 1,
531 description => "Set a host name for the container.",
532 type => 'string', format => 'dns-name',
533 maxLength => 255,
534 },
535 description => {
536 optional => 1,
537 type => 'string',
422e7a1b
TL
538 description => "Description for the Container. Shown in the web-interface CT's summary."
539 ." This is saved as comment inside the configuration file.",
540 maxLength => 1024 * 8,
1b4cf758
FG
541 },
542 searchdomain => {
543 optional => 1,
544 type => 'string', format => 'dns-name-list',
545 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
546 },
547 nameserver => {
548 optional => 1,
08a58f12 549 type => 'string', format => 'lxc-ip-with-ll-iface-list',
1b4cf758
FG
550 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.",
551 },
e6e308ae
OB
552 timezone => {
553 optional => 1,
554 type => 'string', format => 'pve-ct-timezone',
555 description => "Time zone to use in the container. If option isn't set, then nothing will be done. Can be set to 'host' to match the host time zone, or an arbitrary time zone option from /usr/share/zoneinfo/zone.tab",
556 },
1b4cf758
FG
557 rootfs => get_standard_option('pve-ct-rootfs'),
558 parent => {
559 optional => 1,
560 type => 'string', format => 'pve-configid',
561 maxLength => 40,
562 description => "Parent snapshot name. This is used internally, and should not be modified.",
563 },
564 snaptime => {
565 optional => 1,
566 description => "Timestamp for snapshots.",
567 type => 'integer',
568 minimum => 0,
569 },
570 cmode => {
571 optional => 1,
572 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).",
573 type => 'string',
574 enum => ['shell', 'console', 'tty'],
575 default => 'tty',
576 },
577 protection => {
578 optional => 1,
579 type => 'boolean',
580 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
581 default => 0,
582 },
583 unprivileged => {
584 optional => 1,
585 type => 'boolean',
586 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
587 default => 0,
588 },
5a63f1c5
WB
589 features => {
590 optional => 1,
591 type => 'string',
592 format => $features_desc,
593 description => "Allow containers access to advanced features.",
594 },
1a416433
DC
595 hookscript => {
596 optional => 1,
597 type => 'string',
598 format => 'pve-volume-id',
599 description => 'Script that will be exectued during various steps in the containers lifetime.',
600 },
733e52ec
DC
601 tags => {
602 type => 'string', format => 'pve-tag-list',
603 description => 'Tags of the Container. This is only meta information.',
604 optional => 1,
605 },
cc9967d2
TL
606 debug => {
607 optional => 1,
608 type => 'boolean',
609 description => "Try to be more verbose. For now this only enables debug log-level on start.",
610 default => 0,
611 },
1b4cf758
FG
612};
613
614my $valid_lxc_conf_keys = {
108c6cab
WB
615 'lxc.apparmor.profile' => 1,
616 'lxc.apparmor.allow_incomplete' => 1,
d494e03c
WB
617 'lxc.apparmor.allow_nesting' => 1,
618 'lxc.apparmor.raw' => 1,
108c6cab 619 'lxc.selinux.context' => 1,
1b4cf758
FG
620 'lxc.include' => 1,
621 'lxc.arch' => 1,
108c6cab
WB
622 'lxc.uts.name' => 1,
623 'lxc.signal.halt' => 1,
624 'lxc.signal.reboot' => 1,
625 'lxc.signal.stop' => 1,
626 'lxc.init.cmd' => 1,
627 'lxc.pty.max' => 1,
1b4cf758 628 'lxc.console.logfile' => 1,
108c6cab
WB
629 'lxc.console.path' => 1,
630 'lxc.tty.max' => 1,
631 'lxc.devtty.dir' => 1,
1b4cf758
FG
632 'lxc.hook.autodev' => 1,
633 'lxc.autodev' => 1,
634 'lxc.kmsg' => 1,
108c6cab 635 'lxc.mount.fstab' => 1,
1b4cf758
FG
636 'lxc.mount.entry' => 1,
637 'lxc.mount.auto' => 1,
108c6cab 638 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
1b4cf758
FG
639 'lxc.rootfs.mount' => 1,
640 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
235dbdf3 641 ', please use mount point options in the "rootfs" key',
1b4cf758 642 # lxc.cgroup.*
108c6cab 643 # lxc.prlimit.*
6eb23d1e 644 # lxc.net.*
1b4cf758
FG
645 'lxc.cap.drop' => 1,
646 'lxc.cap.keep' => 1,
108c6cab 647 'lxc.seccomp.profile' => 1,
832b4a02
WB
648 'lxc.seccomp.notify.proxy' => 1,
649 'lxc.seccomp.notify.cookie' => 1,
108c6cab 650 'lxc.idmap' => 1,
1b4cf758
FG
651 'lxc.hook.pre-start' => 1,
652 'lxc.hook.pre-mount' => 1,
653 'lxc.hook.mount' => 1,
654 'lxc.hook.start' => 1,
655 'lxc.hook.stop' => 1,
656 'lxc.hook.post-stop' => 1,
657 'lxc.hook.clone' => 1,
658 'lxc.hook.destroy' => 1,
f71db91b 659 'lxc.hook.version' => 1,
108c6cab
WB
660 'lxc.log.level' => 1,
661 'lxc.log.file' => 1,
1b4cf758
FG
662 'lxc.start.auto' => 1,
663 'lxc.start.delay' => 1,
664 'lxc.start.order' => 1,
665 'lxc.group' => 1,
666 'lxc.environment' => 1,
d4a135f7
WB
667
668 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
669 'lxc.sysctl.fs.mqueue' => 1,
670 'lxc.sysctl.kernel.msgmax' => 1,
671 'lxc.sysctl.kernel.msgmnb' => 1,
672 'lxc.sysctl.kernel.msgmni' => 1,
673 'lxc.sysctl.kernel.sem' => 1,
674 'lxc.sysctl.kernel.shmall' => 1,
675 'lxc.sysctl.kernel.shmmax' => 1,
676 'lxc.sysctl.kernel.shmmni' => 1,
677 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
1b4cf758
FG
678};
679
108c6cab
WB
680my $deprecated_lxc_conf_keys = {
681 # Deprecated (removed with lxc 3.0):
682 'lxc.aa_profile' => 'lxc.apparmor.profile',
683 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
684 'lxc.console' => 'lxc.console.path',
685 'lxc.devttydir' => 'lxc.tty.dir',
686 'lxc.haltsignal' => 'lxc.signal.halt',
687 'lxc.rebootsignal' => 'lxc.signal.reboot',
688 'lxc.stopsignal' => 'lxc.signal.stop',
689 'lxc.id_map' => 'lxc.idmap',
690 'lxc.init_cmd' => 'lxc.init.cmd',
691 'lxc.loglevel' => 'lxc.log.level',
692 'lxc.logfile' => 'lxc.log.file',
693 'lxc.mount' => 'lxc.mount.fstab',
694 'lxc.network.type' => 'lxc.net.INDEX.type',
695 'lxc.network.flags' => 'lxc.net.INDEX.flags',
696 'lxc.network.link' => 'lxc.net.INDEX.link',
697 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
698 'lxc.network.name' => 'lxc.net.INDEX.name',
699 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
700 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
701 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
702 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
703 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
704 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
705 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
706 'lxc.pts' => 'lxc.pty.max',
707 'lxc.se_context' => 'lxc.selinux.context',
708 'lxc.seccomp' => 'lxc.seccomp.profile',
709 'lxc.tty' => 'lxc.tty.max',
710 'lxc.utsname' => 'lxc.uts.name',
711};
712
713sub is_valid_lxc_conf_key {
714 my ($vmid, $key) = @_;
715 if ($key =~ /^lxc\.limit\./) {
716 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
717 return 1;
718 }
719 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
720 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
721 return 1;
722 }
723 my $validity = $valid_lxc_conf_keys->{$key};
724 return $validity if defined($validity);
979ea389 725 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
108c6cab
WB
726 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
727 || $key =~ /^lxc\.net\./; # allow custom network definitions
728 return 0;
729}
730
5e5915c5 731our $netconf_desc = {
1b4cf758
FG
732 type => {
733 type => 'string',
734 optional => 1,
735 description => "Network interface type.",
736 enum => [qw(veth)],
737 },
738 name => {
739 type => 'string',
a069f163
DM
740 format_description => 'string',
741 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
742 pattern => '[-_.\w\d]+',
743 },
744 bridge => {
745 type => 'string',
a069f163 746 format_description => 'bridge',
1b4cf758
FG
747 description => 'Bridge to attach the network device to.',
748 pattern => '[-_.\w\d]+',
749 optional => 1,
750 },
603536e0 751 hwaddr => get_standard_option('mac-addr', {
e6f20294 752 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)',
603536e0 753 }),
1b4cf758
FG
754 mtu => {
755 type => 'integer',
1b4cf758
FG
756 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
757 minimum => 64, # minimum ethernet frame is 64 bytes
758 optional => 1,
759 },
760 ip => {
761 type => 'string',
762 format => 'pve-ipv4-config',
718a67d6 763 format_description => '(IPv4/CIDR|dhcp|manual)',
1b4cf758
FG
764 description => 'IPv4 address in CIDR format.',
765 optional => 1,
766 },
767 gw => {
768 type => 'string',
769 format => 'ipv4',
770 format_description => 'GatewayIPv4',
771 description => 'Default gateway for IPv4 traffic.',
772 optional => 1,
773 },
774 ip6 => {
775 type => 'string',
776 format => 'pve-ipv6-config',
6ea7095c 777 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
1b4cf758
FG
778 description => 'IPv6 address in CIDR format.',
779 optional => 1,
780 },
781 gw6 => {
782 type => 'string',
783 format => 'ipv6',
784 format_description => 'GatewayIPv6',
785 description => 'Default gateway for IPv6 traffic.',
786 optional => 1,
787 },
788 firewall => {
789 type => 'boolean',
1b4cf758
FG
790 description => "Controls whether this interface's firewall rules should be used.",
791 optional => 1,
792 },
793 tag => {
794 type => 'integer',
6b202dd5
DM
795 minimum => 1,
796 maximum => 4094,
1b4cf758
FG
797 description => "VLAN tag for this interface.",
798 optional => 1,
799 },
800 trunks => {
801 type => 'string',
802 pattern => qr/\d+(?:;\d+)*/,
803 format_description => 'vlanid[;vlanid...]',
804 description => "VLAN ids to pass through the interface",
805 optional => 1,
806 },
380962c7
WB
807 rate => {
808 type => 'number',
809 format_description => 'mbps',
810 description => "Apply rate limiting to the interface",
811 optional => 1,
812 },
1b4cf758
FG
813};
814PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
815
6dd2d4cd 816my $MAX_LXC_NETWORKS = 32;
1b4cf758
FG
817for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
818 $confdesc->{"net$i"} = {
819 optional => 1,
820 type => 'string', format => $netconf_desc,
821 description => "Specifies network interfaces for the container.",
822 };
823}
824
e6e308ae
OB
825PVE::JSONSchema::register_format('pve-ct-timezone', \&verify_ct_timezone);
826sub verify_ct_timezone {
827 my ($timezone, $noerr) = @_;
828
829 return if $timezone eq 'host'; # using host settings
830
831 PVE::JSONSchema::pve_verify_timezone($timezone);
832}
833
1b4cf758
FG
834PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
835sub verify_lxc_mp_string {
836 my ($mp, $noerr) = @_;
837
838 # do not allow:
839 # /./ or /../
840 # /. or /.. at the end
841 # ../ at the beginning
842
843 if($mp =~ m@/\.\.?/@ ||
844 $mp =~ m@/\.\.?$@ ||
845 $mp =~ m@^\.\./@) {
846 return undef if $noerr;
847 die "$mp contains illegal character sequences\n";
848 }
849 return $mp;
850}
851
852my $mp_desc = {
853 %$rootfs_desc,
84820d40
DM
854 backup => {
855 type => 'boolean',
235dbdf3
FG
856 description => 'Whether to include the mount point in backups.',
857 verbose_description => 'Whether to include the mount point in backups '.
858 '(only used for volume mount points).',
84820d40
DM
859 optional => 1,
860 },
1b4cf758
FG
861 mp => {
862 type => 'string',
863 format => 'pve-lxc-mp-string',
864 format_description => 'Path',
235dbdf3 865 description => 'Path to the mount point as seen from inside the container '.
52b6f941 866 '(must not contain symlinks).',
235dbdf3 867 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
52b6f941 868 "NOTE: Must not contain any symlinks for security reasons."
1b4cf758
FG
869 },
870};
871PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
872
9dabb518
FE
873my $unused_desc = {
874 volume => {
875 type => 'string',
876 default_key => 1,
877 format => 'pve-volume-id',
878 format_description => 'volume',
879 description => 'The volume that is not used currently.',
880 }
1b4cf758
FG
881};
882
883for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
884 $confdesc->{"mp$i"} = {
885 optional => 1,
886 type => 'string', format => $mp_desc,
f2593307
FE
887 description => "Use volume as container mount point. Use the special " .
888 "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
1b4cf758
FG
889 optional => 1,
890 };
891}
892
9dabb518
FE
893for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
894 $confdesc->{"unused$i"} = {
895 optional => 1,
896 type => 'string', format => $unused_desc,
897 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
898 }
1b4cf758
FG
899}
900
901sub parse_pct_config {
3f031adb 902 my ($filename, $raw, $strict) = @_;
1b4cf758
FG
903
904 return undef if !defined($raw);
905
906 my $res = {
907 digest => Digest::SHA::sha1_hex($raw),
908 snapshots => {},
7547dc63 909 pending => {},
1b4cf758
FG
910 };
911
3f031adb
FG
912 my $handle_error = sub {
913 my ($msg) = @_;
914
915 if ($strict) {
916 die $msg;
917 } else {
918 warn $msg;
919 }
920 };
921
1b4cf758
FG
922 $filename =~ m|/lxc/(\d+).conf$|
923 || die "got strange filename '$filename'";
924
925 my $vmid = $1;
926
927 my $conf = $res;
928 my $descr = '';
929 my $section = '';
930
931 my @lines = split(/\n/, $raw);
932 foreach my $line (@lines) {
933 next if $line =~ m/^\s*$/;
934
7547dc63
OB
935 if ($line =~ m/^\[pve:pending\]\s*$/i) {
936 $section = 'pending';
937 $conf->{description} = $descr if $descr;
938 $descr = '';
939 $conf = $res->{$section} = {};
940 next;
941 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
1b4cf758
FG
942 $section = $1;
943 $conf->{description} = $descr if $descr;
944 $descr = '';
945 $conf = $res->{snapshots}->{$section} = {};
946 next;
947 }
948
6f0d5e63 949 if ($line =~ m/^\#(.*)$/) {
1b4cf758
FG
950 $descr .= PVE::Tools::decode_text($1) . "\n";
951 next;
952 }
953
954 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
955 my $key = $1;
956 my $value = $3;
108c6cab
WB
957 my $validity = is_valid_lxc_conf_key($vmid, $key);
958 if ($validity eq 1) {
1b4cf758
FG
959 push @{$conf->{lxc}}, [$key, $value];
960 } elsif (my $errmsg = $validity) {
3f031adb 961 $handle_error->("vm $vmid - $key: $errmsg\n");
1b4cf758 962 } else {
3f031adb 963 $handle_error->("vm $vmid - unable to parse config: $line\n");
1b4cf758
FG
964 }
965 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
966 $descr .= PVE::Tools::decode_text($2);
967 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
968 $conf->{snapstate} = $1;
7547dc63
OB
969 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
970 my $value = $1;
971 if ($section eq 'pending') {
972 $conf->{delete} = $value;
973 } else {
3f031adb 974 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
7547dc63 975 }
648529ba 976 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
1b4cf758
FG
977 my $key = $1;
978 my $value = $2;
979 eval { $value = PVE::LXC::Config->check_type($key, $value); };
3f031adb 980 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
1b4cf758
FG
981 $conf->{$key} = $value;
982 } else {
3f031adb 983 $handle_error->("vm $vmid - unable to parse config: $line\n");
1b4cf758
FG
984 }
985 }
986
987 $conf->{description} = $descr if $descr;
988
989 delete $res->{snapstate}; # just to be sure
990
991 return $res;
992}
993
994sub write_pct_config {
995 my ($filename, $conf) = @_;
996
997 delete $conf->{snapstate}; # just to be sure
998
999 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
1000 my $used_volids = {};
1001 foreach my $vid (@$volidlist) {
1002 $used_volids->{$vid} = 1;
1003 }
1004
1005 # remove 'unusedX' settings if the volume is still used
1006 foreach my $key (keys %$conf) {
1007 my $value = $conf->{$key};
1008 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1009 delete $conf->{$key};
1010 }
1011 }
1012
1013 my $generate_raw_config = sub {
1014 my ($conf) = @_;
1015
1016 my $raw = '';
1017
1018 # add description as comment to top of file
1019 my $descr = $conf->{description} || '';
1020 foreach my $cl (split(/\n/, $descr)) {
7547dc63 1021 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
1b4cf758
FG
1022 }
1023
1024 foreach my $key (sort keys %$conf) {
1025 next if $key eq 'digest' || $key eq 'description' ||
1026 $key eq 'pending' || $key eq 'snapshots' ||
1027 $key eq 'snapname' || $key eq 'lxc';
1028 my $value = $conf->{$key};
1029 die "detected invalid newline inside property '$key'\n"
1030 if $value =~ m/\n/;
1031 $raw .= "$key: $value\n";
1032 }
1033
1034 if (my $lxcconf = $conf->{lxc}) {
1035 foreach my $entry (@$lxcconf) {
1036 my ($k, $v) = @$entry;
1037 $raw .= "$k: $v\n";
1038 }
1039 }
1040
1041 return $raw;
1042 };
1043
1044 my $raw = &$generate_raw_config($conf);
1045
7547dc63
OB
1046 if (scalar(keys %{$conf->{pending}})){
1047 $raw .= "\n[pve:pending]\n";
1048 $raw .= &$generate_raw_config($conf->{pending});
1049 }
1050
1b4cf758
FG
1051 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
1052 $raw .= "\n[$snapname]\n";
1053 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
1054 }
1055
1056 return $raw;
1057}
1058
1059sub update_pct_config {
6517e001 1060 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1b4cf758 1061
6517e001 1062 my $storage_cfg = PVE::Storage::config();
1b4cf758 1063
6517e001
OB
1064 foreach my $opt (@$revert) {
1065 delete $conf->{pending}->{$opt};
1066 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1b4cf758
FG
1067 }
1068
6517e001
OB
1069 # write updates to pending section
1070 my $modified = {}; # record modified options
1b4cf758 1071
6517e001
OB
1072 foreach my $opt (@$delete) {
1073 if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
1074 warn "cannot delete '$opt' - not set in current configuration!\n";
1075 next;
1b4cf758 1076 }
6517e001
OB
1077 $modified->{$opt} = 1;
1078 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1079 die "unable to delete required option '$opt'\n";
1080 } elsif ($opt =~ m/^unused(\d+)$/) {
1081 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1082 } elsif ($opt =~ m/^mp(\d+)$/) {
1083 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1084 } elsif ($opt eq 'unprivileged') {
1085 die "unable to delete read-only option: '$opt'\n";
1b4cf758 1086 }
6517e001 1087 $class->add_to_pending_delete($conf, $opt);
1b4cf758
FG
1088 }
1089
1b3213ae
FG
1090 my $check_content_type = sub {
1091 my ($mp) = @_;
1092 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
6517e001 1093 my $storage_config = PVE::Storage::storage_config($storage_cfg, $sid);
1b3213ae
FG
1094 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1095 if !$storage_config->{content}->{rootdir};
1096 };
1b4cf758 1097
c10951f7 1098 foreach my $opt (sort keys %$param) { # add/change
6517e001 1099 $modified->{$opt} = 1;
1b4cf758 1100 my $value = $param->{$opt};
6517e001
OB
1101 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1102 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
e4034859 1103 my $mp = $class->parse_volume($opt, $value);
3927ae96 1104 $check_content_type->($mp) if ($mp->{type} eq 'volume');
6517e001
OB
1105 } elsif ($opt eq 'hookscript') {
1106 PVE::GuestHelpers::check_hookscript($value);
1b4cf758 1107 } elsif ($opt eq 'nameserver') {
6517e001 1108 $value = PVE::LXC::verify_nameserver_list($value);
1b4cf758 1109 } elsif ($opt eq 'searchdomain') {
6517e001 1110 $value = PVE::LXC::verify_searchdomain_list($value);
1b4cf758
FG
1111 } elsif ($opt eq 'unprivileged') {
1112 die "unable to modify read-only option: '$opt'\n";
1b4cf758 1113 }
6517e001
OB
1114 $conf->{pending}->{$opt} = $value;
1115 $class->remove_from_pending_delete($conf, $opt);
f8aa3d35
WL
1116 }
1117
6517e001 1118 my $changes = $class->cleanup_pending($conf);
1b4cf758 1119
6517e001
OB
1120 my $errors = {};
1121 if ($running) {
1122 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1123 } else {
1124 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1b4cf758
FG
1125 }
1126
6517e001 1127 return $errors;
1b4cf758
FG
1128}
1129
1130sub check_type {
1131 my ($class, $key, $value) = @_;
1132
1133 die "unknown setting '$key'\n" if !$confdesc->{$key};
1134
1135 my $type = $confdesc->{$key}->{type};
1136
1137 if (!defined($value)) {
1138 die "got undefined value\n";
1139 }
1140
1141 if ($value =~ m/[\n\r]/) {
1142 die "property contains a line feed\n";
1143 }
1144
1145 if ($type eq 'boolean') {
1146 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1147 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1148 die "type check ('boolean') failed - got '$value'\n";
1149 } elsif ($type eq 'integer') {
1150 return int($1) if $value =~ m/^(\d+)$/;
1151 die "type check ('integer') failed - got '$value'\n";
1152 } elsif ($type eq 'number') {
1153 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1154 die "type check ('number') failed - got '$value'\n";
1155 } elsif ($type eq 'string') {
1156 if (my $fmt = $confdesc->{$key}->{format}) {
1157 PVE::JSONSchema::check_format($fmt, $value);
1158 return $value;
1159 }
1160 return $value;
1161 } else {
1162 die "internal error"
1163 }
1164}
1165
1166
1167# add JSON properties for create and set function
1168sub json_config_properties {
1169 my ($class, $prop) = @_;
1170
1171 foreach my $opt (keys %$confdesc) {
1172 next if $opt eq 'parent' || $opt eq 'snaptime';
1173 next if $prop->{$opt};
1174 $prop->{$opt} = $confdesc->{$opt};
1175 }
1176
1177 return $prop;
1178}
1179
c0a17956 1180my $parse_ct_mountpoint_full = sub {
1b4cf758
FG
1181 my ($class, $desc, $data, $noerr) = @_;
1182
1183 $data //= '';
1184
1185 my $res;
1186 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1187 if ($@) {
1188 return undef if $noerr;
1189 die $@;
1190 }
1191
1192 if (defined(my $size = $res->{size})) {
1193 $size = PVE::JSONSchema::parse_size($size);
1194 if (!defined($size)) {
1195 return undef if $noerr;
1196 die "invalid size: $size\n";
1197 }
1198 $res->{size} = $size;
1199 }
1200
1201 $res->{type} = $class->classify_mountpoint($res->{volume});
1202
1203 return $res;
1204};
1205
1b4cf758
FG
1206sub print_ct_mountpoint {
1207 my ($class, $info, $nomp) = @_;
1208 my $skip = [ 'type' ];
1209 push @$skip, 'mp' if $nomp;
1210 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1211}
1212
a66c8869
FE
1213sub print_ct_unused {
1214 my ($class, $info) = @_;
1215
1216 my $skip = [ 'type' ];
1217 return PVE::JSONSchema::print_property_string($info, $unused_desc, $skip);
1218}
1219
5e5d76cf
FE
1220sub parse_volume {
1221 my ($class, $key, $volume_string, $noerr) = @_;
1222
1223 if ($key eq 'rootfs') {
c0a17956 1224 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
e4034859
FE
1225 $res->{mp} = '/' if defined($res);
1226 return $res;
9dabb518 1227 } elsif ($key =~ m/^mp\d+$/) {
c0a17956 1228 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
9dabb518
FE
1229 } elsif ($key =~ m/^unused\d+$/) {
1230 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
5e5d76cf
FE
1231 }
1232
c2be33b8
FE
1233 die "parse_volume - unknown type: $key\n" if !$noerr;
1234
1235 return;
5e5d76cf
FE
1236}
1237
1238sub print_volume {
1239 my ($class, $key, $volume) = @_;
1240
a66c8869
FE
1241 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1242
5e5d76cf
FE
1243 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1244}
1245
1246sub volid_key {
1247 my ($class) = @_;
1248
1249 return 'volume';
1250}
1251
1b4cf758
FG
1252sub print_lxc_network {
1253 my ($class, $net) = @_;
1254 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1255}
1256
1257sub parse_lxc_network {
1258 my ($class, $data) = @_;
1259
f89af842 1260 return {} if !$data;
1b4cf758 1261
f89af842 1262 my $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1b4cf758
FG
1263
1264 $res->{type} = 'veth';
2f19133b
WB
1265 if (!$res->{hwaddr}) {
1266 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1267 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1268 }
1b4cf758
FG
1269
1270 return $res;
1271}
1272
5a63f1c5
WB
1273sub parse_features {
1274 my ($class, $data) = @_;
1275 return {} if !$data;
1276 return PVE::JSONSchema::parse_property_string($features_desc, $data);
1277}
1278
1b4cf758
FG
1279sub option_exists {
1280 my ($class, $name) = @_;
1281
1282 return defined($confdesc->{$name});
1283}
1284# END JSON config code
1285
32e15a2b
OB
1286my $LXC_FASTPLUG_OPTIONS= {
1287 'description' => 1,
1288 'onboot' => 1,
1289 'startup' => 1,
1290 'protection' => 1,
1291 'hostname' => 1,
1292 'hookscript' => 1,
1293 'cores' => 1,
1294 'tags' => 1,
db15c375 1295 'lock' => 1,
32e15a2b
OB
1296};
1297
1298sub vmconfig_hotplug_pending {
1299 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1300
1301 my $pid = PVE::LXC::find_lxc_pid($vmid);
1302 my $rootdir = "/proc/$pid/root";
1303
1304 my $add_hotplug_error = sub {
1305 my ($opt, $msg) = @_;
1306 $errors->{$opt} = "unable to hotplug $opt: $msg";
1307 };
1308
c10951f7 1309 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
32e15a2b
OB
1310 next if $selection && !$selection->{$opt};
1311 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1312 $conf->{$opt} = delete $conf->{pending}->{$opt};
32e15a2b
OB
1313 }
1314 }
1315
2a4fddef
WB
1316 my $cgroup = PVE::LXC::CGroup->new($vmid);
1317
32e15a2b
OB
1318 # There's no separate swap size to configure, there's memory and "total"
1319 # memory (iow. memory+swap). This means we have to change them together.
1320 my $hotplug_memory_done;
1321 my $hotplug_memory = sub {
1322 my ($wanted_memory, $wanted_swap) = @_;
2a4fddef
WB
1323
1324 $wanted_memory = int($wanted_memory * 1024 * 1024) if defined($wanted_memory);
1325 $wanted_swap = int($wanted_swap * 1024 * 1024) if defined($wanted_swap);
1326 $cgroup->change_memory_limit($wanted_memory, $wanted_swap);
1327
32e15a2b
OB
1328 $hotplug_memory_done = 1;
1329 };
1330
1331 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1332 # FIXME: $force deletion is not implemented for CTs
8ec10817 1333 foreach my $opt (sort keys %$pending_delete_hash) {
32e15a2b
OB
1334 next if $selection && !$selection->{$opt};
1335 eval {
1336 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1337 # pass
1338 } elsif ($opt =~ m/^unused(\d+)$/) {
1339 PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
1340 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1341 } elsif ($opt eq 'swap') {
1342 $hotplug_memory->(undef, 0);
1343 } elsif ($opt eq 'cpulimit') {
04a62bd0 1344 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
32e15a2b 1345 } elsif ($opt eq 'cpuunits') {
389d00c9 1346 $cgroup->change_cpu_shares(undef, 1024);
32e15a2b
OB
1347 } elsif ($opt =~ m/^net(\d)$/) {
1348 my $netid = $1;
1349 PVE::Network::veth_delete("veth${vmid}i$netid");
1350 } else {
1351 die "skip\n"; # skip non-hotpluggable opts
1352 }
1353 };
1354 if (my $err = $@) {
1355 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1356 } else {
1357 delete $conf->{$opt};
1358 $class->remove_from_pending_delete($conf, $opt);
1359 }
1360 }
1361
c10951f7 1362 foreach my $opt (sort keys %{$conf->{pending}}) {
32e15a2b
OB
1363 next if $opt eq 'delete'; # just to be sure
1364 next if $selection && !$selection->{$opt};
1365 my $value = $conf->{pending}->{$opt};
1366 eval {
1367 if ($opt eq 'cpulimit') {
6e8ce610
WB
1368 my $quota = 100000 * $value;
1369 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
32e15a2b 1370 } elsif ($opt eq 'cpuunits') {
389d00c9 1371 $cgroup->change_cpu_shares($value, 1024);
32e15a2b
OB
1372 } elsif ($opt =~ m/^net(\d+)$/) {
1373 my $netid = $1;
1374 my $net = $class->parse_lxc_network($value);
7eff309b 1375 $value = $class->print_lxc_network($net);
32e15a2b
OB
1376 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1377 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1378 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1379 $hotplug_memory->($conf->{pending}->{memory}, $conf->{pending}->{swap});
1380 }
b2de4c04
WB
1381 } elsif ($opt =~ m/^mp(\d+)$/) {
1382 if (!PVE::LXC::Tools::can_use_new_mount_api()) {
1383 die "skip\n";
1384 }
1385
c7ce07e0
OB
1386 if (exists($conf->{$opt})) {
1387 die "skip\n"; # don't try to hotplug over existing mp
1388 }
1389
b2de4c04
WB
1390 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1391 # apply_pending_mountpoint modifies the value if it creates a new disk
1392 $value = $conf->{pending}->{$opt};
32e15a2b
OB
1393 } else {
1394 die "skip\n"; # skip non-hotpluggable
1395 }
1396 };
1397 if (my $err = $@) {
1398 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1399 } else {
1400 $conf->{$opt} = $value;
1401 delete $conf->{pending}->{$opt};
1402 }
1403 }
32e15a2b
OB
1404}
1405
1406sub vmconfig_apply_pending {
1407 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1408
1409 my $add_apply_error = sub {
1410 my ($opt, $msg) = @_;
1411 my $err_msg = "unable to apply pending change $opt : $msg";
1412 $errors->{$opt} = $err_msg;
1413 warn $err_msg;
1414 };
1415
32e15a2b
OB
1416 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1417 # FIXME: $force deletion is not implemented for CTs
8ec10817 1418 foreach my $opt (sort keys %$pending_delete_hash) {
32e15a2b 1419 next if $selection && !$selection->{$opt};
32e15a2b
OB
1420 eval {
1421 if ($opt =~ m/^mp(\d+)$/) {
e4034859 1422 my $mp = $class->parse_volume($opt, $conf->{$opt});
32e15a2b
OB
1423 if ($mp->{type} eq 'volume') {
1424 $class->add_unused_volume($conf, $mp->{volume})
1425 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1426 }
1427 } elsif ($opt =~ m/^unused(\d+)$/) {
1428 PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
1429 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1430 }
1431 };
1432 if (my $err = $@) {
1433 $add_apply_error->($opt, $err);
1434 } else {
1435 delete $conf->{$opt};
1436 $class->remove_from_pending_delete($conf, $opt);
1437 }
1438 }
1439
132c0a90
OB
1440 $class->cleanup_pending($conf);
1441
c10951f7 1442 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
32e15a2b
OB
1443 next if $opt eq 'delete'; # just to be sure
1444 next if $selection && !$selection->{$opt};
1445 eval {
1446 if ($opt =~ m/^mp(\d+)$/) {
869081a2 1447 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
7eff309b
OB
1448 } elsif ($opt =~ m/^net(\d+)$/) {
1449 my $netid = $1;
1450 my $net = $class->parse_lxc_network($conf->{pending}->{$opt});
1451 $conf->{pending}->{$opt} = $class->print_lxc_network($net);
32e15a2b
OB
1452 }
1453 };
1454 if (my $err = $@) {
1455 $add_apply_error->($opt, $err);
1456 } else {
32e15a2b
OB
1457 $conf->{$opt} = delete $conf->{pending}->{$opt};
1458 }
1459 }
32e15a2b
OB
1460}
1461
869081a2
WB
1462my $rescan_volume = sub {
1463 my ($storecfg, $mp) = @_;
1464 eval {
0c69dcfc 1465 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5);
869081a2
WB
1466 };
1467 warn "Could not rescan volume size - $@\n" if $@;
1468};
1469
1470sub apply_pending_mountpoint {
1471 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1472
e4034859 1473 my $mp = $class->parse_volume($opt, $conf->{pending}->{$opt});
869081a2 1474 my $old = $conf->{$opt};
ec99cbdc
FE
1475 if ($mp->{type} eq 'volume' && $mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) {
1476 my $original_value = $conf->{pending}->{$opt};
1477 my $vollist = PVE::LXC::create_disks(
1478 $storecfg,
1479 $vmid,
1480 { $opt => $original_value },
1481 $conf,
1482 1,
1483 );
1484 if ($running) {
1485 # Re-parse mount point:
1486 my $mp = $class->parse_volume($opt, $conf->{pending}->{$opt});
1487 eval {
b2de4c04 1488 PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg);
ec99cbdc
FE
1489 };
1490 my $err = $@;
1491 if ($err) {
1492 PVE::LXC::destroy_disks($storecfg, $vollist);
1493 # The pending-changes code collects errors but keeps on looping through further
1494 # pending changes, so unroll the change in $conf as well if destroy_disks()
1495 # didn't die().
1496 $conf->{pending}->{$opt} = $original_value;
1497 die $err;
b2de4c04 1498 }
869081a2 1499 }
ec99cbdc
FE
1500 } else {
1501 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1502 $rescan_volume->($storecfg, $mp) if $mp->{type} eq 'volume';
1503 if ($running) {
1504 PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg);
1505 }
1506 $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp);
869081a2
WB
1507 }
1508
1509 if (defined($old)) {
e4034859 1510 my $mp = $class->parse_volume($opt, $old);
869081a2
WB
1511 if ($mp->{type} eq 'volume') {
1512 $class->add_unused_volume($conf, $mp->{volume})
1513 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1514 }
1515 }
1516}
1517
d250604f
FG
1518sub classify_mountpoint {
1519 my ($class, $vol) = @_;
1520 if ($vol =~ m!^/!) {
1521 return 'device' if $vol =~ m!^/dev/!;
1522 return 'bind';
1523 }
1524 return 'volume';
1525}
1526
7b4237c5 1527my $__is_volume_in_use = sub {
72e6bc20 1528 my ($class, $config, $volid) = @_;
d250604f
FG
1529 my $used = 0;
1530
015740e6 1531 $class->foreach_volume($config, sub {
d250604f
FG
1532 my ($ms, $mountpoint) = @_;
1533 return if $used;
1534 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1535 });
1536
72e6bc20
WB
1537 return $used;
1538};
1539
1540sub is_volume_in_use_by_snapshots {
1541 my ($class, $config, $volid) = @_;
1542
1543 if (my $snapshots = $config->{snapshots}) {
d250604f 1544 foreach my $snap (keys %$snapshots) {
7b4237c5 1545 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
d250604f
FG
1546 }
1547 }
1548
72e6bc20 1549 return 0;
7b4237c5 1550}
72e6bc20
WB
1551
1552sub is_volume_in_use {
d063af00 1553 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
7b4237c5 1554 return 1 if $__is_volume_in_use->($class, $config, $volid);
72e6bc20 1555 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
d063af00 1556 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending}, $volid);
72e6bc20 1557 return 0;
d250604f
FG
1558}
1559
1560sub has_dev_console {
1561 my ($class, $conf) = @_;
1562
1563 return !(defined($conf->{console}) && !$conf->{console});
1564}
1565
be7942f0
DM
1566sub has_lxc_entry {
1567 my ($class, $conf, $keyname) = @_;
1568
1569 if (my $lxcconf = $conf->{lxc}) {
1570 foreach my $entry (@$lxcconf) {
1571 my ($key, undef) = @$entry;
1572 return 1 if $key eq $keyname;
1573 }
1574 }
1575
1576 return 0;
1577}
1578
1b4cf758
FG
1579sub get_tty_count {
1580 my ($class, $conf) = @_;
1581
1582 return $conf->{tty} // $confdesc->{tty}->{default};
1583}
1584
1585sub get_cmode {
1586 my ($class, $conf) = @_;
1587
1588 return $conf->{cmode} // $confdesc->{cmode}->{default};
1589}
1590
5e5d76cf 1591sub valid_volume_keys {
d250604f
FG
1592 my ($class, $reverse) = @_;
1593
1594 my @names = ('rootfs');
1595
1596 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1597 push @names, "mp$i";
1598 }
1599
1600 return $reverse ? reverse @names : @names;
1601}
1602
eacc42f0
AL
1603sub valid_volume_keys_with_unused {
1604 my ($class, $reverse) = @_;
1605 my @names = $class->valid_volume_keys();
1606 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1607 push @names, "unused$i";
1608 }
1609 return $reverse ? reverse @names : @names;
1610}
1611
d250604f
FG
1612sub get_vm_volumes {
1613 my ($class, $conf, $excludes) = @_;
1614
1615 my $vollist = [];
1616
015740e6 1617 $class->foreach_volume($conf, sub {
d250604f
FG
1618 my ($ms, $mountpoint) = @_;
1619
1620 return if $excludes && $ms eq $excludes;
1621
1622 my $volid = $mountpoint->{volume};
1623 return if !$volid || $mountpoint->{type} ne 'volume';
1624
1625 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1626 return if !$sid;
1627
1628 push @$vollist, $volid;
1629 });
1630
1631 return $vollist;
1632}
1633
f78c87a8 1634sub get_replicatable_volumes {
70996986 1635 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
f78c87a8
DM
1636
1637 my $volhash = {};
1638
1639 my $test_volid = sub {
1640 my ($volid, $mountpoint) = @_;
1641
1642 return if !$volid;
1643
5cf90b0c 1644 my $mptype = $mountpoint->{type};
896cd762 1645 my $replicate = $mountpoint->{replicate} // 1;
d2a046b7
DC
1646
1647 if ($mptype ne 'volume') {
1648 # skip bindmounts if replicate = 0 even for cleanup,
1649 # since bind mounts could not have been replicated ever
1650 return if !$replicate;
1651 die "unable to replicate mountpoint type '$mptype'\n";
1652 }
5cf90b0c
DM
1653
1654 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1655 return if !$storeid;
1656
af21e699 1657 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
5cf90b0c
DM
1658 return if $scfg->{shared};
1659
1660 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1661 return if !$owner || ($owner != $vmid);
1662
1663 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1664
d2a046b7 1665 return if !$cleanup && !$replicate;
f78c87a8
DM
1666
1667 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
e65dce6b 1668 return if $cleanup || $noerr;
f78c87a8
DM
1669 die "missing replicate feature on volume '$volid'\n";
1670 }
1671
1672 $volhash->{$volid} = 1;
1673 };
1674
015740e6 1675 $class->foreach_volume($conf, sub {
f78c87a8
DM
1676 my ($ms, $mountpoint) = @_;
1677 $test_volid->($mountpoint->{volume}, $mountpoint);
1678 });
1679
1680 foreach my $snapname (keys %{$conf->{snapshots}}) {
1681 my $snap = $conf->{snapshots}->{$snapname};
015740e6 1682 $class->foreach_volume($snap, sub {
f78c87a8
DM
1683 my ($ms, $mountpoint) = @_;
1684 $test_volid->($mountpoint->{volume}, $mountpoint);
1685 });
1686 }
1687
b03664ae
DM
1688 # add 'unusedX' volumes to volhash
1689 foreach my $key (keys %$conf) {
1690 if ($key =~ m/^unused/) {
1691 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1692 }
1693 }
1694
f78c87a8
DM
1695 return $volhash;
1696}
1697
efd1706d
AL
1698sub get_backup_volumes {
1699 my ($class, $conf) = @_;
1700
1701 my $return_volumes = [];
1702
1703 my $test_mountpoint = sub {
1704 my ($key, $volume) = @_;
1705
1706 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1707
efd1706d
AL
1708 push @$return_volumes, {
1709 key => $key,
1710 included => $included,
1711 reason => $reason,
1712 volume_config => $volume,
1713 };
1714 };
1715
1716 PVE::LXC::Config->foreach_volume($conf, $test_mountpoint);
1717
1718 return $return_volumes;
1719}
1720
f78c87a8 17211;