10 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
14 use PVE
::JSONSchema
qw(get_standard_option);
15 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
17 use PVE
::AccessControl
;
21 my $nodename = PVE
::INotify
::nodename
();
23 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
25 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
26 sub verify_lxc_network
{
27 my ($value, $noerr) = @_;
29 return $value if parse_lxc_network
($value);
31 return undef if $noerr;
33 die "unable to parse network setting\n";
36 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
37 sub verify_ct_mountpoint
{
38 my ($value, $noerr) = @_;
40 return $value if parse_ct_mountpoint
($value);
42 return undef if $noerr;
44 die "unable to parse CT mountpoint options\n";
47 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
48 type
=> 'string', format
=> 'pve-ct-mountpoint',
49 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
50 description
=> "Use volume as container root.",
54 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
55 description
=> "The name of the snapshot.",
56 type
=> 'string', format
=> 'pve-configid',
64 description
=> "Lock/unlock the VM.",
65 enum
=> [qw(migrate backup snapshot rollback)],
70 description
=> "Specifies whether a VM will be started during system bootup.",
73 startup
=> get_standard_option
('pve-startup-order'),
77 description
=> "Enable/disable Template.",
83 enum
=> ['amd64', 'i386'],
84 description
=> "OS architecture type.",
90 enum
=> ['debian', 'ubuntu', 'centos'],
91 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
96 description
=> "Attach a console device (/dev/console) to the container.",
102 description
=> "Specify the number of tty available to the container",
110 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
118 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 weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
126 description
=> "Amount of RAM for the VM in MB.",
133 description
=> "Amount of SWAP for the VM in MB.",
139 description
=> "Set a host name for the container.",
146 description
=> "Container description. Only used on the configuration web interface.",
151 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
156 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
158 rootfs
=> get_standard_option
('pve-ct-rootfs'),
161 type
=> 'string', format
=> 'pve-configid',
163 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
167 description
=> "Timestamp for snapshots.",
173 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).",
175 enum
=> ['shell', 'console', 'tty'],
180 my $valid_lxc_conf_keys = {
184 'lxc.haltsignal' => 1,
185 'lxc.rebootsignal' => 1,
186 'lxc.stopsignal' => 1,
188 'lxc.network.type' => 1,
189 'lxc.network.flags' => 1,
190 'lxc.network.link' => 1,
191 'lxc.network.mtu' => 1,
192 'lxc.network.name' => 1,
193 'lxc.network.hwaddr' => 1,
194 'lxc.network.ipv4' => 1,
195 'lxc.network.ipv4.gateway' => 1,
196 'lxc.network.ipv6' => 1,
197 'lxc.network.ipv6.gateway' => 1,
198 'lxc.network.script.up' => 1,
199 'lxc.network.script.down' => 1,
201 'lxc.console.logfile' => 1,
204 'lxc.devttydir' => 1,
205 'lxc.hook.autodev' => 1,
209 'lxc.mount.entry' => 1,
210 'lxc.mount.auto' => 1,
212 'lxc.rootfs.mount' => 1,
213 'lxc.rootfs.options' => 1,
217 'lxc.aa_profile' => 1,
218 'lxc.aa_allow_incomplete' => 1,
219 'lxc.se_context' => 1,
222 'lxc.hook.pre-start' => 1,
223 'lxc.hook.pre-mount' => 1,
224 'lxc.hook.mount' => 1,
225 'lxc.hook.start' => 1,
226 'lxc.hook.post-stop' => 1,
227 'lxc.hook.clone' => 1,
228 'lxc.hook.destroy' => 1,
231 'lxc.start.auto' => 1,
232 'lxc.start.delay' => 1,
233 'lxc.start.order' => 1,
235 'lxc.environment' => 1,
242 my $MAX_LXC_NETWORKS = 10;
243 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
244 $confdesc->{"net$i"} = {
246 type
=> 'string', format
=> 'pve-lxc-network',
247 description
=> "Specifies network interfaces for the container.\n\n".
248 "The string should have the follow format:\n\n".
249 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
250 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
251 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
252 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
256 my $MAX_MOUNT_POINTS = 10;
257 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
258 $confdesc->{"mp$i"} = {
260 type
=> 'string', format
=> 'pve-ct-mountpoint',
261 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
262 description
=> "Use volume as container mount point (experimental feature).",
267 sub write_pct_config
{
268 my ($filename, $conf) = @_;
270 delete $conf->{snapstate
}; # just to be sure
272 my $generate_raw_config = sub {
277 # add description as comment to top of file
278 my $descr = $conf->{description
} || '';
279 foreach my $cl (split(/\n/, $descr)) {
280 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
283 foreach my $key (sort keys %$conf) {
284 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
285 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
286 $raw .= "$key: $conf->{$key}\n";
289 if (my $lxcconf = $conf->{lxc
}) {
290 foreach my $entry (@$lxcconf) {
291 my ($k, $v) = @$entry;
299 my $raw = &$generate_raw_config($conf);
301 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
302 $raw .= "\n[$snapname]\n";
303 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
310 my ($key, $value) = @_;
312 die "unknown setting '$key'\n" if !$confdesc->{$key};
314 my $type = $confdesc->{$key}->{type
};
316 if (!defined($value)) {
317 die "got undefined value\n";
320 if ($value =~ m/[\n\r]/) {
321 die "property contains a line feed\n";
324 if ($type eq 'boolean') {
325 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
326 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
327 die "type check ('boolean') failed - got '$value'\n";
328 } elsif ($type eq 'integer') {
329 return int($1) if $value =~ m/^(\d+)$/;
330 die "type check ('integer') failed - got '$value'\n";
331 } elsif ($type eq 'number') {
332 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
333 die "type check ('number') failed - got '$value'\n";
334 } elsif ($type eq 'string') {
335 if (my $fmt = $confdesc->{$key}->{format
}) {
336 PVE
::JSONSchema
::check_format
($fmt, $value);
345 sub parse_pct_config
{
346 my ($filename, $raw) = @_;
348 return undef if !defined($raw);
351 digest
=> Digest
::SHA
::sha1_hex
($raw),
355 $filename =~ m
|/lxc/(\d
+).conf
$|
356 || die "got strange filename '$filename'";
364 my @lines = split(/\n/, $raw);
365 foreach my $line (@lines) {
366 next if $line =~ m/^\s*$/;
368 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
370 $conf->{description
} = $descr if $descr;
372 $conf = $res->{snapshots
}->{$section} = {};
376 if ($line =~ m/^\#(.*)\s*$/) {
377 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
381 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
384 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
385 push @{$conf->{lxc
}}, [$key, $value];
387 warn "vm $vmid - unable to parse config: $line\n";
389 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
390 $descr .= PVE
::Tools
::decode_text
($2);
391 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
392 $conf->{snapstate
} = $1;
393 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
396 eval { $value = check_type
($key, $value); };
397 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
398 $conf->{$key} = $value;
400 warn "vm $vmid - unable to parse config: $line\n";
404 $conf->{description
} = $descr if $descr;
406 delete $res->{snapstate
}; # just to be sure
412 my $vmlist = PVE
::Cluster
::get_vmlist
();
414 return $res if !$vmlist || !$vmlist->{ids
};
415 my $ids = $vmlist->{ids
};
417 foreach my $vmid (keys %$ids) {
418 next if !$vmid; # skip CT0
419 my $d = $ids->{$vmid};
420 next if !$d->{node
} || $d->{node
} ne $nodename;
421 next if !$d->{type
} || $d->{type
} ne 'lxc';
422 $res->{$vmid}->{type
} = 'lxc';
427 sub cfs_config_path
{
428 my ($vmid, $node) = @_;
430 $node = $nodename if !$node;
431 return "nodes/$node/lxc/$vmid.conf";
435 my ($vmid, $node) = @_;
437 my $cfspath = cfs_config_path
($vmid, $node);
438 return "/etc/pve/$cfspath";
442 my ($vmid, $node) = @_;
444 $node = $nodename if !$node;
445 my $cfspath = cfs_config_path
($vmid, $node);
447 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
448 die "container $vmid does not exists\n" if !defined($conf);
454 my ($vmid, $conf) = @_;
456 my $dir = "/etc/pve/nodes/$nodename/lxc";
459 write_config
($vmid, $conf);
465 unlink config_file
($vmid, $nodename);
469 my ($vmid, $conf) = @_;
471 my $cfspath = cfs_config_path
($vmid);
473 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
476 # flock: we use one file handle per process, so lock file
477 # can be called multiple times and succeeds for the same process.
479 my $lock_handles = {};
480 my $lockdir = "/run/lock/lxc";
485 return "$lockdir/pve-config-{$vmid}.lock";
489 my ($vmid, $timeout) = @_;
491 $timeout = 10 if !$timeout;
494 my $filename = lock_filename
($vmid);
496 mkdir $lockdir if !-d
$lockdir;
498 my $lock_func = sub {
499 if (!$lock_handles->{$$}->{$filename}) {
500 my $fh = new IO
::File
(">>$filename") ||
501 die "can't open file - $!\n";
502 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
505 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
506 print STDERR
"trying to aquire lock...";
509 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
510 # try again on EINTR (see bug #273)
511 if ($success || ($! != EINTR
)) {
516 print STDERR
" failed\n";
517 die "can't aquire lock - $!\n";
520 $lock_handles->{$$}->{$filename}->{refcount
}++;
522 print STDERR
" OK\n";
526 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
529 die "can't lock file '$filename' - $err";
536 my $filename = lock_filename
($vmid);
538 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
539 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
540 if ($refcount <= 0) {
541 $lock_handles->{$$}->{$filename} = undef;
548 my ($vmid, $timeout, $code, @param) = @_;
552 lock_aquire
($vmid, $timeout);
553 eval { $res = &$code(@param) };
565 return defined($confdesc->{$name});
568 # add JSON properties for create and set function
569 sub json_config_properties
{
572 foreach my $opt (keys %$confdesc) {
573 next if $opt eq 'parent' || $opt eq 'snaptime';
574 next if $prop->{$opt};
575 $prop->{$opt} = $confdesc->{$opt};
581 sub json_config_properties_no_rootfs
{
584 foreach my $opt (keys %$confdesc) {
585 next if $prop->{$opt};
586 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
587 $prop->{$opt} = $confdesc->{$opt};
593 # container status helpers
595 sub list_active_containers
{
597 my $filename = "/proc/net/unix";
599 # similar test is used by lcxcontainers.c: list_active_containers
602 my $fh = IO
::File-
>new ($filename, "r");
605 while (defined(my $line = <$fh>)) {
606 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
608 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
619 # warning: this is slow
623 my $active_hash = list_active_containers
();
625 return 1 if defined($active_hash->{$vmid});
630 sub get_container_disk_usage
{
633 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
643 if (my ($fsid, $total, $used, $avail) = $line =~
644 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
652 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
661 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
663 my $active_hash = list_active_containers
();
665 foreach my $vmid (keys %$list) {
666 my $d = $list->{$vmid};
668 my $running = defined($active_hash->{$vmid});
670 $d->{status
} = $running ?
'running' : 'stopped';
672 my $cfspath = cfs_config_path
($vmid);
673 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
675 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
676 $d->{name
} =~ s/[\s]//g;
678 $d->{cpus
} = $conf->{cpulimit
} // 0;
681 my $res = get_container_disk_usage
($vmid);
682 $d->{disk
} = $res->{used
};
683 $d->{maxdisk
} = $res->{total
};
686 # use 4GB by default ??
687 if (my $rootfs = $conf->{rootfs
}) {
688 my $rootinfo = parse_ct_mountpoint
($rootfs);
689 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
691 $d->{maxdisk
} = 4*1024*1024*1024;
697 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
698 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
709 $d->{template
} = is_template
($conf);
712 foreach my $vmid (keys %$list) {
713 my $d = $list->{$vmid};
714 next if $d->{status
} ne 'running';
716 $d->{uptime
} = 100; # fixme:
718 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
719 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
721 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
722 my @bytes = split(/\n/, $blkio_bytes);
723 foreach my $byte (@bytes) {
724 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
725 $d->{diskread
} = $2 if $key eq 'Read';
726 $d->{diskwrite
} = $2 if $key eq 'Write';
734 my $parse_size = sub {
737 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
738 my ($size, $unit) = ($1, $3);
741 $size = $size * 1024;
742 } elsif ($unit eq 'M') {
743 $size = $size * 1024 * 1024;
744 } elsif ($unit eq 'G') {
745 $size = $size * 1024 * 1024 * 1024;
751 sub parse_ct_mountpoint
{
758 foreach my $p (split (/,/, $data)) {
759 next if $p =~ m/^\s*$/;
761 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
762 my ($k, $v) = ($1, $2);
763 return undef if defined($res->{$k});
766 if (!$res->{volume
} && $p !~ m/=/) {
774 return undef if !$res->{volume
};
776 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
779 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
785 sub print_ct_mountpoint
{
790 die "missing volume\n" if !$info->{volume
};
792 foreach my $o ('size', 'backup') {
793 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
796 return "$info->{volume}$opts";
799 sub print_lxc_network
{
802 die "no network name defined\n" if !$net->{name
};
804 my $res = "name=$net->{name}";
806 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
807 next if !defined($net->{$k});
808 $res .= ",$k=$net->{$k}";
814 sub parse_lxc_network
{
819 return $res if !$data;
821 foreach my $pv (split (/,/, $data)) {
822 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
829 $res->{type
} = 'veth';
830 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
835 sub read_cgroup_value
{
836 my ($group, $vmid, $name, $full) = @_;
838 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
840 return PVE
::Tools
::file_get_contents
($path) if $full;
842 return PVE
::Tools
::file_read_firstline
($path);
845 sub write_cgroup_value
{
846 my ($group, $vmid, $name, $value) = @_;
848 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
849 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
853 sub find_lxc_console_pids
{
857 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
860 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
863 my @args = split(/\0/, $cmdline);
865 # serach for lxc-console -n <vmid>
866 return if scalar(@args) != 3;
867 return if $args[1] ne '-n';
868 return if $args[2] !~ m/^\d+$/;
869 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
873 push @{$res->{$vmid}}, $pid;
885 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
887 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
889 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
894 my $ipv4_reverse_mask = [
930 # Note: we cannot use Net:IP, because that only allows strict
932 sub parse_ipv4_cidr
{
933 my ($cidr, $noerr) = @_;
935 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
936 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
939 return undef if $noerr;
941 die "unable to parse ipv4 address/mask\n";
947 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
950 sub update_lxc_config
{
951 my ($storage_cfg, $vmid, $conf) = @_;
953 my $dir = "/var/lib/lxc/$vmid";
955 if ($conf->{template
}) {
957 unlink "$dir/config";
964 die "missing 'arch' - internal error" if !$conf->{arch
};
965 $raw .= "lxc.arch = $conf->{arch}\n";
967 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
968 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
969 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
974 if (!has_dev_console
($conf)) {
975 $raw .= "lxc.console = none\n";
976 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
979 my $ttycount = get_tty_count
($conf);
980 $raw .= "lxc.tty = $ttycount\n";
982 my $utsname = $conf->{hostname
} || "CT$vmid";
983 $raw .= "lxc.utsname = $utsname\n";
985 my $memory = $conf->{memory
} || 512;
986 my $swap = $conf->{swap
} // 0;
988 my $lxcmem = int($memory*1024*1024);
989 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
991 my $lxcswap = int(($memory + $swap)*1024*1024);
992 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
994 if (my $cpulimit = $conf->{cpulimit
}) {
995 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
996 my $value = int(100000*$cpulimit);
997 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1000 my $shares = $conf->{cpuunits
} || 1024;
1001 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1003 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1004 my $volid = $mountpoint->{volume
};
1005 my $path = volid_path
($volid, $storage_cfg);
1006 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1009 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1010 $path = "loop:$path" if $scfg->{path
};
1013 $raw .= "lxc.rootfs = $path\n";
1016 foreach my $k (keys %$conf) {
1017 next if $k !~ m/^net(\d+)$/;
1019 my $d = parse_lxc_network
($conf->{$k});
1021 $raw .= "lxc.network.type = veth\n";
1022 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1023 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1024 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1025 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1028 if (my $lxcconf = $conf->{lxc
}) {
1029 foreach my $entry (@$lxcconf) {
1030 my ($k, $v) = @$entry;
1031 $netcount++ if $k eq 'lxc.network.type';
1032 $raw .= "$k = $v\n";
1036 $raw .= "lxc.network.type = empty\n" if !$netcount;
1038 File
::Path
::mkpath
("$dir/rootfs");
1040 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1043 # verify and cleanup nameserver list (replace \0 with ' ')
1044 sub verify_nameserver_list
{
1045 my ($nameserver_list) = @_;
1048 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1049 PVE
::JSONSchema
::pve_verify_ip
($server);
1050 push @list, $server;
1053 return join(' ', @list);
1056 sub verify_searchdomain_list
{
1057 my ($searchdomain_list) = @_;
1060 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1061 # todo: should we add checks for valid dns domains?
1062 push @list, $server;
1065 return join(' ', @list);
1068 sub update_pct_config
{
1069 my ($vmid, $conf, $running, $param, $delete) = @_;
1075 my $pid = find_lxc_pid
($vmid);
1076 $rootdir = "/proc/$pid/root";
1079 if (defined($delete)) {
1080 foreach my $opt (@$delete) {
1081 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1082 die "unable to delete required option '$opt'\n";
1083 } elsif ($opt eq 'swap') {
1084 delete $conf->{$opt};
1085 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1086 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1087 delete $conf->{$opt};
1088 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1089 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1090 delete $conf->{$opt};
1091 push @nohotplug, $opt;
1093 } elsif ($opt =~ m/^net(\d)$/) {
1094 delete $conf->{$opt};
1097 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1098 } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
1103 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1107 # There's no separate swap size to configure, there's memory and "total"
1108 # memory (iow. memory+swap). This means we have to change them together.
1109 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1110 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1111 if (defined($wanted_memory) || defined($wanted_swap)) {
1113 $wanted_memory //= ($conf->{memory
} || 512);
1114 $wanted_swap //= ($conf->{swap
} || 0);
1116 my $total = $wanted_memory + $wanted_swap;
1118 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1119 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1121 $conf->{memory
} = $wanted_memory;
1122 $conf->{swap
} = $wanted_swap;
1124 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1127 foreach my $opt (keys %$param) {
1128 my $value = $param->{$opt};
1129 if ($opt eq 'hostname') {
1130 $conf->{$opt} = $value;
1131 } elsif ($opt eq 'onboot') {
1132 $conf->{$opt} = $value ?
1 : 0;
1133 } elsif ($opt eq 'startup') {
1134 $conf->{$opt} = $value;
1135 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1136 $conf->{$opt} = $value;
1137 push @nohotplug, $opt;
1139 } elsif ($opt eq 'nameserver') {
1140 my $list = verify_nameserver_list
($value);
1141 $conf->{$opt} = $list;
1142 push @nohotplug, $opt;
1144 } elsif ($opt eq 'searchdomain') {
1145 my $list = verify_searchdomain_list
($value);
1146 $conf->{$opt} = $list;
1147 push @nohotplug, $opt;
1149 } elsif ($opt eq 'cpulimit') {
1150 $conf->{$opt} = $value;
1151 push @nohotplug, $opt; # fixme: hotplug
1153 } elsif ($opt eq 'cpuunits') {
1154 $conf->{$opt} = $value;
1155 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1156 } elsif ($opt eq 'description') {
1157 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1158 } elsif ($opt =~ m/^net(\d+)$/) {
1160 my $net = parse_lxc_network
($value);
1162 $conf->{$opt} = print_lxc_network
($net);
1164 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1166 } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
1167 die "implement me: $opt";
1169 die "implement me: $opt";
1171 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1174 if ($running && scalar(@nohotplug)) {
1175 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1179 sub has_dev_console
{
1182 return !(defined($conf->{console
}) && !$conf->{console
});
1188 return $conf->{tty
} // $confdesc->{tty
}->{default};
1194 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1197 sub get_console_command
{
1198 my ($vmid, $conf) = @_;
1200 my $cmode = get_cmode
($conf);
1202 if ($cmode eq 'console') {
1203 return ['lxc-console', '-n', $vmid, '-t', 0];
1204 } elsif ($cmode eq 'tty') {
1205 return ['lxc-console', '-n', $vmid];
1206 } elsif ($cmode eq 'shell') {
1207 return ['lxc-attach', '--clear-env', '-n', $vmid];
1209 die "internal error";
1213 sub get_primary_ips
{
1216 # return data from net0
1218 return undef if !defined($conf->{net0
});
1219 my $net = parse_lxc_network
($conf->{net0
});
1221 my $ipv4 = $net->{ip
};
1223 if ($ipv4 =~ /^(dhcp|manual)$/) {
1229 my $ipv6 = $net->{ip6
};
1231 if ($ipv6 =~ /^(dhcp|manual)$/) {
1238 return ($ipv4, $ipv6);
1242 sub destroy_lxc_container
{
1243 my ($storage_cfg, $vmid, $conf) = @_;
1245 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1246 if (defined($rootinfo->{volume
})) {
1247 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1248 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1250 rmdir "/var/lib/lxc/$vmid/rootfs";
1251 unlink "/var/lib/lxc/$vmid/config";
1252 rmdir "/var/lib/lxc/$vmid";
1253 destroy_config
($vmid);
1255 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1256 #PVE::Tools::run_command($cmd);
1259 sub vm_stop_cleanup
{
1260 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1265 my $vollist = get_vm_volumes
($conf);
1266 my $loopdevlist = get_vm_volumes
($conf, 'rootfs');
1268 PVE
::LXC
::dettach_loops
($storage_cfg, $loopdevlist);
1269 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1272 warn $@ if $@; # avoid errors - just warn
1275 my $safe_num_ne = sub {
1278 return 0 if !defined($a) && !defined($b);
1279 return 1 if !defined($a);
1280 return 1 if !defined($b);
1285 my $safe_string_ne = sub {
1288 return 0 if !defined($a) && !defined($b);
1289 return 1 if !defined($a);
1290 return 1 if !defined($b);
1296 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1298 if ($newnet->{type
} ne 'veth') {
1299 # for when there are physical interfaces
1300 die "cannot update interface of type $newnet->{type}";
1303 my $veth = "veth${vmid}i${netid}";
1304 my $eth = $newnet->{name
};
1306 if (my $oldnetcfg = $conf->{$opt}) {
1307 my $oldnet = parse_lxc_network
($oldnetcfg);
1309 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1310 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1312 PVE
::Network
::veth_delete
($veth);
1313 delete $conf->{$opt};
1314 PVE
::LXC
::write_config
($vmid, $conf);
1316 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1318 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1319 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1320 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1322 if ($oldnet->{bridge
}) {
1323 PVE
::Network
::tap_unplug
($veth);
1324 foreach (qw(bridge tag firewall)) {
1325 delete $oldnet->{$_};
1327 $conf->{$opt} = print_lxc_network
($oldnet);
1328 PVE
::LXC
::write_config
($vmid, $conf);
1331 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1332 foreach (qw(bridge tag firewall)) {
1333 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1335 $conf->{$opt} = print_lxc_network
($oldnet);
1336 PVE
::LXC
::write_config
($vmid, $conf);
1339 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1342 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1346 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1348 my $veth = "veth${vmid}i${netid}";
1349 my $vethpeer = $veth . "p";
1350 my $eth = $newnet->{name
};
1352 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1353 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1355 # attach peer in container
1356 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1357 PVE
::Tools
::run_command
($cmd);
1359 # link up peer in container
1360 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1361 PVE
::Tools
::run_command
($cmd);
1363 my $done = { type
=> 'veth' };
1364 foreach (qw(bridge tag firewall hwaddr name)) {
1365 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1367 $conf->{$opt} = print_lxc_network
($done);
1369 PVE
::LXC
::write_config
($vmid, $conf);
1372 sub update_ipconfig
{
1373 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1375 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1377 my $optdata = parse_lxc_network
($conf->{$opt});
1381 my $cmdargs = shift;
1382 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1384 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1386 my $change_ip_config = sub {
1387 my ($ipversion) = @_;
1389 my $family_opt = "-$ipversion";
1390 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1391 my $gw= "gw$suffix";
1392 my $ip= "ip$suffix";
1394 my $newip = $newnet->{$ip};
1395 my $newgw = $newnet->{$gw};
1396 my $oldip = $optdata->{$ip};
1398 my $change_ip = &$safe_string_ne($oldip, $newip);
1399 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1401 return if !$change_ip && !$change_gw;
1403 # step 1: add new IP, if this fails we cancel
1404 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1405 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1412 # step 2: replace gateway
1413 # If this fails we delete the added IP and cancel.
1414 # If it succeeds we save the config and delete the old IP, ignoring
1415 # errors. The config is then saved.
1416 # Note: 'ip route replace' can add
1419 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1422 # the route was not replaced, the old IP is still available
1423 # rollback (delete new IP) and cancel
1425 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1426 warn $@ if $@; # no need to die here
1431 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1432 # if the route was not deleted, the guest might have deleted it manually
1438 # from this point on we save the configuration
1439 # step 3: delete old IP ignoring errors
1440 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1441 # We need to enable promote_secondaries, otherwise our newly added
1442 # address will be removed along with the old one.
1445 if ($ipversion == 4) {
1446 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1447 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1448 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1450 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1452 warn $@ if $@; # no need to die here
1454 if ($ipversion == 4) {
1455 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1459 foreach my $property ($ip, $gw) {
1460 if ($newnet->{$property}) {
1461 $optdata->{$property} = $newnet->{$property};
1463 delete $optdata->{$property};
1466 $conf->{$opt} = print_lxc_network
($optdata);
1467 PVE
::LXC
::write_config
($vmid, $conf);
1468 $lxc_setup->setup_network($conf);
1471 &$change_ip_config(4);
1472 &$change_ip_config(6);
1476 # Internal snapshots
1478 # NOTE: Snapshot create/delete involves several non-atomic
1479 # action, and can take a long time.
1480 # So we try to avoid locking the file and use 'lock' variable
1481 # inside the config file instead.
1483 my $snapshot_copy_config = sub {
1484 my ($source, $dest) = @_;
1486 foreach my $k (keys %$source) {
1487 next if $k eq 'snapshots';
1488 next if $k eq 'snapstate';
1489 next if $k eq 'snaptime';
1490 next if $k eq 'vmstate';
1491 next if $k eq 'lock';
1492 next if $k eq 'digest';
1493 next if $k eq 'description';
1495 $dest->{$k} = $source->{$k};
1499 my $snapshot_prepare = sub {
1500 my ($vmid, $snapname, $comment) = @_;
1504 my $updatefn = sub {
1506 my $conf = load_config
($vmid);
1508 die "you can't take a snapshot if it's a template\n"
1509 if is_template
($conf);
1513 $conf->{lock} = 'snapshot';
1515 die "snapshot name '$snapname' already used\n"
1516 if defined($conf->{snapshots
}->{$snapname});
1518 my $storecfg = PVE
::Storage
::config
();
1519 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1521 $snap = $conf->{snapshots
}->{$snapname} = {};
1523 &$snapshot_copy_config($conf, $snap);
1525 $snap->{'snapstate'} = "prepare";
1526 $snap->{'snaptime'} = time();
1527 $snap->{'description'} = $comment if $comment;
1528 $conf->{snapshots
}->{$snapname} = $snap;
1530 PVE
::LXC
::write_config
($vmid, $conf);
1533 lock_container
($vmid, 10, $updatefn);
1538 my $snapshot_commit = sub {
1539 my ($vmid, $snapname) = @_;
1541 my $updatefn = sub {
1543 my $conf = load_config
($vmid);
1545 die "missing snapshot lock\n"
1546 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1548 die "snapshot '$snapname' does not exist\n"
1549 if !defined($conf->{snapshots
}->{$snapname});
1551 die "wrong snapshot state\n"
1552 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1553 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1555 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1556 delete $conf->{lock};
1557 $conf->{parent
} = $snapname;
1559 PVE
::LXC
::write_config
($vmid, $conf);
1562 lock_container
($vmid, 10 ,$updatefn);
1566 my ($feature, $conf, $storecfg, $snapname) = @_;
1568 #Fixme add other drives if necessary.
1571 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1572 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1574 return $err ?
0 : 1;
1577 sub snapshot_create
{
1578 my ($vmid, $snapname, $comment) = @_;
1580 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1582 my $conf = load_config
($vmid);
1584 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1585 my $running = check_running
($vmid);
1588 PVE
::Tools
::run_command
($cmd);
1591 my $storecfg = PVE
::Storage
::config
();
1592 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1593 my $volid = $rootinfo->{volume
};
1595 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1597 PVE
::Tools
::run_command
($cmd);
1600 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1601 &$snapshot_commit($vmid, $snapname);
1604 snapshot_delete
($vmid, $snapname, 1);
1609 sub snapshot_delete
{
1610 my ($vmid, $snapname, $force) = @_;
1616 my $updatefn = sub {
1618 $conf = load_config
($vmid);
1620 die "you can't delete a snapshot if vm is a template\n"
1621 if is_template
($conf);
1623 $snap = $conf->{snapshots
}->{$snapname};
1627 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1629 $snap->{snapstate
} = 'delete';
1631 PVE
::LXC
::write_config
($vmid, $conf);
1634 lock_container
($vmid, 10, $updatefn);
1636 my $storecfg = PVE
::Storage
::config
();
1638 my $del_snap = sub {
1642 if ($conf->{parent
} eq $snapname) {
1643 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1644 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1646 delete $conf->{parent
};
1650 delete $conf->{snapshots
}->{$snapname};
1652 PVE
::LXC
::write_config
($vmid, $conf);
1655 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1656 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1657 my $volid = $rootinfo->{volume
};
1660 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1664 if(!$err || ($err && $force)) {
1665 lock_container
($vmid, 10, $del_snap);
1667 die "Can't delete snapshot: $vmid $snapname $err\n";
1672 sub snapshot_rollback
{
1673 my ($vmid, $snapname) = @_;
1675 my $storecfg = PVE
::Storage
::config
();
1677 my $conf = load_config
($vmid);
1679 die "you can't rollback if vm is a template\n" if is_template
($conf);
1681 my $snap = $conf->{snapshots
}->{$snapname};
1683 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1685 my $rootfs = $snap->{rootfs
};
1686 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1687 my $volid = $rootinfo->{volume
};
1689 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1691 my $updatefn = sub {
1693 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1694 if $snap->{snapstate
};
1698 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1700 die "unable to rollback vm $vmid: vm is running\n"
1701 if check_running
($vmid);
1703 $conf->{lock} = 'rollback';
1707 # copy snapshot config to current config
1709 my $tmp_conf = $conf;
1710 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1711 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1712 delete $conf->{snaptime
};
1713 delete $conf->{snapname
};
1714 $conf->{parent
} = $snapname;
1716 PVE
::LXC
::write_config
($vmid, $conf);
1719 my $unlockfn = sub {
1720 delete $conf->{lock};
1721 PVE
::LXC
::write_config
($vmid, $conf);
1724 lock_container
($vmid, 10, $updatefn);
1726 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1728 lock_container
($vmid, 5, $unlockfn);
1731 sub template_create
{
1732 my ($vmid, $conf) = @_;
1734 my $storecfg = PVE
::Storage
::config
();
1736 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1737 my $volid = $rootinfo->{volume
};
1739 die "Template feature is not available for '$volid'\n"
1740 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1742 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1744 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1745 $rootinfo->{volume
} = $template_volid;
1746 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1748 write_config
($vmid, $conf);
1754 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1757 sub foreach_mountpoint
{
1758 my ($conf, $func) = @_;
1760 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1761 $mountpoint->{mp
} = '/'; # just to be sure
1762 &$func('rootfs', $mountpoint);
1764 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1766 next if !defined($conf->{$key});
1767 $mountpoint = parse_ct_mountpoint
($conf->{$key});
1768 &$func($key, $mountpoint);
1772 sub loopdevices_list
{
1777 if ($line =~ m/^(\/dev\
/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1778 $loopdev->{$1} = $2;
1782 PVE
::Tools
::run_command
(['losetup'], outfunc
=> $parser);
1787 sub blockdevices_list
{
1790 dir_glob_foreach
("/sys/dev/block/", '(\d+):(\d+)', sub {
1791 my (undef, $major, $minor) = @_;
1792 my $bdev = readlink("/sys/dev/block/$major:$minor");
1793 $bdev =~ s/\.\.\/\.\.\/devices\
/virtual\/block\
//\
/dev\//;
1794 $bdevs->{$bdev}->{major
} = $major;
1795 $bdevs->{$bdev}->{minor
} = $minor;
1801 my ($loopdevs, $path) = @_;
1803 foreach my $dev (keys %$loopdevs){
1804 return $dev if $loopdevs->{$dev} eq $path;
1808 sub check_ct_modify_config_perm
{
1809 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1811 return 1 if $authuser ne 'root@pam';
1813 foreach my $opt (@$key_list) {
1815 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1816 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1817 } elsif ($opt eq 'disk') {
1818 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1819 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1820 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1821 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1822 $opt eq 'searchdomain' || $opt eq 'hostname') {
1823 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1825 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1834 my ($volid, $storage_cfg, $loopdevs) = @_;
1838 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1842 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1843 $path = PVE
::Storage
::path
($storage_cfg, $volid);
1845 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1846 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1848 die "unable to use template as mountpoint\n" if $isBase;
1850 if ($format eq 'subvol') {
1852 } elsif ($format eq 'raw') {
1854 if ($scfg->{path
}) {
1855 $path = PVE
::LXC
::find_loopdev
($loopdevs, $path) if $loopdevs;
1856 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1859 die "unsupported storage type '$scfg->{type}'\n";
1862 die "unsupported image format '$format'\n";
1864 } elsif ($volid =~ m
|^/dev/.+|) {
1866 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
1869 die "unsupported storage";
1876 my ($storage_cfg, $vollist) = @_;
1880 foreach my $volid (@$vollist) {
1882 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1883 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1885 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1886 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1888 if (($format ne 'subvol') &&
1889 ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1890 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1895 $loopdev = $line if $line =~m
|^/dev/loop\d
+$|;
1896 $loopdevs->{$loopdev} = $path;
1899 PVE
::Tools
::run_command
(['losetup', '--find', '--show', $path], outfunc
=> $parser);
1907 my ($storage_cfg, $vollist) = @_;
1909 my $loopdevs = loopdevices_list
();
1911 foreach my $volid (@$vollist) {
1913 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1914 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1916 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1917 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1919 if($format ne 'subvol' && ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1920 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1921 foreach my $dev (keys %$loopdevs){
1922 PVE
::Tools
::run_command
(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1929 sub mountpoint_mount
{
1930 my ($mountpoint, $rootdir, $storage_cfg, $loopdevs) = @_;
1932 my $volid = $mountpoint->{volume
};
1933 my $mount = $mountpoint->{mp
};
1935 return if !$volid || !$mount;
1937 $rootdir =~ s!/+$!!;
1938 my $mount_path = "$rootdir/$mount";
1939 File
::Path
::mkpath
($mount_path);
1941 if ($volid =~ m
|^/dev/.+|) {
1942 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]);
1946 my $path = PVE
::LXC
::volid_path
($volid, $storage_cfg, $loopdevs);
1948 if ($path !~ m
|^/dev/.+|) {
1949 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
1953 PVE
::Tools
::run_command
(['mount', $path, $mount_path]);
1956 sub get_vm_volumes
{
1957 my ($conf, $excludes) = @_;
1961 PVE
::LXC
::foreach_mountpoint
($conf, sub {
1962 my ($ms, $mountpoint) = @_;
1964 return if $excludes && $ms eq $excludes;
1966 my $volid = $mountpoint->{volume
};
1968 return if !$volid || $volid =~ m
|^/|;
1970 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1973 push @$vollist, $volid;