12 use Fcntl
qw(O_RDONLY);
14 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
18 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full);
21 use PVE
::AccessControl
;
23 use Time
::HiRes qw
(gettimeofday
);
27 my $nodename = PVE
::INotify
::nodename
();
29 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
31 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
33 '--xattrs-include=user.*',
34 '--xattrs-include=security.capability',
35 '--warning=no-xattr-write' ];
37 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
43 format
=> 'pve-lxc-mp-string',
44 format_description
=> 'volume',
45 description
=> 'Volume, device or directory to mount into the container.',
49 format_description
=> '[1|0]',
50 description
=> 'Whether to include the mountpoint in backups.',
55 format
=> 'disk-size',
56 format_description
=> 'DiskSize',
57 description
=> 'Volume size (read only value).',
62 format_description
=> 'acl',
63 description
=> 'Explicitly enable or disable ACL support.',
68 format_description
=> 'ro',
69 description
=> 'Read-only mountpoint (not supported with bind mounts)',
74 format_description
=> '[0|1]',
75 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
80 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
81 type
=> 'string', format
=> $rootfs_desc,
82 description
=> "Use volume as container root.",
86 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
87 description
=> "The name of the snapshot.",
88 type
=> 'string', format
=> 'pve-configid',
96 description
=> "Lock/unlock the VM.",
97 enum
=> [qw(migrate backup snapshot rollback)],
102 description
=> "Specifies whether a VM will be started during system bootup.",
105 startup
=> get_standard_option
('pve-startup-order'),
109 description
=> "Enable/disable Template.",
115 enum
=> ['amd64', 'i386'],
116 description
=> "OS architecture type.",
122 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine'],
123 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
128 description
=> "Attach a console device (/dev/console) to the container.",
134 description
=> "Specify the number of tty available to the container",
142 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.",
150 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.",
158 description
=> "Amount of RAM for the VM in MB.",
165 description
=> "Amount of SWAP for the VM in MB.",
171 description
=> "Set a host name for the container.",
172 type
=> 'string', format
=> 'dns-name',
178 description
=> "Container description. Only used on the configuration web interface.",
182 type
=> 'string', format
=> 'dns-name-list',
183 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
187 type
=> 'string', format
=> 'address-list',
188 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.",
190 rootfs
=> get_standard_option
('pve-ct-rootfs'),
193 type
=> 'string', format
=> 'pve-configid',
195 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
199 description
=> "Timestamp for snapshots.",
205 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).",
207 enum
=> ['shell', 'console', 'tty'],
213 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
219 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
224 my $valid_lxc_conf_keys = {
228 'lxc.haltsignal' => 1,
229 'lxc.rebootsignal' => 1,
230 'lxc.stopsignal' => 1,
232 'lxc.network.type' => 1,
233 'lxc.network.flags' => 1,
234 'lxc.network.link' => 1,
235 'lxc.network.mtu' => 1,
236 'lxc.network.name' => 1,
237 'lxc.network.hwaddr' => 1,
238 'lxc.network.ipv4' => 1,
239 'lxc.network.ipv4.gateway' => 1,
240 'lxc.network.ipv6' => 1,
241 'lxc.network.ipv6.gateway' => 1,
242 'lxc.network.script.up' => 1,
243 'lxc.network.script.down' => 1,
245 'lxc.console.logfile' => 1,
248 'lxc.devttydir' => 1,
249 'lxc.hook.autodev' => 1,
253 'lxc.mount.entry' => 1,
254 'lxc.mount.auto' => 1,
255 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
256 'lxc.rootfs.mount' => 1,
257 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
258 ', please use mountpoint options in the "rootfs" key',
262 'lxc.aa_profile' => 1,
263 'lxc.aa_allow_incomplete' => 1,
264 'lxc.se_context' => 1,
267 'lxc.hook.pre-start' => 1,
268 'lxc.hook.pre-mount' => 1,
269 'lxc.hook.mount' => 1,
270 'lxc.hook.start' => 1,
271 'lxc.hook.stop' => 1,
272 'lxc.hook.post-stop' => 1,
273 'lxc.hook.clone' => 1,
274 'lxc.hook.destroy' => 1,
277 'lxc.start.auto' => 1,
278 'lxc.start.delay' => 1,
279 'lxc.start.order' => 1,
281 'lxc.environment' => 1,
288 description
=> "Network interface type.",
293 format_description
=> 'String',
294 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
295 pattern
=> '[-_.\w\d]+',
299 format_description
=> 'vmbr<Number>',
300 description
=> 'Bridge to attach the network device to.',
301 pattern
=> '[-_.\w\d]+',
306 format_description
=> 'MAC',
307 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
308 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
313 format_description
=> 'Number',
314 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
315 minimum
=> 64, # minimum ethernet frame is 64 bytes
320 format
=> 'pve-ipv4-config',
321 format_description
=> 'IPv4Format/CIDR',
322 description
=> 'IPv4 address in CIDR format.',
328 format_description
=> 'GatewayIPv4',
329 description
=> 'Default gateway for IPv4 traffic.',
334 format
=> 'pve-ipv6-config',
335 format_description
=> 'IPv6Format/CIDR',
336 description
=> 'IPv6 address in CIDR format.',
342 format_description
=> 'GatewayIPv6',
343 description
=> 'Default gateway for IPv6 traffic.',
348 format_description
=> '[1|0]',
349 description
=> "Controls whether this interface's firewall rules should be used.",
354 format_description
=> 'VlanNo',
357 description
=> "VLAN tag for this interface.",
362 pattern
=> qr/\d+(?:;\d+)*/,
363 format_description
=> 'vlanid[;vlanid...]',
364 description
=> "VLAN ids to pass through the interface",
368 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
370 my $MAX_LXC_NETWORKS = 10;
371 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
372 $confdesc->{"net$i"} = {
374 type
=> 'string', format
=> $netconf_desc,
375 description
=> "Specifies network interfaces for the container.",
379 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
380 sub verify_lxc_mp_string
{
381 my ($mp, $noerr) = @_;
385 # /. or /.. at the end
386 # ../ at the beginning
388 if($mp =~ m
@/\.\
.?
/@ ||
391 return undef if $noerr;
392 die "$mp contains illegal character sequences\n";
401 format
=> 'pve-lxc-mp-string',
402 format_description
=> 'Path',
403 description
=> 'Path to the mountpoint as seen from inside the container.',
406 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
410 type
=> 'string', format
=> 'pve-volume-id',
411 description
=> "Reference to unused volumes.",
414 my $MAX_MOUNT_POINTS = 10;
415 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
416 $confdesc->{"mp$i"} = {
418 type
=> 'string', format
=> $mp_desc,
419 description
=> "Use volume as container mount point (experimental feature).",
424 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
425 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
426 $confdesc->{"unused$i"} = $unuseddesc;
429 sub write_pct_config
{
430 my ($filename, $conf) = @_;
432 delete $conf->{snapstate
}; # just to be sure
434 my $generate_raw_config = sub {
439 # add description as comment to top of file
440 my $descr = $conf->{description
} || '';
441 foreach my $cl (split(/\n/, $descr)) {
442 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
445 foreach my $key (sort keys %$conf) {
446 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
447 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
448 my $value = $conf->{$key};
449 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
450 $raw .= "$key: $value\n";
453 if (my $lxcconf = $conf->{lxc
}) {
454 foreach my $entry (@$lxcconf) {
455 my ($k, $v) = @$entry;
463 my $raw = &$generate_raw_config($conf);
465 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
466 $raw .= "\n[$snapname]\n";
467 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
474 my ($key, $value) = @_;
476 die "unknown setting '$key'\n" if !$confdesc->{$key};
478 my $type = $confdesc->{$key}->{type
};
480 if (!defined($value)) {
481 die "got undefined value\n";
484 if ($value =~ m/[\n\r]/) {
485 die "property contains a line feed\n";
488 if ($type eq 'boolean') {
489 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
490 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
491 die "type check ('boolean') failed - got '$value'\n";
492 } elsif ($type eq 'integer') {
493 return int($1) if $value =~ m/^(\d+)$/;
494 die "type check ('integer') failed - got '$value'\n";
495 } elsif ($type eq 'number') {
496 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
497 die "type check ('number') failed - got '$value'\n";
498 } elsif ($type eq 'string') {
499 if (my $fmt = $confdesc->{$key}->{format
}) {
500 PVE
::JSONSchema
::check_format
($fmt, $value);
509 sub parse_pct_config
{
510 my ($filename, $raw) = @_;
512 return undef if !defined($raw);
515 digest
=> Digest
::SHA
::sha1_hex
($raw),
519 $filename =~ m
|/lxc/(\d
+).conf
$|
520 || die "got strange filename '$filename'";
528 my @lines = split(/\n/, $raw);
529 foreach my $line (@lines) {
530 next if $line =~ m/^\s*$/;
532 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
534 $conf->{description
} = $descr if $descr;
536 $conf = $res->{snapshots
}->{$section} = {};
540 if ($line =~ m/^\#(.*)\s*$/) {
541 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
545 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
548 my $validity = $valid_lxc_conf_keys->{$key} || 0;
549 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
550 push @{$conf->{lxc
}}, [$key, $value];
551 } elsif (my $errmsg = $validity) {
552 warn "vm $vmid - $key: $errmsg\n";
554 warn "vm $vmid - unable to parse config: $line\n";
556 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
557 $descr .= PVE
::Tools
::decode_text
($2);
558 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
559 $conf->{snapstate
} = $1;
560 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
563 eval { $value = check_type
($key, $value); };
564 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
565 $conf->{$key} = $value;
567 warn "vm $vmid - unable to parse config: $line\n";
571 $conf->{description
} = $descr if $descr;
573 delete $res->{snapstate
}; # just to be sure
579 my $vmlist = PVE
::Cluster
::get_vmlist
();
581 return $res if !$vmlist || !$vmlist->{ids
};
582 my $ids = $vmlist->{ids
};
584 foreach my $vmid (keys %$ids) {
585 next if !$vmid; # skip CT0
586 my $d = $ids->{$vmid};
587 next if !$d->{node
} || $d->{node
} ne $nodename;
588 next if !$d->{type
} || $d->{type
} ne 'lxc';
589 $res->{$vmid}->{type
} = 'lxc';
594 sub cfs_config_path
{
595 my ($vmid, $node) = @_;
597 $node = $nodename if !$node;
598 return "nodes/$node/lxc/$vmid.conf";
602 my ($vmid, $node) = @_;
604 my $cfspath = cfs_config_path
($vmid, $node);
605 return "/etc/pve/$cfspath";
609 my ($vmid, $node) = @_;
611 $node = $nodename if !$node;
612 my $cfspath = cfs_config_path
($vmid, $node);
614 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
615 die "container $vmid does not exist\n" if !defined($conf);
621 my ($vmid, $conf) = @_;
623 my $dir = "/etc/pve/nodes/$nodename/lxc";
626 write_config
($vmid, $conf);
632 unlink config_file
($vmid, $nodename);
636 my ($vmid, $conf) = @_;
638 my $cfspath = cfs_config_path
($vmid);
640 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
643 # flock: we use one file handle per process, so lock file
644 # can be called multiple times and will succeed for the same process.
646 my $lock_handles = {};
647 my $lockdir = "/run/lock/lxc";
649 sub config_file_lock
{
652 return "$lockdir/pve-config-${vmid}.lock";
655 sub lock_config_full
{
656 my ($vmid, $timeout, $code, @param) = @_;
658 my $filename = config_file_lock
($vmid);
660 mkdir $lockdir if !-d
$lockdir;
662 my $res = lock_file
($filename, $timeout, $code, @param);
669 sub lock_config_mode
{
670 my ($vmid, $timeout, $shared, $code, @param) = @_;
672 my $filename = config_file_lock
($vmid);
674 mkdir $lockdir if !-d
$lockdir;
676 my $res = lock_file_full
($filename, $timeout, $shared, $code, @param);
684 my ($vmid, $code, @param) = @_;
686 return lock_config_full
($vmid, 10, $code, @param);
692 return defined($confdesc->{$name});
695 # add JSON properties for create and set function
696 sub json_config_properties
{
699 foreach my $opt (keys %$confdesc) {
700 next if $opt eq 'parent' || $opt eq 'snaptime';
701 next if $prop->{$opt};
702 $prop->{$opt} = $confdesc->{$opt};
708 # container status helpers
710 sub list_active_containers
{
712 my $filename = "/proc/net/unix";
714 # similar test is used by lcxcontainers.c: list_active_containers
717 my $fh = IO
::File-
>new ($filename, "r");
720 while (defined(my $line = <$fh>)) {
721 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
723 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
734 # warning: this is slow
738 my $active_hash = list_active_containers
();
740 return 1 if defined($active_hash->{$vmid});
745 sub get_container_disk_usage
{
746 my ($vmid, $pid) = @_;
748 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
751 my $last_proc_vmid_stat;
753 my $parse_cpuacct_stat = sub {
756 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
760 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
773 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
775 my $active_hash = list_active_containers
();
777 my $cpucount = $cpuinfo->{cpus
} || 1;
779 my $cdtime = gettimeofday
;
781 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
783 foreach my $vmid (keys %$list) {
784 my $d = $list->{$vmid};
786 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
787 warn $@ if $@; # ignore errors (consider them stopped)
789 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
791 my $cfspath = cfs_config_path
($vmid);
792 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
794 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
795 $d->{name
} =~ s/[\s]//g;
797 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
800 my $res = get_container_disk_usage
($vmid, $d->{pid
});
801 $d->{disk
} = $res->{used
};
802 $d->{maxdisk
} = $res->{total
};
805 # use 4GB by default ??
806 if (my $rootfs = $conf->{rootfs
}) {
807 my $rootinfo = parse_ct_rootfs
($rootfs);
808 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
810 $d->{maxdisk
} = 4*1024*1024*1024;
816 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
817 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
828 $d->{template
} = is_template
($conf);
831 foreach my $vmid (keys %$list) {
832 my $d = $list->{$vmid};
835 next if !$pid; # skip stopped CTs
837 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
838 $d->{uptime
} = time - $ctime; # the method lxcfs uses
840 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
841 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
843 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
844 my @bytes = split(/\n/, $blkio_bytes);
845 foreach my $byte (@bytes) {
846 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
847 $d->{diskread
} = $2 if $key eq 'Read';
848 $d->{diskwrite
} = $2 if $key eq 'Write';
852 my $pstat = &$parse_cpuacct_stat($vmid);
854 my $used = $pstat->{utime} + $pstat->{stime
};
856 my $old = $last_proc_vmid_stat->{$vmid};
858 $last_proc_vmid_stat->{$vmid} = {
866 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
869 my $dutime = $used - $old->{used
};
871 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
872 $last_proc_vmid_stat->{$vmid} = {
878 $d->{cpu
} = $old->{cpu
};
882 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
884 foreach my $dev (keys %$netdev) {
885 next if $dev !~ m/^veth([1-9]\d*)i/;
887 my $d = $list->{$vmid};
891 $d->{netout
} += $netdev->{$dev}->{receive
};
892 $d->{netin
} += $netdev->{$dev}->{transmit
};
899 sub classify_mountpoint
{
902 return 'device' if $vol =~ m!^/dev/!;
908 my $parse_ct_mountpoint_full = sub {
909 my ($desc, $data, $noerr) = @_;
914 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
916 return undef if $noerr;
920 if (defined(my $size = $res->{size
})) {
921 $size = PVE
::JSONSchema
::parse_size
($size);
922 if (!defined($size)) {
923 return undef if $noerr;
924 die "invalid size: $size\n";
926 $res->{size
} = $size;
929 $res->{type
} = classify_mountpoint
($res->{volume
});
934 sub parse_ct_rootfs
{
935 my ($data, $noerr) = @_;
937 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
939 $res->{mp
} = '/' if defined($res);
944 sub parse_ct_mountpoint
{
945 my ($data, $noerr) = @_;
947 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
950 sub print_ct_mountpoint
{
951 my ($info, $nomp) = @_;
952 my $skip = [ 'type' ];
953 push @$skip, 'mp' if $nomp;
954 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
957 sub print_lxc_network
{
959 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
962 sub parse_lxc_network
{
967 return $res if !$data;
969 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
971 $res->{type
} = 'veth';
972 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
977 sub read_cgroup_value
{
978 my ($group, $vmid, $name, $full) = @_;
980 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
982 return PVE
::Tools
::file_get_contents
($path) if $full;
984 return PVE
::Tools
::file_read_firstline
($path);
987 sub write_cgroup_value
{
988 my ($group, $vmid, $name, $value) = @_;
990 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
991 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
995 sub find_lxc_console_pids
{
999 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1002 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1003 return if !$cmdline;
1005 my @args = split(/\0/, $cmdline);
1007 # search for lxc-console -n <vmid>
1008 return if scalar(@args) != 3;
1009 return if $args[1] ne '-n';
1010 return if $args[2] !~ m/^\d+$/;
1011 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1013 my $vmid = $args[2];
1015 push @{$res->{$vmid}}, $pid;
1027 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1029 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1031 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1036 # Note: we cannot use Net:IP, because that only allows strict
1038 sub parse_ipv4_cidr
{
1039 my ($cidr, $noerr) = @_;
1041 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1042 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1045 return undef if $noerr;
1047 die "unable to parse ipv4 address/mask\n";
1053 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1056 sub check_protection
{
1057 my ($vm_conf, $err_msg) = @_;
1059 if ($vm_conf->{protection
}) {
1060 die "$err_msg - protection mode enabled\n";
1064 sub update_lxc_config
{
1065 my ($storage_cfg, $vmid, $conf) = @_;
1067 my $dir = "/var/lib/lxc/$vmid";
1069 if ($conf->{template
}) {
1071 unlink "$dir/config";
1078 die "missing 'arch' - internal error" if !$conf->{arch
};
1079 $raw .= "lxc.arch = $conf->{arch}\n";
1081 my $unprivileged = $conf->{unprivileged
};
1082 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1084 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1085 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine)$/x) {
1086 my $inc ="/usr/share/lxc/config/$ostype.common.conf";
1087 $inc ="/usr/share/lxc/config/common.conf" if !-f
$inc;
1088 $raw .= "lxc.include = $inc\n";
1089 if ($unprivileged || $custom_idmap) {
1090 $inc = "/usr/share/lxc/config/$ostype.userns.conf";
1091 $inc = "/usr/share/lxc/config/userns.conf" if !-f
$inc;
1092 $raw .= "lxc.include = $inc\n"
1095 die "implement me (ostype $ostype)";
1098 # WARNING: DO NOT REMOVE this without making sure that loop device nodes
1099 # cannot be exposed to the container with r/w access (cgroup perms).
1100 # When this is enabled mounts will still remain in the monitor's namespace
1101 # after the container unmounted them and thus will not detach from their
1102 # files while the container is running!
1103 $raw .= "lxc.monitor.unshare = 1\n";
1105 # Should we read them from /etc/subuid?
1106 if ($unprivileged && !$custom_idmap) {
1107 $raw .= "lxc.id_map = u 0 100000 65536\n";
1108 $raw .= "lxc.id_map = g 0 100000 65536\n";
1111 if (!has_dev_console
($conf)) {
1112 $raw .= "lxc.console = none\n";
1113 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1116 my $ttycount = get_tty_count
($conf);
1117 $raw .= "lxc.tty = $ttycount\n";
1119 # some init scripts expect a linux terminal (turnkey).
1120 $raw .= "lxc.environment = TERM=linux\n";
1122 my $utsname = $conf->{hostname
} || "CT$vmid";
1123 $raw .= "lxc.utsname = $utsname\n";
1125 my $memory = $conf->{memory
} || 512;
1126 my $swap = $conf->{swap
} // 0;
1128 my $lxcmem = int($memory*1024*1024);
1129 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1131 my $lxcswap = int(($memory + $swap)*1024*1024);
1132 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1134 if (my $cpulimit = $conf->{cpulimit
}) {
1135 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1136 my $value = int(100000*$cpulimit);
1137 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1140 my $shares = $conf->{cpuunits
} || 1024;
1141 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1143 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1145 $raw .= "lxc.rootfs = $dir/rootfs\n";
1148 foreach my $k (keys %$conf) {
1149 next if $k !~ m/^net(\d+)$/;
1151 my $d = parse_lxc_network
($conf->{$k});
1153 $raw .= "lxc.network.type = veth\n";
1154 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1155 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1156 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1157 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1160 if (my $lxcconf = $conf->{lxc
}) {
1161 foreach my $entry (@$lxcconf) {
1162 my ($k, $v) = @$entry;
1163 $netcount++ if $k eq 'lxc.network.type';
1164 $raw .= "$k = $v\n";
1168 $raw .= "lxc.network.type = empty\n" if !$netcount;
1170 File
::Path
::mkpath
("$dir/rootfs");
1172 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1175 # verify and cleanup nameserver list (replace \0 with ' ')
1176 sub verify_nameserver_list
{
1177 my ($nameserver_list) = @_;
1180 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1181 PVE
::JSONSchema
::pve_verify_ip
($server);
1182 push @list, $server;
1185 return join(' ', @list);
1188 sub verify_searchdomain_list
{
1189 my ($searchdomain_list) = @_;
1192 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1193 # todo: should we add checks for valid dns domains?
1194 push @list, $server;
1197 return join(' ', @list);
1200 sub is_volume_in_use
{
1201 my ($config, $volid) = @_;
1204 foreach_mountpoint
($config, sub {
1205 my ($ms, $mountpoint) = @_;
1207 if ($mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid) {
1215 sub add_unused_volume
{
1216 my ($config, $volid) = @_;
1219 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1220 my $test = "unused$ind";
1221 if (my $vid = $config->{$test}) {
1222 return if $vid eq $volid; # do not add duplicates
1228 die "Too many unused volumes - please delete them first.\n" if !$key;
1230 $config->{$key} = $volid;
1235 sub update_pct_config
{
1236 my ($vmid, $conf, $running, $param, $delete) = @_;
1241 my @deleted_volumes;
1245 my $pid = find_lxc_pid
($vmid);
1246 $rootdir = "/proc/$pid/root";
1249 my $hotplug_error = sub {
1251 push @nohotplug, @_;
1258 if (defined($delete)) {
1259 foreach my $opt (@$delete) {
1260 if (!exists($conf->{$opt})) {
1261 warn "no such option: $opt\n";
1265 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1266 die "unable to delete required option '$opt'\n";
1267 } elsif ($opt eq 'swap') {
1268 delete $conf->{$opt};
1269 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1270 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1271 delete $conf->{$opt};
1272 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1273 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1274 next if $hotplug_error->($opt);
1275 delete $conf->{$opt};
1276 } elsif ($opt =~ m/^net(\d)$/) {
1277 delete $conf->{$opt};
1280 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1281 } elsif ($opt eq 'protection') {
1282 delete $conf->{$opt};
1283 } elsif ($opt =~ m/^unused(\d+)$/) {
1284 next if $hotplug_error->($opt);
1285 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1286 push @deleted_volumes, $conf->{$opt};
1287 delete $conf->{$opt};
1288 } elsif ($opt =~ m/^mp(\d+)$/) {
1289 next if $hotplug_error->($opt);
1290 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1291 my $mp = parse_ct_mountpoint
($conf->{$opt});
1292 delete $conf->{$opt};
1293 if ($mp->{type
} eq 'volume' && !is_volume_in_use
($conf, $mp->{volume
})) {
1294 add_unused_volume
($conf, $mp->{volume
});
1296 } elsif ($opt eq 'unprivileged') {
1297 die "unable to delete read-only option: '$opt'\n";
1299 die "implement me (delete: $opt)"
1301 write_config
($vmid, $conf) if $running;
1305 # There's no separate swap size to configure, there's memory and "total"
1306 # memory (iow. memory+swap). This means we have to change them together.
1307 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1308 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1309 if (defined($wanted_memory) || defined($wanted_swap)) {
1311 my $old_memory = ($conf->{memory
} || 512);
1312 my $old_swap = ($conf->{swap
} || 0);
1314 $wanted_memory //= $old_memory;
1315 $wanted_swap //= $old_swap;
1317 my $total = $wanted_memory + $wanted_swap;
1319 my $old_total = $old_memory + $old_swap;
1320 if ($total > $old_total) {
1321 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1322 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1324 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1325 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1328 $conf->{memory
} = $wanted_memory;
1329 $conf->{swap
} = $wanted_swap;
1331 write_config
($vmid, $conf) if $running;
1334 my $used_volids = {};
1336 foreach my $opt (keys %$param) {
1337 my $value = $param->{$opt};
1338 if ($opt eq 'hostname') {
1339 $conf->{$opt} = $value;
1340 } elsif ($opt eq 'onboot') {
1341 $conf->{$opt} = $value ?
1 : 0;
1342 } elsif ($opt eq 'startup') {
1343 $conf->{$opt} = $value;
1344 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1345 next if $hotplug_error->($opt);
1346 $conf->{$opt} = $value;
1347 } elsif ($opt eq 'nameserver') {
1348 next if $hotplug_error->($opt);
1349 my $list = verify_nameserver_list
($value);
1350 $conf->{$opt} = $list;
1351 } elsif ($opt eq 'searchdomain') {
1352 next if $hotplug_error->($opt);
1353 my $list = verify_searchdomain_list
($value);
1354 $conf->{$opt} = $list;
1355 } elsif ($opt eq 'cpulimit') {
1356 next if $hotplug_error->($opt); # FIXME: hotplug
1357 $conf->{$opt} = $value;
1358 } elsif ($opt eq 'cpuunits') {
1359 $conf->{$opt} = $value;
1360 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1361 } elsif ($opt eq 'description') {
1362 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1363 } elsif ($opt =~ m/^net(\d+)$/) {
1365 my $net = parse_lxc_network
($value);
1367 $conf->{$opt} = print_lxc_network
($net);
1369 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1371 } elsif ($opt eq 'protection') {
1372 $conf->{$opt} = $value ?
1 : 0;
1373 } elsif ($opt =~ m/^mp(\d+)$/) {
1374 next if $hotplug_error->($opt);
1375 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1376 my $old = $conf->{$opt};
1377 $conf->{$opt} = $value;
1378 if (defined($old)) {
1379 my $mp = parse_ct_mountpoint
($old);
1380 if ($mp->{type
} eq 'volume' && !is_volume_in_use
($conf, $mp->{volume
})) {
1381 add_unused_volume
($conf, $mp->{volume
});
1385 my $mp = parse_ct_mountpoint
($value);
1386 $used_volids->{$mp->{volume
}} = 1;
1387 } elsif ($opt eq 'rootfs') {
1388 next if $hotplug_error->($opt);
1389 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1390 my $old = $conf->{$opt};
1391 $conf->{$opt} = $value;
1392 if (defined($old)) {
1393 my $mp = parse_ct_rootfs
($old);
1394 if ($mp->{type
} eq 'volume' && !is_volume_in_use
($conf, $mp->{volume
})) {
1395 add_unused_volume
($conf, $mp->{volume
});
1398 my $mp = parse_ct_rootfs
($value);
1399 $used_volids->{$mp->{volume
}} = 1;
1400 } elsif ($opt eq 'unprivileged') {
1401 die "unable to modify read-only option: '$opt'\n";
1403 die "implement me: $opt";
1405 write_config
($vmid, $conf) if $running;
1410 # Remove unused disks after re-adding
1411 foreach my $key (keys %$conf) {
1412 next if $key !~ /^unused\d+/;
1413 my $volid = $conf->{$key};
1414 if ($used_volids->{$volid}) {
1415 delete $conf->{$key};
1419 # Apply deletions and creations of new volumes
1420 if (@deleted_volumes) {
1421 my $storage_cfg = PVE
::Storage
::config
();
1422 foreach my $volume (@deleted_volumes) {
1423 next if $used_volids->{$volume}; # could have been re-added, too
1424 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1429 my $storage_cfg = PVE
::Storage
::config
();
1430 create_disks
($storage_cfg, $vmid, $conf, $conf);
1433 # This should be the last thing we do here
1434 if ($running && scalar(@nohotplug)) {
1435 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1439 sub has_dev_console
{
1442 return !(defined($conf->{console
}) && !$conf->{console
});
1448 return $conf->{tty
} // $confdesc->{tty
}->{default};
1454 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1457 sub get_console_command
{
1458 my ($vmid, $conf) = @_;
1460 my $cmode = get_cmode
($conf);
1462 if ($cmode eq 'console') {
1463 return ['lxc-console', '-n', $vmid, '-t', 0];
1464 } elsif ($cmode eq 'tty') {
1465 return ['lxc-console', '-n', $vmid];
1466 } elsif ($cmode eq 'shell') {
1467 return ['lxc-attach', '--clear-env', '-n', $vmid];
1469 die "internal error";
1473 sub get_primary_ips
{
1476 # return data from net0
1478 return undef if !defined($conf->{net0
});
1479 my $net = parse_lxc_network
($conf->{net0
});
1481 my $ipv4 = $net->{ip
};
1483 if ($ipv4 =~ /^(dhcp|manual)$/) {
1489 my $ipv6 = $net->{ip6
};
1491 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1498 return ($ipv4, $ipv6);
1501 sub delete_mountpoint_volume
{
1502 my ($storage_cfg, $vmid, $volume) = @_;
1504 return if classify_mountpoint
($volume) ne 'volume';
1506 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1507 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1510 sub destroy_lxc_container
{
1511 my ($storage_cfg, $vmid, $conf) = @_;
1513 foreach_mountpoint
($conf, sub {
1514 my ($ms, $mountpoint) = @_;
1515 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1518 rmdir "/var/lib/lxc/$vmid/rootfs";
1519 unlink "/var/lib/lxc/$vmid/config";
1520 rmdir "/var/lib/lxc/$vmid";
1521 destroy_config
($vmid);
1523 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1524 #PVE::Tools::run_command($cmd);
1527 sub vm_stop_cleanup
{
1528 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1533 my $vollist = get_vm_volumes
($conf);
1534 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1537 warn $@ if $@; # avoid errors - just warn
1540 my $safe_num_ne = sub {
1543 return 0 if !defined($a) && !defined($b);
1544 return 1 if !defined($a);
1545 return 1 if !defined($b);
1550 my $safe_string_ne = sub {
1553 return 0 if !defined($a) && !defined($b);
1554 return 1 if !defined($a);
1555 return 1 if !defined($b);
1561 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1563 if ($newnet->{type
} ne 'veth') {
1564 # for when there are physical interfaces
1565 die "cannot update interface of type $newnet->{type}";
1568 my $veth = "veth${vmid}i${netid}";
1569 my $eth = $newnet->{name
};
1571 if (my $oldnetcfg = $conf->{$opt}) {
1572 my $oldnet = parse_lxc_network
($oldnetcfg);
1574 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1575 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1577 PVE
::Network
::veth_delete
($veth);
1578 delete $conf->{$opt};
1579 write_config
($vmid, $conf);
1581 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1583 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1584 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1585 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1587 if ($oldnet->{bridge
}) {
1588 PVE
::Network
::tap_unplug
($veth);
1589 foreach (qw(bridge tag firewall)) {
1590 delete $oldnet->{$_};
1592 $conf->{$opt} = print_lxc_network
($oldnet);
1593 write_config
($vmid, $conf);
1596 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1597 foreach (qw(bridge tag firewall)) {
1598 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1600 $conf->{$opt} = print_lxc_network
($oldnet);
1601 write_config
($vmid, $conf);
1604 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1607 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1611 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1613 my $veth = "veth${vmid}i${netid}";
1614 my $vethpeer = $veth . "p";
1615 my $eth = $newnet->{name
};
1617 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1618 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1620 # attach peer in container
1621 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1622 PVE
::Tools
::run_command
($cmd);
1624 # link up peer in container
1625 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1626 PVE
::Tools
::run_command
($cmd);
1628 my $done = { type
=> 'veth' };
1629 foreach (qw(bridge tag firewall hwaddr name)) {
1630 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1632 $conf->{$opt} = print_lxc_network
($done);
1634 write_config
($vmid, $conf);
1637 sub update_ipconfig
{
1638 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1640 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1642 my $optdata = parse_lxc_network
($conf->{$opt});
1646 my $cmdargs = shift;
1647 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1649 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1651 my $change_ip_config = sub {
1652 my ($ipversion) = @_;
1654 my $family_opt = "-$ipversion";
1655 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1656 my $gw= "gw$suffix";
1657 my $ip= "ip$suffix";
1659 my $newip = $newnet->{$ip};
1660 my $newgw = $newnet->{$gw};
1661 my $oldip = $optdata->{$ip};
1663 my $change_ip = &$safe_string_ne($oldip, $newip);
1664 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1666 return if !$change_ip && !$change_gw;
1668 # step 1: add new IP, if this fails we cancel
1669 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1670 if ($change_ip && $is_real_ip) {
1671 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1678 # step 2: replace gateway
1679 # If this fails we delete the added IP and cancel.
1680 # If it succeeds we save the config and delete the old IP, ignoring
1681 # errors. The config is then saved.
1682 # Note: 'ip route replace' can add
1686 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1687 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1689 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1693 # the route was not replaced, the old IP is still available
1694 # rollback (delete new IP) and cancel
1696 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1697 warn $@ if $@; # no need to die here
1702 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1703 # if the route was not deleted, the guest might have deleted it manually
1709 # from this point on we save the configuration
1710 # step 3: delete old IP ignoring errors
1711 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1712 # We need to enable promote_secondaries, otherwise our newly added
1713 # address will be removed along with the old one.
1716 if ($ipversion == 4) {
1717 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1718 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1719 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1721 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1723 warn $@ if $@; # no need to die here
1725 if ($ipversion == 4) {
1726 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1730 foreach my $property ($ip, $gw) {
1731 if ($newnet->{$property}) {
1732 $optdata->{$property} = $newnet->{$property};
1734 delete $optdata->{$property};
1737 $conf->{$opt} = print_lxc_network
($optdata);
1738 write_config
($vmid, $conf);
1739 $lxc_setup->setup_network($conf);
1742 &$change_ip_config(4);
1743 &$change_ip_config(6);
1747 # Internal snapshots
1749 # NOTE: Snapshot create/delete involves several non-atomic
1750 # actions, and can take a long time.
1751 # So we try to avoid locking the file and use the 'lock' variable
1752 # inside the config file instead.
1754 my $snapshot_copy_config = sub {
1755 my ($source, $dest) = @_;
1757 foreach my $k (keys %$source) {
1758 next if $k eq 'snapshots';
1759 next if $k eq 'snapstate';
1760 next if $k eq 'snaptime';
1761 next if $k eq 'vmstate';
1762 next if $k eq 'lock';
1763 next if $k eq 'digest';
1764 next if $k eq 'description';
1765 next if $k =~ m/^unused\d+$/;
1767 $dest->{$k} = $source->{$k};
1771 my $snapshot_apply_config = sub {
1772 my ($conf, $snap) = @_;
1774 # copy snapshot list
1776 snapshots
=> $conf->{snapshots
},
1779 # keep description and list of unused disks
1780 foreach my $k (keys %$conf) {
1781 next if !($k =~ m/^unused\d+$/ || $k eq 'description');
1782 $newconf->{$k} = $conf->{$k};
1785 &$snapshot_copy_config($snap, $newconf);
1790 my $snapshot_save_vmstate = sub {
1791 die "implement me - snapshot_save_vmstate\n";
1794 sub snapshot_prepare
{
1795 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1799 my $updatefn = sub {
1801 my $conf = load_config
($vmid);
1803 die "you can't take a snapshot if it's a template\n"
1804 if is_template
($conf);
1808 $conf->{lock} = 'snapshot';
1810 die "snapshot name '$snapname' already used\n"
1811 if defined($conf->{snapshots
}->{$snapname});
1813 my $storecfg = PVE
::Storage
::config
();
1815 # workaround until mp snapshots are implemented
1816 my $feature = $snapname eq 'vzdump' ?
'vzdump' : 'snapshot';
1817 die "snapshot feature is not available\n" if !has_feature
($feature, $conf, $storecfg);
1819 $snap = $conf->{snapshots
}->{$snapname} = {};
1821 if ($save_vmstate && check_running
($vmid)) {
1822 &$snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
1825 &$snapshot_copy_config($conf, $snap);
1827 $snap->{snapstate
} = "prepare";
1828 $snap->{snaptime
} = time();
1829 $snap->{description
} = $comment if $comment;
1831 write_config
($vmid, $conf);
1834 lock_config
($vmid, $updatefn);
1839 sub snapshot_commit
{
1840 my ($vmid, $snapname) = @_;
1842 my $updatefn = sub {
1844 my $conf = load_config
($vmid);
1846 die "missing snapshot lock\n"
1847 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1849 my $snap = $conf->{snapshots
}->{$snapname};
1850 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1852 die "wrong snapshot state\n"
1853 if !($snap->{snapstate
} && $snap->{snapstate
} eq "prepare");
1855 delete $snap->{snapstate
};
1856 delete $conf->{lock};
1858 my $newconf = &$snapshot_apply_config($conf, $snap);
1860 $newconf->{parent
} = $snapname;
1862 write_config
($vmid, $newconf);
1865 lock_config
($vmid, $updatefn);
1869 my ($feature, $conf, $storecfg, $snapname) = @_;
1872 my $vzdump = $feature eq 'vzdump';
1873 $feature = 'snapshot' if $vzdump;
1875 foreach_mountpoint
($conf, sub {
1876 my ($ms, $mountpoint) = @_;
1878 return if $err; # skip further test
1879 return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup
};
1881 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1883 # TODO: implement support for mountpoints
1884 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1888 return $err ?
0 : 1;
1891 my $enter_namespace = sub {
1892 my ($vmid, $pid, $which, $type) = @_;
1893 sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
1894 or die "failed to open $which namespace of container $vmid: $!\n";
1895 PVE
::Tools
::setns
(fileno($fd), $type)
1896 or die "failed to enter $which namespace of container $vmid: $!\n";
1900 my $do_syncfs = sub {
1901 my ($vmid, $pid, $socket) = @_;
1903 &$enter_namespace($vmid, $pid, 'mnt', PVE
::Tools
::CLONE_NEWNS
);
1905 # Tell the parent process to start reading our /proc/mounts
1906 print {$socket} "go\n";
1909 # Receive /proc/self/mounts
1910 my $mountdata = do { local $/ = undef; <$socket> };
1913 # Now sync all mountpoints...
1914 my $mounts = PVE
::ProcFSTools
::parse_mounts
($mountdata);
1915 foreach my $mp (@$mounts) {
1916 my ($what, $dir, $fs) = @$mp;
1917 next if $fs eq 'fuse.lxcfs';
1918 eval { PVE
::Tools
::sync_mountpoint
($dir); };
1923 sub sync_container_namespace
{
1925 my $pid = find_lxc_pid
($vmid);
1927 # SOCK_DGRAM is nicer for barriers but cannot be slurped
1928 socketpair my $pfd, my $cfd, AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
1929 or die "failed to create socketpair: $!\n";
1932 die "fork failed: $!\n" if !defined($child);
1937 &$do_syncfs($vmid, $pid, $cfd);
1947 die "failed to enter container namespace\n" if $go ne "go\n";
1949 open my $mounts, '<', "/proc/$child/mounts"
1950 or die "failed to open container's /proc/mounts: $!\n";
1951 my $mountdata = do { local $/ = undef; <$mounts> };
1953 print {$pfd} $mountdata;
1956 while (waitpid($child, 0) != $child) {}
1957 die "failed to sync container namespace\n" if $? != 0;
1960 sub snapshot_create
{
1961 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1963 my $snap = snapshot_prepare
($vmid, $snapname, $save_vmstate, $comment);
1965 my $conf = load_config
($vmid);
1967 my $running = check_running
($vmid);
1976 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1977 sync_container_namespace
($vmid);
1980 my $storecfg = PVE
::Storage
::config
();
1981 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1982 my $volid = $rootinfo->{volume
};
1984 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1985 $drivehash->{rootfs
} = 1;
1990 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1995 eval { snapshot_delete
($vmid, $snapname, 1, $drivehash); };
2000 snapshot_commit
($vmid, $snapname);
2003 # Note: $drivehash is only set when called from snapshot_create.
2004 sub snapshot_delete
{
2005 my ($vmid, $snapname, $force, $drivehash) = @_;
2011 my $unlink_parent = sub {
2012 my ($confref, $new_parent) = @_;
2014 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
2016 $confref->{parent
} = $new_parent;
2018 delete $confref->{parent
};
2023 my $updatefn = sub {
2024 my ($remove_drive) = @_;
2026 my $conf = load_config
($vmid);
2030 die "you can't delete a snapshot if vm is a template\n"
2031 if is_template
($conf);
2034 $snap = $conf->{snapshots
}->{$snapname};
2036 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2038 # remove parent refs
2040 &$unlink_parent($conf, $snap->{parent
});
2041 foreach my $sn (keys %{$conf->{snapshots
}}) {
2042 next if $sn eq $snapname;
2043 &$unlink_parent($conf->{snapshots
}->{$sn}, $snap->{parent
});
2047 if ($remove_drive) {
2048 if ($remove_drive eq 'vmstate') {
2049 die "implement me - saving vmstate\n";
2051 die "implement me - remove drive\n";
2056 $snap->{snapstate
} = 'delete';
2058 delete $conf->{snapshots
}->{$snapname};
2059 delete $conf->{lock} if $drivehash;
2062 write_config
($vmid, $conf);
2065 lock_config
($vmid, $updatefn);
2067 # now remove vmstate file
2068 # never set for LXC!
2069 my $storecfg = PVE
::Storage
::config
();
2071 if ($snap->{vmstate
}) {
2072 die "implement me - saving vmstate\n";
2075 # now remove all volume snapshots
2076 # only rootfs for now!
2078 my $rootfs = $snap->{rootfs
};
2079 my $rootinfo = parse_ct_rootfs
($rootfs);
2080 my $volid = $rootinfo->{volume
};
2081 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
2084 die $err if !$force;
2088 # now cleanup config
2090 lock_config
($vmid, $updatefn);
2093 sub snapshot_rollback
{
2094 my ($vmid, $snapname) = @_;
2098 my $storecfg = PVE
::Storage
::config
();
2100 my $conf = load_config
($vmid);
2102 my $get_snapshot_config = sub {
2104 die "you can't rollback if vm is a template\n" if is_template
($conf);
2106 my $res = $conf->{snapshots
}->{$snapname};
2108 die "snapshot '$snapname' does not exist\n" if !defined($res);
2113 my $snap = &$get_snapshot_config();
2115 # only for rootfs for now!
2116 my $rootfs = $snap->{rootfs
};
2117 my $rootinfo = parse_ct_rootfs
($rootfs);
2118 my $volid = $rootinfo->{volume
};
2120 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
2122 my $updatefn = sub {
2124 $conf = load_config
($vmid);
2126 $snap = &$get_snapshot_config();
2128 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
2129 if $snap->{snapstate
};
2133 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
2136 die "unable to rollback vm $vmid: vm is running\n"
2137 if check_running
($vmid);
2140 $conf->{lock} = 'rollback';
2142 die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
2143 delete $conf->{lock};
2149 # copy snapshot config to current config
2150 $conf = &$snapshot_apply_config($conf, $snap);
2151 $conf->{parent
} = $snapname;
2154 write_config
($vmid, $conf);
2156 if (!$prepare && $snap->{vmstate
}) {
2157 die "implement me - save vmstate";
2161 lock_config
($vmid, $updatefn);
2163 # only rootfs for now!
2164 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
2167 lock_config
($vmid, $updatefn);
2170 sub template_create
{
2171 my ($vmid, $conf) = @_;
2173 my $storecfg = PVE
::Storage
::config
();
2175 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
2176 my $volid = $rootinfo->{volume
};
2178 die "Template feature is not available for '$volid'\n"
2179 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
2181 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2183 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
2184 $rootinfo->{volume
} = $template_volid;
2185 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
2187 write_config
($vmid, $conf);
2193 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2196 sub mountpoint_names
{
2199 my @names = ('rootfs');
2201 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2202 push @names, "mp$i";
2205 return $reverse ?
reverse @names : @names;
2209 sub foreach_mountpoint_full
{
2210 my ($conf, $reverse, $func) = @_;
2212 foreach my $key (mountpoint_names
($reverse)) {
2213 my $value = $conf->{$key};
2214 next if !defined($value);
2215 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2216 next if !defined($mountpoint);
2218 &$func($key, $mountpoint);
2222 sub foreach_mountpoint
{
2223 my ($conf, $func) = @_;
2225 foreach_mountpoint_full
($conf, 0, $func);
2228 sub foreach_mountpoint_reverse
{
2229 my ($conf, $func) = @_;
2231 foreach_mountpoint_full
($conf, 1, $func);
2234 sub check_ct_modify_config_perm
{
2235 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2237 return 1 if $authuser ne 'root@pam';
2239 foreach my $opt (@$key_list) {
2241 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2242 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2243 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2244 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2245 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2246 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2247 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2248 $opt eq 'searchdomain' || $opt eq 'hostname') {
2249 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2251 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2259 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2261 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2262 my $volid_list = get_vm_volumes
($conf);
2264 foreach_mountpoint_reverse
($conf, sub {
2265 my ($ms, $mountpoint) = @_;
2267 my $volid = $mountpoint->{volume
};
2268 my $mount = $mountpoint->{mp
};
2270 return if !$volid || !$mount;
2272 my $mount_path = "$rootdir/$mount";
2273 $mount_path =~ s!/+!/!g;
2275 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2278 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2291 my ($vmid, $storage_cfg, $conf) = @_;
2293 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2294 File
::Path
::make_path
($rootdir);
2296 my $volid_list = get_vm_volumes
($conf);
2297 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2300 foreach_mountpoint
($conf, sub {
2301 my ($ms, $mountpoint) = @_;
2303 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2307 warn "mounting container failed\n";
2308 umount_all
($vmid, $storage_cfg, $conf, 1);
2316 sub mountpoint_mount_path
{
2317 my ($mountpoint, $storage_cfg, $snapname) = @_;
2319 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2322 my $check_mount_path = sub {
2324 $path = File
::Spec-
>canonpath($path);
2325 my $real = Cwd
::realpath
($path);
2326 if ($real ne $path) {
2327 die "mount path modified by symlink: $path != $real";
2336 if ($line =~ m
@^(/dev/loop\d
+):@) {
2340 my $cmd = ['losetup', '--associated', $path];
2341 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2345 # Run a function with a file attached to a loop device.
2346 # The loop device is always detached afterwards (or set to autoclear).
2347 # Returns the loop device.
2348 sub run_with_loopdev
{
2349 my ($func, $file) = @_;
2353 if ($line =~ m
@^(/dev/loop\d
+)$@) {
2357 PVE
::Tools
::run_command
(['losetup', '--show', '-f', $file], outfunc
=> $parser);
2358 die "failed to setup loop device for $file\n" if !$device;
2359 eval { &$func($device); };
2361 PVE
::Tools
::run_command
(['losetup', '-d', $device]);
2367 my ($dir, $dest, $ro, @extra_opts) = @_;
2368 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
2370 eval { PVE
::Tools
::run_command
(['mount', '-o', 'bind,remount,ro', $dest]); };
2372 warn "bindmount error\n";
2373 # don't leave writable bind-mounts behind...
2374 PVE
::Tools
::run_command
(['umount', $dest]);
2380 # use $rootdir = undef to just return the corresponding mount path
2381 sub mountpoint_mount
{
2382 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2384 my $volid = $mountpoint->{volume
};
2385 my $mount = $mountpoint->{mp
};
2386 my $type = $mountpoint->{type
};
2387 my $quota = !$snapname && !$mountpoint->{ro
} && $mountpoint->{quota
};
2390 return if !$volid || !$mount;
2394 if (defined($rootdir)) {
2395 $rootdir =~ s!/+$!!;
2396 $mount_path = "$rootdir/$mount";
2397 $mount_path =~ s!/+!/!g;
2398 &$check_mount_path($mount_path);
2399 File
::Path
::mkpath
($mount_path);
2402 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2404 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2407 if (defined($mountpoint->{acl
})) {
2408 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2410 my $readonly = $mountpoint->{ro
};
2412 my @extra_opts = ('-o', $optstring);
2416 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2417 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2419 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2420 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2422 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2424 if ($format eq 'subvol') {
2427 if ($scfg->{type
} eq 'zfspool') {
2428 my $path_arg = $path;
2429 $path_arg =~ s!^/+!!;
2430 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2432 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2435 bindmount
($path, $mount_path, $readonly, @extra_opts);
2436 warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
2439 return wantarray ?
($path, 0, $mounted_dev) : $path;
2440 } elsif ($format eq 'raw' || $format eq 'iso') {
2444 if ($format eq 'iso') {
2445 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2446 } elsif ($isBase || defined($snapname)) {
2447 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2450 push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
2452 push @extra_opts, '-o', 'ro' if $readonly;
2453 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2457 my $use_loopdev = 0;
2458 if ($scfg->{path
}) {
2459 $mounted_dev = run_with_loopdev
($domount, $path);
2461 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2462 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2463 $mounted_dev = $path;
2466 die "unsupported storage type '$scfg->{type}'\n";
2468 return wantarray ?
($path, $use_loopdev, $mounted_dev) : $path;
2470 die "unsupported image format '$format'\n";
2472 } elsif ($type eq 'device') {
2473 push @extra_opts, '-o', 'ro' if $readonly;
2474 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2475 return wantarray ?
($volid, 0, $volid) : $volid;
2476 } elsif ($type eq 'bind') {
2477 die "directory '$volid' does not exist\n" if ! -d
$volid;
2478 &$check_mount_path($volid);
2479 bindmount
($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
2480 warn "cannot enable quota control for bind mounts\n" if $quota;
2481 return wantarray ?
($volid, 0, undef) : $volid;
2484 die "unsupported storage";
2487 sub get_vm_volumes
{
2488 my ($conf, $excludes) = @_;
2492 foreach_mountpoint
($conf, sub {
2493 my ($ms, $mountpoint) = @_;
2495 return if $excludes && $ms eq $excludes;
2497 my $volid = $mountpoint->{volume
};
2499 return if !$volid || $mountpoint->{type
} ne 'volume';
2501 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2504 push @$vollist, $volid;
2511 my ($dev, $rootuid, $rootgid) = @_;
2513 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2514 '-E', "root_owner=$rootuid:$rootgid",
2519 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2521 if ($volid =~ m!^/dev/.+!) {
2526 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2528 die "cannot format volume '$volid' with no storage\n" if !$storage;
2530 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2532 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2534 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2535 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2537 die "cannot format volume '$volid' (format == $format)\n"
2538 if $format ne 'raw';
2540 mkfs
($path, $rootuid, $rootgid);
2544 my ($storecfg, $vollist) = @_;
2546 foreach my $volid (@$vollist) {
2547 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2553 my ($storecfg, $vmid, $settings, $conf) = @_;
2558 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2559 my $chown_vollist = [];
2561 foreach_mountpoint
($settings, sub {
2562 my ($ms, $mountpoint) = @_;
2564 my $volid = $mountpoint->{volume
};
2565 my $mp = $mountpoint->{mp
};
2567 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2569 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2570 my ($storeid, $size_gb) = ($1, $2);
2572 my $size_kb = int(${size_gb
}*1024) * 1024;
2574 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2575 # fixme: use better naming ct-$vmid-disk-X.raw?
2577 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2579 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2581 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2583 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2585 push @$chown_vollist, $volid;
2587 } elsif ($scfg->{type
} eq 'zfspool') {
2589 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2591 push @$chown_vollist, $volid;
2592 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2594 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2595 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2597 } elsif ($scfg->{type
} eq 'rbd') {
2599 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2600 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2601 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2603 die "unable to create containers on storage type '$scfg->{type}'\n";
2605 push @$vollist, $volid;
2606 $mountpoint->{volume
} = $volid;
2607 $mountpoint->{size
} = $size_kb * 1024;
2608 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2610 # use specified/existing volid/dir/device
2611 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2615 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2616 foreach my $volid (@$chown_vollist) {
2617 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2618 chown($rootuid, $rootgid, $path);
2620 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2622 # free allocated images on error
2624 destroy_disks
($storecfg, $vollist);
2630 # bash completion helper
2632 sub complete_os_templates
{
2633 my ($cmdname, $pname, $cvalue) = @_;
2635 my $cfg = PVE
::Storage
::config
();
2639 if ($cvalue =~ m/^([^:]+):/) {
2643 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2644 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2647 foreach my $id (keys %$data) {
2648 foreach my $item (@{$data->{$id}}) {
2649 push @$res, $item->{volid
} if defined($item->{volid
});
2656 my $complete_ctid_full = sub {
2659 my $idlist = vmstatus
();
2661 my $active_hash = list_active_containers
();
2665 foreach my $id (keys %$idlist) {
2666 my $d = $idlist->{$id};
2667 if (defined($running)) {
2668 next if $d->{template
};
2669 next if $running && !$active_hash->{$id};
2670 next if !$running && $active_hash->{$id};
2679 return &$complete_ctid_full();
2682 sub complete_ctid_stopped
{
2683 return &$complete_ctid_full(0);
2686 sub complete_ctid_running
{
2687 return &$complete_ctid_full(1);
2697 my $lxc = $conf->{lxc
};
2698 foreach my $entry (@$lxc) {
2699 my ($key, $value) = @$entry;
2700 next if $key ne 'lxc.id_map';
2701 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2702 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2703 push @$id_map, [$type, $ct, $host, $length];
2705 $rootuid = $host if $type eq 'u';
2706 $rootgid = $host if $type eq 'g';
2709 die "failed to parse id_map: $value\n";
2713 if (!@$id_map && $conf->{unprivileged
}) {
2714 # Should we read them from /etc/subuid?
2715 $id_map = [ ['u', '0', '100000', '65536'],
2716 ['g', '0', '100000', '65536'] ];
2717 $rootuid = $rootgid = 100000;
2720 return ($id_map, $rootuid, $rootgid);
2723 sub userns_command
{
2726 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];