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);
20 my $nodename = PVE
::INotify
::nodename
();
22 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
24 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
25 sub verify_lxc_network
{
26 my ($value, $noerr) = @_;
28 return $value if parse_lxc_network
($value);
30 return undef if $noerr;
32 die "unable to parse network setting\n";
35 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
36 sub verify_ct_mountpoint
{
37 my ($value, $noerr) = @_;
39 return $value if parse_ct_mountpoint
($value);
41 return undef if $noerr;
43 die "unable to parse CT mountpoint options\n";
46 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
47 type
=> 'string', format
=> 'pve-ct-mountpoint',
48 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
49 description
=> "Use volume as container root.",
57 description
=> "Lock/unlock the VM.",
58 enum
=> [qw(migrate backup snapshot rollback)],
63 description
=> "Specifies whether a VM will be started during system bootup.",
66 startup
=> get_standard_option
('pve-startup-order'),
70 description
=> "Enable/disable Template.",
76 enum
=> ['amd64', 'i386'],
77 description
=> "OS architecture type.",
83 enum
=> ['debian', 'ubuntu', 'centos'],
84 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
89 description
=> "Attach a console device (/dev/console) to the container.",
95 description
=> "Specify the number of tty available to the container",
103 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.",
111 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.",
119 description
=> "Amount of RAM for the VM in MB.",
126 description
=> "Amount of SWAP for the VM in MB.",
132 description
=> "Set a host name for the container.",
139 description
=> "Container description. Only used on the configuration web interface.",
144 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
149 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.",
151 rootfs
=> get_standard_option
('pve-ct-rootfs'),
154 type
=> 'string', format
=> 'pve-configid',
156 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
160 description
=> "Timestamp for snapshots.",
166 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).",
168 enum
=> ['shell', 'console', 'tty'],
173 my $valid_lxc_conf_keys = {
177 'lxc.haltsignal' => 1,
178 'lxc.rebootsignal' => 1,
179 'lxc.stopsignal' => 1,
181 'lxc.network.type' => 1,
182 'lxc.network.flags' => 1,
183 'lxc.network.link' => 1,
184 'lxc.network.mtu' => 1,
185 'lxc.network.name' => 1,
186 'lxc.network.hwaddr' => 1,
187 'lxc.network.ipv4' => 1,
188 'lxc.network.ipv4.gateway' => 1,
189 'lxc.network.ipv6' => 1,
190 'lxc.network.ipv6.gateway' => 1,
191 'lxc.network.script.up' => 1,
192 'lxc.network.script.down' => 1,
194 'lxc.console.logfile' => 1,
197 'lxc.devttydir' => 1,
198 'lxc.hook.autodev' => 1,
202 'lxc.mount.entry' => 1,
203 'lxc.mount.auto' => 1,
205 'lxc.rootfs.mount' => 1,
206 'lxc.rootfs.options' => 1,
210 'lxc.aa_profile' => 1,
211 'lxc.aa_allow_incomplete' => 1,
212 'lxc.se_context' => 1,
215 'lxc.hook.pre-start' => 1,
216 'lxc.hook.pre-mount' => 1,
217 'lxc.hook.mount' => 1,
218 'lxc.hook.start' => 1,
219 'lxc.hook.post-stop' => 1,
220 'lxc.hook.clone' => 1,
221 'lxc.hook.destroy' => 1,
224 'lxc.start.auto' => 1,
225 'lxc.start.delay' => 1,
226 'lxc.start.order' => 1,
228 'lxc.environment' => 1,
235 my $MAX_LXC_NETWORKS = 10;
236 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
237 $confdesc->{"net$i"} = {
239 type
=> 'string', format
=> 'pve-lxc-network',
240 description
=> "Specifies network interfaces for the container.\n\n".
241 "The string should have the follow format:\n\n".
242 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
243 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
244 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
245 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
249 my $MAX_MOUNT_POINTS = 10;
250 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
251 $confdesc->{"mp$i"} = {
253 type
=> 'string', format
=> 'pve-ct-mountpoint',
254 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
255 description
=> "Use volume as container mount point.",
260 sub write_pct_config
{
261 my ($filename, $conf) = @_;
263 delete $conf->{snapstate
}; # just to be sure
265 my $generate_raw_config = sub {
270 # add description as comment to top of file
271 my $descr = $conf->{description
} || '';
272 foreach my $cl (split(/\n/, $descr)) {
273 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
276 foreach my $key (sort keys %$conf) {
277 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
278 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
279 $raw .= "$key: $conf->{$key}\n";
282 if (my $lxcconf = $conf->{lxc
}) {
283 foreach my $entry (@$lxcconf) {
284 my ($k, $v) = @$entry;
292 my $raw = &$generate_raw_config($conf);
294 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
295 $raw .= "\n[$snapname]\n";
296 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
303 my ($key, $value) = @_;
305 die "unknown setting '$key'\n" if !$confdesc->{$key};
307 my $type = $confdesc->{$key}->{type
};
309 if (!defined($value)) {
310 die "got undefined value\n";
313 if ($value =~ m/[\n\r]/) {
314 die "property contains a line feed\n";
317 if ($type eq 'boolean') {
318 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
319 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
320 die "type check ('boolean') failed - got '$value'\n";
321 } elsif ($type eq 'integer') {
322 return int($1) if $value =~ m/^(\d+)$/;
323 die "type check ('integer') failed - got '$value'\n";
324 } elsif ($type eq 'number') {
325 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
326 die "type check ('number') failed - got '$value'\n";
327 } elsif ($type eq 'string') {
328 if (my $fmt = $confdesc->{$key}->{format
}) {
329 PVE
::JSONSchema
::check_format
($fmt, $value);
338 sub parse_pct_config
{
339 my ($filename, $raw) = @_;
341 return undef if !defined($raw);
344 digest
=> Digest
::SHA
::sha1_hex
($raw),
348 $filename =~ m
|/lxc/(\d
+).conf
$|
349 || die "got strange filename '$filename'";
357 my @lines = split(/\n/, $raw);
358 foreach my $line (@lines) {
359 next if $line =~ m/^\s*$/;
361 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
363 $conf->{description
} = $descr if $descr;
365 $conf = $res->{snapshots
}->{$section} = {};
369 if ($line =~ m/^\#(.*)\s*$/) {
370 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
374 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
377 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
378 push @{$conf->{lxc
}}, [$key, $value];
380 warn "vm $vmid - unable to parse config: $line\n";
382 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
383 $descr .= PVE
::Tools
::decode_text
($2);
384 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
385 $conf->{snapstate
} = $1;
386 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
389 eval { $value = check_type
($key, $value); };
390 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
391 $conf->{$key} = $value;
393 warn "vm $vmid - unable to parse config: $line\n";
397 $conf->{description
} = $descr if $descr;
399 delete $res->{snapstate
}; # just to be sure
405 my $vmlist = PVE
::Cluster
::get_vmlist
();
407 return $res if !$vmlist || !$vmlist->{ids
};
408 my $ids = $vmlist->{ids
};
410 foreach my $vmid (keys %$ids) {
411 next if !$vmid; # skip CT0
412 my $d = $ids->{$vmid};
413 next if !$d->{node
} || $d->{node
} ne $nodename;
414 next if !$d->{type
} || $d->{type
} ne 'lxc';
415 $res->{$vmid}->{type
} = 'lxc';
420 sub cfs_config_path
{
421 my ($vmid, $node) = @_;
423 $node = $nodename if !$node;
424 return "nodes/$node/lxc/$vmid.conf";
428 my ($vmid, $node) = @_;
430 my $cfspath = cfs_config_path
($vmid, $node);
431 return "/etc/pve/$cfspath";
437 my $cfspath = cfs_config_path
($vmid);
439 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
440 die "container $vmid does not exists\n" if !defined($conf);
446 my ($vmid, $conf) = @_;
448 my $dir = "/etc/pve/nodes/$nodename/lxc";
451 write_config
($vmid, $conf);
457 unlink config_file
($vmid, $nodename);
461 my ($vmid, $conf) = @_;
463 my $cfspath = cfs_config_path
($vmid);
465 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
468 # flock: we use one file handle per process, so lock file
469 # can be called multiple times and succeeds for the same process.
471 my $lock_handles = {};
472 my $lockdir = "/run/lock/lxc";
477 return "$lockdir/pve-config-{$vmid}.lock";
481 my ($vmid, $timeout) = @_;
483 $timeout = 10 if !$timeout;
486 my $filename = lock_filename
($vmid);
488 mkdir $lockdir if !-d
$lockdir;
490 my $lock_func = sub {
491 if (!$lock_handles->{$$}->{$filename}) {
492 my $fh = new IO
::File
(">>$filename") ||
493 die "can't open file - $!\n";
494 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
497 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
498 print STDERR
"trying to aquire lock...";
501 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
502 # try again on EINTR (see bug #273)
503 if ($success || ($! != EINTR
)) {
508 print STDERR
" failed\n";
509 die "can't aquire lock - $!\n";
512 $lock_handles->{$$}->{$filename}->{refcount
}++;
514 print STDERR
" OK\n";
518 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
521 die "can't lock file '$filename' - $err";
528 my $filename = lock_filename
($vmid);
530 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
531 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
532 if ($refcount <= 0) {
533 $lock_handles->{$$}->{$filename} = undef;
540 my ($vmid, $timeout, $code, @param) = @_;
544 lock_aquire
($vmid, $timeout);
545 eval { $res = &$code(@param) };
557 return defined($confdesc->{$name});
560 # add JSON properties for create and set function
561 sub json_config_properties
{
564 foreach my $opt (keys %$confdesc) {
565 next if $opt eq 'parent' || $opt eq 'snaptime';
566 next if $prop->{$opt};
567 $prop->{$opt} = $confdesc->{$opt};
573 sub json_config_properties_no_rootfs
{
576 foreach my $opt (keys %$confdesc) {
577 next if $prop->{$opt};
578 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
579 $prop->{$opt} = $confdesc->{$opt};
585 # container status helpers
587 sub list_active_containers
{
589 my $filename = "/proc/net/unix";
591 # similar test is used by lcxcontainers.c: list_active_containers
594 my $fh = IO
::File-
>new ($filename, "r");
597 while (defined(my $line = <$fh>)) {
598 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
600 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
611 # warning: this is slow
615 my $active_hash = list_active_containers
();
617 return 1 if defined($active_hash->{$vmid});
622 sub get_container_disk_usage
{
625 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
635 if (my ($fsid, $total, $used, $avail) = $line =~
636 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
644 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
653 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
655 my $active_hash = list_active_containers
();
657 foreach my $vmid (keys %$list) {
658 my $d = $list->{$vmid};
660 my $running = defined($active_hash->{$vmid});
662 $d->{status
} = $running ?
'running' : 'stopped';
664 my $cfspath = cfs_config_path
($vmid);
665 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
667 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
668 $d->{name
} =~ s/[\s]//g;
670 $d->{cpus
} = $conf->{cpulimit
} // 0;
673 my $res = get_container_disk_usage
($vmid);
674 $d->{disk
} = $res->{used
};
675 $d->{maxdisk
} = $res->{total
};
678 # use 4GB by default ??
679 if (my $rootfs = $conf->{rootfs
}) {
680 my $rootinfo = parse_ct_mountpoint
($rootfs);
681 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
683 $d->{maxdisk
} = 4*1024*1024*1024;
689 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
690 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
701 $d->{template
} = is_template
($conf);
704 foreach my $vmid (keys %$list) {
705 my $d = $list->{$vmid};
706 next if $d->{status
} ne 'running';
708 $d->{uptime
} = 100; # fixme:
710 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
711 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
713 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
714 my @bytes = split(/\n/, $blkio_bytes);
715 foreach my $byte (@bytes) {
716 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
717 $d->{diskread
} = $2 if $key eq 'Read';
718 $d->{diskwrite
} = $2 if $key eq 'Write';
726 my $parse_size = sub {
729 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
730 my ($size, $unit) = ($1, $3);
733 $size = $size * 1024;
734 } elsif ($unit eq 'M') {
735 $size = $size * 1024 * 1024;
736 } elsif ($unit eq 'G') {
737 $size = $size * 1024 * 1024 * 1024;
743 sub parse_ct_mountpoint
{
750 foreach my $p (split (/,/, $data)) {
751 next if $p =~ m/^\s*$/;
753 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
754 my ($k, $v) = ($1, $2);
755 return undef if defined($res->{$k});
758 if (!$res->{volume
} && $p !~ m/=/) {
766 return undef if !$res->{volume
};
768 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
771 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
777 sub print_ct_mountpoint
{
782 die "missing volume\n" if !$info->{volume
};
784 foreach my $o ('size', 'backup') {
785 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
788 return "$info->{volume}$opts";
791 sub print_lxc_network
{
794 die "no network name defined\n" if !$net->{name
};
796 my $res = "name=$net->{name}";
798 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
799 next if !defined($net->{$k});
800 $res .= ",$k=$net->{$k}";
806 sub parse_lxc_network
{
811 return $res if !$data;
813 foreach my $pv (split (/,/, $data)) {
814 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
821 $res->{type
} = 'veth';
822 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
827 sub read_cgroup_value
{
828 my ($group, $vmid, $name, $full) = @_;
830 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
832 return PVE
::Tools
::file_get_contents
($path) if $full;
834 return PVE
::Tools
::file_read_firstline
($path);
837 sub write_cgroup_value
{
838 my ($group, $vmid, $name, $value) = @_;
840 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
841 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
845 sub find_lxc_console_pids
{
849 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
852 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
855 my @args = split(/\0/, $cmdline);
857 # serach for lxc-console -n <vmid>
858 return if scalar(@args) != 3;
859 return if $args[1] ne '-n';
860 return if $args[2] !~ m/^\d+$/;
861 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
865 push @{$res->{$vmid}}, $pid;
877 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
879 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
881 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
886 my $ipv4_reverse_mask = [
922 # Note: we cannot use Net:IP, because that only allows strict
924 sub parse_ipv4_cidr
{
925 my ($cidr, $noerr) = @_;
927 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
928 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
931 return undef if $noerr;
933 die "unable to parse ipv4 address/mask\n";
939 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
942 sub update_lxc_config
{
943 my ($storage_cfg, $vmid, $conf) = @_;
945 my $dir = "/var/lib/lxc/$vmid";
947 if ($conf->{template
}) {
949 unlink "$dir/config";
956 die "missing 'arch' - internal error" if !$conf->{arch
};
957 $raw .= "lxc.arch = $conf->{arch}\n";
959 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
960 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
961 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
966 if (!has_dev_console
($conf)) {
967 $raw .= "lxc.console = none\n";
968 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
971 my $ttycount = get_tty_count
($conf);
972 $raw .= "lxc.tty = $ttycount\n";
974 my $utsname = $conf->{hostname
} || "CT$vmid";
975 $raw .= "lxc.utsname = $utsname\n";
977 my $memory = $conf->{memory
} || 512;
978 my $swap = $conf->{swap
} // 0;
980 my $lxcmem = int($memory*1024*1024);
981 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
983 my $lxcswap = int(($memory + $swap)*1024*1024);
984 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
986 if (my $cpulimit = $conf->{cpulimit
}) {
987 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
988 my $value = int(100000*$cpulimit);
989 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
992 my $shares = $conf->{cpuunits
} || 1024;
993 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
995 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
996 my $volid = $rootinfo->{volume
};
997 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
999 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1000 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1002 die "unable to use template as rootfs\n" if $isBase;
1004 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1005 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1007 if ($format eq 'subvol') {
1008 $raw .= "lxc.rootfs = $path\n";
1009 } elsif ($format eq 'raw') {
1010 if ($scfg->{path
}) {
1011 $raw .= "lxc.rootfs = loop:$path\n";
1012 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1013 $raw .= "lxc.rootfs = $path\n";
1015 die "unsupported storage type '$scfg->{type}'\n";
1018 die "unsupported image format '$format'\n";
1022 foreach my $k (keys %$conf) {
1023 next if $k !~ m/^net(\d+)$/;
1025 my $d = parse_lxc_network
($conf->{$k});
1027 $raw .= "lxc.network.type = veth\n";
1028 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1029 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1030 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1031 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1034 if (my $lxcconf = $conf->{lxc
}) {
1035 foreach my $entry (@$lxcconf) {
1036 my ($k, $v) = @$entry;
1037 $netcount++ if $k eq 'lxc.network.type';
1038 $raw .= "$k = $v\n";
1042 $raw .= "lxc.network.type = empty\n" if !$netcount;
1044 File
::Path
::mkpath
("$dir/rootfs");
1046 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1049 # verify and cleanup nameserver list (replace \0 with ' ')
1050 sub verify_nameserver_list
{
1051 my ($nameserver_list) = @_;
1054 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1055 PVE
::JSONSchema
::pve_verify_ip
($server);
1056 push @list, $server;
1059 return join(' ', @list);
1062 sub verify_searchdomain_list
{
1063 my ($searchdomain_list) = @_;
1066 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1067 # todo: should we add checks for valid dns domains?
1068 push @list, $server;
1071 return join(' ', @list);
1074 sub update_pct_config
{
1075 my ($vmid, $conf, $running, $param, $delete) = @_;
1081 my $pid = find_lxc_pid
($vmid);
1082 $rootdir = "/proc/$pid/root";
1085 if (defined($delete)) {
1086 foreach my $opt (@$delete) {
1087 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1088 die "unable to delete required option '$opt'\n";
1089 } elsif ($opt eq 'swap') {
1090 delete $conf->{$opt};
1091 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1092 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1093 delete $conf->{$opt};
1094 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1095 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1096 delete $conf->{$opt};
1097 push @nohotplug, $opt;
1099 } elsif ($opt =~ m/^net(\d)$/) {
1100 delete $conf->{$opt};
1103 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1107 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1111 # There's no separate swap size to configure, there's memory and "total"
1112 # memory (iow. memory+swap). This means we have to change them together.
1113 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1114 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1115 if (defined($wanted_memory) || defined($wanted_swap)) {
1117 $wanted_memory //= ($conf->{memory
} || 512);
1118 $wanted_swap //= ($conf->{swap
} || 0);
1120 my $total = $wanted_memory + $wanted_swap;
1122 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1123 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1125 $conf->{memory
} = $wanted_memory;
1126 $conf->{swap
} = $wanted_swap;
1128 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1131 foreach my $opt (keys %$param) {
1132 my $value = $param->{$opt};
1133 if ($opt eq 'hostname') {
1134 $conf->{$opt} = $value;
1135 } elsif ($opt eq 'onboot') {
1136 $conf->{$opt} = $value ?
1 : 0;
1137 } elsif ($opt eq 'startup') {
1138 $conf->{$opt} = $value;
1139 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1140 $conf->{$opt} = $value;
1141 push @nohotplug, $opt;
1143 } elsif ($opt eq 'nameserver') {
1144 my $list = verify_nameserver_list
($value);
1145 $conf->{$opt} = $list;
1146 push @nohotplug, $opt;
1148 } elsif ($opt eq 'searchdomain') {
1149 my $list = verify_searchdomain_list
($value);
1150 $conf->{$opt} = $list;
1151 push @nohotplug, $opt;
1153 } elsif ($opt eq 'cpulimit') {
1154 $conf->{$opt} = $value;
1155 push @nohotplug, $opt; # fixme: hotplug
1157 } elsif ($opt eq 'cpuunits') {
1158 $conf->{$opt} = $value;
1159 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1160 } elsif ($opt eq 'description') {
1161 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1162 } elsif ($opt =~ m/^net(\d+)$/) {
1164 my $net = parse_lxc_network
($value);
1166 $conf->{$opt} = print_lxc_network
($net);
1168 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1171 die "implement me: $opt";
1173 PVE
::LXC
::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 = PVE
::LXC
::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) = @_;
1266 PVE
::LXC
::foreach_mountpoint
($conf, sub {
1267 my ($ms, $mountpoint) = @_;
1268 PVE
::Storage
::deactivate_volumes
($storage_cfg, [$mountpoint->{volume
}]);
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
::LXCSetup-
>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;