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";
444 my $cfspath = cfs_config_path
($vmid);
446 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
447 die "container $vmid does not exists\n" if !defined($conf);
453 my ($vmid, $conf) = @_;
455 my $dir = "/etc/pve/nodes/$nodename/lxc";
458 write_config
($vmid, $conf);
464 unlink config_file
($vmid, $nodename);
468 my ($vmid, $conf) = @_;
470 my $cfspath = cfs_config_path
($vmid);
472 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
475 # flock: we use one file handle per process, so lock file
476 # can be called multiple times and succeeds for the same process.
478 my $lock_handles = {};
479 my $lockdir = "/run/lock/lxc";
484 return "$lockdir/pve-config-{$vmid}.lock";
488 my ($vmid, $timeout) = @_;
490 $timeout = 10 if !$timeout;
493 my $filename = lock_filename
($vmid);
495 mkdir $lockdir if !-d
$lockdir;
497 my $lock_func = sub {
498 if (!$lock_handles->{$$}->{$filename}) {
499 my $fh = new IO
::File
(">>$filename") ||
500 die "can't open file - $!\n";
501 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
504 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
505 print STDERR
"trying to aquire lock...";
508 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
509 # try again on EINTR (see bug #273)
510 if ($success || ($! != EINTR
)) {
515 print STDERR
" failed\n";
516 die "can't aquire lock - $!\n";
519 $lock_handles->{$$}->{$filename}->{refcount
}++;
521 print STDERR
" OK\n";
525 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
528 die "can't lock file '$filename' - $err";
535 my $filename = lock_filename
($vmid);
537 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
538 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
539 if ($refcount <= 0) {
540 $lock_handles->{$$}->{$filename} = undef;
547 my ($vmid, $timeout, $code, @param) = @_;
551 lock_aquire
($vmid, $timeout);
552 eval { $res = &$code(@param) };
564 return defined($confdesc->{$name});
567 # add JSON properties for create and set function
568 sub json_config_properties
{
571 foreach my $opt (keys %$confdesc) {
572 next if $opt eq 'parent' || $opt eq 'snaptime';
573 next if $prop->{$opt};
574 $prop->{$opt} = $confdesc->{$opt};
580 sub json_config_properties_no_rootfs
{
583 foreach my $opt (keys %$confdesc) {
584 next if $prop->{$opt};
585 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
586 $prop->{$opt} = $confdesc->{$opt};
592 # container status helpers
594 sub list_active_containers
{
596 my $filename = "/proc/net/unix";
598 # similar test is used by lcxcontainers.c: list_active_containers
601 my $fh = IO
::File-
>new ($filename, "r");
604 while (defined(my $line = <$fh>)) {
605 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
607 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
618 # warning: this is slow
622 my $active_hash = list_active_containers
();
624 return 1 if defined($active_hash->{$vmid});
629 sub get_container_disk_usage
{
632 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
642 if (my ($fsid, $total, $used, $avail) = $line =~
643 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
651 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
660 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
662 my $active_hash = list_active_containers
();
664 foreach my $vmid (keys %$list) {
665 my $d = $list->{$vmid};
667 my $running = defined($active_hash->{$vmid});
669 $d->{status
} = $running ?
'running' : 'stopped';
671 my $cfspath = cfs_config_path
($vmid);
672 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
674 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
675 $d->{name
} =~ s/[\s]//g;
677 $d->{cpus
} = $conf->{cpulimit
} // 0;
680 my $res = get_container_disk_usage
($vmid);
681 $d->{disk
} = $res->{used
};
682 $d->{maxdisk
} = $res->{total
};
685 # use 4GB by default ??
686 if (my $rootfs = $conf->{rootfs
}) {
687 my $rootinfo = parse_ct_mountpoint
($rootfs);
688 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
690 $d->{maxdisk
} = 4*1024*1024*1024;
696 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
697 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
708 $d->{template
} = is_template
($conf);
711 foreach my $vmid (keys %$list) {
712 my $d = $list->{$vmid};
713 next if $d->{status
} ne 'running';
715 $d->{uptime
} = 100; # fixme:
717 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
718 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
720 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
721 my @bytes = split(/\n/, $blkio_bytes);
722 foreach my $byte (@bytes) {
723 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
724 $d->{diskread
} = $2 if $key eq 'Read';
725 $d->{diskwrite
} = $2 if $key eq 'Write';
733 my $parse_size = sub {
736 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
737 my ($size, $unit) = ($1, $3);
740 $size = $size * 1024;
741 } elsif ($unit eq 'M') {
742 $size = $size * 1024 * 1024;
743 } elsif ($unit eq 'G') {
744 $size = $size * 1024 * 1024 * 1024;
750 sub parse_ct_mountpoint
{
757 foreach my $p (split (/,/, $data)) {
758 next if $p =~ m/^\s*$/;
760 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
761 my ($k, $v) = ($1, $2);
762 return undef if defined($res->{$k});
765 if (!$res->{volume
} && $p !~ m/=/) {
773 return undef if !$res->{volume
};
775 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
778 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
784 sub print_ct_mountpoint
{
789 die "missing volume\n" if !$info->{volume
};
791 foreach my $o ('size', 'backup') {
792 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
795 return "$info->{volume}$opts";
798 sub print_lxc_network
{
801 die "no network name defined\n" if !$net->{name
};
803 my $res = "name=$net->{name}";
805 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
806 next if !defined($net->{$k});
807 $res .= ",$k=$net->{$k}";
813 sub parse_lxc_network
{
818 return $res if !$data;
820 foreach my $pv (split (/,/, $data)) {
821 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
828 $res->{type
} = 'veth';
829 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
834 sub read_cgroup_value
{
835 my ($group, $vmid, $name, $full) = @_;
837 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
839 return PVE
::Tools
::file_get_contents
($path) if $full;
841 return PVE
::Tools
::file_read_firstline
($path);
844 sub write_cgroup_value
{
845 my ($group, $vmid, $name, $value) = @_;
847 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
848 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
852 sub find_lxc_console_pids
{
856 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
859 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
862 my @args = split(/\0/, $cmdline);
864 # serach for lxc-console -n <vmid>
865 return if scalar(@args) != 3;
866 return if $args[1] ne '-n';
867 return if $args[2] !~ m/^\d+$/;
868 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
872 push @{$res->{$vmid}}, $pid;
884 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
886 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
888 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
893 my $ipv4_reverse_mask = [
929 # Note: we cannot use Net:IP, because that only allows strict
931 sub parse_ipv4_cidr
{
932 my ($cidr, $noerr) = @_;
934 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
935 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
938 return undef if $noerr;
940 die "unable to parse ipv4 address/mask\n";
946 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
949 sub update_lxc_config
{
950 my ($storage_cfg, $vmid, $conf) = @_;
952 my $dir = "/var/lib/lxc/$vmid";
954 if ($conf->{template
}) {
956 unlink "$dir/config";
963 die "missing 'arch' - internal error" if !$conf->{arch
};
964 $raw .= "lxc.arch = $conf->{arch}\n";
966 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
967 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
968 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
973 if (!has_dev_console
($conf)) {
974 $raw .= "lxc.console = none\n";
975 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
978 my $ttycount = get_tty_count
($conf);
979 $raw .= "lxc.tty = $ttycount\n";
981 my $utsname = $conf->{hostname
} || "CT$vmid";
982 $raw .= "lxc.utsname = $utsname\n";
984 my $memory = $conf->{memory
} || 512;
985 my $swap = $conf->{swap
} // 0;
987 my $lxcmem = int($memory*1024*1024);
988 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
990 my $lxcswap = int(($memory + $swap)*1024*1024);
991 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
993 if (my $cpulimit = $conf->{cpulimit
}) {
994 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
995 my $value = int(100000*$cpulimit);
996 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
999 my $shares = $conf->{cpuunits
} || 1024;
1000 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1002 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1003 my $volid = $mountpoint->{volume
};
1004 my $path = volid_path
($volid, $storage_cfg);
1005 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1008 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1009 $path = "loop:$path" if $scfg->{path
};
1012 $raw .= "lxc.rootfs = $path\n";
1015 foreach my $k (keys %$conf) {
1016 next if $k !~ m/^net(\d+)$/;
1018 my $d = parse_lxc_network
($conf->{$k});
1020 $raw .= "lxc.network.type = veth\n";
1021 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1022 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1023 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1024 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1027 if (my $lxcconf = $conf->{lxc
}) {
1028 foreach my $entry (@$lxcconf) {
1029 my ($k, $v) = @$entry;
1030 $netcount++ if $k eq 'lxc.network.type';
1031 $raw .= "$k = $v\n";
1035 $raw .= "lxc.network.type = empty\n" if !$netcount;
1037 File
::Path
::mkpath
("$dir/rootfs");
1039 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1042 # verify and cleanup nameserver list (replace \0 with ' ')
1043 sub verify_nameserver_list
{
1044 my ($nameserver_list) = @_;
1047 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1048 PVE
::JSONSchema
::pve_verify_ip
($server);
1049 push @list, $server;
1052 return join(' ', @list);
1055 sub verify_searchdomain_list
{
1056 my ($searchdomain_list) = @_;
1059 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1060 # todo: should we add checks for valid dns domains?
1061 push @list, $server;
1064 return join(' ', @list);
1067 sub update_pct_config
{
1068 my ($vmid, $conf, $running, $param, $delete) = @_;
1074 my $pid = find_lxc_pid
($vmid);
1075 $rootdir = "/proc/$pid/root";
1078 if (defined($delete)) {
1079 foreach my $opt (@$delete) {
1080 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1081 die "unable to delete required option '$opt'\n";
1082 } elsif ($opt eq 'swap') {
1083 delete $conf->{$opt};
1084 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1085 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1086 delete $conf->{$opt};
1087 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1088 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1089 delete $conf->{$opt};
1090 push @nohotplug, $opt;
1092 } elsif ($opt =~ m/^net(\d)$/) {
1093 delete $conf->{$opt};
1096 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1100 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1104 # There's no separate swap size to configure, there's memory and "total"
1105 # memory (iow. memory+swap). This means we have to change them together.
1106 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1107 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1108 if (defined($wanted_memory) || defined($wanted_swap)) {
1110 $wanted_memory //= ($conf->{memory
} || 512);
1111 $wanted_swap //= ($conf->{swap
} || 0);
1113 my $total = $wanted_memory + $wanted_swap;
1115 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1116 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1118 $conf->{memory
} = $wanted_memory;
1119 $conf->{swap
} = $wanted_swap;
1121 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1124 foreach my $opt (keys %$param) {
1125 my $value = $param->{$opt};
1126 if ($opt eq 'hostname') {
1127 $conf->{$opt} = $value;
1128 } elsif ($opt eq 'onboot') {
1129 $conf->{$opt} = $value ?
1 : 0;
1130 } elsif ($opt eq 'startup') {
1131 $conf->{$opt} = $value;
1132 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1133 $conf->{$opt} = $value;
1134 push @nohotplug, $opt;
1136 } elsif ($opt eq 'nameserver') {
1137 my $list = verify_nameserver_list
($value);
1138 $conf->{$opt} = $list;
1139 push @nohotplug, $opt;
1141 } elsif ($opt eq 'searchdomain') {
1142 my $list = verify_searchdomain_list
($value);
1143 $conf->{$opt} = $list;
1144 push @nohotplug, $opt;
1146 } elsif ($opt eq 'cpulimit') {
1147 $conf->{$opt} = $value;
1148 push @nohotplug, $opt; # fixme: hotplug
1150 } elsif ($opt eq 'cpuunits') {
1151 $conf->{$opt} = $value;
1152 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1153 } elsif ($opt eq 'description') {
1154 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1155 } elsif ($opt =~ m/^net(\d+)$/) {
1157 my $net = parse_lxc_network
($value);
1159 $conf->{$opt} = print_lxc_network
($net);
1161 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1164 die "implement me: $opt";
1166 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1169 if ($running && scalar(@nohotplug)) {
1170 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1174 sub has_dev_console
{
1177 return !(defined($conf->{console
}) && !$conf->{console
});
1183 return $conf->{tty
} // $confdesc->{tty
}->{default};
1189 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1192 sub get_console_command
{
1193 my ($vmid, $conf) = @_;
1195 my $cmode = get_cmode
($conf);
1197 if ($cmode eq 'console') {
1198 return ['lxc-console', '-n', $vmid, '-t', 0];
1199 } elsif ($cmode eq 'tty') {
1200 return ['lxc-console', '-n', $vmid];
1201 } elsif ($cmode eq 'shell') {
1202 return ['lxc-attach', '--clear-env', '-n', $vmid];
1204 die "internal error";
1208 sub get_primary_ips
{
1211 # return data from net0
1213 return undef if !defined($conf->{net0
});
1214 my $net = parse_lxc_network
($conf->{net0
});
1216 my $ipv4 = $net->{ip
};
1218 if ($ipv4 =~ /^(dhcp|manual)$/) {
1224 my $ipv6 = $net->{ip6
};
1226 if ($ipv6 =~ /^(dhcp|manual)$/) {
1233 return ($ipv4, $ipv6);
1237 sub destroy_lxc_container
{
1238 my ($storage_cfg, $vmid, $conf) = @_;
1240 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1241 if (defined($rootinfo->{volume
})) {
1242 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1243 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1245 rmdir "/var/lib/lxc/$vmid/rootfs";
1246 unlink "/var/lib/lxc/$vmid/config";
1247 rmdir "/var/lib/lxc/$vmid";
1248 destroy_config
($vmid);
1250 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1251 #PVE::Tools::run_command($cmd);
1254 sub vm_stop_cleanup
{
1255 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1260 my $vollist = get_vm_volumes
($conf);
1261 my $loopdevlist = get_vm_volumes
($conf, 'rootfs');
1263 PVE
::LXC
::dettach_loops
($storage_cfg, $loopdevlist);
1264 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1267 warn $@ if $@; # avoid errors - just warn
1270 my $safe_num_ne = sub {
1273 return 0 if !defined($a) && !defined($b);
1274 return 1 if !defined($a);
1275 return 1 if !defined($b);
1280 my $safe_string_ne = sub {
1283 return 0 if !defined($a) && !defined($b);
1284 return 1 if !defined($a);
1285 return 1 if !defined($b);
1291 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1293 if ($newnet->{type
} ne 'veth') {
1294 # for when there are physical interfaces
1295 die "cannot update interface of type $newnet->{type}";
1298 my $veth = "veth${vmid}i${netid}";
1299 my $eth = $newnet->{name
};
1301 if (my $oldnetcfg = $conf->{$opt}) {
1302 my $oldnet = parse_lxc_network
($oldnetcfg);
1304 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1305 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1307 PVE
::Network
::veth_delete
($veth);
1308 delete $conf->{$opt};
1309 PVE
::LXC
::write_config
($vmid, $conf);
1311 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1313 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1314 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1315 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1317 if ($oldnet->{bridge
}) {
1318 PVE
::Network
::tap_unplug
($veth);
1319 foreach (qw(bridge tag firewall)) {
1320 delete $oldnet->{$_};
1322 $conf->{$opt} = print_lxc_network
($oldnet);
1323 PVE
::LXC
::write_config
($vmid, $conf);
1326 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1327 foreach (qw(bridge tag firewall)) {
1328 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1330 $conf->{$opt} = print_lxc_network
($oldnet);
1331 PVE
::LXC
::write_config
($vmid, $conf);
1334 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1337 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1341 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1343 my $veth = "veth${vmid}i${netid}";
1344 my $vethpeer = $veth . "p";
1345 my $eth = $newnet->{name
};
1347 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1348 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1350 # attach peer in container
1351 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1352 PVE
::Tools
::run_command
($cmd);
1354 # link up peer in container
1355 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1356 PVE
::Tools
::run_command
($cmd);
1358 my $done = { type
=> 'veth' };
1359 foreach (qw(bridge tag firewall hwaddr name)) {
1360 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1362 $conf->{$opt} = print_lxc_network
($done);
1364 PVE
::LXC
::write_config
($vmid, $conf);
1367 sub update_ipconfig
{
1368 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1370 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1372 my $optdata = parse_lxc_network
($conf->{$opt});
1376 my $cmdargs = shift;
1377 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1379 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1381 my $change_ip_config = sub {
1382 my ($ipversion) = @_;
1384 my $family_opt = "-$ipversion";
1385 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1386 my $gw= "gw$suffix";
1387 my $ip= "ip$suffix";
1389 my $newip = $newnet->{$ip};
1390 my $newgw = $newnet->{$gw};
1391 my $oldip = $optdata->{$ip};
1393 my $change_ip = &$safe_string_ne($oldip, $newip);
1394 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1396 return if !$change_ip && !$change_gw;
1398 # step 1: add new IP, if this fails we cancel
1399 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1400 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1407 # step 2: replace gateway
1408 # If this fails we delete the added IP and cancel.
1409 # If it succeeds we save the config and delete the old IP, ignoring
1410 # errors. The config is then saved.
1411 # Note: 'ip route replace' can add
1414 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1417 # the route was not replaced, the old IP is still available
1418 # rollback (delete new IP) and cancel
1420 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1421 warn $@ if $@; # no need to die here
1426 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1427 # if the route was not deleted, the guest might have deleted it manually
1433 # from this point on we save the configuration
1434 # step 3: delete old IP ignoring errors
1435 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1436 # We need to enable promote_secondaries, otherwise our newly added
1437 # address will be removed along with the old one.
1440 if ($ipversion == 4) {
1441 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1442 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1443 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1445 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1447 warn $@ if $@; # no need to die here
1449 if ($ipversion == 4) {
1450 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1454 foreach my $property ($ip, $gw) {
1455 if ($newnet->{$property}) {
1456 $optdata->{$property} = $newnet->{$property};
1458 delete $optdata->{$property};
1461 $conf->{$opt} = print_lxc_network
($optdata);
1462 PVE
::LXC
::write_config
($vmid, $conf);
1463 $lxc_setup->setup_network($conf);
1466 &$change_ip_config(4);
1467 &$change_ip_config(6);
1471 # Internal snapshots
1473 # NOTE: Snapshot create/delete involves several non-atomic
1474 # action, and can take a long time.
1475 # So we try to avoid locking the file and use 'lock' variable
1476 # inside the config file instead.
1478 my $snapshot_copy_config = sub {
1479 my ($source, $dest) = @_;
1481 foreach my $k (keys %$source) {
1482 next if $k eq 'snapshots';
1483 next if $k eq 'snapstate';
1484 next if $k eq 'snaptime';
1485 next if $k eq 'vmstate';
1486 next if $k eq 'lock';
1487 next if $k eq 'digest';
1488 next if $k eq 'description';
1490 $dest->{$k} = $source->{$k};
1494 my $snapshot_prepare = sub {
1495 my ($vmid, $snapname, $comment) = @_;
1499 my $updatefn = sub {
1501 my $conf = load_config
($vmid);
1503 die "you can't take a snapshot if it's a template\n"
1504 if is_template
($conf);
1508 $conf->{lock} = 'snapshot';
1510 die "snapshot name '$snapname' already used\n"
1511 if defined($conf->{snapshots
}->{$snapname});
1513 my $storecfg = PVE
::Storage
::config
();
1514 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1516 $snap = $conf->{snapshots
}->{$snapname} = {};
1518 &$snapshot_copy_config($conf, $snap);
1520 $snap->{'snapstate'} = "prepare";
1521 $snap->{'snaptime'} = time();
1522 $snap->{'description'} = $comment if $comment;
1523 $conf->{snapshots
}->{$snapname} = $snap;
1525 PVE
::LXC
::write_config
($vmid, $conf);
1528 lock_container
($vmid, 10, $updatefn);
1533 my $snapshot_commit = sub {
1534 my ($vmid, $snapname) = @_;
1536 my $updatefn = sub {
1538 my $conf = load_config
($vmid);
1540 die "missing snapshot lock\n"
1541 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1543 die "snapshot '$snapname' does not exist\n"
1544 if !defined($conf->{snapshots
}->{$snapname});
1546 die "wrong snapshot state\n"
1547 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1548 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1550 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1551 delete $conf->{lock};
1552 $conf->{parent
} = $snapname;
1554 PVE
::LXC
::write_config
($vmid, $conf);
1557 lock_container
($vmid, 10 ,$updatefn);
1561 my ($feature, $conf, $storecfg, $snapname) = @_;
1563 #Fixme add other drives if necessary.
1566 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1567 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1569 return $err ?
0 : 1;
1572 sub snapshot_create
{
1573 my ($vmid, $snapname, $comment) = @_;
1575 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1577 my $conf = load_config
($vmid);
1579 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1580 my $running = check_running
($vmid);
1583 PVE
::Tools
::run_command
($cmd);
1586 my $storecfg = PVE
::Storage
::config
();
1587 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1588 my $volid = $rootinfo->{volume
};
1590 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1592 PVE
::Tools
::run_command
($cmd);
1595 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1596 &$snapshot_commit($vmid, $snapname);
1599 snapshot_delete
($vmid, $snapname, 1);
1604 sub snapshot_delete
{
1605 my ($vmid, $snapname, $force) = @_;
1611 my $updatefn = sub {
1613 $conf = load_config
($vmid);
1615 die "you can't delete a snapshot if vm is a template\n"
1616 if is_template
($conf);
1618 $snap = $conf->{snapshots
}->{$snapname};
1622 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1624 $snap->{snapstate
} = 'delete';
1626 PVE
::LXC
::write_config
($vmid, $conf);
1629 lock_container
($vmid, 10, $updatefn);
1631 my $storecfg = PVE
::Storage
::config
();
1633 my $del_snap = sub {
1637 if ($conf->{parent
} eq $snapname) {
1638 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1639 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1641 delete $conf->{parent
};
1645 delete $conf->{snapshots
}->{$snapname};
1647 PVE
::LXC
::write_config
($vmid, $conf);
1650 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1651 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1652 my $volid = $rootinfo->{volume
};
1655 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1659 if(!$err || ($err && $force)) {
1660 lock_container
($vmid, 10, $del_snap);
1662 die "Can't delete snapshot: $vmid $snapname $err\n";
1667 sub snapshot_rollback
{
1668 my ($vmid, $snapname) = @_;
1670 my $storecfg = PVE
::Storage
::config
();
1672 my $conf = load_config
($vmid);
1674 die "you can't rollback if vm is a template\n" if is_template
($conf);
1676 my $snap = $conf->{snapshots
}->{$snapname};
1678 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1680 my $rootfs = $snap->{rootfs
};
1681 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1682 my $volid = $rootinfo->{volume
};
1684 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1686 my $updatefn = sub {
1688 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1689 if $snap->{snapstate
};
1693 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1695 die "unable to rollback vm $vmid: vm is running\n"
1696 if check_running
($vmid);
1698 $conf->{lock} = 'rollback';
1702 # copy snapshot config to current config
1704 my $tmp_conf = $conf;
1705 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1706 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1707 delete $conf->{snaptime
};
1708 delete $conf->{snapname
};
1709 $conf->{parent
} = $snapname;
1711 PVE
::LXC
::write_config
($vmid, $conf);
1714 my $unlockfn = sub {
1715 delete $conf->{lock};
1716 PVE
::LXC
::write_config
($vmid, $conf);
1719 lock_container
($vmid, 10, $updatefn);
1721 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1723 lock_container
($vmid, 5, $unlockfn);
1726 sub template_create
{
1727 my ($vmid, $conf) = @_;
1729 my $storecfg = PVE
::Storage
::config
();
1731 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1732 my $volid = $rootinfo->{volume
};
1734 die "Template feature is not available for '$volid'\n"
1735 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1737 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1739 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1740 $rootinfo->{volume
} = $template_volid;
1741 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1743 write_config
($vmid, $conf);
1749 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1752 sub foreach_mountpoint
{
1753 my ($conf, $func) = @_;
1755 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1756 $mountpoint->{mp
} = '/'; # just to be sure
1757 &$func('rootfs', $mountpoint);
1759 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1761 next if !defined($conf->{$key});
1762 $mountpoint = parse_ct_mountpoint
($conf->{$key});
1763 &$func($key, $mountpoint);
1767 sub loopdevices_list
{
1772 if ($line =~ m/^(\/dev\
/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1773 $loopdev->{$1} = $2;
1777 PVE
::Tools
::run_command
(['losetup'], outfunc
=> $parser);
1782 sub blockdevices_list
{
1785 dir_glob_foreach
("/sys/dev/block/", '(\d+):(\d+)', sub {
1786 my (undef, $major, $minor) = @_;
1787 my $bdev = readlink("/sys/dev/block/$major:$minor");
1788 $bdev =~ s/\.\.\/\.\.\/devices\
/virtual\/block\
//\
/dev\//;
1789 $bdevs->{$bdev}->{major
} = $major;
1790 $bdevs->{$bdev}->{minor
} = $minor;
1796 my ($loopdevs, $path) = @_;
1798 foreach my $dev (keys %$loopdevs){
1799 return $dev if $loopdevs->{$dev} eq $path;
1803 sub check_ct_modify_config_perm
{
1804 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1806 return 1 if $authuser ne 'root@pam';
1808 foreach my $opt (@$key_list) {
1810 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1811 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1812 } elsif ($opt eq 'disk') {
1813 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1814 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1815 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1816 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1817 $opt eq 'searchdomain' || $opt eq 'hostname') {
1818 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1820 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1829 my ($volid, $storage_cfg, $loopdevs) = @_;
1833 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1837 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1838 $path = PVE
::Storage
::path
($storage_cfg, $volid);
1840 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1841 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1843 die "unable to use template as mountpoint\n" if $isBase;
1845 if ($format eq 'subvol') {
1847 } elsif ($format eq 'raw') {
1849 if ($scfg->{path
}) {
1850 $path = PVE
::LXC
::find_loopdev
($loopdevs, $path) if $loopdevs;
1851 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1854 die "unsupported storage type '$scfg->{type}'\n";
1857 die "unsupported image format '$format'\n";
1859 } elsif ($volid =~ m
|^/dev/.+|) {
1861 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
1864 die "unsupported storage";
1871 my ($storage_cfg, $vollist) = @_;
1875 foreach my $volid (@$vollist) {
1877 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1878 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1880 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1881 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1883 if (($format ne 'subvol') &&
1884 ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1885 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1890 $loopdev = $line if $line =~m
|^/dev/loop\d
+$|;
1891 $loopdevs->{$loopdev} = $path;
1894 PVE
::Tools
::run_command
(['losetup', '--find', '--show', $path], outfunc
=> $parser);
1902 my ($storage_cfg, $vollist) = @_;
1904 my $loopdevs = loopdevices_list
();
1906 foreach my $volid (@$vollist) {
1908 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1909 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1911 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1912 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1914 if($format ne 'subvol' && ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1915 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1916 foreach my $dev (keys %$loopdevs){
1917 PVE
::Tools
::run_command
(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1924 sub mountpoint_mount
{
1925 my ($mountpoint, $rootdir, $storage_cfg, $loopdevs) = @_;
1927 my $volid = $mountpoint->{volume
};
1928 my $mount = $mountpoint->{mp
};
1930 return if !$volid || !$mount;
1932 $rootdir =~ s!/+$!!;
1933 my $mount_path = "$rootdir/$mount";
1934 File
::Path
::mkpath
($mount_path);
1936 if ($volid =~ m
|^/dev/.+|) {
1937 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]);
1941 my $path = PVE
::LXC
::volid_path
($volid, $storage_cfg, $loopdevs);
1943 if ($path !~ m
|^/dev/.+|) {
1944 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
1948 PVE
::Tools
::run_command
(['mount', $path, $mount_path]);
1951 sub get_vm_volumes
{
1952 my ($conf, $excludes) = @_;
1956 PVE
::LXC
::foreach_mountpoint
($conf, sub {
1957 my ($ms, $mountpoint) = @_;
1959 return if $excludes && $ms eq $excludes;
1961 my $volid = $mountpoint->{volume
};
1963 return if !$volid || $volid =~ m
|^/|;
1965 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1968 push @$vollist, $volid;