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', 'unmanaged'],
123 description
=> "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
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 | unmanaged)$/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";
1402 } elsif ($opt eq 'ostype') {
1403 next if $hotplug_error->($opt);
1404 $conf->{$opt} = $value;
1406 die "implement me: $opt";
1408 write_config
($vmid, $conf) if $running;
1413 # Remove unused disks after re-adding
1414 foreach my $key (keys %$conf) {
1415 next if $key !~ /^unused\d+/;
1416 my $volid = $conf->{$key};
1417 if ($used_volids->{$volid}) {
1418 delete $conf->{$key};
1422 # Apply deletions and creations of new volumes
1423 if (@deleted_volumes) {
1424 my $storage_cfg = PVE
::Storage
::config
();
1425 foreach my $volume (@deleted_volumes) {
1426 next if $used_volids->{$volume}; # could have been re-added, too
1427 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1432 my $storage_cfg = PVE
::Storage
::config
();
1433 create_disks
($storage_cfg, $vmid, $conf, $conf);
1436 # This should be the last thing we do here
1437 if ($running && scalar(@nohotplug)) {
1438 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1442 sub has_dev_console
{
1445 return !(defined($conf->{console
}) && !$conf->{console
});
1451 return $conf->{tty
} // $confdesc->{tty
}->{default};
1457 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1460 sub get_console_command
{
1461 my ($vmid, $conf) = @_;
1463 my $cmode = get_cmode
($conf);
1465 if ($cmode eq 'console') {
1466 return ['lxc-console', '-n', $vmid, '-t', 0];
1467 } elsif ($cmode eq 'tty') {
1468 return ['lxc-console', '-n', $vmid];
1469 } elsif ($cmode eq 'shell') {
1470 return ['lxc-attach', '--clear-env', '-n', $vmid];
1472 die "internal error";
1476 sub get_primary_ips
{
1479 # return data from net0
1481 return undef if !defined($conf->{net0
});
1482 my $net = parse_lxc_network
($conf->{net0
});
1484 my $ipv4 = $net->{ip
};
1486 if ($ipv4 =~ /^(dhcp|manual)$/) {
1492 my $ipv6 = $net->{ip6
};
1494 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1501 return ($ipv4, $ipv6);
1504 sub delete_mountpoint_volume
{
1505 my ($storage_cfg, $vmid, $volume) = @_;
1507 return if classify_mountpoint
($volume) ne 'volume';
1509 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1510 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1513 sub destroy_lxc_container
{
1514 my ($storage_cfg, $vmid, $conf) = @_;
1516 foreach_mountpoint
($conf, sub {
1517 my ($ms, $mountpoint) = @_;
1518 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1521 rmdir "/var/lib/lxc/$vmid/rootfs";
1522 unlink "/var/lib/lxc/$vmid/config";
1523 rmdir "/var/lib/lxc/$vmid";
1524 destroy_config
($vmid);
1526 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1527 #PVE::Tools::run_command($cmd);
1530 sub vm_stop_cleanup
{
1531 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1536 my $vollist = get_vm_volumes
($conf);
1537 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1540 warn $@ if $@; # avoid errors - just warn
1543 my $safe_num_ne = sub {
1546 return 0 if !defined($a) && !defined($b);
1547 return 1 if !defined($a);
1548 return 1 if !defined($b);
1553 my $safe_string_ne = sub {
1556 return 0 if !defined($a) && !defined($b);
1557 return 1 if !defined($a);
1558 return 1 if !defined($b);
1564 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1566 if ($newnet->{type
} ne 'veth') {
1567 # for when there are physical interfaces
1568 die "cannot update interface of type $newnet->{type}";
1571 my $veth = "veth${vmid}i${netid}";
1572 my $eth = $newnet->{name
};
1574 if (my $oldnetcfg = $conf->{$opt}) {
1575 my $oldnet = parse_lxc_network
($oldnetcfg);
1577 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1578 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1580 PVE
::Network
::veth_delete
($veth);
1581 delete $conf->{$opt};
1582 write_config
($vmid, $conf);
1584 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1586 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1587 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1588 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1590 if ($oldnet->{bridge
}) {
1591 PVE
::Network
::tap_unplug
($veth);
1592 foreach (qw(bridge tag firewall)) {
1593 delete $oldnet->{$_};
1595 $conf->{$opt} = print_lxc_network
($oldnet);
1596 write_config
($vmid, $conf);
1599 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1600 foreach (qw(bridge tag firewall)) {
1601 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1603 $conf->{$opt} = print_lxc_network
($oldnet);
1604 write_config
($vmid, $conf);
1607 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1610 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1614 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1616 my $veth = "veth${vmid}i${netid}";
1617 my $vethpeer = $veth . "p";
1618 my $eth = $newnet->{name
};
1620 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1621 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1623 # attach peer in container
1624 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1625 PVE
::Tools
::run_command
($cmd);
1627 # link up peer in container
1628 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1629 PVE
::Tools
::run_command
($cmd);
1631 my $done = { type
=> 'veth' };
1632 foreach (qw(bridge tag firewall hwaddr name)) {
1633 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1635 $conf->{$opt} = print_lxc_network
($done);
1637 write_config
($vmid, $conf);
1640 sub update_ipconfig
{
1641 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1643 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1645 my $optdata = parse_lxc_network
($conf->{$opt});
1649 my $cmdargs = shift;
1650 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1652 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1654 my $change_ip_config = sub {
1655 my ($ipversion) = @_;
1657 my $family_opt = "-$ipversion";
1658 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1659 my $gw= "gw$suffix";
1660 my $ip= "ip$suffix";
1662 my $newip = $newnet->{$ip};
1663 my $newgw = $newnet->{$gw};
1664 my $oldip = $optdata->{$ip};
1666 my $change_ip = &$safe_string_ne($oldip, $newip);
1667 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1669 return if !$change_ip && !$change_gw;
1671 # step 1: add new IP, if this fails we cancel
1672 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1673 if ($change_ip && $is_real_ip) {
1674 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1681 # step 2: replace gateway
1682 # If this fails we delete the added IP and cancel.
1683 # If it succeeds we save the config and delete the old IP, ignoring
1684 # errors. The config is then saved.
1685 # Note: 'ip route replace' can add
1689 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1690 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1692 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1696 # the route was not replaced, the old IP is still available
1697 # rollback (delete new IP) and cancel
1699 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1700 warn $@ if $@; # no need to die here
1705 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1706 # if the route was not deleted, the guest might have deleted it manually
1712 # from this point on we save the configuration
1713 # step 3: delete old IP ignoring errors
1714 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1715 # We need to enable promote_secondaries, otherwise our newly added
1716 # address will be removed along with the old one.
1719 if ($ipversion == 4) {
1720 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1721 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1722 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1724 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1726 warn $@ if $@; # no need to die here
1728 if ($ipversion == 4) {
1729 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1733 foreach my $property ($ip, $gw) {
1734 if ($newnet->{$property}) {
1735 $optdata->{$property} = $newnet->{$property};
1737 delete $optdata->{$property};
1740 $conf->{$opt} = print_lxc_network
($optdata);
1741 write_config
($vmid, $conf);
1742 $lxc_setup->setup_network($conf);
1745 &$change_ip_config(4);
1746 &$change_ip_config(6);
1750 # Internal snapshots
1752 # NOTE: Snapshot create/delete involves several non-atomic
1753 # actions, and can take a long time.
1754 # So we try to avoid locking the file and use the 'lock' variable
1755 # inside the config file instead.
1757 my $snapshot_copy_config = sub {
1758 my ($source, $dest) = @_;
1760 foreach my $k (keys %$source) {
1761 next if $k eq 'snapshots';
1762 next if $k eq 'snapstate';
1763 next if $k eq 'snaptime';
1764 next if $k eq 'vmstate';
1765 next if $k eq 'lock';
1766 next if $k eq 'digest';
1767 next if $k eq 'description';
1768 next if $k =~ m/^unused\d+$/;
1770 $dest->{$k} = $source->{$k};
1774 my $snapshot_apply_config = sub {
1775 my ($conf, $snap) = @_;
1777 # copy snapshot list
1779 snapshots
=> $conf->{snapshots
},
1782 # keep description and list of unused disks
1783 foreach my $k (keys %$conf) {
1784 next if !($k =~ m/^unused\d+$/ || $k eq 'description');
1785 $newconf->{$k} = $conf->{$k};
1788 &$snapshot_copy_config($snap, $newconf);
1793 my $snapshot_save_vmstate = sub {
1794 die "implement me - snapshot_save_vmstate\n";
1797 sub snapshot_prepare
{
1798 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1802 my $updatefn = sub {
1804 my $conf = load_config
($vmid);
1806 die "you can't take a snapshot if it's a template\n"
1807 if is_template
($conf);
1811 $conf->{lock} = 'snapshot';
1813 die "snapshot name '$snapname' already used\n"
1814 if defined($conf->{snapshots
}->{$snapname});
1816 my $storecfg = PVE
::Storage
::config
();
1818 # workaround until mp snapshots are implemented
1819 my $feature = $snapname eq 'vzdump' ?
'vzdump' : 'snapshot';
1820 die "snapshot feature is not available\n" if !has_feature
($feature, $conf, $storecfg);
1822 $snap = $conf->{snapshots
}->{$snapname} = {};
1824 if ($save_vmstate && check_running
($vmid)) {
1825 &$snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
1828 &$snapshot_copy_config($conf, $snap);
1830 $snap->{snapstate
} = "prepare";
1831 $snap->{snaptime
} = time();
1832 $snap->{description
} = $comment if $comment;
1834 write_config
($vmid, $conf);
1837 lock_config
($vmid, $updatefn);
1842 sub snapshot_commit
{
1843 my ($vmid, $snapname) = @_;
1845 my $updatefn = sub {
1847 my $conf = load_config
($vmid);
1849 die "missing snapshot lock\n"
1850 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1852 my $snap = $conf->{snapshots
}->{$snapname};
1853 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1855 die "wrong snapshot state\n"
1856 if !($snap->{snapstate
} && $snap->{snapstate
} eq "prepare");
1858 delete $snap->{snapstate
};
1859 delete $conf->{lock};
1861 my $newconf = &$snapshot_apply_config($conf, $snap);
1863 $newconf->{parent
} = $snapname;
1865 write_config
($vmid, $newconf);
1868 lock_config
($vmid, $updatefn);
1872 my ($feature, $conf, $storecfg, $snapname) = @_;
1875 my $vzdump = $feature eq 'vzdump';
1876 $feature = 'snapshot' if $vzdump;
1878 foreach_mountpoint
($conf, sub {
1879 my ($ms, $mountpoint) = @_;
1881 return if $err; # skip further test
1882 return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup
};
1884 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1886 # TODO: implement support for mountpoints
1887 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1891 return $err ?
0 : 1;
1894 my $enter_namespace = sub {
1895 my ($vmid, $pid, $which, $type) = @_;
1896 sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
1897 or die "failed to open $which namespace of container $vmid: $!\n";
1898 PVE
::Tools
::setns
(fileno($fd), $type)
1899 or die "failed to enter $which namespace of container $vmid: $!\n";
1903 my $do_syncfs = sub {
1904 my ($vmid, $pid, $socket) = @_;
1906 &$enter_namespace($vmid, $pid, 'mnt', PVE
::Tools
::CLONE_NEWNS
);
1908 # Tell the parent process to start reading our /proc/mounts
1909 print {$socket} "go\n";
1912 # Receive /proc/self/mounts
1913 my $mountdata = do { local $/ = undef; <$socket> };
1916 # Now sync all mountpoints...
1917 my $mounts = PVE
::ProcFSTools
::parse_mounts
($mountdata);
1918 foreach my $mp (@$mounts) {
1919 my ($what, $dir, $fs) = @$mp;
1920 next if $fs eq 'fuse.lxcfs';
1921 eval { PVE
::Tools
::sync_mountpoint
($dir); };
1926 sub sync_container_namespace
{
1928 my $pid = find_lxc_pid
($vmid);
1930 # SOCK_DGRAM is nicer for barriers but cannot be slurped
1931 socketpair my $pfd, my $cfd, AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
1932 or die "failed to create socketpair: $!\n";
1935 die "fork failed: $!\n" if !defined($child);
1940 &$do_syncfs($vmid, $pid, $cfd);
1950 die "failed to enter container namespace\n" if $go ne "go\n";
1952 open my $mounts, '<', "/proc/$child/mounts"
1953 or die "failed to open container's /proc/mounts: $!\n";
1954 my $mountdata = do { local $/ = undef; <$mounts> };
1956 print {$pfd} $mountdata;
1959 while (waitpid($child, 0) != $child) {}
1960 die "failed to sync container namespace\n" if $? != 0;
1963 sub snapshot_create
{
1964 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1966 my $snap = snapshot_prepare
($vmid, $snapname, $save_vmstate, $comment);
1968 my $conf = load_config
($vmid);
1970 my $running = check_running
($vmid);
1979 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1980 sync_container_namespace
($vmid);
1983 my $storecfg = PVE
::Storage
::config
();
1984 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1985 my $volid = $rootinfo->{volume
};
1987 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1988 $drivehash->{rootfs
} = 1;
1993 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1998 eval { snapshot_delete
($vmid, $snapname, 1, $drivehash); };
2003 snapshot_commit
($vmid, $snapname);
2006 # Note: $drivehash is only set when called from snapshot_create.
2007 sub snapshot_delete
{
2008 my ($vmid, $snapname, $force, $drivehash) = @_;
2014 my $unlink_parent = sub {
2015 my ($confref, $new_parent) = @_;
2017 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
2019 $confref->{parent
} = $new_parent;
2021 delete $confref->{parent
};
2026 my $updatefn = sub {
2027 my ($remove_drive) = @_;
2029 my $conf = load_config
($vmid);
2033 die "you can't delete a snapshot if vm is a template\n"
2034 if is_template
($conf);
2037 $snap = $conf->{snapshots
}->{$snapname};
2039 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2041 # remove parent refs
2043 &$unlink_parent($conf, $snap->{parent
});
2044 foreach my $sn (keys %{$conf->{snapshots
}}) {
2045 next if $sn eq $snapname;
2046 &$unlink_parent($conf->{snapshots
}->{$sn}, $snap->{parent
});
2050 if ($remove_drive) {
2051 if ($remove_drive eq 'vmstate') {
2052 die "implement me - saving vmstate\n";
2054 die "implement me - remove drive\n";
2059 $snap->{snapstate
} = 'delete';
2061 delete $conf->{snapshots
}->{$snapname};
2062 delete $conf->{lock} if $drivehash;
2065 write_config
($vmid, $conf);
2068 lock_config
($vmid, $updatefn);
2070 # now remove vmstate file
2071 # never set for LXC!
2072 my $storecfg = PVE
::Storage
::config
();
2074 if ($snap->{vmstate
}) {
2075 die "implement me - saving vmstate\n";
2078 # now remove all volume snapshots
2079 # only rootfs for now!
2081 my $rootfs = $snap->{rootfs
};
2082 my $rootinfo = parse_ct_rootfs
($rootfs);
2083 my $volid = $rootinfo->{volume
};
2084 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
2087 die $err if !$force;
2091 # now cleanup config
2093 lock_config
($vmid, $updatefn);
2096 sub snapshot_rollback
{
2097 my ($vmid, $snapname) = @_;
2101 my $storecfg = PVE
::Storage
::config
();
2103 my $conf = load_config
($vmid);
2105 my $get_snapshot_config = sub {
2107 die "you can't rollback if vm is a template\n" if is_template
($conf);
2109 my $res = $conf->{snapshots
}->{$snapname};
2111 die "snapshot '$snapname' does not exist\n" if !defined($res);
2116 my $snap = &$get_snapshot_config();
2118 # only for rootfs for now!
2119 my $rootfs = $snap->{rootfs
};
2120 my $rootinfo = parse_ct_rootfs
($rootfs);
2121 my $volid = $rootinfo->{volume
};
2123 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
2125 my $updatefn = sub {
2127 $conf = load_config
($vmid);
2129 $snap = &$get_snapshot_config();
2131 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
2132 if $snap->{snapstate
};
2136 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
2139 die "unable to rollback vm $vmid: vm is running\n"
2140 if check_running
($vmid);
2143 $conf->{lock} = 'rollback';
2145 die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
2146 delete $conf->{lock};
2152 # copy snapshot config to current config
2153 $conf = &$snapshot_apply_config($conf, $snap);
2154 $conf->{parent
} = $snapname;
2157 write_config
($vmid, $conf);
2159 if (!$prepare && $snap->{vmstate
}) {
2160 die "implement me - save vmstate";
2164 lock_config
($vmid, $updatefn);
2166 # only rootfs for now!
2167 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
2170 lock_config
($vmid, $updatefn);
2173 sub template_create
{
2174 my ($vmid, $conf) = @_;
2176 my $storecfg = PVE
::Storage
::config
();
2178 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
2179 my $volid = $rootinfo->{volume
};
2181 die "Template feature is not available for '$volid'\n"
2182 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
2184 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2186 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
2187 $rootinfo->{volume
} = $template_volid;
2188 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
2190 write_config
($vmid, $conf);
2196 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2199 sub mountpoint_names
{
2202 my @names = ('rootfs');
2204 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2205 push @names, "mp$i";
2208 return $reverse ?
reverse @names : @names;
2212 sub foreach_mountpoint_full
{
2213 my ($conf, $reverse, $func) = @_;
2215 foreach my $key (mountpoint_names
($reverse)) {
2216 my $value = $conf->{$key};
2217 next if !defined($value);
2218 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2219 next if !defined($mountpoint);
2221 &$func($key, $mountpoint);
2225 sub foreach_mountpoint
{
2226 my ($conf, $func) = @_;
2228 foreach_mountpoint_full
($conf, 0, $func);
2231 sub foreach_mountpoint_reverse
{
2232 my ($conf, $func) = @_;
2234 foreach_mountpoint_full
($conf, 1, $func);
2237 sub check_ct_modify_config_perm
{
2238 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2240 return 1 if $authuser ne 'root@pam';
2242 foreach my $opt (@$key_list) {
2244 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2245 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2246 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2247 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2248 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2249 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2250 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2251 $opt eq 'searchdomain' || $opt eq 'hostname') {
2252 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2254 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2262 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2264 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2265 my $volid_list = get_vm_volumes
($conf);
2267 foreach_mountpoint_reverse
($conf, sub {
2268 my ($ms, $mountpoint) = @_;
2270 my $volid = $mountpoint->{volume
};
2271 my $mount = $mountpoint->{mp
};
2273 return if !$volid || !$mount;
2275 my $mount_path = "$rootdir/$mount";
2276 $mount_path =~ s!/+!/!g;
2278 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2281 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2294 my ($vmid, $storage_cfg, $conf) = @_;
2296 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2297 File
::Path
::make_path
($rootdir);
2299 my $volid_list = get_vm_volumes
($conf);
2300 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2303 foreach_mountpoint
($conf, sub {
2304 my ($ms, $mountpoint) = @_;
2306 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2310 warn "mounting container failed\n";
2311 umount_all
($vmid, $storage_cfg, $conf, 1);
2319 sub mountpoint_mount_path
{
2320 my ($mountpoint, $storage_cfg, $snapname) = @_;
2322 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2325 my $check_mount_path = sub {
2327 $path = File
::Spec-
>canonpath($path);
2328 my $real = Cwd
::realpath
($path);
2329 if ($real ne $path) {
2330 die "mount path modified by symlink: $path != $real";
2339 if ($line =~ m
@^(/dev/loop\d
+):@) {
2343 my $cmd = ['losetup', '--associated', $path];
2344 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2348 # Run a function with a file attached to a loop device.
2349 # The loop device is always detached afterwards (or set to autoclear).
2350 # Returns the loop device.
2351 sub run_with_loopdev
{
2352 my ($func, $file) = @_;
2356 if ($line =~ m
@^(/dev/loop\d
+)$@) {
2360 PVE
::Tools
::run_command
(['losetup', '--show', '-f', $file], outfunc
=> $parser);
2361 die "failed to setup loop device for $file\n" if !$device;
2362 eval { &$func($device); };
2364 PVE
::Tools
::run_command
(['losetup', '-d', $device]);
2370 my ($dir, $dest, $ro, @extra_opts) = @_;
2371 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
2373 eval { PVE
::Tools
::run_command
(['mount', '-o', 'bind,remount,ro', $dest]); };
2375 warn "bindmount error\n";
2376 # don't leave writable bind-mounts behind...
2377 PVE
::Tools
::run_command
(['umount', $dest]);
2383 # use $rootdir = undef to just return the corresponding mount path
2384 sub mountpoint_mount
{
2385 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2387 my $volid = $mountpoint->{volume
};
2388 my $mount = $mountpoint->{mp
};
2389 my $type = $mountpoint->{type
};
2390 my $quota = !$snapname && !$mountpoint->{ro
} && $mountpoint->{quota
};
2393 return if !$volid || !$mount;
2397 if (defined($rootdir)) {
2398 $rootdir =~ s!/+$!!;
2399 $mount_path = "$rootdir/$mount";
2400 $mount_path =~ s!/+!/!g;
2401 &$check_mount_path($mount_path);
2402 File
::Path
::mkpath
($mount_path);
2405 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2407 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2410 if (defined($mountpoint->{acl
})) {
2411 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2413 my $readonly = $mountpoint->{ro
};
2415 my @extra_opts = ('-o', $optstring);
2419 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2420 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2422 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2423 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2425 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2427 if ($format eq 'subvol') {
2430 if ($scfg->{type
} eq 'zfspool') {
2431 my $path_arg = $path;
2432 $path_arg =~ s!^/+!!;
2433 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2435 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2438 bindmount
($path, $mount_path, $readonly, @extra_opts);
2439 warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
2442 return wantarray ?
($path, 0, $mounted_dev) : $path;
2443 } elsif ($format eq 'raw' || $format eq 'iso') {
2447 if ($format eq 'iso') {
2448 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2449 } elsif ($isBase || defined($snapname)) {
2450 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2453 push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
2455 push @extra_opts, '-o', 'ro' if $readonly;
2456 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2460 my $use_loopdev = 0;
2461 if ($scfg->{path
}) {
2462 $mounted_dev = run_with_loopdev
($domount, $path);
2464 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2465 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2466 $mounted_dev = $path;
2469 die "unsupported storage type '$scfg->{type}'\n";
2471 return wantarray ?
($path, $use_loopdev, $mounted_dev) : $path;
2473 die "unsupported image format '$format'\n";
2475 } elsif ($type eq 'device') {
2476 push @extra_opts, '-o', 'ro' if $readonly;
2477 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2478 return wantarray ?
($volid, 0, $volid) : $volid;
2479 } elsif ($type eq 'bind') {
2480 die "directory '$volid' does not exist\n" if ! -d
$volid;
2481 &$check_mount_path($volid);
2482 bindmount
($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
2483 warn "cannot enable quota control for bind mounts\n" if $quota;
2484 return wantarray ?
($volid, 0, undef) : $volid;
2487 die "unsupported storage";
2490 sub get_vm_volumes
{
2491 my ($conf, $excludes) = @_;
2495 foreach_mountpoint
($conf, sub {
2496 my ($ms, $mountpoint) = @_;
2498 return if $excludes && $ms eq $excludes;
2500 my $volid = $mountpoint->{volume
};
2502 return if !$volid || $mountpoint->{type
} ne 'volume';
2504 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2507 push @$vollist, $volid;
2514 my ($dev, $rootuid, $rootgid) = @_;
2516 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2517 '-E', "root_owner=$rootuid:$rootgid",
2522 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2524 if ($volid =~ m!^/dev/.+!) {
2529 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2531 die "cannot format volume '$volid' with no storage\n" if !$storage;
2533 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2535 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2537 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2538 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2540 die "cannot format volume '$volid' (format == $format)\n"
2541 if $format ne 'raw';
2543 mkfs
($path, $rootuid, $rootgid);
2547 my ($storecfg, $vollist) = @_;
2549 foreach my $volid (@$vollist) {
2550 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2556 my ($storecfg, $vmid, $settings, $conf) = @_;
2561 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2562 my $chown_vollist = [];
2564 foreach_mountpoint
($settings, sub {
2565 my ($ms, $mountpoint) = @_;
2567 my $volid = $mountpoint->{volume
};
2568 my $mp = $mountpoint->{mp
};
2570 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2572 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2573 my ($storeid, $size_gb) = ($1, $2);
2575 my $size_kb = int(${size_gb
}*1024) * 1024;
2577 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2578 # fixme: use better naming ct-$vmid-disk-X.raw?
2580 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2582 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2584 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2586 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2588 push @$chown_vollist, $volid;
2590 } elsif ($scfg->{type
} eq 'zfspool') {
2592 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2594 push @$chown_vollist, $volid;
2595 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2597 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2598 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2600 } elsif ($scfg->{type
} eq 'rbd') {
2602 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2603 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2604 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2606 die "unable to create containers on storage type '$scfg->{type}'\n";
2608 push @$vollist, $volid;
2609 $mountpoint->{volume
} = $volid;
2610 $mountpoint->{size
} = $size_kb * 1024;
2611 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2613 # use specified/existing volid/dir/device
2614 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2618 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2619 foreach my $volid (@$chown_vollist) {
2620 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2621 chown($rootuid, $rootgid, $path);
2623 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2625 # free allocated images on error
2627 destroy_disks
($storecfg, $vollist);
2633 # bash completion helper
2635 sub complete_os_templates
{
2636 my ($cmdname, $pname, $cvalue) = @_;
2638 my $cfg = PVE
::Storage
::config
();
2642 if ($cvalue =~ m/^([^:]+):/) {
2646 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2647 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2650 foreach my $id (keys %$data) {
2651 foreach my $item (@{$data->{$id}}) {
2652 push @$res, $item->{volid
} if defined($item->{volid
});
2659 my $complete_ctid_full = sub {
2662 my $idlist = vmstatus
();
2664 my $active_hash = list_active_containers
();
2668 foreach my $id (keys %$idlist) {
2669 my $d = $idlist->{$id};
2670 if (defined($running)) {
2671 next if $d->{template
};
2672 next if $running && !$active_hash->{$id};
2673 next if !$running && $active_hash->{$id};
2682 return &$complete_ctid_full();
2685 sub complete_ctid_stopped
{
2686 return &$complete_ctid_full(0);
2689 sub complete_ctid_running
{
2690 return &$complete_ctid_full(1);
2700 my $lxc = $conf->{lxc
};
2701 foreach my $entry (@$lxc) {
2702 my ($key, $value) = @$entry;
2703 next if $key ne 'lxc.id_map';
2704 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2705 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2706 push @$id_map, [$type, $ct, $host, $length];
2708 $rootuid = $host if $type eq 'u';
2709 $rootgid = $host if $type eq 'g';
2712 die "failed to parse id_map: $value\n";
2716 if (!@$id_map && $conf->{unprivileged
}) {
2717 # Should we read them from /etc/subuid?
2718 $id_map = [ ['u', '0', '100000', '65536'],
2719 ['g', '0', '100000', '65536'] ];
2720 $rootuid = $rootgid = 100000;
2723 return ($id_map, $rootuid, $rootgid);
2726 sub userns_command
{
2729 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];