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