]> git.proxmox.com Git - pve-container.git/blame_incremental - src/PVE/LXC/Config.pm
Use foreach_volume instead of foreach_mountpoint-variants
[pve-container.git] / src / PVE / LXC / Config.pm
... / ...
CommitLineData
1package PVE::LXC::Config;
2
3use strict;
4use warnings;
5
6use PVE::AbstractConfig;
7use PVE::Cluster qw(cfs_register_file);
8use PVE::DataCenterConfig;
9use PVE::GuestHelpers;
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";
21my $MAX_MOUNT_POINTS = 256;
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
30sub __config_max_unused_disks {
31 my ($class) = @_;
32
33 return $MAX_UNUSED_DISKS;
34}
35
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
49sub mountpoint_backup_enabled {
50 my ($class, $mp_key, $mountpoint) = @_;
51
52 return 1 if $mp_key eq 'rootfs';
53
54 return 0 if $mountpoint->{type} ne 'volume';
55
56 return 1 if $mountpoint->{backup};
57
58 return 0;
59}
60
61sub has_feature {
62 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
63 my $err;
64
65 my $opts;
66 if ($feature eq 'copy' || $feature eq 'clone') {
67 $opts = {'valid_target_formats' => ['raw', 'subvol']};
68 }
69
70 $class->foreach_volume($conf, sub {
71 my ($ms, $mountpoint) = @_;
72
73 return if $err; # skip further test
74 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
75
76 $err = 1
77 if !PVE::Storage::volume_has_feature($storecfg, $feature,
78 $mountpoint->{volume},
79 $snapname, $running, $opts);
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) {
106 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
107 warn $@ if $@;
108 } else {
109 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
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
119 return if $snapname eq 'vzdump' &&
120 !$class->mountpoint_backup_enabled($ms, $mountpoint);
121
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};
132 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
133 delete $snap->{$remove_drive};
134
135 $class->add_unused_volume($snap, $mountpoint->{volume})
136 if ($mountpoint->{type} eq 'volume');
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 {
147 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
148
149 return if $snapname eq 'vzdump' &&
150 !$class->mountpoint_backup_enabled($ms, $mountpoint);
151
152 my $storecfg = PVE::Storage::config();
153 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
154 push @$unused, $mountpoint->{volume};
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
174 PVE::LXC::vm_stop($vmid, 1)
175 if $class->__snapshot_check_running($vmid);
176}
177
178sub __snapshot_rollback_vm_start {
179 my ($class, $vmid, $vmstate, $data);
180
181 die "implement me - save vmstate\n";
182}
183
184sub __snapshot_rollback_get_unused {
185 my ($class, $conf, $snap) = @_;
186
187 my $unused = [];
188
189 $class->foreach_volume($conf, sub {
190 my ($vs, $volume) = @_;
191
192 return if $volume->{type} ne 'volume';
193
194 my $found = 0;
195 my $volid = $volume->{volume};
196
197 $class->foreach_volume($snap, sub {
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
213# END implemented abstract methods from PVE::AbstractConfig
214
215# BEGIN JSON config code
216
217cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
218
219
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;
225}
226
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 },
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',
244 description => 'Explicitly enable or disable ACL support.',
245 optional => 1,
246 },
247 mountoptions => {
248 optional => 1,
249 type => 'string',
250 description => 'Extra mount options for rootfs/mps.',
251 format_description => 'opt[;opt...]',
252 pattern => qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
253 },
254 ro => {
255 type => 'boolean',
256 description => 'Read-only mount point',
257 optional => 1,
258 },
259 quota => {
260 type => 'boolean',
261 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
262 optional => 1,
263 },
264 replicate => {
265 type => 'boolean',
266 description => 'Will include this volume to a storage replica job.',
267 optional => 1,
268 default => 1,
269 },
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 },
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
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;...',
296 pattern => qr/[a-zA-Z0-9_; ]+/,
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 },
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 },
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 },
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 },
340};
341
342my $confdesc = {
343 lock => {
344 optional => 1,
345 type => 'string',
346 description => "Lock/unlock the VM.",
347 enum => [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
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',
365 enum => ['amd64', 'i386', 'arm64', 'armhf'],
366 description => "OS architecture type.",
367 default => 'amd64',
368 },
369 ostype => {
370 optional => 1,
371 type => 'string',
372 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
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 },
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 },
396 cpulimit => {
397 optional => 1,
398 type => 'number',
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.",
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',
435 description => "Container description. Only used on the configuration web interface.",
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 },
479 features => {
480 optional => 1,
481 type => 'string',
482 format => $features_desc,
483 description => "Allow containers access to advanced features.",
484 },
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 },
491 tags => {
492 type => 'string', format => 'pve-tag-list',
493 description => 'Tags of the Container. This is only meta information.',
494 optional => 1,
495 },
496};
497
498my $valid_lxc_conf_keys = {
499 'lxc.apparmor.profile' => 1,
500 'lxc.apparmor.allow_incomplete' => 1,
501 'lxc.apparmor.allow_nesting' => 1,
502 'lxc.apparmor.raw' => 1,
503 'lxc.selinux.context' => 1,
504 'lxc.include' => 1,
505 'lxc.arch' => 1,
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,
512 'lxc.console.logfile' => 1,
513 'lxc.console.path' => 1,
514 'lxc.tty.max' => 1,
515 'lxc.devtty.dir' => 1,
516 'lxc.hook.autodev' => 1,
517 'lxc.autodev' => 1,
518 'lxc.kmsg' => 1,
519 'lxc.mount.fstab' => 1,
520 'lxc.mount.entry' => 1,
521 'lxc.mount.auto' => 1,
522 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
523 'lxc.rootfs.mount' => 1,
524 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
525 ', please use mount point options in the "rootfs" key',
526 # lxc.cgroup.*
527 # lxc.prlimit.*
528 # lxc.net.*
529 'lxc.cap.drop' => 1,
530 'lxc.cap.keep' => 1,
531 'lxc.seccomp.profile' => 1,
532 'lxc.seccomp.notify.proxy' => 1,
533 'lxc.seccomp.notify.cookie' => 1,
534 'lxc.idmap' => 1,
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,
543 'lxc.hook.version' => 1,
544 'lxc.log.level' => 1,
545 'lxc.log.file' => 1,
546 'lxc.start.auto' => 1,
547 'lxc.start.delay' => 1,
548 'lxc.start.order' => 1,
549 'lxc.group' => 1,
550 'lxc.environment' => 1,
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,
562};
563
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);
609 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
610 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
611 || $key =~ /^lxc\.net\./; # allow custom network definitions
612 return 0;
613}
614
615our $netconf_desc = {
616 type => {
617 type => 'string',
618 optional => 1,
619 description => "Network interface type.",
620 enum => [qw(veth)],
621 },
622 name => {
623 type => 'string',
624 format_description => 'string',
625 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
626 pattern => '[-_.\w\d]+',
627 },
628 bridge => {
629 type => 'string',
630 format_description => 'bridge',
631 description => 'Bridge to attach the network device to.',
632 pattern => '[-_.\w\d]+',
633 optional => 1,
634 },
635 hwaddr => get_standard_option('mac-addr', {
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)',
637 }),
638 mtu => {
639 type => 'integer',
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',
647 format_description => '(IPv4/CIDR|dhcp|manual)',
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',
661 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
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',
674 description => "Controls whether this interface's firewall rules should be used.",
675 optional => 1,
676 },
677 tag => {
678 type => 'integer',
679 minimum => 1,
680 maximum => 4094,
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 },
691 rate => {
692 type => 'number',
693 format_description => 'mbps',
694 description => "Apply rate limiting to the interface",
695 optional => 1,
696 },
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,
729 backup => {
730 type => 'boolean',
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).',
734 optional => 1,
735 },
736 mp => {
737 type => 'string',
738 format => 'pve-lxc-mp-string',
739 format_description => 'Path',
740 description => 'Path to the mount point as seen from inside the container '.
741 '(must not contain symlinks).',
742 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
743 "NOTE: Must not contain any symlinks for security reasons."
744 },
745};
746PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
747
748my $unuseddesc = {
749 optional => 1,
750 type => 'string', format => 'pve-volume-id',
751 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
752};
753
754for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
755 $confdesc->{"mp$i"} = {
756 optional => 1,
757 type => 'string', format => $mp_desc,
758 description => "Use volume as container mount point.",
759 optional => 1,
760 };
761}
762
763for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
764 $confdesc->{"unused$i"} = $unuseddesc;
765}
766
767sub parse_pct_config {
768 my ($filename, $raw) = @_;
769
770 return undef if !defined($raw);
771
772 my $res = {
773 digest => Digest::SHA::sha1_hex($raw),
774 snapshots => {},
775 pending => {},
776 };
777
778 $filename =~ m|/lxc/(\d+).conf$|
779 || die "got strange filename '$filename'";
780
781 my $vmid = $1;
782
783 my $conf = $res;
784 my $descr = '';
785 my $section = '';
786
787 my @lines = split(/\n/, $raw);
788 foreach my $line (@lines) {
789 next if $line =~ m/^\s*$/;
790
791 if ($line =~ m/^\[pve:pending\]\s*$/i) {
792 $section = 'pending';
793 $conf->{description} = $descr if $descr;
794 $descr = '';
795 $conf = $res->{$section} = {};
796 next;
797 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
798 $section = $1;
799 $conf->{description} = $descr if $descr;
800 $descr = '';
801 $conf = $res->{snapshots}->{$section} = {};
802 next;
803 }
804
805 if ($line =~ m/^\#(.*)\s*$/) {
806 $descr .= PVE::Tools::decode_text($1) . "\n";
807 next;
808 }
809
810 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
811 my $key = $1;
812 my $value = $3;
813 my $validity = is_valid_lxc_conf_key($vmid, $key);
814 if ($validity eq 1) {
815 push @{$conf->{lxc}}, [$key, $value];
816 } elsif (my $errmsg = $validity) {
817 warn "vm $vmid - $key: $errmsg\n";
818 } else {
819 warn "vm $vmid - unable to parse config: $line\n";
820 }
821 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
822 $descr .= PVE::Tools::decode_text($2);
823 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
824 $conf->{snapstate} = $1;
825 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
826 my $value = $1;
827 if ($section eq 'pending') {
828 $conf->{delete} = $value;
829 } else {
830 warn "vm $vmid - property 'delete' is only allowed in [pve:pending]\n";
831 }
832 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
833 my $key = $1;
834 my $value = $2;
835 eval { $value = PVE::LXC::Config->check_type($key, $value); };
836 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
837 $conf->{$key} = $value;
838 } else {
839 warn "vm $vmid - unable to parse config: $line\n";
840 }
841 }
842
843 $conf->{description} = $descr if $descr;
844
845 delete $res->{snapstate}; # just to be sure
846
847 return $res;
848}
849
850sub write_pct_config {
851 my ($filename, $conf) = @_;
852
853 delete $conf->{snapstate}; # just to be sure
854
855 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
856 my $used_volids = {};
857 foreach my $vid (@$volidlist) {
858 $used_volids->{$vid} = 1;
859 }
860
861 # remove 'unusedX' settings if the volume is still used
862 foreach my $key (keys %$conf) {
863 my $value = $conf->{$key};
864 if ($key =~ m/^unused/ && $used_volids->{$value}) {
865 delete $conf->{$key};
866 }
867 }
868
869 my $generate_raw_config = sub {
870 my ($conf) = @_;
871
872 my $raw = '';
873
874 # add description as comment to top of file
875 my $descr = $conf->{description} || '';
876 foreach my $cl (split(/\n/, $descr)) {
877 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
878 }
879
880 foreach my $key (sort keys %$conf) {
881 next if $key eq 'digest' || $key eq 'description' ||
882 $key eq 'pending' || $key eq 'snapshots' ||
883 $key eq 'snapname' || $key eq 'lxc';
884 my $value = $conf->{$key};
885 die "detected invalid newline inside property '$key'\n"
886 if $value =~ m/\n/;
887 $raw .= "$key: $value\n";
888 }
889
890 if (my $lxcconf = $conf->{lxc}) {
891 foreach my $entry (@$lxcconf) {
892 my ($k, $v) = @$entry;
893 $raw .= "$k: $v\n";
894 }
895 }
896
897 return $raw;
898 };
899
900 my $raw = &$generate_raw_config($conf);
901
902 if (scalar(keys %{$conf->{pending}})){
903 $raw .= "\n[pve:pending]\n";
904 $raw .= &$generate_raw_config($conf->{pending});
905 }
906
907 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
908 $raw .= "\n[$snapname]\n";
909 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
910 }
911
912 return $raw;
913}
914
915sub update_pct_config {
916 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
917
918 my $storage_cfg = PVE::Storage::config();
919
920 foreach my $opt (@$revert) {
921 delete $conf->{pending}->{$opt};
922 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
923 }
924
925 # write updates to pending section
926 my $modified = {}; # record modified options
927
928 foreach my $opt (@$delete) {
929 if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
930 warn "cannot delete '$opt' - not set in current configuration!\n";
931 next;
932 }
933 $modified->{$opt} = 1;
934 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
935 die "unable to delete required option '$opt'\n";
936 } elsif ($opt =~ m/^unused(\d+)$/) {
937 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
938 } elsif ($opt =~ m/^mp(\d+)$/) {
939 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
940 } elsif ($opt eq 'unprivileged') {
941 die "unable to delete read-only option: '$opt'\n";
942 }
943 $class->add_to_pending_delete($conf, $opt);
944 }
945
946 my $check_content_type = sub {
947 my ($mp) = @_;
948 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
949 my $storage_config = PVE::Storage::storage_config($storage_cfg, $sid);
950 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
951 if !$storage_config->{content}->{rootdir};
952 };
953
954 foreach my $opt (sort keys %$param) { # add/change
955 $modified->{$opt} = 1;
956 my $value = $param->{$opt};
957 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
958 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
959 my $mp = $opt eq 'rootfs' ? $class->parse_ct_rootfs($value) : $class->parse_ct_mountpoint($value);
960 $check_content_type->($mp) if ($mp->{type} eq 'volume');
961 } elsif ($opt eq 'hookscript') {
962 PVE::GuestHelpers::check_hookscript($value);
963 } elsif ($opt eq 'nameserver') {
964 $value = PVE::LXC::verify_nameserver_list($value);
965 } elsif ($opt eq 'searchdomain') {
966 $value = PVE::LXC::verify_searchdomain_list($value);
967 } elsif ($opt eq 'unprivileged') {
968 die "unable to modify read-only option: '$opt'\n";
969 }
970 $conf->{pending}->{$opt} = $value;
971 $class->remove_from_pending_delete($conf, $opt);
972 }
973
974 my $changes = $class->cleanup_pending($conf);
975
976 my $errors = {};
977 if ($running) {
978 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
979 } else {
980 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
981 }
982
983 return $errors;
984}
985
986sub check_type {
987 my ($class, $key, $value) = @_;
988
989 die "unknown setting '$key'\n" if !$confdesc->{$key};
990
991 my $type = $confdesc->{$key}->{type};
992
993 if (!defined($value)) {
994 die "got undefined value\n";
995 }
996
997 if ($value =~ m/[\n\r]/) {
998 die "property contains a line feed\n";
999 }
1000
1001 if ($type eq 'boolean') {
1002 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1003 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1004 die "type check ('boolean') failed - got '$value'\n";
1005 } elsif ($type eq 'integer') {
1006 return int($1) if $value =~ m/^(\d+)$/;
1007 die "type check ('integer') failed - got '$value'\n";
1008 } elsif ($type eq 'number') {
1009 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1010 die "type check ('number') failed - got '$value'\n";
1011 } elsif ($type eq 'string') {
1012 if (my $fmt = $confdesc->{$key}->{format}) {
1013 PVE::JSONSchema::check_format($fmt, $value);
1014 return $value;
1015 }
1016 return $value;
1017 } else {
1018 die "internal error"
1019 }
1020}
1021
1022
1023# add JSON properties for create and set function
1024sub json_config_properties {
1025 my ($class, $prop) = @_;
1026
1027 foreach my $opt (keys %$confdesc) {
1028 next if $opt eq 'parent' || $opt eq 'snaptime';
1029 next if $prop->{$opt};
1030 $prop->{$opt} = $confdesc->{$opt};
1031 }
1032
1033 return $prop;
1034}
1035
1036sub __parse_ct_mountpoint_full {
1037 my ($class, $desc, $data, $noerr) = @_;
1038
1039 $data //= '';
1040
1041 my $res;
1042 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1043 if ($@) {
1044 return undef if $noerr;
1045 die $@;
1046 }
1047
1048 if (defined(my $size = $res->{size})) {
1049 $size = PVE::JSONSchema::parse_size($size);
1050 if (!defined($size)) {
1051 return undef if $noerr;
1052 die "invalid size: $size\n";
1053 }
1054 $res->{size} = $size;
1055 }
1056
1057 $res->{type} = $class->classify_mountpoint($res->{volume});
1058
1059 return $res;
1060};
1061
1062sub parse_ct_rootfs {
1063 my ($class, $data, $noerr) = @_;
1064
1065 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1066
1067 $res->{mp} = '/' if defined($res);
1068
1069 return $res;
1070}
1071
1072sub parse_ct_mountpoint {
1073 my ($class, $data, $noerr) = @_;
1074
1075 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1076}
1077
1078sub print_ct_mountpoint {
1079 my ($class, $info, $nomp) = @_;
1080 my $skip = [ 'type' ];
1081 push @$skip, 'mp' if $nomp;
1082 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1083}
1084
1085sub parse_volume {
1086 my ($class, $key, $volume_string, $noerr) = @_;
1087
1088 if ($key eq 'rootfs') {
1089 return $class->parse_ct_rootfs($volume_string, $noerr);
1090 } elsif ($key =~ m/^mp\d+$/ || $key =~ m/^unused\d+$/) {
1091 return $class->parse_ct_mountpoint($volume_string, $noerr);
1092 }
1093
1094 die "parse_volume - unknown type: $key\n";
1095}
1096
1097sub print_volume {
1098 my ($class, $key, $volume) = @_;
1099
1100 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1101}
1102
1103sub volid_key {
1104 my ($class) = @_;
1105
1106 return 'volume';
1107}
1108
1109sub print_lxc_network {
1110 my ($class, $net) = @_;
1111 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1112}
1113
1114sub parse_lxc_network {
1115 my ($class, $data) = @_;
1116
1117 my $res = {};
1118
1119 return $res if !$data;
1120
1121 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1122
1123 $res->{type} = 'veth';
1124 if (!$res->{hwaddr}) {
1125 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1126 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1127 }
1128
1129 return $res;
1130}
1131
1132sub parse_features {
1133 my ($class, $data) = @_;
1134 return {} if !$data;
1135 return PVE::JSONSchema::parse_property_string($features_desc, $data);
1136}
1137
1138sub option_exists {
1139 my ($class, $name) = @_;
1140
1141 return defined($confdesc->{$name});
1142}
1143# END JSON config code
1144
1145my $LXC_FASTPLUG_OPTIONS= {
1146 'description' => 1,
1147 'onboot' => 1,
1148 'startup' => 1,
1149 'protection' => 1,
1150 'hostname' => 1,
1151 'hookscript' => 1,
1152 'cores' => 1,
1153 'tags' => 1,
1154 'lock' => 1,
1155};
1156
1157sub vmconfig_hotplug_pending {
1158 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1159
1160 my $pid = PVE::LXC::find_lxc_pid($vmid);
1161 my $rootdir = "/proc/$pid/root";
1162
1163 my $add_hotplug_error = sub {
1164 my ($opt, $msg) = @_;
1165 $errors->{$opt} = "unable to hotplug $opt: $msg";
1166 };
1167
1168 my $changes;
1169 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
1170 next if $selection && !$selection->{$opt};
1171 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1172 $conf->{$opt} = delete $conf->{pending}->{$opt};
1173 $changes = 1;
1174 }
1175 }
1176
1177 if ($changes) {
1178 $class->write_config($vmid, $conf);
1179 }
1180
1181 my $cgroup = PVE::LXC::CGroup->new($vmid);
1182
1183 # There's no separate swap size to configure, there's memory and "total"
1184 # memory (iow. memory+swap). This means we have to change them together.
1185 my $hotplug_memory_done;
1186 my $hotplug_memory = sub {
1187 my ($wanted_memory, $wanted_swap) = @_;
1188
1189 $wanted_memory = int($wanted_memory * 1024 * 1024) if defined($wanted_memory);
1190 $wanted_swap = int($wanted_swap * 1024 * 1024) if defined($wanted_swap);
1191 $cgroup->change_memory_limit($wanted_memory, $wanted_swap);
1192
1193 $hotplug_memory_done = 1;
1194 };
1195
1196 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1197 # FIXME: $force deletion is not implemented for CTs
1198 foreach my $opt (sort keys %$pending_delete_hash) {
1199 next if $selection && !$selection->{$opt};
1200 eval {
1201 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1202 # pass
1203 } elsif ($opt =~ m/^unused(\d+)$/) {
1204 PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
1205 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1206 } elsif ($opt eq 'swap') {
1207 $hotplug_memory->(undef, 0);
1208 } elsif ($opt eq 'cpulimit') {
1209 $cgroup->change_cpu_quota(-1, 100000);
1210 } elsif ($opt eq 'cpuunits') {
1211 $cgroup->change_cpu_shares(undef, $confdesc->{cpuunits}->{default});
1212 } elsif ($opt =~ m/^net(\d)$/) {
1213 my $netid = $1;
1214 PVE::Network::veth_delete("veth${vmid}i$netid");
1215 } else {
1216 die "skip\n"; # skip non-hotpluggable opts
1217 }
1218 };
1219 if (my $err = $@) {
1220 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1221 } else {
1222 delete $conf->{$opt};
1223 $class->remove_from_pending_delete($conf, $opt);
1224 }
1225 }
1226
1227 foreach my $opt (sort keys %{$conf->{pending}}) {
1228 next if $opt eq 'delete'; # just to be sure
1229 next if $selection && !$selection->{$opt};
1230 my $value = $conf->{pending}->{$opt};
1231 eval {
1232 if ($opt eq 'cpulimit') {
1233 my $quota = 100000 * $value;
1234 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1235 } elsif ($opt eq 'cpuunits') {
1236 $cgroup->change_cpu_shares($value, $confdesc->{cpuunits}->{default});
1237 } elsif ($opt =~ m/^net(\d+)$/) {
1238 my $netid = $1;
1239 my $net = $class->parse_lxc_network($value);
1240 $value = $class->print_lxc_network($net);
1241 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1242 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1243 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1244 $hotplug_memory->($conf->{pending}->{memory}, $conf->{pending}->{swap});
1245 }
1246 } elsif ($opt =~ m/^mp(\d+)$/) {
1247 if (!PVE::LXC::Tools::can_use_new_mount_api()) {
1248 die "skip\n";
1249 }
1250
1251 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1252 # apply_pending_mountpoint modifies the value if it creates a new disk
1253 $value = $conf->{pending}->{$opt};
1254 } else {
1255 die "skip\n"; # skip non-hotpluggable
1256 }
1257 };
1258 if (my $err = $@) {
1259 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1260 } else {
1261 $conf->{$opt} = $value;
1262 delete $conf->{pending}->{$opt};
1263 }
1264 }
1265
1266 $class->write_config($vmid, $conf);
1267}
1268
1269sub vmconfig_apply_pending {
1270 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1271
1272 my $add_apply_error = sub {
1273 my ($opt, $msg) = @_;
1274 my $err_msg = "unable to apply pending change $opt : $msg";
1275 $errors->{$opt} = $err_msg;
1276 warn $err_msg;
1277 };
1278
1279 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1280 # FIXME: $force deletion is not implemented for CTs
1281 foreach my $opt (sort keys %$pending_delete_hash) {
1282 next if $selection && !$selection->{$opt};
1283 eval {
1284 if ($opt =~ m/^mp(\d+)$/) {
1285 my $mp = $class->parse_ct_mountpoint($conf->{$opt});
1286 if ($mp->{type} eq 'volume') {
1287 $class->add_unused_volume($conf, $mp->{volume})
1288 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1289 }
1290 } elsif ($opt =~ m/^unused(\d+)$/) {
1291 PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
1292 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1293 }
1294 };
1295 if (my $err = $@) {
1296 $add_apply_error->($opt, $err);
1297 } else {
1298 delete $conf->{$opt};
1299 $class->remove_from_pending_delete($conf, $opt);
1300 }
1301 }
1302
1303 $class->cleanup_pending($conf);
1304
1305 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
1306 next if $opt eq 'delete'; # just to be sure
1307 next if $selection && !$selection->{$opt};
1308 eval {
1309 if ($opt =~ m/^mp(\d+)$/) {
1310 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1311 } elsif ($opt =~ m/^net(\d+)$/) {
1312 my $netid = $1;
1313 my $net = $class->parse_lxc_network($conf->{pending}->{$opt});
1314 $conf->{pending}->{$opt} = $class->print_lxc_network($net);
1315 }
1316 };
1317 if (my $err = $@) {
1318 $add_apply_error->($opt, $err);
1319 } else {
1320 $conf->{$opt} = delete $conf->{pending}->{$opt};
1321 }
1322 }
1323
1324 $class->write_config($vmid, $conf);
1325}
1326
1327my $rescan_volume = sub {
1328 my ($storecfg, $mp) = @_;
1329 eval {
1330 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5);
1331 };
1332 warn "Could not rescan volume size - $@\n" if $@;
1333};
1334
1335sub apply_pending_mountpoint {
1336 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1337
1338 my $mp = $class->parse_ct_mountpoint($conf->{pending}->{$opt});
1339 my $old = $conf->{$opt};
1340 if ($mp->{type} eq 'volume') {
1341 if ($mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) {
1342 my $original_value = $conf->{pending}->{$opt};
1343 my $vollist = PVE::LXC::create_disks(
1344 $storecfg,
1345 $vmid,
1346 { $opt => $original_value },
1347 $conf,
1348 1,
1349 );
1350 if ($running) {
1351 # Re-parse mount point:
1352 my $mp = $class->parse_ct_mountpoint($conf->{pending}->{$opt});
1353 eval {
1354 PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg);
1355 };
1356 my $err = $@;
1357 if ($err) {
1358 PVE::LXC::destroy_disks($storecfg, $vollist);
1359 # The pending-changes code collects errors but keeps on looping through further
1360 # pending changes, so unroll the change in $conf as well if destroy_disks()
1361 # didn't die().
1362 $conf->{pending}->{$opt} = $original_value;
1363 die $err;
1364 }
1365 }
1366 } else {
1367 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1368 $rescan_volume->($storecfg, $mp);
1369 if ($running) {
1370 PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg);
1371 }
1372 $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp);
1373 }
1374 }
1375
1376 if (defined($old)) {
1377 my $mp = $class->parse_ct_mountpoint($old);
1378 if ($mp->{type} eq 'volume') {
1379 $class->add_unused_volume($conf, $mp->{volume})
1380 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1381 }
1382 }
1383}
1384
1385sub classify_mountpoint {
1386 my ($class, $vol) = @_;
1387 if ($vol =~ m!^/!) {
1388 return 'device' if $vol =~ m!^/dev/!;
1389 return 'bind';
1390 }
1391 return 'volume';
1392}
1393
1394my $__is_volume_in_use = sub {
1395 my ($class, $config, $volid) = @_;
1396 my $used = 0;
1397
1398 $class->foreach_volume($config, sub {
1399 my ($ms, $mountpoint) = @_;
1400 return if $used;
1401 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1402 });
1403
1404 return $used;
1405};
1406
1407sub is_volume_in_use_by_snapshots {
1408 my ($class, $config, $volid) = @_;
1409
1410 if (my $snapshots = $config->{snapshots}) {
1411 foreach my $snap (keys %$snapshots) {
1412 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1413 }
1414 }
1415
1416 return 0;
1417}
1418
1419sub is_volume_in_use {
1420 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1421 return 1 if $__is_volume_in_use->($class, $config, $volid);
1422 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1423 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending}, $volid);
1424 return 0;
1425}
1426
1427sub has_dev_console {
1428 my ($class, $conf) = @_;
1429
1430 return !(defined($conf->{console}) && !$conf->{console});
1431}
1432
1433sub has_lxc_entry {
1434 my ($class, $conf, $keyname) = @_;
1435
1436 if (my $lxcconf = $conf->{lxc}) {
1437 foreach my $entry (@$lxcconf) {
1438 my ($key, undef) = @$entry;
1439 return 1 if $key eq $keyname;
1440 }
1441 }
1442
1443 return 0;
1444}
1445
1446sub get_tty_count {
1447 my ($class, $conf) = @_;
1448
1449 return $conf->{tty} // $confdesc->{tty}->{default};
1450}
1451
1452sub get_cmode {
1453 my ($class, $conf) = @_;
1454
1455 return $conf->{cmode} // $confdesc->{cmode}->{default};
1456}
1457
1458sub valid_volume_keys {
1459 my ($class, $reverse) = @_;
1460
1461 my @names = ('rootfs');
1462
1463 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1464 push @names, "mp$i";
1465 }
1466
1467 return $reverse ? reverse @names : @names;
1468}
1469
1470sub get_vm_volumes {
1471 my ($class, $conf, $excludes) = @_;
1472
1473 my $vollist = [];
1474
1475 $class->foreach_volume($conf, sub {
1476 my ($ms, $mountpoint) = @_;
1477
1478 return if $excludes && $ms eq $excludes;
1479
1480 my $volid = $mountpoint->{volume};
1481 return if !$volid || $mountpoint->{type} ne 'volume';
1482
1483 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1484 return if !$sid;
1485
1486 push @$vollist, $volid;
1487 });
1488
1489 return $vollist;
1490}
1491
1492sub get_replicatable_volumes {
1493 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1494
1495 my $volhash = {};
1496
1497 my $test_volid = sub {
1498 my ($volid, $mountpoint) = @_;
1499
1500 return if !$volid;
1501
1502 my $mptype = $mountpoint->{type};
1503 my $replicate = $mountpoint->{replicate} // 1;
1504
1505 if ($mptype ne 'volume') {
1506 # skip bindmounts if replicate = 0 even for cleanup,
1507 # since bind mounts could not have been replicated ever
1508 return if !$replicate;
1509 die "unable to replicate mountpoint type '$mptype'\n";
1510 }
1511
1512 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1513 return if !$storeid;
1514
1515 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
1516 return if $scfg->{shared};
1517
1518 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1519 return if !$owner || ($owner != $vmid);
1520
1521 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1522
1523 return if !$cleanup && !$replicate;
1524
1525 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
1526 return if $cleanup || $noerr;
1527 die "missing replicate feature on volume '$volid'\n";
1528 }
1529
1530 $volhash->{$volid} = 1;
1531 };
1532
1533 $class->foreach_volume($conf, sub {
1534 my ($ms, $mountpoint) = @_;
1535 $test_volid->($mountpoint->{volume}, $mountpoint);
1536 });
1537
1538 foreach my $snapname (keys %{$conf->{snapshots}}) {
1539 my $snap = $conf->{snapshots}->{$snapname};
1540 $class->foreach_volume($snap, sub {
1541 my ($ms, $mountpoint) = @_;
1542 $test_volid->($mountpoint->{volume}, $mountpoint);
1543 });
1544 }
1545
1546 # add 'unusedX' volumes to volhash
1547 foreach my $key (keys %$conf) {
1548 if ($key =~ m/^unused/) {
1549 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1550 }
1551 }
1552
1553 return $volhash;
1554}
1555
15561;