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