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