]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
VZDump: mark template disks as ro
[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
d250604f 70 $class->foreach_mountpoint($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) {
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
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};
1b4cf758 132 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($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
189 $class->__snapshot_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->__snapshot_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
4518000b
FG
213sub __snapshot_foreach_volume {
214 my ($class, $conf, $func) = @_;
215
d250604f 216 $class->foreach_mountpoint($conf, $func);
4518000b
FG
217}
218
67afe46e
FG
219# END implemented abstract methods from PVE::AbstractConfig
220
1b4cf758
FG
221# BEGIN JSON config code
222
223cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
224
2bf24eb3 225
e80cb0cd
TL
226my $valid_mount_option_re = qr/(noatime|nodev|nosuid|noexec)/;
227
228sub is_valid_mount_option {
229 my ($option) = @_;
230 return $option =~ $valid_mount_option_re;
2bf24eb3
OB
231}
232
1b4cf758
FG
233my $rootfs_desc = {
234 volume => {
235 type => 'string',
236 default_key => 1,
237 format => 'pve-lxc-mp-string',
238 format_description => 'volume',
239 description => 'Volume, device or directory to mount into the container.',
240 },
1b4cf758
FG
241 size => {
242 type => 'string',
243 format => 'disk-size',
244 format_description => 'DiskSize',
245 description => 'Volume size (read only value).',
246 optional => 1,
247 },
248 acl => {
249 type => 'boolean',
1b4cf758
FG
250 description => 'Explicitly enable or disable ACL support.',
251 optional => 1,
252 },
2bf24eb3
OB
253 mountoptions => {
254 optional => 1,
255 type => 'string',
256 description => 'Extra mount options for rootfs/mps.',
257 format_description => 'opt[;opt...]',
e80cb0cd 258 pattern => qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
2bf24eb3 259 },
1b4cf758
FG
260 ro => {
261 type => 'boolean',
235dbdf3 262 description => 'Read-only mount point',
1b4cf758
FG
263 optional => 1,
264 },
265 quota => {
266 type => 'boolean',
1b4cf758
FG
267 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
268 optional => 1,
269 },
76ec0820 270 replicate => {
f8aa3d35
WL
271 type => 'boolean',
272 description => 'Will include this volume to a storage replica job.',
273 optional => 1,
274 default => 1,
275 },
552e168f
FG
276 shared => {
277 type => 'boolean',
278 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
279 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!",
280 optional => 1,
281 default => 0,
282 },
1b4cf758
FG
283};
284
285PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
286 type => 'string', format => $rootfs_desc,
287 description => "Use volume as container root.",
288 optional => 1,
289});
290
5a63f1c5
WB
291my $features_desc = {
292 mount => {
293 optional => 1,
294 type => 'string',
295 description => "Allow mounting file systems of specific types."
296 ." This should be a list of file system types as used with the mount command."
297 ." Note that this can have negative effects on the container's security."
298 ." With access to a loop device, mounting a file can circumvent the mknod"
299 ." permission of the devices cgroup, mounting an NFS file system can"
300 ." block the host's I/O completely and prevent it from rebooting, etc.",
301 format_description => 'fstype;fstype;...',
e188f1bb 302 pattern => qr/[a-zA-Z0-9_; ]+/,
5a63f1c5
WB
303 },
304 nesting => {
305 optional => 1,
306 type => 'boolean',
307 default => 0,
308 description => "Allow nesting."
309 ." Best used with unprivileged containers with additional id mapping."
310 ." Note that this will expose procfs and sysfs contents of the host"
311 ." to the guest.",
312 },
313 keyctl => {
314 optional => 1,
315 type => 'boolean',
316 default => 0,
317 description => "For unprivileged containers only: Allow the use of the keyctl() system call."
318 ." This is required to use docker inside a container."
319 ." By default unprivileged containers will see this system call as non-existent."
320 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
321 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
322 ." Essentially, you can choose between running systemd-networkd or docker.",
323 },
96f8d2a2
WB
324 fuse => {
325 optional => 1,
326 type => 'boolean',
327 default => 0,
328 description => "Allow using 'fuse' file systems in a container."
329 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
330 },
2df08734
WB
331 mknod => {
332 optional => 1,
333 type => 'boolean',
334 default => 0,
335 description => "Allow unprivileged containers to use mknod() to add certain device nodes."
336 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
337 ." This is experimental.",
338 },
741b7737
TL
339 force_rw_sys => {
340 optional => 1,
341 type => 'boolean',
342 default => 0,
343 description => "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
344 ." This can break networking under newer (>= v245) systemd-network use."
345 },
5a63f1c5
WB
346};
347
1b4cf758
FG
348my $confdesc = {
349 lock => {
350 optional => 1,
351 type => 'string',
352 description => "Lock/unlock the VM.",
7fc1d9eb 353 enum => [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
1b4cf758
FG
354 },
355 onboot => {
356 optional => 1,
357 type => 'boolean',
358 description => "Specifies whether a VM will be started during system bootup.",
359 default => 0,
360 },
361 startup => get_standard_option('pve-startup-order'),
362 template => {
363 optional => 1,
364 type => 'boolean',
365 description => "Enable/disable Template.",
366 default => 0,
367 },
368 arch => {
369 optional => 1,
370 type => 'string',
e1d54a38 371 enum => ['amd64', 'i386', 'arm64', 'armhf'],
1b4cf758
FG
372 description => "OS architecture type.",
373 default => 'amd64',
374 },
375 ostype => {
376 optional => 1,
377 type => 'string',
ed027b58 378 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
1b4cf758
FG
379 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.",
380 },
381 console => {
382 optional => 1,
383 type => 'boolean',
384 description => "Attach a console device (/dev/console) to the container.",
385 default => 1,
386 },
387 tty => {
388 optional => 1,
389 type => 'integer',
390 description => "Specify the number of tty available to the container",
391 minimum => 0,
392 maximum => 6,
393 default => 2,
394 },
f2357408
DM
395 cores => {
396 optional => 1,
397 type => 'integer',
398 description => "The number of cores assigned to the container. A container can use all available cores by default.",
399 minimum => 1,
400 maximum => 128,
401 },
1b4cf758
FG
402 cpulimit => {
403 optional => 1,
404 type => 'number',
064529c3 405 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
406 minimum => 0,
407 maximum => 128,
408 default => 0,
409 },
410 cpuunits => {
411 optional => 1,
412 type => 'integer',
413 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.",
414 minimum => 0,
415 maximum => 500000,
416 default => 1024,
417 },
418 memory => {
419 optional => 1,
420 type => 'integer',
421 description => "Amount of RAM for the VM in MB.",
422 minimum => 16,
423 default => 512,
424 },
425 swap => {
426 optional => 1,
427 type => 'integer',
428 description => "Amount of SWAP for the VM in MB.",
429 minimum => 0,
430 default => 512,
431 },
432 hostname => {
433 optional => 1,
434 description => "Set a host name for the container.",
435 type => 'string', format => 'dns-name',
436 maxLength => 255,
437 },
438 description => {
439 optional => 1,
440 type => 'string',
a069f163 441 description => "Container description. Only used on the configuration web interface.",
1b4cf758
FG
442 },
443 searchdomain => {
444 optional => 1,
445 type => 'string', format => 'dns-name-list',
446 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
447 },
448 nameserver => {
449 optional => 1,
450 type => 'string', format => 'address-list',
451 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.",
452 },
453 rootfs => get_standard_option('pve-ct-rootfs'),
454 parent => {
455 optional => 1,
456 type => 'string', format => 'pve-configid',
457 maxLength => 40,
458 description => "Parent snapshot name. This is used internally, and should not be modified.",
459 },
460 snaptime => {
461 optional => 1,
462 description => "Timestamp for snapshots.",
463 type => 'integer',
464 minimum => 0,
465 },
466 cmode => {
467 optional => 1,
468 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).",
469 type => 'string',
470 enum => ['shell', 'console', 'tty'],
471 default => 'tty',
472 },
473 protection => {
474 optional => 1,
475 type => 'boolean',
476 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
477 default => 0,
478 },
479 unprivileged => {
480 optional => 1,
481 type => 'boolean',
482 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
483 default => 0,
484 },
5a63f1c5
WB
485 features => {
486 optional => 1,
487 type => 'string',
488 format => $features_desc,
489 description => "Allow containers access to advanced features.",
490 },
1a416433
DC
491 hookscript => {
492 optional => 1,
493 type => 'string',
494 format => 'pve-volume-id',
495 description => 'Script that will be exectued during various steps in the containers lifetime.',
496 },
733e52ec
DC
497 tags => {
498 type => 'string', format => 'pve-tag-list',
499 description => 'Tags of the Container. This is only meta information.',
500 optional => 1,
501 },
1b4cf758
FG
502};
503
504my $valid_lxc_conf_keys = {
108c6cab
WB
505 'lxc.apparmor.profile' => 1,
506 'lxc.apparmor.allow_incomplete' => 1,
d494e03c
WB
507 'lxc.apparmor.allow_nesting' => 1,
508 'lxc.apparmor.raw' => 1,
108c6cab 509 'lxc.selinux.context' => 1,
1b4cf758
FG
510 'lxc.include' => 1,
511 'lxc.arch' => 1,
108c6cab
WB
512 'lxc.uts.name' => 1,
513 'lxc.signal.halt' => 1,
514 'lxc.signal.reboot' => 1,
515 'lxc.signal.stop' => 1,
516 'lxc.init.cmd' => 1,
517 'lxc.pty.max' => 1,
1b4cf758 518 'lxc.console.logfile' => 1,
108c6cab
WB
519 'lxc.console.path' => 1,
520 'lxc.tty.max' => 1,
521 'lxc.devtty.dir' => 1,
1b4cf758
FG
522 'lxc.hook.autodev' => 1,
523 'lxc.autodev' => 1,
524 'lxc.kmsg' => 1,
108c6cab 525 'lxc.mount.fstab' => 1,
1b4cf758
FG
526 'lxc.mount.entry' => 1,
527 'lxc.mount.auto' => 1,
108c6cab 528 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
1b4cf758
FG
529 'lxc.rootfs.mount' => 1,
530 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
235dbdf3 531 ', please use mount point options in the "rootfs" key',
1b4cf758 532 # lxc.cgroup.*
108c6cab 533 # lxc.prlimit.*
6eb23d1e 534 # lxc.net.*
1b4cf758
FG
535 'lxc.cap.drop' => 1,
536 'lxc.cap.keep' => 1,
108c6cab 537 'lxc.seccomp.profile' => 1,
832b4a02
WB
538 'lxc.seccomp.notify.proxy' => 1,
539 'lxc.seccomp.notify.cookie' => 1,
108c6cab 540 'lxc.idmap' => 1,
1b4cf758
FG
541 'lxc.hook.pre-start' => 1,
542 'lxc.hook.pre-mount' => 1,
543 'lxc.hook.mount' => 1,
544 'lxc.hook.start' => 1,
545 'lxc.hook.stop' => 1,
546 'lxc.hook.post-stop' => 1,
547 'lxc.hook.clone' => 1,
548 'lxc.hook.destroy' => 1,
f71db91b 549 'lxc.hook.version' => 1,
108c6cab
WB
550 'lxc.log.level' => 1,
551 'lxc.log.file' => 1,
1b4cf758
FG
552 'lxc.start.auto' => 1,
553 'lxc.start.delay' => 1,
554 'lxc.start.order' => 1,
555 'lxc.group' => 1,
556 'lxc.environment' => 1,
d4a135f7
WB
557
558 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
559 'lxc.sysctl.fs.mqueue' => 1,
560 'lxc.sysctl.kernel.msgmax' => 1,
561 'lxc.sysctl.kernel.msgmnb' => 1,
562 'lxc.sysctl.kernel.msgmni' => 1,
563 'lxc.sysctl.kernel.sem' => 1,
564 'lxc.sysctl.kernel.shmall' => 1,
565 'lxc.sysctl.kernel.shmmax' => 1,
566 'lxc.sysctl.kernel.shmmni' => 1,
567 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
1b4cf758
FG
568};
569
108c6cab
WB
570my $deprecated_lxc_conf_keys = {
571 # Deprecated (removed with lxc 3.0):
572 'lxc.aa_profile' => 'lxc.apparmor.profile',
573 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
574 'lxc.console' => 'lxc.console.path',
575 'lxc.devttydir' => 'lxc.tty.dir',
576 'lxc.haltsignal' => 'lxc.signal.halt',
577 'lxc.rebootsignal' => 'lxc.signal.reboot',
578 'lxc.stopsignal' => 'lxc.signal.stop',
579 'lxc.id_map' => 'lxc.idmap',
580 'lxc.init_cmd' => 'lxc.init.cmd',
581 'lxc.loglevel' => 'lxc.log.level',
582 'lxc.logfile' => 'lxc.log.file',
583 'lxc.mount' => 'lxc.mount.fstab',
584 'lxc.network.type' => 'lxc.net.INDEX.type',
585 'lxc.network.flags' => 'lxc.net.INDEX.flags',
586 'lxc.network.link' => 'lxc.net.INDEX.link',
587 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
588 'lxc.network.name' => 'lxc.net.INDEX.name',
589 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
590 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
591 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
592 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
593 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
594 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
595 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
596 'lxc.pts' => 'lxc.pty.max',
597 'lxc.se_context' => 'lxc.selinux.context',
598 'lxc.seccomp' => 'lxc.seccomp.profile',
599 'lxc.tty' => 'lxc.tty.max',
600 'lxc.utsname' => 'lxc.uts.name',
601};
602
603sub is_valid_lxc_conf_key {
604 my ($vmid, $key) = @_;
605 if ($key =~ /^lxc\.limit\./) {
606 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
607 return 1;
608 }
609 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
610 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
611 return 1;
612 }
613 my $validity = $valid_lxc_conf_keys->{$key};
614 return $validity if defined($validity);
615 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
616 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
617 || $key =~ /^lxc\.net\./; # allow custom network definitions
618 return 0;
619}
620
5e5915c5 621our $netconf_desc = {
1b4cf758
FG
622 type => {
623 type => 'string',
624 optional => 1,
625 description => "Network interface type.",
626 enum => [qw(veth)],
627 },
628 name => {
629 type => 'string',
a069f163
DM
630 format_description => 'string',
631 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
632 pattern => '[-_.\w\d]+',
633 },
634 bridge => {
635 type => 'string',
a069f163 636 format_description => 'bridge',
1b4cf758
FG
637 description => 'Bridge to attach the network device to.',
638 pattern => '[-_.\w\d]+',
639 optional => 1,
640 },
603536e0 641 hwaddr => get_standard_option('mac-addr', {
e6f20294 642 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 643 }),
1b4cf758
FG
644 mtu => {
645 type => 'integer',
1b4cf758
FG
646 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
647 minimum => 64, # minimum ethernet frame is 64 bytes
648 optional => 1,
649 },
650 ip => {
651 type => 'string',
652 format => 'pve-ipv4-config',
718a67d6 653 format_description => '(IPv4/CIDR|dhcp|manual)',
1b4cf758
FG
654 description => 'IPv4 address in CIDR format.',
655 optional => 1,
656 },
657 gw => {
658 type => 'string',
659 format => 'ipv4',
660 format_description => 'GatewayIPv4',
661 description => 'Default gateway for IPv4 traffic.',
662 optional => 1,
663 },
664 ip6 => {
665 type => 'string',
666 format => 'pve-ipv6-config',
6ea7095c 667 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
1b4cf758
FG
668 description => 'IPv6 address in CIDR format.',
669 optional => 1,
670 },
671 gw6 => {
672 type => 'string',
673 format => 'ipv6',
674 format_description => 'GatewayIPv6',
675 description => 'Default gateway for IPv6 traffic.',
676 optional => 1,
677 },
678 firewall => {
679 type => 'boolean',
1b4cf758
FG
680 description => "Controls whether this interface's firewall rules should be used.",
681 optional => 1,
682 },
683 tag => {
684 type => 'integer',
6b202dd5
DM
685 minimum => 1,
686 maximum => 4094,
1b4cf758
FG
687 description => "VLAN tag for this interface.",
688 optional => 1,
689 },
690 trunks => {
691 type => 'string',
692 pattern => qr/\d+(?:;\d+)*/,
693 format_description => 'vlanid[;vlanid...]',
694 description => "VLAN ids to pass through the interface",
695 optional => 1,
696 },
380962c7
WB
697 rate => {
698 type => 'number',
699 format_description => 'mbps',
700 description => "Apply rate limiting to the interface",
701 optional => 1,
702 },
1b4cf758
FG
703};
704PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
705
706my $MAX_LXC_NETWORKS = 10;
707for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
708 $confdesc->{"net$i"} = {
709 optional => 1,
710 type => 'string', format => $netconf_desc,
711 description => "Specifies network interfaces for the container.",
712 };
713}
714
715PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
716sub verify_lxc_mp_string {
717 my ($mp, $noerr) = @_;
718
719 # do not allow:
720 # /./ or /../
721 # /. or /.. at the end
722 # ../ at the beginning
723
724 if($mp =~ m@/\.\.?/@ ||
725 $mp =~ m@/\.\.?$@ ||
726 $mp =~ m@^\.\./@) {
727 return undef if $noerr;
728 die "$mp contains illegal character sequences\n";
729 }
730 return $mp;
731}
732
733my $mp_desc = {
734 %$rootfs_desc,
84820d40
DM
735 backup => {
736 type => 'boolean',
235dbdf3
FG
737 description => 'Whether to include the mount point in backups.',
738 verbose_description => 'Whether to include the mount point in backups '.
739 '(only used for volume mount points).',
84820d40
DM
740 optional => 1,
741 },
1b4cf758
FG
742 mp => {
743 type => 'string',
744 format => 'pve-lxc-mp-string',
745 format_description => 'Path',
235dbdf3 746 description => 'Path to the mount point as seen from inside the container '.
52b6f941 747 '(must not contain symlinks).',
235dbdf3 748 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
52b6f941 749 "NOTE: Must not contain any symlinks for security reasons."
1b4cf758
FG
750 },
751};
752PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
753
754my $unuseddesc = {
755 optional => 1,
756 type => 'string', format => 'pve-volume-id',
2928e616 757 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
1b4cf758
FG
758};
759
760for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
761 $confdesc->{"mp$i"} = {
762 optional => 1,
763 type => 'string', format => $mp_desc,
2928e616 764 description => "Use volume as container mount point.",
1b4cf758
FG
765 optional => 1,
766 };
767}
768
769for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
770 $confdesc->{"unused$i"} = $unuseddesc;
771}
772
773sub parse_pct_config {
774 my ($filename, $raw) = @_;
775
776 return undef if !defined($raw);
777
778 my $res = {
779 digest => Digest::SHA::sha1_hex($raw),
780 snapshots => {},
7547dc63 781 pending => {},
1b4cf758
FG
782 };
783
784 $filename =~ m|/lxc/(\d+).conf$|
785 || die "got strange filename '$filename'";
786
787 my $vmid = $1;
788
789 my $conf = $res;
790 my $descr = '';
791 my $section = '';
792
793 my @lines = split(/\n/, $raw);
794 foreach my $line (@lines) {
795 next if $line =~ m/^\s*$/;
796
7547dc63
OB
797 if ($line =~ m/^\[pve:pending\]\s*$/i) {
798 $section = 'pending';
799 $conf->{description} = $descr if $descr;
800 $descr = '';
801 $conf = $res->{$section} = {};
802 next;
803 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
1b4cf758
FG
804 $section = $1;
805 $conf->{description} = $descr if $descr;
806 $descr = '';
807 $conf = $res->{snapshots}->{$section} = {};
808 next;
809 }
810
811 if ($line =~ m/^\#(.*)\s*$/) {
812 $descr .= PVE::Tools::decode_text($1) . "\n";
813 next;
814 }
815
816 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
817 my $key = $1;
818 my $value = $3;
108c6cab
WB
819 my $validity = is_valid_lxc_conf_key($vmid, $key);
820 if ($validity eq 1) {
1b4cf758
FG
821 push @{$conf->{lxc}}, [$key, $value];
822 } elsif (my $errmsg = $validity) {
823 warn "vm $vmid - $key: $errmsg\n";
824 } else {
825 warn "vm $vmid - unable to parse config: $line\n";
826 }
827 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
828 $descr .= PVE::Tools::decode_text($2);
829 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
830 $conf->{snapstate} = $1;
7547dc63
OB
831 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
832 my $value = $1;
833 if ($section eq 'pending') {
834 $conf->{delete} = $value;
835 } else {
836 warn "vm $vmid - property 'delete' is only allowed in [pve:pending]\n";
837 }
1b4cf758
FG
838 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
839 my $key = $1;
840 my $value = $2;
841 eval { $value = PVE::LXC::Config->check_type($key, $value); };
842 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
843 $conf->{$key} = $value;
844 } else {
845 warn "vm $vmid - unable to parse config: $line\n";
846 }
847 }
848
849 $conf->{description} = $descr if $descr;
850
851 delete $res->{snapstate}; # just to be sure
852
853 return $res;
854}
855
856sub write_pct_config {
857 my ($filename, $conf) = @_;
858
859 delete $conf->{snapstate}; # just to be sure
860
861 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
862 my $used_volids = {};
863 foreach my $vid (@$volidlist) {
864 $used_volids->{$vid} = 1;
865 }
866
867 # remove 'unusedX' settings if the volume is still used
868 foreach my $key (keys %$conf) {
869 my $value = $conf->{$key};
870 if ($key =~ m/^unused/ && $used_volids->{$value}) {
871 delete $conf->{$key};
872 }
873 }
874
875 my $generate_raw_config = sub {
876 my ($conf) = @_;
877
878 my $raw = '';
879
880 # add description as comment to top of file
881 my $descr = $conf->{description} || '';
882 foreach my $cl (split(/\n/, $descr)) {
7547dc63 883 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
1b4cf758
FG
884 }
885
886 foreach my $key (sort keys %$conf) {
887 next if $key eq 'digest' || $key eq 'description' ||
888 $key eq 'pending' || $key eq 'snapshots' ||
889 $key eq 'snapname' || $key eq 'lxc';
890 my $value = $conf->{$key};
891 die "detected invalid newline inside property '$key'\n"
892 if $value =~ m/\n/;
893 $raw .= "$key: $value\n";
894 }
895
896 if (my $lxcconf = $conf->{lxc}) {
897 foreach my $entry (@$lxcconf) {
898 my ($k, $v) = @$entry;
899 $raw .= "$k: $v\n";
900 }
901 }
902
903 return $raw;
904 };
905
906 my $raw = &$generate_raw_config($conf);
907
7547dc63
OB
908 if (scalar(keys %{$conf->{pending}})){
909 $raw .= "\n[pve:pending]\n";
910 $raw .= &$generate_raw_config($conf->{pending});
911 }
912
1b4cf758
FG
913 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
914 $raw .= "\n[$snapname]\n";
915 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
916 }
917
918 return $raw;
919}
920
921sub update_pct_config {
6517e001 922 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1b4cf758 923
6517e001 924 my $storage_cfg = PVE::Storage::config();
1b4cf758 925
6517e001
OB
926 foreach my $opt (@$revert) {
927 delete $conf->{pending}->{$opt};
928 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1b4cf758
FG
929 }
930
6517e001
OB
931 # write updates to pending section
932 my $modified = {}; # record modified options
1b4cf758 933
6517e001
OB
934 foreach my $opt (@$delete) {
935 if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
936 warn "cannot delete '$opt' - not set in current configuration!\n";
937 next;
1b4cf758 938 }
6517e001
OB
939 $modified->{$opt} = 1;
940 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
941 die "unable to delete required option '$opt'\n";
942 } elsif ($opt =~ m/^unused(\d+)$/) {
943 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
944 } elsif ($opt =~ m/^mp(\d+)$/) {
945 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
946 } elsif ($opt eq 'unprivileged') {
947 die "unable to delete read-only option: '$opt'\n";
1b4cf758 948 }
6517e001 949 $class->add_to_pending_delete($conf, $opt);
1b4cf758
FG
950 }
951
1b3213ae
FG
952 my $check_content_type = sub {
953 my ($mp) = @_;
954 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
6517e001 955 my $storage_config = PVE::Storage::storage_config($storage_cfg, $sid);
1b3213ae
FG
956 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
957 if !$storage_config->{content}->{rootdir};
958 };
1b4cf758 959
c10951f7 960 foreach my $opt (sort keys %$param) { # add/change
6517e001 961 $modified->{$opt} = 1;
1b4cf758 962 my $value = $param->{$opt};
6517e001
OB
963 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
964 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
965 my $mp = $opt eq 'rootfs' ? $class->parse_ct_rootfs($value) : $class->parse_ct_mountpoint($value);
3927ae96 966 $check_content_type->($mp) if ($mp->{type} eq 'volume');
6517e001
OB
967 } elsif ($opt eq 'hookscript') {
968 PVE::GuestHelpers::check_hookscript($value);
1b4cf758 969 } elsif ($opt eq 'nameserver') {
6517e001 970 $value = PVE::LXC::verify_nameserver_list($value);
1b4cf758 971 } elsif ($opt eq 'searchdomain') {
6517e001 972 $value = PVE::LXC::verify_searchdomain_list($value);
1b4cf758
FG
973 } elsif ($opt eq 'unprivileged') {
974 die "unable to modify read-only option: '$opt'\n";
1b4cf758 975 }
6517e001
OB
976 $conf->{pending}->{$opt} = $value;
977 $class->remove_from_pending_delete($conf, $opt);
f8aa3d35
WL
978 }
979
6517e001 980 my $changes = $class->cleanup_pending($conf);
1b4cf758 981
6517e001
OB
982 my $errors = {};
983 if ($running) {
984 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
985 } else {
986 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1b4cf758
FG
987 }
988
6517e001 989 return $errors;
1b4cf758
FG
990}
991
992sub check_type {
993 my ($class, $key, $value) = @_;
994
995 die "unknown setting '$key'\n" if !$confdesc->{$key};
996
997 my $type = $confdesc->{$key}->{type};
998
999 if (!defined($value)) {
1000 die "got undefined value\n";
1001 }
1002
1003 if ($value =~ m/[\n\r]/) {
1004 die "property contains a line feed\n";
1005 }
1006
1007 if ($type eq 'boolean') {
1008 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1009 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1010 die "type check ('boolean') failed - got '$value'\n";
1011 } elsif ($type eq 'integer') {
1012 return int($1) if $value =~ m/^(\d+)$/;
1013 die "type check ('integer') failed - got '$value'\n";
1014 } elsif ($type eq 'number') {
1015 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1016 die "type check ('number') failed - got '$value'\n";
1017 } elsif ($type eq 'string') {
1018 if (my $fmt = $confdesc->{$key}->{format}) {
1019 PVE::JSONSchema::check_format($fmt, $value);
1020 return $value;
1021 }
1022 return $value;
1023 } else {
1024 die "internal error"
1025 }
1026}
1027
1028
1029# add JSON properties for create and set function
1030sub json_config_properties {
1031 my ($class, $prop) = @_;
1032
1033 foreach my $opt (keys %$confdesc) {
1034 next if $opt eq 'parent' || $opt eq 'snaptime';
1035 next if $prop->{$opt};
1036 $prop->{$opt} = $confdesc->{$opt};
1037 }
1038
1039 return $prop;
1040}
1041
1042sub __parse_ct_mountpoint_full {
1043 my ($class, $desc, $data, $noerr) = @_;
1044
1045 $data //= '';
1046
1047 my $res;
1048 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1049 if ($@) {
1050 return undef if $noerr;
1051 die $@;
1052 }
1053
1054 if (defined(my $size = $res->{size})) {
1055 $size = PVE::JSONSchema::parse_size($size);
1056 if (!defined($size)) {
1057 return undef if $noerr;
1058 die "invalid size: $size\n";
1059 }
1060 $res->{size} = $size;
1061 }
1062
1063 $res->{type} = $class->classify_mountpoint($res->{volume});
1064
1065 return $res;
1066};
1067
1068sub parse_ct_rootfs {
1069 my ($class, $data, $noerr) = @_;
1070
1071 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1072
1073 $res->{mp} = '/' if defined($res);
1074
1075 return $res;
1076}
1077
1078sub parse_ct_mountpoint {
1079 my ($class, $data, $noerr) = @_;
1080
1081 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1082}
1083
1084sub print_ct_mountpoint {
1085 my ($class, $info, $nomp) = @_;
1086 my $skip = [ 'type' ];
1087 push @$skip, 'mp' if $nomp;
1088 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1089}
1090
1091sub print_lxc_network {
1092 my ($class, $net) = @_;
1093 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1094}
1095
1096sub parse_lxc_network {
1097 my ($class, $data) = @_;
1098
1099 my $res = {};
1100
1101 return $res if !$data;
1102
1103 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1104
1105 $res->{type} = 'veth';
2f19133b
WB
1106 if (!$res->{hwaddr}) {
1107 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1108 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1109 }
1b4cf758
FG
1110
1111 return $res;
1112}
1113
5a63f1c5
WB
1114sub parse_features {
1115 my ($class, $data) = @_;
1116 return {} if !$data;
1117 return PVE::JSONSchema::parse_property_string($features_desc, $data);
1118}
1119
1b4cf758
FG
1120sub option_exists {
1121 my ($class, $name) = @_;
1122
1123 return defined($confdesc->{$name});
1124}
1125# END JSON config code
1126
32e15a2b
OB
1127my $LXC_FASTPLUG_OPTIONS= {
1128 'description' => 1,
1129 'onboot' => 1,
1130 'startup' => 1,
1131 'protection' => 1,
1132 'hostname' => 1,
1133 'hookscript' => 1,
1134 'cores' => 1,
1135 'tags' => 1,
db15c375 1136 'lock' => 1,
32e15a2b
OB
1137};
1138
1139sub vmconfig_hotplug_pending {
1140 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1141
1142 my $pid = PVE::LXC::find_lxc_pid($vmid);
1143 my $rootdir = "/proc/$pid/root";
1144
1145 my $add_hotplug_error = sub {
1146 my ($opt, $msg) = @_;
1147 $errors->{$opt} = "unable to hotplug $opt: $msg";
1148 };
1149
1150 my $changes;
c10951f7 1151 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
32e15a2b
OB
1152 next if $selection && !$selection->{$opt};
1153 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1154 $conf->{$opt} = delete $conf->{pending}->{$opt};
1155 $changes = 1;
1156 }
1157 }
1158
1159 if ($changes) {
1160 $class->write_config($vmid, $conf);
1161 }
1162
1163 # There's no separate swap size to configure, there's memory and "total"
1164 # memory (iow. memory+swap). This means we have to change them together.
1165 my $hotplug_memory_done;
1166 my $hotplug_memory = sub {
1167 my ($wanted_memory, $wanted_swap) = @_;
1168 my $old_memory = ($conf->{memory} || $confdesc->{memory}->{default});
1169 my $old_swap = ($conf->{swap} || $confdesc->{swap}->{default});
1170
1171 $wanted_memory //= $old_memory;
1172 $wanted_swap //= $old_swap;
1173
1174 my $total = $wanted_memory + $wanted_swap;
1175 my $old_total = $old_memory + $old_swap;
1176
1177 if ($total > $old_total) {
1178 PVE::LXC::write_cgroup_value("memory", $vmid,
1179 "memory.memsw.limit_in_bytes",
1180 int($total*1024*1024));
1181 PVE::LXC::write_cgroup_value("memory", $vmid,
1182 "memory.limit_in_bytes",
1183 int($wanted_memory*1024*1024));
1184 } else {
1185 PVE::LXC::write_cgroup_value("memory", $vmid,
1186 "memory.limit_in_bytes",
1187 int($wanted_memory*1024*1024));
1188 PVE::LXC::write_cgroup_value("memory", $vmid,
1189 "memory.memsw.limit_in_bytes",
1190 int($total*1024*1024));
1191 }
1192 $hotplug_memory_done = 1;
1193 };
1194
1195 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1196 # FIXME: $force deletion is not implemented for CTs
8ec10817 1197 foreach my $opt (sort keys %$pending_delete_hash) {
32e15a2b
OB
1198 next if $selection && !$selection->{$opt};
1199 eval {
1200 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1201 # pass
1202 } elsif ($opt =~ m/^unused(\d+)$/) {
1203 PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
1204 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1205 } elsif ($opt eq 'swap') {
1206 $hotplug_memory->(undef, 0);
1207 } elsif ($opt eq 'cpulimit') {
1208 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_period_us", -1);
1209 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
1210 } elsif ($opt eq 'cpuunits') {
6f433df0 1211 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
32e15a2b
OB
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
c10951f7 1227 foreach my $opt (sort keys %{$conf->{pending}}) {
32e15a2b
OB
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 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_period_us", 100000);
1234 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1235 } elsif ($opt eq 'cpuunits') {
1236 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1237 } elsif ($opt =~ m/^net(\d+)$/) {
1238 my $netid = $1;
1239 my $net = $class->parse_lxc_network($value);
7eff309b 1240 $value = $class->print_lxc_network($net);
32e15a2b
OB
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 }
b2de4c04
WB
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};
32e15a2b
OB
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
32e15a2b
OB
1279 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1280 # FIXME: $force deletion is not implemented for CTs
8ec10817 1281 foreach my $opt (sort keys %$pending_delete_hash) {
32e15a2b 1282 next if $selection && !$selection->{$opt};
32e15a2b
OB
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
132c0a90
OB
1303 $class->cleanup_pending($conf);
1304
c10951f7 1305 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
32e15a2b
OB
1306 next if $opt eq 'delete'; # just to be sure
1307 next if $selection && !$selection->{$opt};
1308 eval {
1309 if ($opt =~ m/^mp(\d+)$/) {
869081a2 1310 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
7eff309b
OB
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);
32e15a2b
OB
1315 }
1316 };
1317 if (my $err = $@) {
1318 $add_apply_error->($opt, $err);
1319 } else {
32e15a2b
OB
1320 $conf->{$opt} = delete $conf->{pending}->{$opt};
1321 }
1322 }
1323
1324 $class->write_config($vmid, $conf);
1325}
1326
869081a2
WB
1327my $rescan_volume = sub {
1328 my ($storecfg, $mp) = @_;
1329 eval {
0c69dcfc 1330 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5);
869081a2
WB
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) {
b2de4c04 1342 my $original_value = $conf->{pending}->{$opt};
869081a2
WB
1343 my $vollist = PVE::LXC::create_disks(
1344 $storecfg,
1345 $vmid,
b2de4c04 1346 { $opt => $original_value },
869081a2
WB
1347 $conf,
1348 1,
1349 );
b2de4c04
WB
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 }
869081a2 1366 } else {
b2de4c04 1367 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
869081a2 1368 $rescan_volume->($storecfg, $mp);
b2de4c04
WB
1369 if ($running) {
1370 PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg);
1371 }
869081a2
WB
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
d250604f
FG
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
7b4237c5 1394my $__is_volume_in_use = sub {
72e6bc20 1395 my ($class, $config, $volid) = @_;
d250604f
FG
1396 my $used = 0;
1397
1398 $class->foreach_mountpoint($config, sub {
1399 my ($ms, $mountpoint) = @_;
1400 return if $used;
1401 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1402 });
1403
72e6bc20
WB
1404 return $used;
1405};
1406
1407sub is_volume_in_use_by_snapshots {
1408 my ($class, $config, $volid) = @_;
1409
1410 if (my $snapshots = $config->{snapshots}) {
d250604f 1411 foreach my $snap (keys %$snapshots) {
7b4237c5 1412 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
d250604f
FG
1413 }
1414 }
1415
72e6bc20 1416 return 0;
7b4237c5 1417}
72e6bc20
WB
1418
1419sub is_volume_in_use {
d063af00 1420 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
7b4237c5 1421 return 1 if $__is_volume_in_use->($class, $config, $volid);
72e6bc20 1422 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
d063af00 1423 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending}, $volid);
72e6bc20 1424 return 0;
d250604f
FG
1425}
1426
1427sub has_dev_console {
1428 my ($class, $conf) = @_;
1429
1430 return !(defined($conf->{console}) && !$conf->{console});
1431}
1432
be7942f0
DM
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
1b4cf758
FG
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
d250604f
FG
1458sub mountpoint_names {
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 foreach_mountpoint_full {
8915b450 1471 my ($class, $conf, $reverse, $func, @param) = @_;
d250604f 1472
717f70b7
FG
1473 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1474 foreach my $key (@$mps) {
d250604f 1475 my $value = $conf->{$key};
1b4cf758 1476 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
d250604f
FG
1477 next if !defined($mountpoint);
1478
8915b450 1479 &$func($key, $mountpoint, @param);
d250604f
FG
1480 }
1481}
1482
1483sub foreach_mountpoint {
8915b450 1484 my ($class, $conf, $func, @param) = @_;
d250604f 1485
8915b450 1486 $class->foreach_mountpoint_full($conf, 0, $func, @param);
d250604f
FG
1487}
1488
1489sub foreach_mountpoint_reverse {
8915b450 1490 my ($class, $conf, $func, @param) = @_;
d250604f 1491
8915b450 1492 $class->foreach_mountpoint_full($conf, 1, $func, @param);
d250604f
FG
1493}
1494
1495sub get_vm_volumes {
1496 my ($class, $conf, $excludes) = @_;
1497
1498 my $vollist = [];
1499
1500 $class->foreach_mountpoint($conf, sub {
1501 my ($ms, $mountpoint) = @_;
1502
1503 return if $excludes && $ms eq $excludes;
1504
1505 my $volid = $mountpoint->{volume};
1506 return if !$volid || $mountpoint->{type} ne 'volume';
1507
1508 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1509 return if !$sid;
1510
1511 push @$vollist, $volid;
1512 });
1513
1514 return $vollist;
1515}
1516
f78c87a8 1517sub get_replicatable_volumes {
70996986 1518 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
f78c87a8
DM
1519
1520 my $volhash = {};
1521
1522 my $test_volid = sub {
1523 my ($volid, $mountpoint) = @_;
1524
1525 return if !$volid;
1526
5cf90b0c 1527 my $mptype = $mountpoint->{type};
896cd762 1528 my $replicate = $mountpoint->{replicate} // 1;
d2a046b7
DC
1529
1530 if ($mptype ne 'volume') {
1531 # skip bindmounts if replicate = 0 even for cleanup,
1532 # since bind mounts could not have been replicated ever
1533 return if !$replicate;
1534 die "unable to replicate mountpoint type '$mptype'\n";
1535 }
5cf90b0c
DM
1536
1537 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1538 return if !$storeid;
1539
af21e699 1540 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
5cf90b0c
DM
1541 return if $scfg->{shared};
1542
1543 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1544 return if !$owner || ($owner != $vmid);
1545
1546 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1547
d2a046b7 1548 return if !$cleanup && !$replicate;
f78c87a8
DM
1549
1550 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
e65dce6b 1551 return if $cleanup || $noerr;
f78c87a8
DM
1552 die "missing replicate feature on volume '$volid'\n";
1553 }
1554
1555 $volhash->{$volid} = 1;
1556 };
1557
1558 $class->foreach_mountpoint($conf, sub {
1559 my ($ms, $mountpoint) = @_;
1560 $test_volid->($mountpoint->{volume}, $mountpoint);
1561 });
1562
1563 foreach my $snapname (keys %{$conf->{snapshots}}) {
1564 my $snap = $conf->{snapshots}->{$snapname};
1565 $class->foreach_mountpoint($snap, sub {
1566 my ($ms, $mountpoint) = @_;
1567 $test_volid->($mountpoint->{volume}, $mountpoint);
1568 });
1569 }
1570
b03664ae
DM
1571 # add 'unusedX' volumes to volhash
1572 foreach my $key (keys %$conf) {
1573 if ($key =~ m/^unused/) {
1574 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1575 }
1576 }
1577
f78c87a8
DM
1578 return $volhash;
1579}
1580
15811;