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 print STDERR
" OK\n";
523 $lock_handles->{$$}->{$filename}->{refcount
}++;
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 !defined($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 $mountpoint->{mp
} = '/';
1005 my $volid = $mountpoint->{volume
};
1006 my $path = mountpoint_mount_path
($mountpoint, $storage_cfg);
1008 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1011 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1012 $path = "loop:$path" if $scfg->{path
};
1015 $raw .= "lxc.rootfs = $path\n";
1018 foreach my $k (keys %$conf) {
1019 next if $k !~ m/^net(\d+)$/;
1021 my $d = parse_lxc_network
($conf->{$k});
1023 $raw .= "lxc.network.type = veth\n";
1024 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1025 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1026 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1027 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1030 if (my $lxcconf = $conf->{lxc
}) {
1031 foreach my $entry (@$lxcconf) {
1032 my ($k, $v) = @$entry;
1033 $netcount++ if $k eq 'lxc.network.type';
1034 $raw .= "$k = $v\n";
1038 $raw .= "lxc.network.type = empty\n" if !$netcount;
1040 File
::Path
::mkpath
("$dir/rootfs");
1042 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1045 # verify and cleanup nameserver list (replace \0 with ' ')
1046 sub verify_nameserver_list
{
1047 my ($nameserver_list) = @_;
1050 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1051 PVE
::JSONSchema
::pve_verify_ip
($server);
1052 push @list, $server;
1055 return join(' ', @list);
1058 sub verify_searchdomain_list
{
1059 my ($searchdomain_list) = @_;
1062 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1063 # todo: should we add checks for valid dns domains?
1064 push @list, $server;
1067 return join(' ', @list);
1070 sub update_pct_config
{
1071 my ($vmid, $conf, $running, $param, $delete) = @_;
1077 my $pid = find_lxc_pid
($vmid);
1078 $rootdir = "/proc/$pid/root";
1081 if (defined($delete)) {
1082 foreach my $opt (@$delete) {
1083 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1084 die "unable to delete required option '$opt'\n";
1085 } elsif ($opt eq 'swap') {
1086 delete $conf->{$opt};
1087 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1088 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1089 delete $conf->{$opt};
1090 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1091 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1092 delete $conf->{$opt};
1093 push @nohotplug, $opt;
1095 } elsif ($opt =~ m/^net(\d)$/) {
1096 delete $conf->{$opt};
1099 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1100 } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
1105 write_config
($vmid, $conf) if $running;
1109 # There's no separate swap size to configure, there's memory and "total"
1110 # memory (iow. memory+swap). This means we have to change them together.
1111 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1112 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1113 if (defined($wanted_memory) || defined($wanted_swap)) {
1115 $wanted_memory //= ($conf->{memory
} || 512);
1116 $wanted_swap //= ($conf->{swap
} || 0);
1118 my $total = $wanted_memory + $wanted_swap;
1120 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1121 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1123 $conf->{memory
} = $wanted_memory;
1124 $conf->{swap
} = $wanted_swap;
1126 write_config
($vmid, $conf) if $running;
1129 foreach my $opt (keys %$param) {
1130 my $value = $param->{$opt};
1131 if ($opt eq 'hostname') {
1132 $conf->{$opt} = $value;
1133 } elsif ($opt eq 'onboot') {
1134 $conf->{$opt} = $value ?
1 : 0;
1135 } elsif ($opt eq 'startup') {
1136 $conf->{$opt} = $value;
1137 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1138 $conf->{$opt} = $value;
1139 push @nohotplug, $opt;
1141 } elsif ($opt eq 'nameserver') {
1142 my $list = verify_nameserver_list
($value);
1143 $conf->{$opt} = $list;
1144 push @nohotplug, $opt;
1146 } elsif ($opt eq 'searchdomain') {
1147 my $list = verify_searchdomain_list
($value);
1148 $conf->{$opt} = $list;
1149 push @nohotplug, $opt;
1151 } elsif ($opt eq 'cpulimit') {
1152 $conf->{$opt} = $value;
1153 push @nohotplug, $opt; # fixme: hotplug
1155 } elsif ($opt eq 'cpuunits') {
1156 $conf->{$opt} = $value;
1157 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1158 } elsif ($opt eq 'description') {
1159 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1160 } elsif ($opt =~ m/^net(\d+)$/) {
1162 my $net = parse_lxc_network
($value);
1164 $conf->{$opt} = print_lxc_network
($net);
1166 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1168 } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
1169 die "implement me: $opt";
1171 die "implement me: $opt";
1173 write_config
($vmid, $conf) if $running;
1176 if ($running && scalar(@nohotplug)) {
1177 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1181 sub has_dev_console
{
1184 return !(defined($conf->{console
}) && !$conf->{console
});
1190 return $conf->{tty
} // $confdesc->{tty
}->{default};
1196 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1199 sub get_console_command
{
1200 my ($vmid, $conf) = @_;
1202 my $cmode = get_cmode
($conf);
1204 if ($cmode eq 'console') {
1205 return ['lxc-console', '-n', $vmid, '-t', 0];
1206 } elsif ($cmode eq 'tty') {
1207 return ['lxc-console', '-n', $vmid];
1208 } elsif ($cmode eq 'shell') {
1209 return ['lxc-attach', '--clear-env', '-n', $vmid];
1211 die "internal error";
1215 sub get_primary_ips
{
1218 # return data from net0
1220 return undef if !defined($conf->{net0
});
1221 my $net = parse_lxc_network
($conf->{net0
});
1223 my $ipv4 = $net->{ip
};
1225 if ($ipv4 =~ /^(dhcp|manual)$/) {
1231 my $ipv6 = $net->{ip6
};
1233 if ($ipv6 =~ /^(dhcp|manual)$/) {
1240 return ($ipv4, $ipv6);
1244 sub destroy_lxc_container
{
1245 my ($storage_cfg, $vmid, $conf) = @_;
1247 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1248 if (defined($rootinfo->{volume
})) {
1249 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1250 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1252 rmdir "/var/lib/lxc/$vmid/rootfs";
1253 unlink "/var/lib/lxc/$vmid/config";
1254 rmdir "/var/lib/lxc/$vmid";
1255 destroy_config
($vmid);
1257 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1258 #PVE::Tools::run_command($cmd);
1261 sub vm_stop_cleanup
{
1262 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1267 my $vollist = get_vm_volumes
($conf);
1268 my $loopdevlist = get_vm_volumes
($conf, 'rootfs');
1270 dettach_loops
($storage_cfg, $loopdevlist);
1271 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1274 warn $@ if $@; # avoid errors - just warn
1277 my $safe_num_ne = sub {
1280 return 0 if !defined($a) && !defined($b);
1281 return 1 if !defined($a);
1282 return 1 if !defined($b);
1287 my $safe_string_ne = sub {
1290 return 0 if !defined($a) && !defined($b);
1291 return 1 if !defined($a);
1292 return 1 if !defined($b);
1298 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1300 if ($newnet->{type
} ne 'veth') {
1301 # for when there are physical interfaces
1302 die "cannot update interface of type $newnet->{type}";
1305 my $veth = "veth${vmid}i${netid}";
1306 my $eth = $newnet->{name
};
1308 if (my $oldnetcfg = $conf->{$opt}) {
1309 my $oldnet = parse_lxc_network
($oldnetcfg);
1311 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1312 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1314 PVE
::Network
::veth_delete
($veth);
1315 delete $conf->{$opt};
1316 write_config
($vmid, $conf);
1318 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1320 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1321 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1322 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1324 if ($oldnet->{bridge
}) {
1325 PVE
::Network
::tap_unplug
($veth);
1326 foreach (qw(bridge tag firewall)) {
1327 delete $oldnet->{$_};
1329 $conf->{$opt} = print_lxc_network
($oldnet);
1330 write_config
($vmid, $conf);
1333 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1334 foreach (qw(bridge tag firewall)) {
1335 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1337 $conf->{$opt} = print_lxc_network
($oldnet);
1338 write_config
($vmid, $conf);
1341 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1344 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1348 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1350 my $veth = "veth${vmid}i${netid}";
1351 my $vethpeer = $veth . "p";
1352 my $eth = $newnet->{name
};
1354 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1355 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1357 # attach peer in container
1358 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1359 PVE
::Tools
::run_command
($cmd);
1361 # link up peer in container
1362 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1363 PVE
::Tools
::run_command
($cmd);
1365 my $done = { type
=> 'veth' };
1366 foreach (qw(bridge tag firewall hwaddr name)) {
1367 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1369 $conf->{$opt} = print_lxc_network
($done);
1371 write_config
($vmid, $conf);
1374 sub update_ipconfig
{
1375 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1377 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1379 my $optdata = parse_lxc_network
($conf->{$opt});
1383 my $cmdargs = shift;
1384 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1386 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1388 my $change_ip_config = sub {
1389 my ($ipversion) = @_;
1391 my $family_opt = "-$ipversion";
1392 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1393 my $gw= "gw$suffix";
1394 my $ip= "ip$suffix";
1396 my $newip = $newnet->{$ip};
1397 my $newgw = $newnet->{$gw};
1398 my $oldip = $optdata->{$ip};
1400 my $change_ip = &$safe_string_ne($oldip, $newip);
1401 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1403 return if !$change_ip && !$change_gw;
1405 # step 1: add new IP, if this fails we cancel
1406 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1407 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1414 # step 2: replace gateway
1415 # If this fails we delete the added IP and cancel.
1416 # If it succeeds we save the config and delete the old IP, ignoring
1417 # errors. The config is then saved.
1418 # Note: 'ip route replace' can add
1421 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1424 # the route was not replaced, the old IP is still available
1425 # rollback (delete new IP) and cancel
1427 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1428 warn $@ if $@; # no need to die here
1433 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1434 # if the route was not deleted, the guest might have deleted it manually
1440 # from this point on we save the configuration
1441 # step 3: delete old IP ignoring errors
1442 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1443 # We need to enable promote_secondaries, otherwise our newly added
1444 # address will be removed along with the old one.
1447 if ($ipversion == 4) {
1448 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1449 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1450 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1452 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1454 warn $@ if $@; # no need to die here
1456 if ($ipversion == 4) {
1457 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1461 foreach my $property ($ip, $gw) {
1462 if ($newnet->{$property}) {
1463 $optdata->{$property} = $newnet->{$property};
1465 delete $optdata->{$property};
1468 $conf->{$opt} = print_lxc_network
($optdata);
1469 write_config
($vmid, $conf);
1470 $lxc_setup->setup_network($conf);
1473 &$change_ip_config(4);
1474 &$change_ip_config(6);
1478 # Internal snapshots
1480 # NOTE: Snapshot create/delete involves several non-atomic
1481 # action, and can take a long time.
1482 # So we try to avoid locking the file and use 'lock' variable
1483 # inside the config file instead.
1485 my $snapshot_copy_config = sub {
1486 my ($source, $dest) = @_;
1488 foreach my $k (keys %$source) {
1489 next if $k eq 'snapshots';
1490 next if $k eq 'snapstate';
1491 next if $k eq 'snaptime';
1492 next if $k eq 'vmstate';
1493 next if $k eq 'lock';
1494 next if $k eq 'digest';
1495 next if $k eq 'description';
1497 $dest->{$k} = $source->{$k};
1501 my $snapshot_prepare = sub {
1502 my ($vmid, $snapname, $comment) = @_;
1506 my $updatefn = sub {
1508 my $conf = load_config
($vmid);
1510 die "you can't take a snapshot if it's a template\n"
1511 if is_template
($conf);
1515 $conf->{lock} = 'snapshot';
1517 die "snapshot name '$snapname' already used\n"
1518 if defined($conf->{snapshots
}->{$snapname});
1520 my $storecfg = PVE
::Storage
::config
();
1521 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1523 $snap = $conf->{snapshots
}->{$snapname} = {};
1525 &$snapshot_copy_config($conf, $snap);
1527 $snap->{'snapstate'} = "prepare";
1528 $snap->{'snaptime'} = time();
1529 $snap->{'description'} = $comment if $comment;
1530 $conf->{snapshots
}->{$snapname} = $snap;
1532 write_config
($vmid, $conf);
1535 lock_container
($vmid, 10, $updatefn);
1540 my $snapshot_commit = sub {
1541 my ($vmid, $snapname) = @_;
1543 my $updatefn = sub {
1545 my $conf = load_config
($vmid);
1547 die "missing snapshot lock\n"
1548 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1550 die "snapshot '$snapname' does not exist\n"
1551 if !defined($conf->{snapshots
}->{$snapname});
1553 die "wrong snapshot state\n"
1554 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1555 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1557 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1558 delete $conf->{lock};
1559 $conf->{parent
} = $snapname;
1561 write_config
($vmid, $conf);
1564 lock_container
($vmid, 10 ,$updatefn);
1568 my ($feature, $conf, $storecfg, $snapname) = @_;
1572 foreach_mountpoint
($conf, sub {
1573 my ($ms, $mountpoint) = @_;
1575 return if $err; # skip further test
1577 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1579 # TODO: implement support for mountpoints
1580 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1584 return $err ?
0 : 1;
1587 sub snapshot_create
{
1588 my ($vmid, $snapname, $comment) = @_;
1590 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1592 my $conf = load_config
($vmid);
1594 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1595 my $running = check_running
($vmid);
1598 PVE
::Tools
::run_command
($cmd);
1601 my $storecfg = PVE
::Storage
::config
();
1602 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1603 my $volid = $rootinfo->{volume
};
1605 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1607 PVE
::Tools
::run_command
($cmd);
1610 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1611 &$snapshot_commit($vmid, $snapname);
1614 snapshot_delete
($vmid, $snapname, 1);
1619 sub snapshot_delete
{
1620 my ($vmid, $snapname, $force) = @_;
1626 my $updatefn = sub {
1628 $conf = load_config
($vmid);
1630 die "you can't delete a snapshot if vm is a template\n"
1631 if is_template
($conf);
1633 $snap = $conf->{snapshots
}->{$snapname};
1637 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1639 $snap->{snapstate
} = 'delete';
1641 write_config
($vmid, $conf);
1644 lock_container
($vmid, 10, $updatefn);
1646 my $storecfg = PVE
::Storage
::config
();
1648 my $del_snap = sub {
1652 if ($conf->{parent
} eq $snapname) {
1653 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1654 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1656 delete $conf->{parent
};
1660 delete $conf->{snapshots
}->{$snapname};
1662 write_config
($vmid, $conf);
1665 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1666 my $rootinfo = parse_ct_mountpoint
($rootfs);
1667 my $volid = $rootinfo->{volume
};
1670 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1674 if(!$err || ($err && $force)) {
1675 lock_container
($vmid, 10, $del_snap);
1677 die "Can't delete snapshot: $vmid $snapname $err\n";
1682 sub snapshot_rollback
{
1683 my ($vmid, $snapname) = @_;
1685 my $storecfg = PVE
::Storage
::config
();
1687 my $conf = load_config
($vmid);
1689 die "you can't rollback if vm is a template\n" if is_template
($conf);
1691 my $snap = $conf->{snapshots
}->{$snapname};
1693 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1695 my $rootfs = $snap->{rootfs
};
1696 my $rootinfo = parse_ct_mountpoint
($rootfs);
1697 my $volid = $rootinfo->{volume
};
1699 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1701 my $updatefn = sub {
1703 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1704 if $snap->{snapstate
};
1708 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1710 die "unable to rollback vm $vmid: vm is running\n"
1711 if check_running
($vmid);
1713 $conf->{lock} = 'rollback';
1717 # copy snapshot config to current config
1719 my $tmp_conf = $conf;
1720 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1721 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1722 delete $conf->{snaptime
};
1723 delete $conf->{snapname
};
1724 $conf->{parent
} = $snapname;
1726 write_config
($vmid, $conf);
1729 my $unlockfn = sub {
1730 delete $conf->{lock};
1731 write_config
($vmid, $conf);
1734 lock_container
($vmid, 10, $updatefn);
1736 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1738 lock_container
($vmid, 5, $unlockfn);
1741 sub template_create
{
1742 my ($vmid, $conf) = @_;
1744 my $storecfg = PVE
::Storage
::config
();
1746 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1747 my $volid = $rootinfo->{volume
};
1749 die "Template feature is not available for '$volid'\n"
1750 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1752 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1754 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1755 $rootinfo->{volume
} = $template_volid;
1756 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1758 write_config
($vmid, $conf);
1764 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1767 sub mountpoint_names
{
1770 my @names = ('rootfs');
1772 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1773 push @names, "mp$i";
1776 return $reverse ?
reverse @names : @names;
1779 sub foreach_mountpoint_full
{
1780 my ($conf, $reverse, $func) = @_;
1782 foreach my $key (mountpoint_names
($reverse)) {
1783 my $value = $conf->{$key};
1784 next if !defined($value);
1785 my $mountpoint = parse_ct_mountpoint
($value);
1786 $mountpoint->{mp
} = '/' if $key eq 'rootfs'; # just to be sure
1787 &$func($key, $mountpoint);
1791 sub foreach_mountpoint
{
1792 my ($conf, $func) = @_;
1794 foreach_mountpoint_full
($conf, 0, $func);
1797 sub foreach_mountpoint_reverse
{
1798 my ($conf, $func) = @_;
1800 foreach_mountpoint_full
($conf, 1, $func);
1803 sub loopdevices_list
{
1808 if ($line =~ m/^(\/dev\
/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1809 $loopdev->{$1} = $2;
1813 PVE
::Tools
::run_command
(['losetup'], outfunc
=> $parser);
1818 sub blockdevices_list
{
1821 dir_glob_foreach
("/sys/dev/block/", '(\d+):(\d+)', sub {
1822 my (undef, $major, $minor) = @_;
1823 my $bdev = readlink("/sys/dev/block/$major:$minor");
1824 $bdev =~ s/\.\.\/\.\.\/devices\
/virtual\/block\
//\
/dev\//;
1825 $bdevs->{$bdev}->{major
} = $major;
1826 $bdevs->{$bdev}->{minor
} = $minor;
1832 my ($loopdevs, $path) = @_;
1834 foreach my $dev (keys %$loopdevs){
1835 return $dev if $loopdevs->{$dev} eq $path;
1839 sub check_ct_modify_config_perm
{
1840 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1842 return 1 if $authuser ne 'root@pam';
1844 foreach my $opt (@$key_list) {
1846 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1847 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1848 } elsif ($opt eq 'disk') {
1849 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1850 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1851 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1852 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1853 $opt eq 'searchdomain' || $opt eq 'hostname') {
1854 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1856 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1864 my ($storage_cfg, $vollist, $snapname) = @_;
1868 foreach my $volid (@$vollist) {
1870 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1871 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1873 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1874 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1876 if (($format ne 'subvol') &&
1877 ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1878 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1883 $loopdev = $line if $line =~m
|^/dev/loop\d
+$|;
1884 $loopdevs->{$loopdev} = $path;
1887 PVE
::Tools
::run_command
(['losetup', '--find', '--show', $path], outfunc
=> $parser);
1895 my ($storage_cfg, $vollist, $snapname) = @_;
1897 my $loopdevs = loopdevices_list
();
1899 foreach my $volid (@$vollist) {
1901 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1902 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1904 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1905 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1907 if($format ne 'subvol' && ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1908 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1909 foreach my $dev (keys %$loopdevs){
1910 PVE
::Tools
::run_command
(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1917 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1919 my $loopdevs = loopdevices_list
();
1921 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1922 my $volid_list = get_vm_volumes
($conf);
1924 foreach_mountpoint_reverse
($conf, sub {
1925 my ($ms, $mountpoint) = @_;
1927 my $volid = $mountpoint->{volume
};
1928 my $mount = $mountpoint->{mp
};
1930 return if !$volid || !$mount;
1932 $mount_path = "$rootdir/$mount";
1934 # fixme: test if mounted?
1936 PVE
::Tools
::run_command
(['umount', '-d', $mountpoint_path]);
1947 PVE
::LXC
::dettach_loops
($storage_cfg, $volid_list);
1951 my ($vmid, $storage_cfg, $conf, $format_raw_images) = @_;
1953 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1955 my $volid_list = get_vm_volumes
($conf);
1956 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1959 my $loopdevs = attach_loops
($storage_cfg, $volid_list);
1961 foreach_mountpoint
($conf, sub {
1962 my ($ms, $mountpoint) = @_;
1964 my $volid = $mountpoint->{volume
};
1965 my $mount = $mountpoint->{mp
};
1967 return if !$volid || !$mount;
1969 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
1970 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1971 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1973 die "unable to mount base volume - internal error" if $isBase;
1975 if ($format_raw_images && $format eq 'raw') {
1976 my $cmd = ['mkfs.ext4', $image_path];
1977 PVE
::Tools
::run_command
($cmd);
1980 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg, $loopdevs);
1984 warn "mounting container failed - $err";
1985 umount_all
($vmid, $storage_cfg, $conf, 1);
1987 umount_all
($vmid, $storage_cfg, $conf, 0);
1994 sub mountpoint_mount_path
{
1995 my ($mountpoint, $storage_cfg, $loopdevs, $snapname) = @_;
1997 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $loopdevs, $snapname);
2000 # use $rootdir = undef to just return the corresponding mount path
2001 sub mountpoint_mount
{
2002 my ($mountpoint, $rootdir, $storage_cfg, $loopdevs, $snapname) = @_;
2004 my $volid = $mountpoint->{volume
};
2005 my $mount = $mountpoint->{mp
};
2007 return if !$volid || !$mount;
2011 if (defined($rootdir)) {
2012 $rootdir =~ s!/+$!!;
2013 $mount_path = "$rootdir/$mount";
2014 File
::Path
::mkpath
($mount_path);
2017 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2019 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2023 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2024 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2026 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2027 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2029 if ($format eq 'subvol') {
2033 if ($scfg->{type
} eq 'zfspool') {
2034 my $path_arg = $path;
2035 $path_arg =~ s!^/+!!;
2036 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2038 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2041 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2046 } elsif ($format eq 'raw') {
2048 if ($scfg->{path
}) {
2049 $path = find_loopdev
($loopdevs, $path) if $loopdevs;
2050 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
2053 die "unsupported storage type '$scfg->{type}'\n";
2056 if ($isBase || defined($snapname)) {
2057 PVE
::Tools
::run_command
(['mount', '-o', 'ro', $path, $mount_path]);
2059 PVE
::Tools
::run_command
(['mount', $path, $mount_path]);
2064 die "unsupported image format '$format'\n";
2066 } elsif ($volid =~ m
|^/dev/.+|) {
2067 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2069 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2070 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2074 die "unsupported storage";
2077 sub get_vm_volumes
{
2078 my ($conf, $excludes) = @_;
2082 foreach_mountpoint
($conf, sub {
2083 my ($ms, $mountpoint) = @_;
2085 return if $excludes && $ms eq $excludes;
2087 my $volid = $mountpoint->{volume
};
2089 return if !$volid || $volid =~ m
|^/|;
2091 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2094 push @$vollist, $volid;