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