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