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