12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
21 use Time
::HiRes qw
(gettimeofday
);
25 my $nodename = PVE
::INotify
::nodename
();
27 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
29 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
31 '--xattrs-include=user.*',
32 '--xattrs-include=security.capability',
33 '--warning=no-xattr-write' ];
35 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
41 format
=> 'pve-lxc-mp-string',
42 format_description
=> 'volume',
43 description
=> 'Volume, device or directory to mount into the container.',
47 format_description
=> '[1|0]',
48 description
=> 'Whether to include the mountpoint in backups.',
53 format
=> 'disk-size',
54 format_description
=> 'DiskSize',
55 description
=> 'Volume size (read only value).',
60 format_description
=> 'acl',
61 description
=> 'Explicitly enable or disable ACL support.',
66 format_description
=> 'ro',
67 description
=> 'Read-only mountpoint (not supported with bind mounts)',
72 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
73 type
=> 'string', format
=> $rootfs_desc,
74 description
=> "Use volume as container root.",
78 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
79 description
=> "The name of the snapshot.",
80 type
=> 'string', format
=> 'pve-configid',
88 description
=> "Lock/unlock the VM.",
89 enum
=> [qw(migrate backup snapshot rollback)],
94 description
=> "Specifies whether a VM will be started during system bootup.",
97 startup
=> get_standard_option
('pve-startup-order'),
101 description
=> "Enable/disable Template.",
107 enum
=> ['amd64', 'i386'],
108 description
=> "OS architecture type.",
114 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux'],
115 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
120 description
=> "Attach a console device (/dev/console) to the container.",
126 description
=> "Specify the number of tty available to the container",
134 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
142 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.",
150 description
=> "Amount of RAM for the VM in MB.",
157 description
=> "Amount of SWAP for the VM in MB.",
163 description
=> "Set a host name for the container.",
164 type
=> 'string', format
=> 'dns-name',
170 description
=> "Container description. Only used on the configuration web interface.",
174 type
=> 'string', format
=> 'dns-name-list',
175 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
179 type
=> 'string', format
=> 'address-list',
180 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.",
182 rootfs
=> get_standard_option
('pve-ct-rootfs'),
185 type
=> 'string', format
=> 'pve-configid',
187 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
191 description
=> "Timestamp for snapshots.",
197 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).",
199 enum
=> ['shell', 'console', 'tty'],
205 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
211 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
216 my $valid_lxc_conf_keys = {
220 'lxc.haltsignal' => 1,
221 'lxc.rebootsignal' => 1,
222 'lxc.stopsignal' => 1,
224 'lxc.network.type' => 1,
225 'lxc.network.flags' => 1,
226 'lxc.network.link' => 1,
227 'lxc.network.mtu' => 1,
228 'lxc.network.name' => 1,
229 'lxc.network.hwaddr' => 1,
230 'lxc.network.ipv4' => 1,
231 'lxc.network.ipv4.gateway' => 1,
232 'lxc.network.ipv6' => 1,
233 'lxc.network.ipv6.gateway' => 1,
234 'lxc.network.script.up' => 1,
235 'lxc.network.script.down' => 1,
237 'lxc.console.logfile' => 1,
240 'lxc.devttydir' => 1,
241 'lxc.hook.autodev' => 1,
245 'lxc.mount.entry' => 1,
246 'lxc.mount.auto' => 1,
247 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
248 'lxc.rootfs.mount' => 1,
249 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
250 ', please use mountpoint options in the "rootfs" key',
254 'lxc.aa_profile' => 1,
255 'lxc.aa_allow_incomplete' => 1,
256 'lxc.se_context' => 1,
259 'lxc.hook.pre-start' => 1,
260 'lxc.hook.pre-mount' => 1,
261 'lxc.hook.mount' => 1,
262 'lxc.hook.start' => 1,
263 'lxc.hook.stop' => 1,
264 'lxc.hook.post-stop' => 1,
265 'lxc.hook.clone' => 1,
266 'lxc.hook.destroy' => 1,
269 'lxc.start.auto' => 1,
270 'lxc.start.delay' => 1,
271 'lxc.start.order' => 1,
273 'lxc.environment' => 1,
280 description
=> "Network interface type.",
285 format_description
=> 'String',
286 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
287 pattern
=> '[-_.\w\d]+',
291 format_description
=> 'vmbr<Number>',
292 description
=> 'Bridge to attach the network device to.',
293 pattern
=> '[-_.\w\d]+',
298 format_description
=> 'MAC',
299 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
300 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
305 format_description
=> 'Number',
306 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
307 minimum
=> 64, # minimum ethernet frame is 64 bytes
312 format
=> 'pve-ipv4-config',
313 format_description
=> 'IPv4Format/CIDR',
314 description
=> 'IPv4 address in CIDR format.',
320 format_description
=> 'GatewayIPv4',
321 description
=> 'Default gateway for IPv4 traffic.',
326 format
=> 'pve-ipv6-config',
327 format_description
=> 'IPv6Format/CIDR',
328 description
=> 'IPv6 address in CIDR format.',
334 format_description
=> 'GatewayIPv6',
335 description
=> 'Default gateway for IPv6 traffic.',
340 format_description
=> '[1|0]',
341 description
=> "Controls whether this interface's firewall rules should be used.",
346 format_description
=> 'VlanNo',
349 description
=> "VLAN tag for this interface.",
354 pattern
=> qr/\d+(?:;\d+)*/,
355 format_description
=> 'vlanid[;vlanid...]',
356 description
=> "VLAN ids to pass through the interface",
360 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
362 my $MAX_LXC_NETWORKS = 10;
363 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
364 $confdesc->{"net$i"} = {
366 type
=> 'string', format
=> $netconf_desc,
367 description
=> "Specifies network interfaces for the container.",
371 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
372 sub verify_lxc_mp_string
{
373 my ($mp, $noerr) = @_;
377 # /. or /.. at the end
378 # ../ at the beginning
380 if($mp =~ m
@/\.\
.?
/@ ||
383 return undef if $noerr;
384 die "$mp contains illegal character sequences\n";
393 format
=> 'pve-lxc-mp-string',
394 format_description
=> 'Path',
395 description
=> 'Path to the mountpoint as seen from inside the container.',
398 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
402 type
=> 'string', format
=> 'pve-volume-id',
403 description
=> "Reference to unused volumes.",
406 my $MAX_MOUNT_POINTS = 10;
407 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
408 $confdesc->{"mp$i"} = {
410 type
=> 'string', format
=> $mp_desc,
411 description
=> "Use volume as container mount point (experimental feature).",
416 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
417 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
418 $confdesc->{"unused$i"} = $unuseddesc;
421 sub write_pct_config
{
422 my ($filename, $conf) = @_;
424 delete $conf->{snapstate
}; # just to be sure
426 my $generate_raw_config = sub {
431 # add description as comment to top of file
432 my $descr = $conf->{description
} || '';
433 foreach my $cl (split(/\n/, $descr)) {
434 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
437 foreach my $key (sort keys %$conf) {
438 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
439 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
440 my $value = $conf->{$key};
441 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
442 $raw .= "$key: $value\n";
445 if (my $lxcconf = $conf->{lxc
}) {
446 foreach my $entry (@$lxcconf) {
447 my ($k, $v) = @$entry;
455 my $raw = &$generate_raw_config($conf);
457 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
458 $raw .= "\n[$snapname]\n";
459 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
466 my ($key, $value) = @_;
468 die "unknown setting '$key'\n" if !$confdesc->{$key};
470 my $type = $confdesc->{$key}->{type
};
472 if (!defined($value)) {
473 die "got undefined value\n";
476 if ($value =~ m/[\n\r]/) {
477 die "property contains a line feed\n";
480 if ($type eq 'boolean') {
481 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
482 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
483 die "type check ('boolean') failed - got '$value'\n";
484 } elsif ($type eq 'integer') {
485 return int($1) if $value =~ m/^(\d+)$/;
486 die "type check ('integer') failed - got '$value'\n";
487 } elsif ($type eq 'number') {
488 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
489 die "type check ('number') failed - got '$value'\n";
490 } elsif ($type eq 'string') {
491 if (my $fmt = $confdesc->{$key}->{format
}) {
492 PVE
::JSONSchema
::check_format
($fmt, $value);
501 sub parse_pct_config
{
502 my ($filename, $raw) = @_;
504 return undef if !defined($raw);
507 digest
=> Digest
::SHA
::sha1_hex
($raw),
511 $filename =~ m
|/lxc/(\d
+).conf
$|
512 || die "got strange filename '$filename'";
520 my @lines = split(/\n/, $raw);
521 foreach my $line (@lines) {
522 next if $line =~ m/^\s*$/;
524 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
526 $conf->{description
} = $descr if $descr;
528 $conf = $res->{snapshots
}->{$section} = {};
532 if ($line =~ m/^\#(.*)\s*$/) {
533 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
537 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
540 my $validity = $valid_lxc_conf_keys->{$key} || 0;
541 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
542 push @{$conf->{lxc
}}, [$key, $value];
543 } elsif (my $errmsg = $validity) {
544 warn "vm $vmid - $key: $errmsg\n";
546 warn "vm $vmid - unable to parse config: $line\n";
548 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
549 $descr .= PVE
::Tools
::decode_text
($2);
550 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
551 $conf->{snapstate
} = $1;
552 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
555 eval { $value = check_type
($key, $value); };
556 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
557 $conf->{$key} = $value;
559 warn "vm $vmid - unable to parse config: $line\n";
563 $conf->{description
} = $descr if $descr;
565 delete $res->{snapstate
}; # just to be sure
571 my $vmlist = PVE
::Cluster
::get_vmlist
();
573 return $res if !$vmlist || !$vmlist->{ids
};
574 my $ids = $vmlist->{ids
};
576 foreach my $vmid (keys %$ids) {
577 next if !$vmid; # skip CT0
578 my $d = $ids->{$vmid};
579 next if !$d->{node
} || $d->{node
} ne $nodename;
580 next if !$d->{type
} || $d->{type
} ne 'lxc';
581 $res->{$vmid}->{type
} = 'lxc';
586 sub cfs_config_path
{
587 my ($vmid, $node) = @_;
589 $node = $nodename if !$node;
590 return "nodes/$node/lxc/$vmid.conf";
594 my ($vmid, $node) = @_;
596 my $cfspath = cfs_config_path
($vmid, $node);
597 return "/etc/pve/$cfspath";
601 my ($vmid, $node) = @_;
603 $node = $nodename if !$node;
604 my $cfspath = cfs_config_path
($vmid, $node);
606 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
607 die "container $vmid does not exist\n" if !defined($conf);
613 my ($vmid, $conf) = @_;
615 my $dir = "/etc/pve/nodes/$nodename/lxc";
618 write_config
($vmid, $conf);
624 unlink config_file
($vmid, $nodename);
628 my ($vmid, $conf) = @_;
630 my $cfspath = cfs_config_path
($vmid);
632 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
635 # flock: we use one file handle per process, so lock file
636 # can be called multiple times and will succeed for the same process.
638 my $lock_handles = {};
639 my $lockdir = "/run/lock/lxc";
644 return "$lockdir/pve-config-${vmid}.lock";
648 my ($vmid, $timeout) = @_;
650 $timeout = 10 if !$timeout;
653 my $filename = lock_filename
($vmid);
655 mkdir $lockdir if !-d
$lockdir;
657 my $lock_func = sub {
658 if (!$lock_handles->{$$}->{$filename}) {
659 my $fh = new IO
::File
(">>$filename") ||
660 die "can't open file - $!\n";
661 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
664 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
665 print STDERR
"trying to aquire lock...";
668 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
669 # try again on EINTR (see bug #273)
670 if ($success || ($! != EINTR
)) {
675 print STDERR
" failed\n";
676 die "can't aquire lock - $!\n";
679 print STDERR
" OK\n";
682 $lock_handles->{$$}->{$filename}->{refcount
}++;
685 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
688 die "can't lock file '$filename' - $err";
695 my $filename = lock_filename
($vmid);
697 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
698 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
699 if ($refcount <= 0) {
700 $lock_handles->{$$}->{$filename} = undef;
707 my ($vmid, $timeout, $code, @param) = @_;
711 lock_aquire
($vmid, $timeout);
712 eval { $res = &$code(@param) };
724 return defined($confdesc->{$name});
727 # add JSON properties for create and set function
728 sub json_config_properties
{
731 foreach my $opt (keys %$confdesc) {
732 next if $opt eq 'parent' || $opt eq 'snaptime';
733 next if $prop->{$opt};
734 $prop->{$opt} = $confdesc->{$opt};
740 sub json_config_properties_no_rootfs
{
743 foreach my $opt (keys %$confdesc) {
744 next if $prop->{$opt};
745 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
746 $prop->{$opt} = $confdesc->{$opt};
752 # container status helpers
754 sub list_active_containers
{
756 my $filename = "/proc/net/unix";
758 # similar test is used by lcxcontainers.c: list_active_containers
761 my $fh = IO
::File-
>new ($filename, "r");
764 while (defined(my $line = <$fh>)) {
765 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
767 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
778 # warning: this is slow
782 my $active_hash = list_active_containers
();
784 return 1 if defined($active_hash->{$vmid});
789 sub get_container_disk_usage
{
790 my ($vmid, $pid) = @_;
792 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
795 my $last_proc_vmid_stat;
797 my $parse_cpuacct_stat = sub {
800 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
804 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
817 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
819 my $active_hash = list_active_containers
();
821 my $cpucount = $cpuinfo->{cpus
} || 1;
823 my $cdtime = gettimeofday
;
825 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
827 foreach my $vmid (keys %$list) {
828 my $d = $list->{$vmid};
830 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
831 warn $@ if $@; # ignore errors (consider them stopped)
833 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
835 my $cfspath = cfs_config_path
($vmid);
836 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
838 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
839 $d->{name
} =~ s/[\s]//g;
841 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
844 my $res = get_container_disk_usage
($vmid, $d->{pid
});
845 $d->{disk
} = $res->{used
};
846 $d->{maxdisk
} = $res->{total
};
849 # use 4GB by default ??
850 if (my $rootfs = $conf->{rootfs
}) {
851 my $rootinfo = parse_ct_rootfs
($rootfs);
852 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
854 $d->{maxdisk
} = 4*1024*1024*1024;
860 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
861 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
872 $d->{template
} = is_template
($conf);
875 foreach my $vmid (keys %$list) {
876 my $d = $list->{$vmid};
879 next if !$pid; # skip stopped CTs
881 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
882 $d->{uptime
} = time - $ctime; # the method lxcfs uses
884 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
885 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
887 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
888 my @bytes = split(/\n/, $blkio_bytes);
889 foreach my $byte (@bytes) {
890 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
891 $d->{diskread
} = $2 if $key eq 'Read';
892 $d->{diskwrite
} = $2 if $key eq 'Write';
896 my $pstat = &$parse_cpuacct_stat($vmid);
898 my $used = $pstat->{utime} + $pstat->{stime
};
900 my $old = $last_proc_vmid_stat->{$vmid};
902 $last_proc_vmid_stat->{$vmid} = {
910 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
913 my $dutime = $used - $old->{used
};
915 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
916 $last_proc_vmid_stat->{$vmid} = {
922 $d->{cpu
} = $old->{cpu
};
926 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
928 foreach my $dev (keys %$netdev) {
929 next if $dev !~ m/^veth([1-9]\d*)i/;
931 my $d = $list->{$vmid};
935 $d->{netout
} += $netdev->{$dev}->{receive
};
936 $d->{netin
} += $netdev->{$dev}->{transmit
};
943 sub classify_mountpoint
{
946 return 'device' if $vol =~ m!^/dev/!;
952 my $parse_ct_mountpoint_full = sub {
953 my ($desc, $data, $noerr) = @_;
958 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
960 return undef if $noerr;
964 if (defined(my $size = $res->{size
})) {
965 $size = PVE
::JSONSchema
::parse_size
($size);
966 if (!defined($size)) {
967 return undef if $noerr;
968 die "invalid size: $size\n";
970 $res->{size
} = $size;
973 $res->{type
} = classify_mountpoint
($res->{volume
});
978 sub parse_ct_rootfs
{
979 my ($data, $noerr) = @_;
981 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
983 $res->{mp
} = '/' if defined($res);
988 sub parse_ct_mountpoint
{
989 my ($data, $noerr) = @_;
991 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
994 sub print_ct_mountpoint
{
995 my ($info, $nomp) = @_;
996 my $skip = [ 'type' ];
997 push @$skip, 'mp' if $nomp;
998 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1001 sub print_lxc_network
{
1003 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1006 sub parse_lxc_network
{
1011 return $res if !$data;
1013 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1015 $res->{type
} = 'veth';
1016 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1021 sub read_cgroup_value
{
1022 my ($group, $vmid, $name, $full) = @_;
1024 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
1026 return PVE
::Tools
::file_get_contents
($path) if $full;
1028 return PVE
::Tools
::file_read_firstline
($path);
1031 sub write_cgroup_value
{
1032 my ($group, $vmid, $name, $value) = @_;
1034 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
1035 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
1039 sub find_lxc_console_pids
{
1043 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1046 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1047 return if !$cmdline;
1049 my @args = split(/\0/, $cmdline);
1051 # search for lxc-console -n <vmid>
1052 return if scalar(@args) != 3;
1053 return if $args[1] ne '-n';
1054 return if $args[2] !~ m/^\d+$/;
1055 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1057 my $vmid = $args[2];
1059 push @{$res->{$vmid}}, $pid;
1071 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1073 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1075 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1080 # Note: we cannot use Net:IP, because that only allows strict
1082 sub parse_ipv4_cidr
{
1083 my ($cidr, $noerr) = @_;
1085 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1086 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1089 return undef if $noerr;
1091 die "unable to parse ipv4 address/mask\n";
1097 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1100 sub check_protection
{
1101 my ($vm_conf, $err_msg) = @_;
1103 if ($vm_conf->{protection
}) {
1104 die "$err_msg - protection mode enabled\n";
1108 sub update_lxc_config
{
1109 my ($storage_cfg, $vmid, $conf) = @_;
1111 my $dir = "/var/lib/lxc/$vmid";
1113 if ($conf->{template
}) {
1115 unlink "$dir/config";
1122 die "missing 'arch' - internal error" if !$conf->{arch
};
1123 $raw .= "lxc.arch = $conf->{arch}\n";
1125 my $unprivileged = $conf->{unprivileged
};
1126 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1128 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1129 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1130 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1131 if ($unprivileged || $custom_idmap) {
1132 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1135 die "implement me (ostype $ostype)";
1138 $raw .= "lxc.monitor.unshare = 1\n";
1140 # Should we read them from /etc/subuid?
1141 if ($unprivileged && !$custom_idmap) {
1142 $raw .= "lxc.id_map = u 0 100000 65536\n";
1143 $raw .= "lxc.id_map = g 0 100000 65536\n";
1146 if (!has_dev_console
($conf)) {
1147 $raw .= "lxc.console = none\n";
1148 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1151 my $ttycount = get_tty_count
($conf);
1152 $raw .= "lxc.tty = $ttycount\n";
1154 # some init scripts expect a linux terminal (turnkey).
1155 $raw .= "lxc.environment = TERM=linux\n";
1157 my $utsname = $conf->{hostname
} || "CT$vmid";
1158 $raw .= "lxc.utsname = $utsname\n";
1160 my $memory = $conf->{memory
} || 512;
1161 my $swap = $conf->{swap
} // 0;
1163 my $lxcmem = int($memory*1024*1024);
1164 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1166 my $lxcswap = int(($memory + $swap)*1024*1024);
1167 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1169 if (my $cpulimit = $conf->{cpulimit
}) {
1170 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1171 my $value = int(100000*$cpulimit);
1172 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1175 my $shares = $conf->{cpuunits
} || 1024;
1176 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1178 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1180 $raw .= "lxc.rootfs = $dir/rootfs\n";
1183 foreach my $k (keys %$conf) {
1184 next if $k !~ m/^net(\d+)$/;
1186 my $d = parse_lxc_network
($conf->{$k});
1188 $raw .= "lxc.network.type = veth\n";
1189 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1190 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1191 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1192 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1195 if (my $lxcconf = $conf->{lxc
}) {
1196 foreach my $entry (@$lxcconf) {
1197 my ($k, $v) = @$entry;
1198 $netcount++ if $k eq 'lxc.network.type';
1199 $raw .= "$k = $v\n";
1203 $raw .= "lxc.network.type = empty\n" if !$netcount;
1205 File
::Path
::mkpath
("$dir/rootfs");
1207 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1210 # verify and cleanup nameserver list (replace \0 with ' ')
1211 sub verify_nameserver_list
{
1212 my ($nameserver_list) = @_;
1215 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1216 PVE
::JSONSchema
::pve_verify_ip
($server);
1217 push @list, $server;
1220 return join(' ', @list);
1223 sub verify_searchdomain_list
{
1224 my ($searchdomain_list) = @_;
1227 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1228 # todo: should we add checks for valid dns domains?
1229 push @list, $server;
1232 return join(' ', @list);
1235 sub add_unused_volume
{
1236 my ($config, $volid) = @_;
1239 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1240 my $test = "unused$ind";
1241 if (my $vid = $config->{$test}) {
1242 return if $vid eq $volid; # do not add duplicates
1248 die "Too many unused volumes - please delete them first.\n" if !$key;
1250 $config->{$key} = $volid;
1255 sub update_pct_config
{
1256 my ($vmid, $conf, $running, $param, $delete) = @_;
1261 my @deleted_volumes;
1265 my $pid = find_lxc_pid
($vmid);
1266 $rootdir = "/proc/$pid/root";
1269 my $hotplug_error = sub {
1271 push @nohotplug, @_;
1278 if (defined($delete)) {
1279 foreach my $opt (@$delete) {
1280 if (!exists($conf->{$opt})) {
1281 warn "no such option: $opt\n";
1285 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1286 die "unable to delete required option '$opt'\n";
1287 } elsif ($opt eq 'swap') {
1288 delete $conf->{$opt};
1289 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1290 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1291 delete $conf->{$opt};
1292 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1293 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1294 next if $hotplug_error->($opt);
1295 delete $conf->{$opt};
1296 } elsif ($opt =~ m/^net(\d)$/) {
1297 delete $conf->{$opt};
1300 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1301 } elsif ($opt eq 'protection') {
1302 delete $conf->{$opt};
1303 } elsif ($opt =~ m/^unused(\d+)$/) {
1304 next if $hotplug_error->($opt);
1305 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1306 push @deleted_volumes, $conf->{$opt};
1307 delete $conf->{$opt};
1308 } elsif ($opt =~ m/^mp(\d+)$/) {
1309 next if $hotplug_error->($opt);
1310 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1311 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1312 if ($mountpoint->{type
} eq 'volume') {
1313 add_unused_volume
($conf, $mountpoint->{volume
})
1315 delete $conf->{$opt};
1316 } elsif ($opt eq 'unprivileged') {
1317 die "unable to delete read-only option: '$opt'\n";
1319 die "implement me (delete: $opt)"
1321 write_config
($vmid, $conf) if $running;
1325 # There's no separate swap size to configure, there's memory and "total"
1326 # memory (iow. memory+swap). This means we have to change them together.
1327 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1328 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1329 if (defined($wanted_memory) || defined($wanted_swap)) {
1331 my $old_memory = ($conf->{memory
} || 512);
1332 my $old_swap = ($conf->{swap
} || 0);
1334 $wanted_memory //= $old_memory;
1335 $wanted_swap //= $old_swap;
1337 my $total = $wanted_memory + $wanted_swap;
1339 my $old_total = $old_memory + $old_swap;
1340 if ($total > $old_total) {
1341 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1342 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1344 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1345 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1348 $conf->{memory
} = $wanted_memory;
1349 $conf->{swap
} = $wanted_swap;
1351 write_config
($vmid, $conf) if $running;
1354 foreach my $opt (keys %$param) {
1355 my $value = $param->{$opt};
1356 if ($opt eq 'hostname') {
1357 $conf->{$opt} = $value;
1358 } elsif ($opt eq 'onboot') {
1359 $conf->{$opt} = $value ?
1 : 0;
1360 } elsif ($opt eq 'startup') {
1361 $conf->{$opt} = $value;
1362 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1363 next if $hotplug_error->($opt);
1364 $conf->{$opt} = $value;
1365 } elsif ($opt eq 'nameserver') {
1366 next if $hotplug_error->($opt);
1367 my $list = verify_nameserver_list
($value);
1368 $conf->{$opt} = $list;
1369 } elsif ($opt eq 'searchdomain') {
1370 next if $hotplug_error->($opt);
1371 my $list = verify_searchdomain_list
($value);
1372 $conf->{$opt} = $list;
1373 } elsif ($opt eq 'cpulimit') {
1374 next if $hotplug_error->($opt); # FIXME: hotplug
1375 $conf->{$opt} = $value;
1376 } elsif ($opt eq 'cpuunits') {
1377 $conf->{$opt} = $value;
1378 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1379 } elsif ($opt eq 'description') {
1380 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1381 } elsif ($opt =~ m/^net(\d+)$/) {
1383 my $net = parse_lxc_network
($value);
1385 $conf->{$opt} = print_lxc_network
($net);
1387 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1389 } elsif ($opt eq 'protection') {
1390 $conf->{$opt} = $value ?
1 : 0;
1391 } elsif ($opt =~ m/^mp(\d+)$/) {
1392 next if $hotplug_error->($opt);
1393 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1394 $conf->{$opt} = $value;
1396 } elsif ($opt eq 'rootfs') {
1397 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1398 die "implement me: $opt";
1399 } elsif ($opt eq 'unprivileged') {
1400 die "unable to modify read-only option: '$opt'\n";
1402 die "implement me: $opt";
1404 write_config
($vmid, $conf) if $running;
1407 if (@deleted_volumes) {
1408 my $storage_cfg = PVE
::Storage
::config
();
1409 foreach my $volume (@deleted_volumes) {
1410 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1415 my $storage_cfg = PVE
::Storage
::config
();
1416 create_disks
($storage_cfg, $vmid, $conf, $conf);
1419 # This should be the last thing we do here
1420 if ($running && scalar(@nohotplug)) {
1421 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1425 sub has_dev_console
{
1428 return !(defined($conf->{console
}) && !$conf->{console
});
1434 return $conf->{tty
} // $confdesc->{tty
}->{default};
1440 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1443 sub get_console_command
{
1444 my ($vmid, $conf) = @_;
1446 my $cmode = get_cmode
($conf);
1448 if ($cmode eq 'console') {
1449 return ['lxc-console', '-n', $vmid, '-t', 0];
1450 } elsif ($cmode eq 'tty') {
1451 return ['lxc-console', '-n', $vmid];
1452 } elsif ($cmode eq 'shell') {
1453 return ['lxc-attach', '--clear-env', '-n', $vmid];
1455 die "internal error";
1459 sub get_primary_ips
{
1462 # return data from net0
1464 return undef if !defined($conf->{net0
});
1465 my $net = parse_lxc_network
($conf->{net0
});
1467 my $ipv4 = $net->{ip
};
1469 if ($ipv4 =~ /^(dhcp|manual)$/) {
1475 my $ipv6 = $net->{ip6
};
1477 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1484 return ($ipv4, $ipv6);
1487 sub delete_mountpoint_volume
{
1488 my ($storage_cfg, $vmid, $volume) = @_;
1490 return if classify_mountpoint
($volume) ne 'volume';
1492 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1493 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1496 sub destroy_lxc_container
{
1497 my ($storage_cfg, $vmid, $conf) = @_;
1499 foreach_mountpoint
($conf, sub {
1500 my ($ms, $mountpoint) = @_;
1501 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1504 rmdir "/var/lib/lxc/$vmid/rootfs";
1505 unlink "/var/lib/lxc/$vmid/config";
1506 rmdir "/var/lib/lxc/$vmid";
1507 destroy_config
($vmid);
1509 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1510 #PVE::Tools::run_command($cmd);
1513 sub vm_stop_cleanup
{
1514 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1519 my $vollist = get_vm_volumes
($conf);
1520 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1523 warn $@ if $@; # avoid errors - just warn
1526 my $safe_num_ne = sub {
1529 return 0 if !defined($a) && !defined($b);
1530 return 1 if !defined($a);
1531 return 1 if !defined($b);
1536 my $safe_string_ne = sub {
1539 return 0 if !defined($a) && !defined($b);
1540 return 1 if !defined($a);
1541 return 1 if !defined($b);
1547 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1549 if ($newnet->{type
} ne 'veth') {
1550 # for when there are physical interfaces
1551 die "cannot update interface of type $newnet->{type}";
1554 my $veth = "veth${vmid}i${netid}";
1555 my $eth = $newnet->{name
};
1557 if (my $oldnetcfg = $conf->{$opt}) {
1558 my $oldnet = parse_lxc_network
($oldnetcfg);
1560 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1561 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1563 PVE
::Network
::veth_delete
($veth);
1564 delete $conf->{$opt};
1565 write_config
($vmid, $conf);
1567 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1569 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1570 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1571 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1573 if ($oldnet->{bridge
}) {
1574 PVE
::Network
::tap_unplug
($veth);
1575 foreach (qw(bridge tag firewall)) {
1576 delete $oldnet->{$_};
1578 $conf->{$opt} = print_lxc_network
($oldnet);
1579 write_config
($vmid, $conf);
1582 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1583 foreach (qw(bridge tag firewall)) {
1584 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1586 $conf->{$opt} = print_lxc_network
($oldnet);
1587 write_config
($vmid, $conf);
1590 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1593 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1597 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1599 my $veth = "veth${vmid}i${netid}";
1600 my $vethpeer = $veth . "p";
1601 my $eth = $newnet->{name
};
1603 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1604 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1606 # attach peer in container
1607 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1608 PVE
::Tools
::run_command
($cmd);
1610 # link up peer in container
1611 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1612 PVE
::Tools
::run_command
($cmd);
1614 my $done = { type
=> 'veth' };
1615 foreach (qw(bridge tag firewall hwaddr name)) {
1616 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1618 $conf->{$opt} = print_lxc_network
($done);
1620 write_config
($vmid, $conf);
1623 sub update_ipconfig
{
1624 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1626 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1628 my $optdata = parse_lxc_network
($conf->{$opt});
1632 my $cmdargs = shift;
1633 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1635 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1637 my $change_ip_config = sub {
1638 my ($ipversion) = @_;
1640 my $family_opt = "-$ipversion";
1641 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1642 my $gw= "gw$suffix";
1643 my $ip= "ip$suffix";
1645 my $newip = $newnet->{$ip};
1646 my $newgw = $newnet->{$gw};
1647 my $oldip = $optdata->{$ip};
1649 my $change_ip = &$safe_string_ne($oldip, $newip);
1650 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1652 return if !$change_ip && !$change_gw;
1654 # step 1: add new IP, if this fails we cancel
1655 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1656 if ($change_ip && $is_real_ip) {
1657 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1664 # step 2: replace gateway
1665 # If this fails we delete the added IP and cancel.
1666 # If it succeeds we save the config and delete the old IP, ignoring
1667 # errors. The config is then saved.
1668 # Note: 'ip route replace' can add
1672 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1673 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1675 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1679 # the route was not replaced, the old IP is still available
1680 # rollback (delete new IP) and cancel
1682 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1683 warn $@ if $@; # no need to die here
1688 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1689 # if the route was not deleted, the guest might have deleted it manually
1695 # from this point on we save the configuration
1696 # step 3: delete old IP ignoring errors
1697 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1698 # We need to enable promote_secondaries, otherwise our newly added
1699 # address will be removed along with the old one.
1702 if ($ipversion == 4) {
1703 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1704 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1705 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1707 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1709 warn $@ if $@; # no need to die here
1711 if ($ipversion == 4) {
1712 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1716 foreach my $property ($ip, $gw) {
1717 if ($newnet->{$property}) {
1718 $optdata->{$property} = $newnet->{$property};
1720 delete $optdata->{$property};
1723 $conf->{$opt} = print_lxc_network
($optdata);
1724 write_config
($vmid, $conf);
1725 $lxc_setup->setup_network($conf);
1728 &$change_ip_config(4);
1729 &$change_ip_config(6);
1733 # Internal snapshots
1735 # NOTE: Snapshot create/delete involves several non-atomic
1736 # actions, and can take a long time.
1737 # So we try to avoid locking the file and use the 'lock' variable
1738 # inside the config file instead.
1740 my $snapshot_copy_config = sub {
1741 my ($source, $dest) = @_;
1743 foreach my $k (keys %$source) {
1744 next if $k eq 'snapshots';
1745 next if $k eq 'snapstate';
1746 next if $k eq 'snaptime';
1747 next if $k eq 'vmstate';
1748 next if $k eq 'lock';
1749 next if $k eq 'digest';
1750 next if $k eq 'description';
1752 $dest->{$k} = $source->{$k};
1756 my $snapshot_prepare = sub {
1757 my ($vmid, $snapname, $comment) = @_;
1761 my $updatefn = sub {
1763 my $conf = load_config
($vmid);
1765 die "you can't take a snapshot if it's a template\n"
1766 if is_template
($conf);
1770 $conf->{lock} = 'snapshot';
1772 die "snapshot name '$snapname' already used\n"
1773 if defined($conf->{snapshots
}->{$snapname});
1775 my $storecfg = PVE
::Storage
::config
();
1776 my $feature = $snapname eq 'vzdump' ?
'vzdump' : 'snapshot';
1777 die "snapshot feature is not available\n" if !has_feature
($feature, $conf, $storecfg);
1779 $snap = $conf->{snapshots
}->{$snapname} = {};
1781 &$snapshot_copy_config($conf, $snap);
1783 $snap->{'snapstate'} = "prepare";
1784 $snap->{'snaptime'} = time();
1785 $snap->{'description'} = $comment if $comment;
1786 $conf->{snapshots
}->{$snapname} = $snap;
1788 write_config
($vmid, $conf);
1791 lock_container
($vmid, 10, $updatefn);
1796 my $snapshot_commit = sub {
1797 my ($vmid, $snapname) = @_;
1799 my $updatefn = sub {
1801 my $conf = load_config
($vmid);
1803 die "missing snapshot lock\n"
1804 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1806 die "snapshot '$snapname' does not exist\n"
1807 if !defined($conf->{snapshots
}->{$snapname});
1809 die "wrong snapshot state\n"
1810 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1811 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1813 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1814 delete $conf->{lock};
1815 $conf->{parent
} = $snapname;
1817 write_config
($vmid, $conf);
1820 lock_container
($vmid, 10 ,$updatefn);
1824 my ($feature, $conf, $storecfg, $snapname) = @_;
1827 my $vzdump = $feature eq 'vzdump';
1828 $feature = 'snapshot' if $vzdump;
1830 foreach_mountpoint
($conf, sub {
1831 my ($ms, $mountpoint) = @_;
1833 return if $err; # skip further test
1834 return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup
};
1836 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1838 # TODO: implement support for mountpoints
1839 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1843 return $err ?
0 : 1;
1846 sub snapshot_create
{
1847 my ($vmid, $snapname, $comment) = @_;
1849 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1851 my $conf = load_config
($vmid);
1853 my $running = check_running
($vmid);
1859 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1861 PVE
::Tools
::run_command
(['/bin/sync']);
1864 my $storecfg = PVE
::Storage
::config
();
1865 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1866 my $volid = $rootinfo->{volume
};
1868 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1869 &$snapshot_commit($vmid, $snapname);
1874 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1879 snapshot_delete
($vmid, $snapname, 1);
1884 sub snapshot_delete
{
1885 my ($vmid, $snapname, $force) = @_;
1891 my $updatefn = sub {
1893 $conf = load_config
($vmid);
1895 die "you can't delete a snapshot if vm is a template\n"
1896 if is_template
($conf);
1898 $snap = $conf->{snapshots
}->{$snapname};
1902 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1904 $snap->{snapstate
} = 'delete';
1906 write_config
($vmid, $conf);
1909 lock_container
($vmid, 10, $updatefn);
1911 my $storecfg = PVE
::Storage
::config
();
1913 my $unlink_parent = sub {
1915 my ($confref, $new_parent) = @_;
1917 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
1919 $confref->{parent
} = $new_parent;
1921 delete $confref->{parent
};
1926 my $del_snap = sub {
1930 my $parent = $conf->{snapshots
}->{$snapname}->{parent
};
1931 foreach my $snapkey (keys %{$conf->{snapshots
}}) {
1932 &$unlink_parent($conf->{snapshots
}->{$snapkey}, $parent);
1935 &$unlink_parent($conf, $parent);
1937 delete $conf->{snapshots
}->{$snapname};
1939 write_config
($vmid, $conf);
1942 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1943 my $rootinfo = parse_ct_rootfs
($rootfs);
1944 my $volid = $rootinfo->{volume
};
1947 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1951 if(!$err || ($err && $force)) {
1952 lock_container
($vmid, 10, $del_snap);
1954 die "Can't delete snapshot: $vmid $snapname $err\n";
1959 sub snapshot_rollback
{
1960 my ($vmid, $snapname) = @_;
1962 my $storecfg = PVE
::Storage
::config
();
1964 my $conf = load_config
($vmid);
1966 die "you can't rollback if vm is a template\n" if is_template
($conf);
1968 my $snap = $conf->{snapshots
}->{$snapname};
1970 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1972 my $rootfs = $snap->{rootfs
};
1973 my $rootinfo = parse_ct_rootfs
($rootfs);
1974 my $volid = $rootinfo->{volume
};
1976 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1978 my $updatefn = sub {
1980 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1981 if $snap->{snapstate
};
1985 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1987 die "unable to rollback vm $vmid: vm is running\n"
1988 if check_running
($vmid);
1990 $conf->{lock} = 'rollback';
1994 # copy snapshot config to current config
1996 my $tmp_conf = $conf;
1997 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1998 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1999 delete $conf->{snaptime
};
2000 delete $conf->{snapname
};
2001 $conf->{parent
} = $snapname;
2003 write_config
($vmid, $conf);
2006 my $unlockfn = sub {
2007 delete $conf->{lock};
2008 write_config
($vmid, $conf);
2011 lock_container
($vmid, 10, $updatefn);
2013 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
2015 lock_container
($vmid, 5, $unlockfn);
2018 sub template_create
{
2019 my ($vmid, $conf) = @_;
2021 my $storecfg = PVE
::Storage
::config
();
2023 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
2024 my $volid = $rootinfo->{volume
};
2026 die "Template feature is not available for '$volid'\n"
2027 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
2029 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2031 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
2032 $rootinfo->{volume
} = $template_volid;
2033 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
2035 write_config
($vmid, $conf);
2041 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2044 sub mountpoint_names
{
2047 my @names = ('rootfs');
2049 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2050 push @names, "mp$i";
2053 return $reverse ?
reverse @names : @names;
2057 sub foreach_mountpoint_full
{
2058 my ($conf, $reverse, $func) = @_;
2060 foreach my $key (mountpoint_names
($reverse)) {
2061 my $value = $conf->{$key};
2062 next if !defined($value);
2063 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2064 next if !defined($mountpoint);
2066 &$func($key, $mountpoint);
2070 sub foreach_mountpoint
{
2071 my ($conf, $func) = @_;
2073 foreach_mountpoint_full
($conf, 0, $func);
2076 sub foreach_mountpoint_reverse
{
2077 my ($conf, $func) = @_;
2079 foreach_mountpoint_full
($conf, 1, $func);
2082 sub check_ct_modify_config_perm
{
2083 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2085 return 1 if $authuser ne 'root@pam';
2087 foreach my $opt (@$key_list) {
2089 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2090 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2091 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2092 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2093 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2094 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2095 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2096 $opt eq 'searchdomain' || $opt eq 'hostname') {
2097 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2099 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2107 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2109 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2110 my $volid_list = get_vm_volumes
($conf);
2112 foreach_mountpoint_reverse
($conf, sub {
2113 my ($ms, $mountpoint) = @_;
2115 my $volid = $mountpoint->{volume
};
2116 my $mount = $mountpoint->{mp
};
2118 return if !$volid || !$mount;
2120 my $mount_path = "$rootdir/$mount";
2121 $mount_path =~ s!/+!/!g;
2123 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2126 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2139 my ($vmid, $storage_cfg, $conf) = @_;
2141 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2142 File
::Path
::make_path
($rootdir);
2144 my $volid_list = get_vm_volumes
($conf);
2145 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2148 foreach_mountpoint
($conf, sub {
2149 my ($ms, $mountpoint) = @_;
2151 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2155 warn "mounting container failed\n";
2156 umount_all
($vmid, $storage_cfg, $conf, 1);
2164 sub mountpoint_mount_path
{
2165 my ($mountpoint, $storage_cfg, $snapname) = @_;
2167 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2170 my $check_mount_path = sub {
2172 $path = File
::Spec-
>canonpath($path);
2173 my $real = Cwd
::realpath
($path);
2174 if ($real ne $path) {
2175 die "mount path modified by symlink: $path != $real";
2184 if ($line =~ m
@^(/dev/loop\d
+):@) {
2188 my $cmd = ['losetup', '--associated', $path];
2189 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2193 # use $rootdir = undef to just return the corresponding mount path
2194 sub mountpoint_mount
{
2195 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2197 my $volid = $mountpoint->{volume
};
2198 my $mount = $mountpoint->{mp
};
2199 my $type = $mountpoint->{type
};
2201 return if !$volid || !$mount;
2205 if (defined($rootdir)) {
2206 $rootdir =~ s!/+$!!;
2207 $mount_path = "$rootdir/$mount";
2208 $mount_path =~ s!/+!/!g;
2209 &$check_mount_path($mount_path);
2210 File
::Path
::mkpath
($mount_path);
2213 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2215 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2218 if (defined($mountpoint->{acl
})) {
2219 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2221 if ($mountpoint->{ro
}) {
2222 $optstring .= ',' if $optstring;
2226 my @extra_opts = ('-o', $optstring);
2230 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2231 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2233 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2234 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2236 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2238 if ($format eq 'subvol') {
2241 if ($scfg->{type
} eq 'zfspool') {
2242 my $path_arg = $path;
2243 $path_arg =~ s!^/+!!;
2244 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2246 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2249 if ($mountpoint->{ro
}) {
2250 die "read-only bind mounts not supported\n";
2252 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]);
2255 return wantarray ?
($path, 0) : $path;
2256 } elsif ($format eq 'raw' || $format eq 'iso') {
2257 my $use_loopdev = 0;
2258 if ($scfg->{path
}) {
2259 push @extra_opts, '-o', 'loop';
2261 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2262 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2265 die "unsupported storage type '$scfg->{type}'\n";
2268 if ($format eq 'iso') {
2269 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2270 } elsif ($isBase || defined($snapname)) {
2271 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2273 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2276 return wantarray ?
($path, $use_loopdev) : $path;
2278 die "unsupported image format '$format'\n";
2280 } elsif ($type eq 'device') {
2281 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2282 return wantarray ?
($volid, 0) : $volid;
2283 } elsif ($type eq 'bind') {
2284 if ($mountpoint->{ro
}) {
2285 die "read-only bind mounts not supported\n";
2286 # Theoretically we'd have to execute both:
2287 # mount -o bind $a $b
2288 # mount -o bind,remount,ro $a $b
2290 die "directory '$volid' does not exist\n" if ! -d
$volid;
2291 &$check_mount_path($volid);
2292 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path;
2293 return wantarray ?
($volid, 0) : $volid;
2296 die "unsupported storage";
2299 sub get_vm_volumes
{
2300 my ($conf, $excludes) = @_;
2304 foreach_mountpoint
($conf, sub {
2305 my ($ms, $mountpoint) = @_;
2307 return if $excludes && $ms eq $excludes;
2309 my $volid = $mountpoint->{volume
};
2311 return if !$volid || $mountpoint->{type
} ne 'volume';
2313 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2316 push @$vollist, $volid;
2323 my ($dev, $rootuid, $rootgid) = @_;
2325 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2326 '-E', "root_owner=$rootuid:$rootgid",
2331 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2333 if ($volid =~ m!^/dev/.+!) {
2338 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2340 die "cannot format volume '$volid' with no storage\n" if !$storage;
2342 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2344 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2346 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2347 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2349 die "cannot format volume '$volid' (format == $format)\n"
2350 if $format ne 'raw';
2352 mkfs
($path, $rootuid, $rootgid);
2356 my ($storecfg, $vollist) = @_;
2358 foreach my $volid (@$vollist) {
2359 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2365 my ($storecfg, $vmid, $settings, $conf) = @_;
2370 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2371 my $chown_vollist = [];
2373 foreach_mountpoint
($settings, sub {
2374 my ($ms, $mountpoint) = @_;
2376 my $volid = $mountpoint->{volume
};
2377 my $mp = $mountpoint->{mp
};
2379 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2381 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2382 my ($storeid, $size_gb) = ($1, $2);
2384 my $size_kb = int(${size_gb
}*1024) * 1024;
2386 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2387 # fixme: use better naming ct-$vmid-disk-X.raw?
2389 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2391 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2393 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2395 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2397 push @$chown_vollist, $volid;
2399 } elsif ($scfg->{type
} eq 'zfspool') {
2401 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2403 push @$chown_vollist, $volid;
2404 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2406 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2407 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2409 } elsif ($scfg->{type
} eq 'rbd') {
2411 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2412 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2413 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2415 die "unable to create containers on storage type '$scfg->{type}'\n";
2417 push @$vollist, $volid;
2418 $mountpoint->{volume
} = $volid;
2419 $mountpoint->{size
} = $size_kb * 1024;
2420 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2422 # use specified/existing volid/dir/device
2423 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2427 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2428 foreach my $volid (@$chown_vollist) {
2429 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2430 chown($rootuid, $rootgid, $path);
2432 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2434 # free allocated images on error
2436 destroy_disks
($storecfg, $vollist);
2442 # bash completion helper
2444 sub complete_os_templates
{
2445 my ($cmdname, $pname, $cvalue) = @_;
2447 my $cfg = PVE
::Storage
::config
();
2451 if ($cvalue =~ m/^([^:]+):/) {
2455 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2456 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2459 foreach my $id (keys %$data) {
2460 foreach my $item (@{$data->{$id}}) {
2461 push @$res, $item->{volid
} if defined($item->{volid
});
2468 my $complete_ctid_full = sub {
2471 my $idlist = vmstatus
();
2473 my $active_hash = list_active_containers
();
2477 foreach my $id (keys %$idlist) {
2478 my $d = $idlist->{$id};
2479 if (defined($running)) {
2480 next if $d->{template
};
2481 next if $running && !$active_hash->{$id};
2482 next if !$running && $active_hash->{$id};
2491 return &$complete_ctid_full();
2494 sub complete_ctid_stopped
{
2495 return &$complete_ctid_full(0);
2498 sub complete_ctid_running
{
2499 return &$complete_ctid_full(1);
2509 my $lxc = $conf->{lxc
};
2510 foreach my $entry (@$lxc) {
2511 my ($key, $value) = @$entry;
2512 next if $key ne 'lxc.id_map';
2513 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2514 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2515 push @$id_map, [$type, $ct, $host, $length];
2517 $rootuid = $host if $type eq 'u';
2518 $rootgid = $host if $type eq 'g';
2521 die "failed to parse id_map: $value\n";
2525 if (!@$id_map && $conf->{unprivileged
}) {
2526 # Should we read them from /etc/subuid?
2527 $id_map = [ ['u', '0', '100000', '65536'],
2528 ['g', '0', '100000', '65536'] ];
2529 $rootuid = $rootgid = 100000;
2532 return ($id_map, $rootuid, $rootgid);
2535 sub userns_command
{
2538 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];