12 use Fcntl
qw(O_RDONLY);
14 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
15 use PVE
::Exception
qw(raise_perm_exc);
19 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full);
22 use PVE
::AccessControl
;
24 use Time
::HiRes qw
(gettimeofday
);
28 my $nodename = PVE
::INotify
::nodename
();
30 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
32 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
34 '--xattrs-include=user.*',
35 '--xattrs-include=security.capability',
36 '--warning=no-xattr-write' ];
38 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
44 format
=> 'pve-lxc-mp-string',
45 format_description
=> 'volume',
46 description
=> 'Volume, device or directory to mount into the container.',
50 format_description
=> '[1|0]',
51 description
=> 'Whether to include the mountpoint in backups.',
56 format
=> 'disk-size',
57 format_description
=> 'DiskSize',
58 description
=> 'Volume size (read only value).',
63 format_description
=> 'acl',
64 description
=> 'Explicitly enable or disable ACL support.',
69 format_description
=> 'ro',
70 description
=> 'Read-only mountpoint (not supported with bind mounts)',
75 format_description
=> '[0|1]',
76 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
81 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
82 type
=> 'string', format
=> $rootfs_desc,
83 description
=> "Use volume as container root.",
87 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
88 description
=> "The name of the snapshot.",
89 type
=> 'string', format
=> 'pve-configid',
97 description
=> "Lock/unlock the VM.",
98 enum
=> [qw(migrate backup snapshot rollback)],
103 description
=> "Specifies whether a VM will be started during system bootup.",
106 startup
=> get_standard_option
('pve-startup-order'),
110 description
=> "Enable/disable Template.",
116 enum
=> ['amd64', 'i386'],
117 description
=> "OS architecture type.",
123 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
124 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.",
129 description
=> "Attach a console device (/dev/console) to the container.",
135 description
=> "Specify the number of tty available to the container",
143 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.",
151 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.",
159 description
=> "Amount of RAM for the VM in MB.",
166 description
=> "Amount of SWAP for the VM in MB.",
172 description
=> "Set a host name for the container.",
173 type
=> 'string', format
=> 'dns-name',
179 description
=> "Container description. Only used on the configuration web interface.",
183 type
=> 'string', format
=> 'dns-name-list',
184 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
188 type
=> 'string', format
=> 'address-list',
189 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.",
191 rootfs
=> get_standard_option
('pve-ct-rootfs'),
194 type
=> 'string', format
=> 'pve-configid',
196 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
200 description
=> "Timestamp for snapshots.",
206 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).",
208 enum
=> ['shell', 'console', 'tty'],
214 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
220 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
225 my $valid_lxc_conf_keys = {
229 'lxc.haltsignal' => 1,
230 'lxc.rebootsignal' => 1,
231 'lxc.stopsignal' => 1,
233 'lxc.network.type' => 1,
234 'lxc.network.flags' => 1,
235 'lxc.network.link' => 1,
236 'lxc.network.mtu' => 1,
237 'lxc.network.name' => 1,
238 'lxc.network.hwaddr' => 1,
239 'lxc.network.ipv4' => 1,
240 'lxc.network.ipv4.gateway' => 1,
241 'lxc.network.ipv6' => 1,
242 'lxc.network.ipv6.gateway' => 1,
243 'lxc.network.script.up' => 1,
244 'lxc.network.script.down' => 1,
246 'lxc.console.logfile' => 1,
249 'lxc.devttydir' => 1,
250 'lxc.hook.autodev' => 1,
254 'lxc.mount.entry' => 1,
255 'lxc.mount.auto' => 1,
256 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
257 'lxc.rootfs.mount' => 1,
258 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
259 ', please use mountpoint options in the "rootfs" key',
263 'lxc.aa_profile' => 1,
264 'lxc.aa_allow_incomplete' => 1,
265 'lxc.se_context' => 1,
268 'lxc.hook.pre-start' => 1,
269 'lxc.hook.pre-mount' => 1,
270 'lxc.hook.mount' => 1,
271 'lxc.hook.start' => 1,
272 'lxc.hook.stop' => 1,
273 'lxc.hook.post-stop' => 1,
274 'lxc.hook.clone' => 1,
275 'lxc.hook.destroy' => 1,
278 'lxc.start.auto' => 1,
279 'lxc.start.delay' => 1,
280 'lxc.start.order' => 1,
282 'lxc.environment' => 1,
289 description
=> "Network interface type.",
294 format_description
=> 'String',
295 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
296 pattern
=> '[-_.\w\d]+',
300 format_description
=> 'vmbr<Number>',
301 description
=> 'Bridge to attach the network device to.',
302 pattern
=> '[-_.\w\d]+',
307 format_description
=> 'MAC',
308 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
309 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
314 format_description
=> 'Number',
315 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
316 minimum
=> 64, # minimum ethernet frame is 64 bytes
321 format
=> 'pve-ipv4-config',
322 format_description
=> 'IPv4Format/CIDR',
323 description
=> 'IPv4 address in CIDR format.',
329 format_description
=> 'GatewayIPv4',
330 description
=> 'Default gateway for IPv4 traffic.',
335 format
=> 'pve-ipv6-config',
336 format_description
=> 'IPv6Format/CIDR',
337 description
=> 'IPv6 address in CIDR format.',
343 format_description
=> 'GatewayIPv6',
344 description
=> 'Default gateway for IPv6 traffic.',
349 format_description
=> '[1|0]',
350 description
=> "Controls whether this interface's firewall rules should be used.",
355 format_description
=> 'VlanNo',
358 description
=> "VLAN tag for this interface.",
363 pattern
=> qr/\d+(?:;\d+)*/,
364 format_description
=> 'vlanid[;vlanid...]',
365 description
=> "VLAN ids to pass through the interface",
369 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
371 my $MAX_LXC_NETWORKS = 10;
372 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
373 $confdesc->{"net$i"} = {
375 type
=> 'string', format
=> $netconf_desc,
376 description
=> "Specifies network interfaces for the container.",
380 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
381 sub verify_lxc_mp_string
{
382 my ($mp, $noerr) = @_;
386 # /. or /.. at the end
387 # ../ at the beginning
389 if($mp =~ m
@/\.\
.?
/@ ||
392 return undef if $noerr;
393 die "$mp contains illegal character sequences\n";
402 format
=> 'pve-lxc-mp-string',
403 format_description
=> 'Path',
404 description
=> 'Path to the mountpoint as seen from inside the container.',
407 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
411 type
=> 'string', format
=> 'pve-volume-id',
412 description
=> "Reference to unused volumes.",
415 my $MAX_MOUNT_POINTS = 10;
416 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
417 $confdesc->{"mp$i"} = {
419 type
=> 'string', format
=> $mp_desc,
420 description
=> "Use volume as container mount point (experimental feature).",
425 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
426 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
427 $confdesc->{"unused$i"} = $unuseddesc;
430 sub write_pct_config
{
431 my ($filename, $conf) = @_;
433 delete $conf->{snapstate
}; # just to be sure
435 my $generate_raw_config = sub {
440 # add description as comment to top of file
441 my $descr = $conf->{description
} || '';
442 foreach my $cl (split(/\n/, $descr)) {
443 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
446 foreach my $key (sort keys %$conf) {
447 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
448 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
449 my $value = $conf->{$key};
450 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
451 $raw .= "$key: $value\n";
454 if (my $lxcconf = $conf->{lxc
}) {
455 foreach my $entry (@$lxcconf) {
456 my ($k, $v) = @$entry;
464 my $raw = &$generate_raw_config($conf);
466 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
467 $raw .= "\n[$snapname]\n";
468 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
475 my ($key, $value) = @_;
477 die "unknown setting '$key'\n" if !$confdesc->{$key};
479 my $type = $confdesc->{$key}->{type
};
481 if (!defined($value)) {
482 die "got undefined value\n";
485 if ($value =~ m/[\n\r]/) {
486 die "property contains a line feed\n";
489 if ($type eq 'boolean') {
490 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
491 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
492 die "type check ('boolean') failed - got '$value'\n";
493 } elsif ($type eq 'integer') {
494 return int($1) if $value =~ m/^(\d+)$/;
495 die "type check ('integer') failed - got '$value'\n";
496 } elsif ($type eq 'number') {
497 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
498 die "type check ('number') failed - got '$value'\n";
499 } elsif ($type eq 'string') {
500 if (my $fmt = $confdesc->{$key}->{format
}) {
501 PVE
::JSONSchema
::check_format
($fmt, $value);
510 sub parse_pct_config
{
511 my ($filename, $raw) = @_;
513 return undef if !defined($raw);
516 digest
=> Digest
::SHA
::sha1_hex
($raw),
520 $filename =~ m
|/lxc/(\d
+).conf
$|
521 || die "got strange filename '$filename'";
529 my @lines = split(/\n/, $raw);
530 foreach my $line (@lines) {
531 next if $line =~ m/^\s*$/;
533 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
535 $conf->{description
} = $descr if $descr;
537 $conf = $res->{snapshots
}->{$section} = {};
541 if ($line =~ m/^\#(.*)\s*$/) {
542 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
546 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
549 my $validity = $valid_lxc_conf_keys->{$key} || 0;
550 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
551 push @{$conf->{lxc
}}, [$key, $value];
552 } elsif (my $errmsg = $validity) {
553 warn "vm $vmid - $key: $errmsg\n";
555 warn "vm $vmid - unable to parse config: $line\n";
557 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
558 $descr .= PVE
::Tools
::decode_text
($2);
559 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
560 $conf->{snapstate
} = $1;
561 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
564 eval { $value = check_type
($key, $value); };
565 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
566 $conf->{$key} = $value;
568 warn "vm $vmid - unable to parse config: $line\n";
572 $conf->{description
} = $descr if $descr;
574 delete $res->{snapstate
}; # just to be sure
580 my $vmlist = PVE
::Cluster
::get_vmlist
();
582 return $res if !$vmlist || !$vmlist->{ids
};
583 my $ids = $vmlist->{ids
};
585 foreach my $vmid (keys %$ids) {
586 next if !$vmid; # skip CT0
587 my $d = $ids->{$vmid};
588 next if !$d->{node
} || $d->{node
} ne $nodename;
589 next if !$d->{type
} || $d->{type
} ne 'lxc';
590 $res->{$vmid}->{type
} = 'lxc';
595 sub cfs_config_path
{
596 my ($vmid, $node) = @_;
598 $node = $nodename if !$node;
599 return "nodes/$node/lxc/$vmid.conf";
603 my ($vmid, $node) = @_;
605 my $cfspath = cfs_config_path
($vmid, $node);
606 return "/etc/pve/$cfspath";
610 my ($vmid, $node) = @_;
612 $node = $nodename if !$node;
613 my $cfspath = cfs_config_path
($vmid, $node);
615 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
616 die "container $vmid does not exist\n" if !defined($conf);
622 my ($vmid, $conf) = @_;
624 my $dir = "/etc/pve/nodes/$nodename/lxc";
627 write_config
($vmid, $conf);
633 unlink config_file
($vmid, $nodename);
637 my ($vmid, $conf) = @_;
639 my $cfspath = cfs_config_path
($vmid);
641 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
644 # flock: we use one file handle per process, so lock file
645 # can be called multiple times and will succeed for the same process.
647 my $lock_handles = {};
648 my $lockdir = "/run/lock/lxc";
650 sub config_file_lock
{
653 return "$lockdir/pve-config-${vmid}.lock";
656 sub lock_config_full
{
657 my ($vmid, $timeout, $code, @param) = @_;
659 my $filename = config_file_lock
($vmid);
661 mkdir $lockdir if !-d
$lockdir;
663 my $res = lock_file
($filename, $timeout, $code, @param);
670 sub lock_config_mode
{
671 my ($vmid, $timeout, $shared, $code, @param) = @_;
673 my $filename = config_file_lock
($vmid);
675 mkdir $lockdir if !-d
$lockdir;
677 my $res = lock_file_full
($filename, $timeout, $shared, $code, @param);
685 my ($vmid, $code, @param) = @_;
687 return lock_config_full
($vmid, 10, $code, @param);
693 return defined($confdesc->{$name});
696 # add JSON properties for create and set function
697 sub json_config_properties
{
700 foreach my $opt (keys %$confdesc) {
701 next if $opt eq 'parent' || $opt eq 'snaptime';
702 next if $prop->{$opt};
703 $prop->{$opt} = $confdesc->{$opt};
709 # container status helpers
711 sub list_active_containers
{
713 my $filename = "/proc/net/unix";
715 # similar test is used by lcxcontainers.c: list_active_containers
718 my $fh = IO
::File-
>new ($filename, "r");
721 while (defined(my $line = <$fh>)) {
722 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
724 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
735 # warning: this is slow
739 my $active_hash = list_active_containers
();
741 return 1 if defined($active_hash->{$vmid});
746 sub get_container_disk_usage
{
747 my ($vmid, $pid) = @_;
749 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
752 my $last_proc_vmid_stat;
754 my $parse_cpuacct_stat = sub {
757 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
761 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
774 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
776 my $active_hash = list_active_containers
();
778 my $cpucount = $cpuinfo->{cpus
} || 1;
780 my $cdtime = gettimeofday
;
782 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
784 foreach my $vmid (keys %$list) {
785 my $d = $list->{$vmid};
787 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
788 warn $@ if $@; # ignore errors (consider them stopped)
790 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
792 my $cfspath = cfs_config_path
($vmid);
793 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
795 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
796 $d->{name
} =~ s/[\s]//g;
798 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
801 my $res = get_container_disk_usage
($vmid, $d->{pid
});
802 $d->{disk
} = $res->{used
};
803 $d->{maxdisk
} = $res->{total
};
806 # use 4GB by default ??
807 if (my $rootfs = $conf->{rootfs
}) {
808 my $rootinfo = parse_ct_rootfs
($rootfs);
809 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
811 $d->{maxdisk
} = 4*1024*1024*1024;
817 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
818 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
829 $d->{template
} = is_template
($conf);
832 foreach my $vmid (keys %$list) {
833 my $d = $list->{$vmid};
836 next if !$pid; # skip stopped CTs
838 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
839 $d->{uptime
} = time - $ctime; # the method lxcfs uses
841 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
842 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
844 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
845 my @bytes = split(/\n/, $blkio_bytes);
846 foreach my $byte (@bytes) {
847 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
848 $d->{diskread
} = $2 if $key eq 'Read';
849 $d->{diskwrite
} = $2 if $key eq 'Write';
853 my $pstat = &$parse_cpuacct_stat($vmid);
855 my $used = $pstat->{utime} + $pstat->{stime
};
857 my $old = $last_proc_vmid_stat->{$vmid};
859 $last_proc_vmid_stat->{$vmid} = {
867 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
870 my $dutime = $used - $old->{used
};
872 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
873 $last_proc_vmid_stat->{$vmid} = {
879 $d->{cpu
} = $old->{cpu
};
883 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
885 foreach my $dev (keys %$netdev) {
886 next if $dev !~ m/^veth([1-9]\d*)i/;
888 my $d = $list->{$vmid};
892 $d->{netout
} += $netdev->{$dev}->{receive
};
893 $d->{netin
} += $netdev->{$dev}->{transmit
};
900 sub classify_mountpoint
{
903 return 'device' if $vol =~ m!^/dev/!;
909 my $parse_ct_mountpoint_full = sub {
910 my ($desc, $data, $noerr) = @_;
915 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
917 return undef if $noerr;
921 if (defined(my $size = $res->{size
})) {
922 $size = PVE
::JSONSchema
::parse_size
($size);
923 if (!defined($size)) {
924 return undef if $noerr;
925 die "invalid size: $size\n";
927 $res->{size
} = $size;
930 $res->{type
} = classify_mountpoint
($res->{volume
});
935 sub parse_ct_rootfs
{
936 my ($data, $noerr) = @_;
938 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
940 $res->{mp
} = '/' if defined($res);
945 sub parse_ct_mountpoint
{
946 my ($data, $noerr) = @_;
948 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
951 sub print_ct_mountpoint
{
952 my ($info, $nomp) = @_;
953 my $skip = [ 'type' ];
954 push @$skip, 'mp' if $nomp;
955 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
958 sub print_lxc_network
{
960 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
963 sub parse_lxc_network
{
968 return $res if !$data;
970 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
972 $res->{type
} = 'veth';
973 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
978 sub read_cgroup_value
{
979 my ($group, $vmid, $name, $full) = @_;
981 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
983 return PVE
::Tools
::file_get_contents
($path) if $full;
985 return PVE
::Tools
::file_read_firstline
($path);
988 sub write_cgroup_value
{
989 my ($group, $vmid, $name, $value) = @_;
991 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
992 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
996 sub find_lxc_console_pids
{
1000 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1003 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1004 return if !$cmdline;
1006 my @args = split(/\0/, $cmdline);
1008 # search for lxc-console -n <vmid>
1009 return if scalar(@args) != 3;
1010 return if $args[1] ne '-n';
1011 return if $args[2] !~ m/^\d+$/;
1012 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1014 my $vmid = $args[2];
1016 push @{$res->{$vmid}}, $pid;
1028 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1030 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1032 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1037 # Note: we cannot use Net:IP, because that only allows strict
1039 sub parse_ipv4_cidr
{
1040 my ($cidr, $noerr) = @_;
1042 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1043 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1046 return undef if $noerr;
1048 die "unable to parse ipv4 address/mask\n";
1054 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1057 sub check_protection
{
1058 my ($vm_conf, $err_msg) = @_;
1060 if ($vm_conf->{protection
}) {
1061 die "$err_msg - protection mode enabled\n";
1065 sub update_lxc_config
{
1066 my ($storage_cfg, $vmid, $conf) = @_;
1068 my $dir = "/var/lib/lxc/$vmid";
1070 if ($conf->{template
}) {
1072 unlink "$dir/config";
1079 die "missing 'arch' - internal error" if !$conf->{arch
};
1080 $raw .= "lxc.arch = $conf->{arch}\n";
1082 my $unprivileged = $conf->{unprivileged
};
1083 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1085 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1086 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine | unmanaged)$/x) {
1087 my $inc ="/usr/share/lxc/config/$ostype.common.conf";
1088 $inc ="/usr/share/lxc/config/common.conf" if !-f
$inc;
1089 $raw .= "lxc.include = $inc\n";
1090 if ($unprivileged || $custom_idmap) {
1091 $inc = "/usr/share/lxc/config/$ostype.userns.conf";
1092 $inc = "/usr/share/lxc/config/userns.conf" if !-f
$inc;
1093 $raw .= "lxc.include = $inc\n"
1096 die "implement me (ostype $ostype)";
1099 # WARNING: DO NOT REMOVE this without making sure that loop device nodes
1100 # cannot be exposed to the container with r/w access (cgroup perms).
1101 # When this is enabled mounts will still remain in the monitor's namespace
1102 # after the container unmounted them and thus will not detach from their
1103 # files while the container is running!
1104 $raw .= "lxc.monitor.unshare = 1\n";
1106 # Should we read them from /etc/subuid?
1107 if ($unprivileged && !$custom_idmap) {
1108 $raw .= "lxc.id_map = u 0 100000 65536\n";
1109 $raw .= "lxc.id_map = g 0 100000 65536\n";
1112 if (!has_dev_console
($conf)) {
1113 $raw .= "lxc.console = none\n";
1114 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1117 my $ttycount = get_tty_count
($conf);
1118 $raw .= "lxc.tty = $ttycount\n";
1120 # some init scripts expect a linux terminal (turnkey).
1121 $raw .= "lxc.environment = TERM=linux\n";
1123 my $utsname = $conf->{hostname
} || "CT$vmid";
1124 $raw .= "lxc.utsname = $utsname\n";
1126 my $memory = $conf->{memory
} || 512;
1127 my $swap = $conf->{swap
} // 0;
1129 my $lxcmem = int($memory*1024*1024);
1130 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1132 my $lxcswap = int(($memory + $swap)*1024*1024);
1133 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1135 if (my $cpulimit = $conf->{cpulimit
}) {
1136 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1137 my $value = int(100000*$cpulimit);
1138 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1141 my $shares = $conf->{cpuunits
} || 1024;
1142 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1144 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1146 $raw .= "lxc.rootfs = $dir/rootfs\n";
1149 foreach my $k (keys %$conf) {
1150 next if $k !~ m/^net(\d+)$/;
1152 my $d = parse_lxc_network
($conf->{$k});
1154 $raw .= "lxc.network.type = veth\n";
1155 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1156 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1157 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1158 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1161 if (my $lxcconf = $conf->{lxc
}) {
1162 foreach my $entry (@$lxcconf) {
1163 my ($k, $v) = @$entry;
1164 $netcount++ if $k eq 'lxc.network.type';
1165 $raw .= "$k = $v\n";
1169 $raw .= "lxc.network.type = empty\n" if !$netcount;
1171 File
::Path
::mkpath
("$dir/rootfs");
1173 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1176 # verify and cleanup nameserver list (replace \0 with ' ')
1177 sub verify_nameserver_list
{
1178 my ($nameserver_list) = @_;
1181 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1182 PVE
::JSONSchema
::pve_verify_ip
($server);
1183 push @list, $server;
1186 return join(' ', @list);
1189 sub verify_searchdomain_list
{
1190 my ($searchdomain_list) = @_;
1193 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1194 # todo: should we add checks for valid dns domains?
1195 push @list, $server;
1198 return join(' ', @list);
1201 sub is_volume_in_use
{
1202 my ($config, $volid) = @_;
1205 foreach_mountpoint
($config, sub {
1206 my ($ms, $mountpoint) = @_;
1208 if ($mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid) {
1216 sub add_unused_volume
{
1217 my ($config, $volid) = @_;
1220 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1221 my $test = "unused$ind";
1222 if (my $vid = $config->{$test}) {
1223 return if $vid eq $volid; # do not add duplicates
1229 die "Too many unused volumes - please delete them first.\n" if !$key;
1231 $config->{$key} = $volid;
1236 sub update_pct_config
{
1237 my ($vmid, $conf, $running, $param, $delete) = @_;
1242 my @deleted_volumes;
1246 my $pid = find_lxc_pid
($vmid);
1247 $rootdir = "/proc/$pid/root";
1250 my $hotplug_error = sub {
1252 push @nohotplug, @_;
1259 if (defined($delete)) {
1260 foreach my $opt (@$delete) {
1261 if (!exists($conf->{$opt})) {
1262 warn "no such option: $opt\n";
1266 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1267 die "unable to delete required option '$opt'\n";
1268 } elsif ($opt eq 'swap') {
1269 delete $conf->{$opt};
1270 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1271 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1272 delete $conf->{$opt};
1273 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1274 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1275 next if $hotplug_error->($opt);
1276 delete $conf->{$opt};
1277 } elsif ($opt =~ m/^net(\d)$/) {
1278 delete $conf->{$opt};
1281 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1282 } elsif ($opt eq 'protection') {
1283 delete $conf->{$opt};
1284 } elsif ($opt =~ m/^unused(\d+)$/) {
1285 next if $hotplug_error->($opt);
1286 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1287 push @deleted_volumes, $conf->{$opt};
1288 delete $conf->{$opt};
1289 } elsif ($opt =~ m/^mp(\d+)$/) {
1290 next if $hotplug_error->($opt);
1291 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1292 my $mp = parse_ct_mountpoint
($conf->{$opt});
1293 delete $conf->{$opt};
1294 if ($mp->{type
} eq 'volume' && !is_volume_in_use
($conf, $mp->{volume
})) {
1295 add_unused_volume
($conf, $mp->{volume
});
1297 } elsif ($opt eq 'unprivileged') {
1298 die "unable to delete read-only option: '$opt'\n";
1300 die "implement me (delete: $opt)"
1302 write_config
($vmid, $conf) if $running;
1306 # There's no separate swap size to configure, there's memory and "total"
1307 # memory (iow. memory+swap). This means we have to change them together.
1308 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1309 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1310 if (defined($wanted_memory) || defined($wanted_swap)) {
1312 my $old_memory = ($conf->{memory
} || 512);
1313 my $old_swap = ($conf->{swap
} || 0);
1315 $wanted_memory //= $old_memory;
1316 $wanted_swap //= $old_swap;
1318 my $total = $wanted_memory + $wanted_swap;
1320 my $old_total = $old_memory + $old_swap;
1321 if ($total > $old_total) {
1322 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1323 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1325 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1326 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1329 $conf->{memory
} = $wanted_memory;
1330 $conf->{swap
} = $wanted_swap;
1332 write_config
($vmid, $conf) if $running;
1335 my $used_volids = {};
1337 foreach my $opt (keys %$param) {
1338 my $value = $param->{$opt};
1339 if ($opt eq 'hostname') {
1340 $conf->{$opt} = $value;
1341 } elsif ($opt eq 'onboot') {
1342 $conf->{$opt} = $value ?
1 : 0;
1343 } elsif ($opt eq 'startup') {
1344 $conf->{$opt} = $value;
1345 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1346 next if $hotplug_error->($opt);
1347 $conf->{$opt} = $value;
1348 } elsif ($opt eq 'nameserver') {
1349 next if $hotplug_error->($opt);
1350 my $list = verify_nameserver_list
($value);
1351 $conf->{$opt} = $list;
1352 } elsif ($opt eq 'searchdomain') {
1353 next if $hotplug_error->($opt);
1354 my $list = verify_searchdomain_list
($value);
1355 $conf->{$opt} = $list;
1356 } elsif ($opt eq 'cpulimit') {
1357 next if $hotplug_error->($opt); # FIXME: hotplug
1358 $conf->{$opt} = $value;
1359 } elsif ($opt eq 'cpuunits') {
1360 $conf->{$opt} = $value;
1361 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1362 } elsif ($opt eq 'description') {
1363 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1364 } elsif ($opt =~ m/^net(\d+)$/) {
1366 my $net = parse_lxc_network
($value);
1368 $conf->{$opt} = print_lxc_network
($net);
1370 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1372 } elsif ($opt eq 'protection') {
1373 $conf->{$opt} = $value ?
1 : 0;
1374 } elsif ($opt =~ m/^mp(\d+)$/) {
1375 next if $hotplug_error->($opt);
1376 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1377 my $old = $conf->{$opt};
1378 $conf->{$opt} = $value;
1379 if (defined($old)) {
1380 my $mp = parse_ct_mountpoint
($old);
1381 if ($mp->{type
} eq 'volume' && !is_volume_in_use
($conf, $mp->{volume
})) {
1382 add_unused_volume
($conf, $mp->{volume
});
1386 my $mp = parse_ct_mountpoint
($value);
1387 $used_volids->{$mp->{volume
}} = 1;
1388 } elsif ($opt eq 'rootfs') {
1389 next if $hotplug_error->($opt);
1390 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1391 my $old = $conf->{$opt};
1392 $conf->{$opt} = $value;
1393 if (defined($old)) {
1394 my $mp = parse_ct_rootfs
($old);
1395 if ($mp->{type
} eq 'volume' && !is_volume_in_use
($conf, $mp->{volume
})) {
1396 add_unused_volume
($conf, $mp->{volume
});
1399 my $mp = parse_ct_rootfs
($value);
1400 $used_volids->{$mp->{volume
}} = 1;
1401 } elsif ($opt eq 'unprivileged') {
1402 die "unable to modify read-only option: '$opt'\n";
1403 } elsif ($opt eq 'ostype') {
1404 next if $hotplug_error->($opt);
1405 $conf->{$opt} = $value;
1407 die "implement me: $opt";
1409 write_config
($vmid, $conf) if $running;
1414 # Remove unused disks after re-adding
1415 foreach my $key (keys %$conf) {
1416 next if $key !~ /^unused\d+/;
1417 my $volid = $conf->{$key};
1418 if ($used_volids->{$volid}) {
1419 delete $conf->{$key};
1423 # Apply deletions and creations of new volumes
1424 if (@deleted_volumes) {
1425 my $storage_cfg = PVE
::Storage
::config
();
1426 foreach my $volume (@deleted_volumes) {
1427 next if $used_volids->{$volume}; # could have been re-added, too
1428 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1433 my $storage_cfg = PVE
::Storage
::config
();
1434 create_disks
($storage_cfg, $vmid, $conf, $conf);
1437 # This should be the last thing we do here
1438 if ($running && scalar(@nohotplug)) {
1439 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1443 sub has_dev_console
{
1446 return !(defined($conf->{console
}) && !$conf->{console
});
1452 return $conf->{tty
} // $confdesc->{tty
}->{default};
1458 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1461 sub get_console_command
{
1462 my ($vmid, $conf) = @_;
1464 my $cmode = get_cmode
($conf);
1466 if ($cmode eq 'console') {
1467 return ['lxc-console', '-n', $vmid, '-t', 0];
1468 } elsif ($cmode eq 'tty') {
1469 return ['lxc-console', '-n', $vmid];
1470 } elsif ($cmode eq 'shell') {
1471 return ['lxc-attach', '--clear-env', '-n', $vmid];
1473 die "internal error";
1477 sub get_primary_ips
{
1480 # return data from net0
1482 return undef if !defined($conf->{net0
});
1483 my $net = parse_lxc_network
($conf->{net0
});
1485 my $ipv4 = $net->{ip
};
1487 if ($ipv4 =~ /^(dhcp|manual)$/) {
1493 my $ipv6 = $net->{ip6
};
1495 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1502 return ($ipv4, $ipv6);
1505 sub delete_mountpoint_volume
{
1506 my ($storage_cfg, $vmid, $volume) = @_;
1508 return if classify_mountpoint
($volume) ne 'volume';
1510 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1511 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1514 sub destroy_lxc_container
{
1515 my ($storage_cfg, $vmid, $conf) = @_;
1517 foreach_mountpoint
($conf, sub {
1518 my ($ms, $mountpoint) = @_;
1519 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1522 rmdir "/var/lib/lxc/$vmid/rootfs";
1523 unlink "/var/lib/lxc/$vmid/config";
1524 rmdir "/var/lib/lxc/$vmid";
1525 destroy_config
($vmid);
1527 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1528 #PVE::Tools::run_command($cmd);
1531 sub vm_stop_cleanup
{
1532 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1537 my $vollist = get_vm_volumes
($conf);
1538 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1541 warn $@ if $@; # avoid errors - just warn
1544 my $safe_num_ne = sub {
1547 return 0 if !defined($a) && !defined($b);
1548 return 1 if !defined($a);
1549 return 1 if !defined($b);
1554 my $safe_string_ne = sub {
1557 return 0 if !defined($a) && !defined($b);
1558 return 1 if !defined($a);
1559 return 1 if !defined($b);
1565 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1567 if ($newnet->{type
} ne 'veth') {
1568 # for when there are physical interfaces
1569 die "cannot update interface of type $newnet->{type}";
1572 my $veth = "veth${vmid}i${netid}";
1573 my $eth = $newnet->{name
};
1575 if (my $oldnetcfg = $conf->{$opt}) {
1576 my $oldnet = parse_lxc_network
($oldnetcfg);
1578 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1579 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1581 PVE
::Network
::veth_delete
($veth);
1582 delete $conf->{$opt};
1583 write_config
($vmid, $conf);
1585 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1587 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1588 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1589 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1591 if ($oldnet->{bridge
}) {
1592 PVE
::Network
::tap_unplug
($veth);
1593 foreach (qw(bridge tag firewall)) {
1594 delete $oldnet->{$_};
1596 $conf->{$opt} = print_lxc_network
($oldnet);
1597 write_config
($vmid, $conf);
1600 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1601 foreach (qw(bridge tag firewall)) {
1602 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1604 $conf->{$opt} = print_lxc_network
($oldnet);
1605 write_config
($vmid, $conf);
1608 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1611 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1615 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1617 my $veth = "veth${vmid}i${netid}";
1618 my $vethpeer = $veth . "p";
1619 my $eth = $newnet->{name
};
1621 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1622 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1624 # attach peer in container
1625 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1626 PVE
::Tools
::run_command
($cmd);
1628 # link up peer in container
1629 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1630 PVE
::Tools
::run_command
($cmd);
1632 my $done = { type
=> 'veth' };
1633 foreach (qw(bridge tag firewall hwaddr name)) {
1634 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1636 $conf->{$opt} = print_lxc_network
($done);
1638 write_config
($vmid, $conf);
1641 sub update_ipconfig
{
1642 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1644 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1646 my $optdata = parse_lxc_network
($conf->{$opt});
1650 my $cmdargs = shift;
1651 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1653 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1655 my $change_ip_config = sub {
1656 my ($ipversion) = @_;
1658 my $family_opt = "-$ipversion";
1659 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1660 my $gw= "gw$suffix";
1661 my $ip= "ip$suffix";
1663 my $newip = $newnet->{$ip};
1664 my $newgw = $newnet->{$gw};
1665 my $oldip = $optdata->{$ip};
1667 my $change_ip = &$safe_string_ne($oldip, $newip);
1668 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1670 return if !$change_ip && !$change_gw;
1672 # step 1: add new IP, if this fails we cancel
1673 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1674 if ($change_ip && $is_real_ip) {
1675 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1682 # step 2: replace gateway
1683 # If this fails we delete the added IP and cancel.
1684 # If it succeeds we save the config and delete the old IP, ignoring
1685 # errors. The config is then saved.
1686 # Note: 'ip route replace' can add
1690 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1691 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1693 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1697 # the route was not replaced, the old IP is still available
1698 # rollback (delete new IP) and cancel
1700 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1701 warn $@ if $@; # no need to die here
1706 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1707 # if the route was not deleted, the guest might have deleted it manually
1713 # from this point on we save the configuration
1714 # step 3: delete old IP ignoring errors
1715 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1716 # We need to enable promote_secondaries, otherwise our newly added
1717 # address will be removed along with the old one.
1720 if ($ipversion == 4) {
1721 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1722 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1723 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1725 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1727 warn $@ if $@; # no need to die here
1729 if ($ipversion == 4) {
1730 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1734 foreach my $property ($ip, $gw) {
1735 if ($newnet->{$property}) {
1736 $optdata->{$property} = $newnet->{$property};
1738 delete $optdata->{$property};
1741 $conf->{$opt} = print_lxc_network
($optdata);
1742 write_config
($vmid, $conf);
1743 $lxc_setup->setup_network($conf);
1746 &$change_ip_config(4);
1747 &$change_ip_config(6);
1751 # Internal snapshots
1753 # NOTE: Snapshot create/delete involves several non-atomic
1754 # actions, and can take a long time.
1755 # So we try to avoid locking the file and use the 'lock' variable
1756 # inside the config file instead.
1758 my $snapshot_copy_config = sub {
1759 my ($source, $dest) = @_;
1761 foreach my $k (keys %$source) {
1762 next if $k eq 'snapshots';
1763 next if $k eq 'snapstate';
1764 next if $k eq 'snaptime';
1765 next if $k eq 'vmstate';
1766 next if $k eq 'lock';
1767 next if $k eq 'digest';
1768 next if $k eq 'description';
1769 next if $k =~ m/^unused\d+$/;
1771 $dest->{$k} = $source->{$k};
1775 my $snapshot_apply_config = sub {
1776 my ($conf, $snap) = @_;
1778 # copy snapshot list
1780 snapshots
=> $conf->{snapshots
},
1783 # keep description and list of unused disks
1784 foreach my $k (keys %$conf) {
1785 next if !($k =~ m/^unused\d+$/ || $k eq 'description');
1786 $newconf->{$k} = $conf->{$k};
1789 &$snapshot_copy_config($snap, $newconf);
1794 my $snapshot_save_vmstate = sub {
1795 die "implement me - snapshot_save_vmstate\n";
1798 sub snapshot_prepare
{
1799 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1803 my $updatefn = sub {
1805 my $conf = load_config
($vmid);
1807 die "you can't take a snapshot if it's a template\n"
1808 if is_template
($conf);
1812 $conf->{lock} = 'snapshot';
1814 die "snapshot name '$snapname' already used\n"
1815 if defined($conf->{snapshots
}->{$snapname});
1817 my $storecfg = PVE
::Storage
::config
();
1819 # workaround until mp snapshots are implemented
1820 my $feature = $snapname eq 'vzdump' ?
'vzdump' : 'snapshot';
1821 die "snapshot feature is not available\n" if !has_feature
($feature, $conf, $storecfg);
1823 $snap = $conf->{snapshots
}->{$snapname} = {};
1825 if ($save_vmstate && check_running
($vmid)) {
1826 &$snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
1829 &$snapshot_copy_config($conf, $snap);
1831 $snap->{snapstate
} = "prepare";
1832 $snap->{snaptime
} = time();
1833 $snap->{description
} = $comment if $comment;
1835 write_config
($vmid, $conf);
1838 lock_config
($vmid, $updatefn);
1843 sub snapshot_commit
{
1844 my ($vmid, $snapname) = @_;
1846 my $updatefn = sub {
1848 my $conf = load_config
($vmid);
1850 die "missing snapshot lock\n"
1851 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1853 my $snap = $conf->{snapshots
}->{$snapname};
1854 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1856 die "wrong snapshot state\n"
1857 if !($snap->{snapstate
} && $snap->{snapstate
} eq "prepare");
1859 delete $snap->{snapstate
};
1860 delete $conf->{lock};
1862 my $newconf = &$snapshot_apply_config($conf, $snap);
1864 $newconf->{parent
} = $snapname;
1866 write_config
($vmid, $newconf);
1869 lock_config
($vmid, $updatefn);
1873 my ($feature, $conf, $storecfg, $snapname) = @_;
1876 my $vzdump = $feature eq 'vzdump';
1877 $feature = 'snapshot' if $vzdump;
1879 foreach_mountpoint
($conf, sub {
1880 my ($ms, $mountpoint) = @_;
1882 return if $err; # skip further test
1883 return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup
};
1885 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1887 # TODO: implement support for mountpoints
1888 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1892 return $err ?
0 : 1;
1895 my $enter_namespace = sub {
1896 my ($vmid, $pid, $which, $type) = @_;
1897 sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
1898 or die "failed to open $which namespace of container $vmid: $!\n";
1899 PVE
::Tools
::setns
(fileno($fd), $type)
1900 or die "failed to enter $which namespace of container $vmid: $!\n";
1904 my $do_syncfs = sub {
1905 my ($vmid, $pid, $socket) = @_;
1907 &$enter_namespace($vmid, $pid, 'mnt', PVE
::Tools
::CLONE_NEWNS
);
1909 # Tell the parent process to start reading our /proc/mounts
1910 print {$socket} "go\n";
1913 # Receive /proc/self/mounts
1914 my $mountdata = do { local $/ = undef; <$socket> };
1917 # Now sync all mountpoints...
1918 my $mounts = PVE
::ProcFSTools
::parse_mounts
($mountdata);
1919 foreach my $mp (@$mounts) {
1920 my ($what, $dir, $fs) = @$mp;
1921 next if $fs eq 'fuse.lxcfs';
1922 eval { PVE
::Tools
::sync_mountpoint
($dir); };
1927 sub sync_container_namespace
{
1929 my $pid = find_lxc_pid
($vmid);
1931 # SOCK_DGRAM is nicer for barriers but cannot be slurped
1932 socketpair my $pfd, my $cfd, AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
1933 or die "failed to create socketpair: $!\n";
1936 die "fork failed: $!\n" if !defined($child);
1941 &$do_syncfs($vmid, $pid, $cfd);
1951 die "failed to enter container namespace\n" if $go ne "go\n";
1953 open my $mounts, '<', "/proc/$child/mounts"
1954 or die "failed to open container's /proc/mounts: $!\n";
1955 my $mountdata = do { local $/ = undef; <$mounts> };
1957 print {$pfd} $mountdata;
1960 while (waitpid($child, 0) != $child) {}
1961 die "failed to sync container namespace\n" if $? != 0;
1964 sub snapshot_create
{
1965 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1967 my $snap = snapshot_prepare
($vmid, $snapname, $save_vmstate, $comment);
1969 my $conf = load_config
($vmid);
1971 my $running = check_running
($vmid);
1980 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1981 sync_container_namespace
($vmid);
1984 my $storecfg = PVE
::Storage
::config
();
1985 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1986 my $volid = $rootinfo->{volume
};
1988 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1989 $drivehash->{rootfs
} = 1;
1994 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1999 eval { snapshot_delete
($vmid, $snapname, 1, $drivehash); };
2004 snapshot_commit
($vmid, $snapname);
2007 # Note: $drivehash is only set when called from snapshot_create.
2008 sub snapshot_delete
{
2009 my ($vmid, $snapname, $force, $drivehash) = @_;
2015 my $unlink_parent = sub {
2016 my ($confref, $new_parent) = @_;
2018 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
2020 $confref->{parent
} = $new_parent;
2022 delete $confref->{parent
};
2027 my $updatefn = sub {
2028 my ($remove_drive) = @_;
2030 my $conf = load_config
($vmid);
2034 die "you can't delete a snapshot if vm is a template\n"
2035 if is_template
($conf);
2038 $snap = $conf->{snapshots
}->{$snapname};
2040 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2042 # remove parent refs
2044 &$unlink_parent($conf, $snap->{parent
});
2045 foreach my $sn (keys %{$conf->{snapshots
}}) {
2046 next if $sn eq $snapname;
2047 &$unlink_parent($conf->{snapshots
}->{$sn}, $snap->{parent
});
2051 if ($remove_drive) {
2052 if ($remove_drive eq 'vmstate') {
2053 die "implement me - saving vmstate\n";
2055 die "implement me - remove drive\n";
2060 $snap->{snapstate
} = 'delete';
2062 delete $conf->{snapshots
}->{$snapname};
2063 delete $conf->{lock} if $drivehash;
2066 write_config
($vmid, $conf);
2069 lock_config
($vmid, $updatefn);
2071 # now remove vmstate file
2072 # never set for LXC!
2073 my $storecfg = PVE
::Storage
::config
();
2075 if ($snap->{vmstate
}) {
2076 die "implement me - saving vmstate\n";
2079 # now remove all volume snapshots
2080 # only rootfs for now!
2082 my $rootfs = $snap->{rootfs
};
2083 my $rootinfo = parse_ct_rootfs
($rootfs);
2084 my $volid = $rootinfo->{volume
};
2085 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
2088 die $err if !$force;
2092 # now cleanup config
2094 lock_config
($vmid, $updatefn);
2097 sub snapshot_rollback
{
2098 my ($vmid, $snapname) = @_;
2102 my $storecfg = PVE
::Storage
::config
();
2104 my $conf = load_config
($vmid);
2106 my $get_snapshot_config = sub {
2108 die "you can't rollback if vm is a template\n" if is_template
($conf);
2110 my $res = $conf->{snapshots
}->{$snapname};
2112 die "snapshot '$snapname' does not exist\n" if !defined($res);
2117 my $snap = &$get_snapshot_config();
2119 # only for rootfs for now!
2120 my $rootfs = $snap->{rootfs
};
2121 my $rootinfo = parse_ct_rootfs
($rootfs);
2122 my $volid = $rootinfo->{volume
};
2124 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
2126 my $updatefn = sub {
2128 $conf = load_config
($vmid);
2130 $snap = &$get_snapshot_config();
2132 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
2133 if $snap->{snapstate
};
2137 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
2138 if check_running
($vmid);
2141 die "unable to rollback vm $vmid: vm is running\n"
2142 if check_running
($vmid);
2145 $conf->{lock} = 'rollback';
2147 die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
2148 delete $conf->{lock};
2154 # copy snapshot config to current config
2155 $conf = &$snapshot_apply_config($conf, $snap);
2156 $conf->{parent
} = $snapname;
2159 write_config
($vmid, $conf);
2161 if (!$prepare && $snap->{vmstate
}) {
2162 die "implement me - save vmstate\n";
2166 lock_config
($vmid, $updatefn);
2168 # only rootfs for now!
2169 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
2172 lock_config
($vmid, $updatefn);
2175 sub template_create
{
2176 my ($vmid, $conf) = @_;
2178 my $storecfg = PVE
::Storage
::config
();
2180 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
2181 my $volid = $rootinfo->{volume
};
2183 die "Template feature is not available for '$volid'\n"
2184 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
2186 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2188 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
2189 $rootinfo->{volume
} = $template_volid;
2190 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
2192 write_config
($vmid, $conf);
2198 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2201 sub mountpoint_names
{
2204 my @names = ('rootfs');
2206 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2207 push @names, "mp$i";
2210 return $reverse ?
reverse @names : @names;
2214 sub foreach_mountpoint_full
{
2215 my ($conf, $reverse, $func) = @_;
2217 foreach my $key (mountpoint_names
($reverse)) {
2218 my $value = $conf->{$key};
2219 next if !defined($value);
2220 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2221 next if !defined($mountpoint);
2223 &$func($key, $mountpoint);
2227 sub foreach_mountpoint
{
2228 my ($conf, $func) = @_;
2230 foreach_mountpoint_full
($conf, 0, $func);
2233 sub foreach_mountpoint_reverse
{
2234 my ($conf, $func) = @_;
2236 foreach_mountpoint_full
($conf, 1, $func);
2239 sub check_ct_modify_config_perm
{
2240 my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_;
2242 return 1 if $authuser eq 'root@pam';
2245 my ($opt, $delete) = @_;
2246 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2247 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2248 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2249 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2251 my $data = $opt eq 'rootfs' ? parse_ct_rootfs
($newconf->{$opt})
2252 : parse_ct_mountpoint
($newconf->{$opt});
2253 raise_perm_exc
("mountpoint type $data->{type}") if $data->{type
} ne 'volume';
2254 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2255 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2256 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2257 $opt eq 'searchdomain' || $opt eq 'hostname') {
2258 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2260 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2264 foreach my $opt (keys %$newconf) {
2267 foreach my $opt (@$delete) {
2275 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2277 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2278 my $volid_list = get_vm_volumes
($conf);
2280 foreach_mountpoint_reverse
($conf, sub {
2281 my ($ms, $mountpoint) = @_;
2283 my $volid = $mountpoint->{volume
};
2284 my $mount = $mountpoint->{mp
};
2286 return if !$volid || !$mount;
2288 my $mount_path = "$rootdir/$mount";
2289 $mount_path =~ s!/+!/!g;
2291 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2294 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2307 my ($vmid, $storage_cfg, $conf) = @_;
2309 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2310 File
::Path
::make_path
($rootdir);
2312 my $volid_list = get_vm_volumes
($conf);
2313 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2316 foreach_mountpoint
($conf, sub {
2317 my ($ms, $mountpoint) = @_;
2319 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2323 warn "mounting container failed\n";
2324 umount_all
($vmid, $storage_cfg, $conf, 1);
2332 sub mountpoint_mount_path
{
2333 my ($mountpoint, $storage_cfg, $snapname) = @_;
2335 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2338 my $check_mount_path = sub {
2340 $path = File
::Spec-
>canonpath($path);
2341 my $real = Cwd
::realpath
($path);
2342 if ($real ne $path) {
2343 die "mount path modified by symlink: $path != $real";
2352 if ($line =~ m
@^(/dev/loop\d
+):@) {
2356 my $cmd = ['losetup', '--associated', $path];
2357 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2361 # Run a function with a file attached to a loop device.
2362 # The loop device is always detached afterwards (or set to autoclear).
2363 # Returns the loop device.
2364 sub run_with_loopdev
{
2365 my ($func, $file) = @_;
2369 if ($line =~ m
@^(/dev/loop\d
+)$@) {
2373 PVE
::Tools
::run_command
(['losetup', '--show', '-f', $file], outfunc
=> $parser);
2374 die "failed to setup loop device for $file\n" if !$device;
2375 eval { &$func($device); };
2377 PVE
::Tools
::run_command
(['losetup', '-d', $device]);
2383 my ($dir, $dest, $ro, @extra_opts) = @_;
2384 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
2386 eval { PVE
::Tools
::run_command
(['mount', '-o', 'bind,remount,ro', $dest]); };
2388 warn "bindmount error\n";
2389 # don't leave writable bind-mounts behind...
2390 PVE
::Tools
::run_command
(['umount', $dest]);
2396 # use $rootdir = undef to just return the corresponding mount path
2397 sub mountpoint_mount
{
2398 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2400 my $volid = $mountpoint->{volume
};
2401 my $mount = $mountpoint->{mp
};
2402 my $type = $mountpoint->{type
};
2403 my $quota = !$snapname && !$mountpoint->{ro
} && $mountpoint->{quota
};
2406 return if !$volid || !$mount;
2410 if (defined($rootdir)) {
2411 $rootdir =~ s!/+$!!;
2412 $mount_path = "$rootdir/$mount";
2413 $mount_path =~ s!/+!/!g;
2414 &$check_mount_path($mount_path);
2415 File
::Path
::mkpath
($mount_path);
2418 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2420 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2423 if (defined($mountpoint->{acl
})) {
2424 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2426 my $readonly = $mountpoint->{ro
};
2428 my @extra_opts = ('-o', $optstring);
2432 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2433 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2435 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2436 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2438 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2440 if ($format eq 'subvol') {
2443 if ($scfg->{type
} eq 'zfspool') {
2444 my $path_arg = $path;
2445 $path_arg =~ s!^/+!!;
2446 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2448 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2451 bindmount
($path, $mount_path, $readonly, @extra_opts);
2452 warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
2455 return wantarray ?
($path, 0, $mounted_dev) : $path;
2456 } elsif ($format eq 'raw' || $format eq 'iso') {
2460 if ($format eq 'iso') {
2461 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2462 } elsif ($isBase || defined($snapname)) {
2463 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2466 push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
2468 push @extra_opts, '-o', 'ro' if $readonly;
2469 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2473 my $use_loopdev = 0;
2474 if ($scfg->{path
}) {
2475 $mounted_dev = run_with_loopdev
($domount, $path);
2477 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2478 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2479 $mounted_dev = $path;
2482 die "unsupported storage type '$scfg->{type}'\n";
2484 return wantarray ?
($path, $use_loopdev, $mounted_dev) : $path;
2486 die "unsupported image format '$format'\n";
2488 } elsif ($type eq 'device') {
2489 push @extra_opts, '-o', 'ro' if $readonly;
2490 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2491 return wantarray ?
($volid, 0, $volid) : $volid;
2492 } elsif ($type eq 'bind') {
2493 die "directory '$volid' does not exist\n" if ! -d
$volid;
2494 &$check_mount_path($volid);
2495 bindmount
($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
2496 warn "cannot enable quota control for bind mounts\n" if $quota;
2497 return wantarray ?
($volid, 0, undef) : $volid;
2500 die "unsupported storage";
2503 sub get_vm_volumes
{
2504 my ($conf, $excludes) = @_;
2508 foreach_mountpoint
($conf, sub {
2509 my ($ms, $mountpoint) = @_;
2511 return if $excludes && $ms eq $excludes;
2513 my $volid = $mountpoint->{volume
};
2515 return if !$volid || $mountpoint->{type
} ne 'volume';
2517 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2520 push @$vollist, $volid;
2527 my ($dev, $rootuid, $rootgid) = @_;
2529 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2530 '-E', "root_owner=$rootuid:$rootgid",
2535 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2537 if ($volid =~ m!^/dev/.+!) {
2542 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2544 die "cannot format volume '$volid' with no storage\n" if !$storage;
2546 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2548 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2550 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2551 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2553 die "cannot format volume '$volid' (format == $format)\n"
2554 if $format ne 'raw';
2556 mkfs
($path, $rootuid, $rootgid);
2560 my ($storecfg, $vollist) = @_;
2562 foreach my $volid (@$vollist) {
2563 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2569 my ($storecfg, $vmid, $settings, $conf) = @_;
2574 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2575 my $chown_vollist = [];
2577 foreach_mountpoint
($settings, sub {
2578 my ($ms, $mountpoint) = @_;
2580 my $volid = $mountpoint->{volume
};
2581 my $mp = $mountpoint->{mp
};
2583 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2585 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2586 my ($storeid, $size_gb) = ($1, $2);
2588 my $size_kb = int(${size_gb
}*1024) * 1024;
2590 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2591 # fixme: use better naming ct-$vmid-disk-X.raw?
2593 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2595 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2597 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2599 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2601 push @$chown_vollist, $volid;
2603 } elsif ($scfg->{type
} eq 'zfspool') {
2605 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2607 push @$chown_vollist, $volid;
2608 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2610 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2611 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2613 } elsif ($scfg->{type
} eq 'rbd') {
2615 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2616 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2617 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2619 die "unable to create containers on storage type '$scfg->{type}'\n";
2621 push @$vollist, $volid;
2622 $mountpoint->{volume
} = $volid;
2623 $mountpoint->{size
} = $size_kb * 1024;
2624 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2626 # use specified/existing volid/dir/device
2627 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2631 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2632 foreach my $volid (@$chown_vollist) {
2633 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2634 chown($rootuid, $rootgid, $path);
2636 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2638 # free allocated images on error
2640 destroy_disks
($storecfg, $vollist);
2646 # bash completion helper
2648 sub complete_os_templates
{
2649 my ($cmdname, $pname, $cvalue) = @_;
2651 my $cfg = PVE
::Storage
::config
();
2655 if ($cvalue =~ m/^([^:]+):/) {
2659 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2660 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2663 foreach my $id (keys %$data) {
2664 foreach my $item (@{$data->{$id}}) {
2665 push @$res, $item->{volid
} if defined($item->{volid
});
2672 my $complete_ctid_full = sub {
2675 my $idlist = vmstatus
();
2677 my $active_hash = list_active_containers
();
2681 foreach my $id (keys %$idlist) {
2682 my $d = $idlist->{$id};
2683 if (defined($running)) {
2684 next if $d->{template
};
2685 next if $running && !$active_hash->{$id};
2686 next if !$running && $active_hash->{$id};
2695 return &$complete_ctid_full();
2698 sub complete_ctid_stopped
{
2699 return &$complete_ctid_full(0);
2702 sub complete_ctid_running
{
2703 return &$complete_ctid_full(1);
2713 my $lxc = $conf->{lxc
};
2714 foreach my $entry (@$lxc) {
2715 my ($key, $value) = @$entry;
2716 next if $key ne 'lxc.id_map';
2717 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2718 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2719 push @$id_map, [$type, $ct, $host, $length];
2721 $rootuid = $host if $type eq 'u';
2722 $rootgid = $host if $type eq 'g';
2725 die "failed to parse id_map: $value\n";
2729 if (!@$id_map && $conf->{unprivileged
}) {
2730 # Should we read them from /etc/subuid?
2731 $id_map = [ ['u', '0', '100000', '65536'],
2732 ['g', '0', '100000', '65536'] ];
2733 $rootuid = $rootgid = 100000;
2736 return ($id_map, $rootuid, $rootgid);
2739 sub userns_command
{
2742 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];