]>
git.proxmox.com Git - pve-storage.git/blob - PVE/Storage.pm
14 use Getopt
::Long
qw(GetOptionsFromArray);
19 use PVE
::Tools
qw(run_command file_read_firstline trim);
20 use PVE
::Cluster
qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
21 use PVE
::Exception
qw(raise_param_exc);
25 my $ISCSIADM = '/usr/bin/iscsiadm';
26 my $UDEVADM = '/sbin/udevadm';
28 $ISCSIADM = undef if ! -X
$ISCSIADM;
30 # fixme: always_call_parser => 1 ??
31 cfs_register_file
('storage.cfg',
35 # generic utility function
38 return cfs_read_file
("storage.cfg");
41 sub check_iscsi_support
{
45 my $msg = "no iscsi support - please install open-iscsi";
47 warn "warning: $msg\n";
57 sub load_stable_scsi_paths
{
59 my $stable_paths = {};
61 my $stabledir = "/dev/disk/by-id";
63 if (my $dh = IO
::Dir-
>new($stabledir)) {
64 while (defined(my $tmp = $dh->read)) {
65 # exclude filenames with part in name (same disk but partitions)
66 # use only filenames with scsi(with multipath i have the same device
67 # with dm-uuid-mpath , dm-name and scsi in name)
68 if($tmp !~ m/-part\d+$/ && $tmp =~ m/^scsi-/) {
69 my $path = "$stabledir/$tmp";
70 my $bdevdest = readlink($path);
71 if ($bdevdest && $bdevdest =~ m
|^../../([^/]+)|) {
72 $stable_paths->{$1}=$tmp;
82 my ($dir, $regex) = @_;
84 my $dh = IO
::Dir-
>new ($dir);
85 return wantarray ?
() : undef if !$dh;
87 while (defined(my $tmp = $dh->read)) {
88 if (my @res = $tmp =~ m/^($regex)$/) {
90 return wantarray ?
@res : $tmp;
95 return wantarray ?
() : undef;
98 sub dir_glob_foreach
{
99 my ($dir, $regex, $func) = @_;
101 my $dh = IO
::Dir-
>new ($dir);
103 while (defined(my $tmp = $dh->read)) {
104 if (my @res = $tmp =~ m/^($regex)$/) {
111 sub read_proc_mounts
{
113 local $/; # enable slurp mode
116 if (my $fd = IO
::File-
>new ("/proc/mounts", "r")) {
124 # PVE::Storage utility functions
126 sub lock_storage_config
{
127 my ($code, $errmsg) = @_;
129 cfs_lock_file
("storage.cfg", undef, $code);
132 $errmsg ?
die "$errmsg: $err" : die $err;
141 content
=> 'content',
149 options
=> 'options',
152 my $required_config = {
154 nfs
=> ['path', 'server', 'export'],
156 iscsi
=> ['portal', 'target'],
161 nfs
=> ['path', 'server', 'export'],
162 lvm
=> ['vgname', 'base'],
163 iscsi
=> ['portal', 'target'],
166 my $default_config = {
172 content
=> [ { images
=> 1, rootdir
=> 1, vztmpl
=> 1, iso
=> 1, backup
=> 1, none
=> 1 },
173 { images
=> 1, rootdir
=> 1 }],
174 format
=> [ { raw
=> 1, qcow2
=> 1, vmdk
=> 1 } , 'raw' ],
184 content
=> [ { images
=> 1, iso
=> 1, backup
=> 1},
186 format
=> [ { raw
=> 1, qcow2
=> 1, vmdk
=> 1 } , 'raw' ],
194 content
=> [ {images
=> 1}, { images
=> 1 }],
203 content
=> [ {images
=> 1, none
=> 1}, { images
=> 1 }],
207 sub valid_content_types
{
210 my $def = $default_config->{$stype};
214 return $def->{content
}->[0];
217 sub content_hash_to_string
{
221 foreach my $ct (keys %$hash) {
222 push @cta, $ct if $hash->{$ct};
225 return join(',', @cta);
228 PVE
::JSONSchema
::register_format
('pve-storage-path', \
&verify_path
);
230 my ($path, $noerr) = @_;
232 # fixme: exclude more shell meta characters?
233 # we need absolute paths
234 if ($path !~ m
|^/[^;\
(\
)]+|) {
235 return undef if $noerr;
236 die "value does not look like a valid absolute path\n";
241 PVE
::JSONSchema
::register_format
('pve-storage-server', \
&verify_server
);
243 my ($server, $noerr) = @_;
245 # fixme: use better regex ?
247 if ($server !~ m/^[[:alnum:]\-\.]+$/) {
248 return undef if $noerr;
249 die "value does not look like a valid server name or IP address\n";
254 PVE
::JSONSchema
::register_format
('pve-storage-portal', \
&verify_portal
);
256 my ($portal, $noerr) = @_;
258 # IP with optional port
259 if ($portal !~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/) {
260 return undef if $noerr;
261 die "value does not look like a valid portal address\n";
266 PVE
::JSONSchema
::register_format
('pve-storage-portal-dns', \
&verify_portal_dns
);
267 sub verify_portal_dns
{
268 my ($portal, $noerr) = @_;
270 # IP or DNS name with optional port
271 if ($portal !~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[[:alnum:]\-\.]+)(:\d+)?$/) {
272 return undef if $noerr;
273 die "value does not look like a valid portal address\n";
278 PVE
::JSONSchema
::register_format
('pve-storage-content', \
&verify_content
);
280 my ($ct, $noerr) = @_;
282 my $valid_content = valid_content_types
('dir'); # dir includes all types
284 if (!$valid_content->{$ct}) {
285 return undef if $noerr;
286 die "invalid content type '$ct'\n";
292 PVE
::JSONSchema
::register_format
('pve-storage-format', \
&verify_format
);
294 my ($fmt, $noerr) = @_;
296 if ($fmt !~ m/(raw|qcow2|vmdk)/) {
297 return undef if $noerr;
298 die "invalid format '$fmt'\n";
304 PVE
::JSONSchema
::register_format
('pve-storage-options', \
&verify_options
);
306 my ($value, $noerr) = @_;
308 # mount options (see man fstab)
309 if ($value !~ m/^\S+$/) {
310 return undef if $noerr;
311 die "invalid options '$value'\n";
318 my ($stype, $ct, $key, $value, $storeid, $noerr) = @_;
320 my $def = $default_config->{$stype};
322 if (!$def) { # should not happen
323 return undef if $noerr;
324 die "unknown storage type '$stype'\n";
327 if (!defined($def->{$key})) {
328 return undef if $noerr;
329 die "unexpected property\n";
332 if (!defined ($value)) {
333 return undef if $noerr;
334 die "got undefined value\n";
337 if ($value =~ m/[\n\r]/) {
338 return undef if $noerr;
339 die "property contains a line feed\n";
343 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
344 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
345 return undef if $noerr;
346 die "type check ('boolean') failed - got '$value'\n";
347 } elsif ($ct eq 'options') {
348 return verify_options
($value, $noerr);
349 } elsif ($ct eq 'path') {
350 return verify_path
($value, $noerr);
351 } elsif ($ct eq 'server') {
352 return verify_server
($value, $noerr);
353 } elsif ($ct eq 'vgname') {
354 return parse_lvm_name
($value, $noerr);
355 } elsif ($ct eq 'portal') {
356 return verify_portal
($value, $noerr);
357 } elsif ($ct eq 'nodes') {
360 foreach my $node (PVE
::Tools
::split_list
($value)) {
361 if (PVE
::JSONSchema
::pve_verify_node_name
($node, $noerr)) {
366 # no node restrictions for local storage
367 if ($storeid && $storeid eq 'local' && scalar(keys(%$res))) {
368 return undef if $noerr;
369 die "storage '$storeid' does not allow node restrictions\n";
373 } elsif ($ct eq 'target') {
375 } elsif ($ct eq 'string') {
377 } elsif ($ct eq 'format') {
378 my $valid_formats = $def->{format
}->[0];
380 if (!$valid_formats->{$value}) {
381 return undef if $noerr;
382 die "storage does not support format '$value'\n";
387 } elsif ($ct eq 'content') {
388 my $valid_content = $def->{content
}->[0];
392 foreach my $c (PVE
::Tools
::split_list
($value)) {
393 if (!$valid_content->{$c}) {
394 return undef if $noerr;
395 die "storage does not support content type '$c'\n";
400 # only local storage may have several content types
401 if ($res->{none
} || !($storeid && $storeid eq 'local')) {
402 if (scalar (keys %$res) > 1) {
403 return undef if $noerr;
404 die "storage does not support multiple content types\n";
408 # no backup to local storage
409 if ($storeid && $storeid eq 'local' && $res->{backup
}) {
410 return undef if $noerr;
411 die "storage 'local' does not support backups\n";
415 } elsif ($ct eq 'volume') {
416 return $value if parse_volume_id
($value, $noerr);
419 return undef if $noerr;
420 die "type check not implemented - internal error\n";
424 my ($filename, $raw) = @_;
428 my $sha1 = Digest
::SHA1-
>new;
432 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
435 $sha1->add ($line); # compute digest
437 next if $line =~ m/^\#/;
438 next if $line =~ m/^\s*$/;
440 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
445 if (!parse_storage_id
($storeid, 1)) {
447 warn "ignoring storage '$storeid' - (illegal characters)\n";
448 } elsif (!$default_config->{$type}) {
450 warn "ignoring storage '$storeid' (unsupported type '$type')\n";
452 $ids->{$storeid}->{type
} = $type;
453 $ids->{$storeid}->{priority
} = $pri++;
456 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
459 next if $line =~ m/^\#/;
460 last if $line =~ m/^\s*$/;
462 next if $ignore; # skip
464 if ($line =~ m/^\s+(\S+)(\s+(.*\S))?\s*$/) {
465 my ($k, $v) = ($1, $3);
466 if (my $ct = $confvars->{$k}) {
467 $v = 1 if $ct eq 'bool' && !defined($v);
469 $ids->{$storeid}->{$k} = check_type
($type, $ct, $k, $v, $storeid);
471 warn "storage '$storeid' - unable to parse value of '$k': $@" if $@;
473 warn "storage '$storeid' - unable to parse value of '$k'\n";
477 warn "storage '$storeid' - ignore config line: $line\n";
481 warn "ignore config line: $line\n";
485 # make sure we have a reasonable 'local:' storage
486 # openvz expects things to be there
487 if (!$ids->{local} || $ids->{local}->{type
} ne 'dir' ||
488 $ids->{local}->{path
} ne '/var/lib/vz') {
492 path
=> '/var/lib/vz',
493 content
=> { images
=> 1, rootdir
=> 1, vztmpl
=> 1, iso
=> 1},
497 # we always need this for OpenVZ
498 $ids->{local}->{content
}->{rootdir
} = 1;
499 $ids->{local}->{content
}->{vztmpl
} = 1;
500 delete ($ids->{local}->{disable
});
502 # remove node restrictions for local storage
503 delete($ids->{local}->{nodes
});
505 foreach my $storeid (keys %$ids) {
506 my $d = $ids->{$storeid};
508 my $req_keys = $required_config->{$d->{type
}};
509 foreach my $k (@$req_keys) {
510 if (!defined ($d->{$k})) {
511 warn "ignoring storage '$storeid' - missing value " .
512 "for required option '$k'\n";
513 delete $ids->{$storeid};
518 my $def = $default_config->{$d->{type
}};
520 if ($def->{content
}) {
521 $d->{content
} = $def->{content
}->[1] if !$d->{content
};
524 if ($d->{type
} eq 'iscsi' || $d->{type
} eq 'nfs') {
529 my $digest = $sha1->hexdigest;
531 my $cfg = { ids
=> $ids, digest
=> $digest};
537 my ($storeid, $stype, $param, $create) = @_;
539 my $settings = { type
=> $stype };
541 die "unknown storage type '$stype'\n"
542 if !$default_config->{$stype};
544 foreach my $opt (keys %$param) {
545 my $value = $param->{$opt};
547 my $ct = $confvars->{$opt};
548 if (defined($value)) {
550 $settings->{$opt} = check_type
($stype, $ct, $opt, $value, $storeid);
552 raise_param_exc
({ $opt => $@ }) if $@;
554 raise_param_exc
({ $opt => "got undefined value" });
559 my $req_keys = $required_config->{$stype};
560 foreach my $k (@$req_keys) {
562 if ($stype eq 'nfs' && !$settings->{path
}) {
563 $settings->{path
} = "/mnt/pve/$storeid";
566 # check if we have a value for all required options
567 if (!defined ($settings->{$k})) {
568 raise_param_exc
({ $k => "property is missing and it is not optional" });
572 my $fixed_keys = $fixed_config->{$stype};
573 foreach my $k (@$fixed_keys) {
575 # only allow to change non-fixed values
577 if (defined ($settings->{$k})) {
578 raise_param_exc
({$k => "can't change value (fixed parameter)"});
586 sub cluster_lock_storage
{
587 my ($storeid, $shared, $timeout, $func, @param) = @_;
591 my $lockid = "pve-storage-$storeid";
592 my $lockdir = "/var/lock/pve-manager";
594 $res = PVE
::Tools
::lock_file
("$lockdir/$lockid", $timeout, $func, @param);
597 $res = PVE
::Cluster
::cfs_lock_storage
($storeid, $timeout, $func, @param);
604 my ($cfg, $storeid, $noerr) = @_;
606 die "no storage id specified\n" if !$storeid;
608 my $scfg = $cfg->{ids
}->{$storeid};
610 die "storage '$storeid' does not exists\n" if (!$noerr && !$scfg);
615 sub storage_check_node
{
616 my ($cfg, $storeid, $node, $noerr) = @_;
618 my $scfg = storage_config
($cfg, $storeid);
620 if ($scfg->{nodes
}) {
621 $node = PVE
::INotify
::nodename
() if !$node || ($node eq 'localhost');
622 if (!$scfg->{nodes
}->{$node}) {
623 die "storage '$storeid' is not available on node '$node'" if !$noerr;
631 sub storage_check_enabled
{
632 my ($cfg, $storeid, $node, $noerr) = @_;
634 my $scfg = storage_config
($cfg, $storeid);
636 if ($scfg->{disable
}) {
637 die "storage '$storeid' is disabled\n" if !$noerr;
641 return storage_check_node
($cfg, $storeid, $node, $noerr);
647 my $ids = $cfg->{ids
};
649 my @sa = sort {$ids->{$a}->{priority
} <=> $ids->{$b}->{priority
}} keys %$ids;
654 sub assert_if_modified
{
655 my ($cfg, $digest) = @_;
657 if ($digest && ($cfg->{digest
} ne $digest)) {
658 die "detected modified storage configuration - try again\n";
662 sub sprint_config_line
{
665 my $ct = $confvars->{$k};
668 return $v ?
"\t$k\n" : '';
669 } elsif ($ct eq 'nodes') {
670 my $nlist = join(',', keys(%$v));
671 return $nlist ?
"\tnodes $nlist\n" : '';
672 } elsif ($ct eq 'content') {
673 my $clist = content_hash_to_string
($v);
675 return "\t$k $clist\n";
677 return "\t$k none\n";
685 my ($filename, $cfg) = @_;
689 my $ids = $cfg->{ids
};
692 foreach my $storeid (keys %$ids) {
693 my $pri = $ids->{$storeid}->{priority
};
694 $maxpri = $pri if $pri && $pri > $maxpri;
696 foreach my $storeid (keys %$ids) {
697 if (!defined ($ids->{$storeid}->{priority
})) {
698 $ids->{$storeid}->{priority
} = ++$maxpri;
702 foreach my $storeid (sort {$ids->{$a}->{priority
} <=> $ids->{$b}->{priority
}} keys %$ids) {
703 my $scfg = $ids->{$storeid};
704 my $type = $scfg->{type
};
705 my $def = $default_config->{$type};
707 die "unknown storage type '$type'\n" if !$def;
709 my $data = "$type: $storeid\n";
711 $data .= "\tdisable\n" if $scfg->{disable
};
713 my $done_hash = { disable
=> 1};
714 foreach my $k (@{$required_config->{$type}}) {
715 $done_hash->{$k} = 1;
716 my $v = $ids->{$storeid}->{$k};
717 die "storage '$storeid' - missing value for required option '$k'\n"
719 $data .= sprint_config_line
($k, $v);
722 foreach my $k (keys %$def) {
723 next if defined ($done_hash->{$k});
724 if (defined (my $v = $ids->{$storeid}->{$k})) {
725 $data .= sprint_config_line
($k, $v);
736 my ($cfg, $storeid, $vmid) = @_;
738 my $path = $cfg->{ids
}->{$storeid}->{path
};
739 return $vmid ?
"$path/images/$vmid" : "$path/images";
743 my ($cfg, $storeid) = @_;
745 my $isodir = $cfg->{ids
}->{$storeid}->{path
};
746 $isodir .= '/template/iso' if $storeid eq 'local';
752 my ($cfg, $storeid) = @_;
754 my $tmpldir = $cfg->{ids
}->{$storeid}->{path
};
755 $tmpldir .= '/template/cache' if $storeid eq 'local';
760 # iscsi utility functions
762 sub iscsi_session_list
{
764 check_iscsi_support
();
766 my $cmd = [$ISCSIADM, '--mode', 'session'];
770 run_command
($cmd, outfunc
=> sub {
773 if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)\s*$/) {
774 my ($session, $target) = ($1, $2);
775 # there can be several sessions per target (multipath)
776 push @{$res->{$target}}, $session;
784 sub iscsi_test_portal
{
787 my ($server, $port) = split(':', $portal);
788 my $p = Net
::Ping-
>new("tcp", 2);
789 $p->port_number($port || 3260);
790 return $p->ping($server);
793 sub iscsi_discovery
{
796 check_iscsi_support
();
798 my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets',
799 '--portal', $portal];
803 return $res if !iscsi_test_portal
($portal); # fixme: raise exception here?
805 run_command
($cmd, outfunc
=> sub {
808 if ($line =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+)\,\S+\s+(\S+)\s*$/) {
811 # one target can have more than one portal (multipath).
812 push @{$res->{$target}}, $portal;
820 my ($target, $portal_in) = @_;
822 check_iscsi_support
();
824 eval { iscsi_discovery
($portal_in); };
827 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login'];
832 my ($target, $portal) = @_;
834 check_iscsi_support
();
836 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--logout'];
840 my $rescan_filename = "/var/run/pve-iscsi-rescan.lock";
842 sub iscsi_session_rescan
{
843 my $session_list = shift;
845 check_iscsi_support
();
847 my $rstat = stat ($rescan_filename);
850 if (my $fh = IO
::File-
>new ($rescan_filename, "a")) {
851 utime undef, undef, $fh;
855 my $atime = $rstat->atime;
856 my $tdiff = time() - $atime;
857 # avoid frequent rescans
858 return if !($tdiff < 0 || $tdiff > 10);
859 utime undef, undef, $rescan_filename;
862 foreach my $session (@$session_list) {
863 my $cmd = [$ISCSIADM, '--mode', 'session', '-r', $session, '-R'];
864 eval { run_command
($cmd, outfunc
=> sub {}); };
869 sub iscsi_device_list
{
873 my $dirname = '/sys/class/iscsi_session';
875 my $stable_paths = load_stable_scsi_paths
();
877 dir_glob_foreach
($dirname, 'session(\d+)', sub {
878 my ($ent, $session) = @_;
880 my $target = file_read_firstline
("$dirname/$ent/targetname");
883 my (undef, $host) = dir_glob_regex
("$dirname/$ent/device", 'target(\d+):.*');
884 return if !defined($host);
886 dir_glob_foreach
("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
887 my ($tmp, $channel, $id, $lun) = @_;
889 my $type = file_read_firstline
("/sys/bus/scsi/devices/$tmp/type");
890 return if !defined($type) || $type ne '0'; # list disks only
893 if (-d
"/sys/bus/scsi/devices/$tmp/block") { # newer kernels
894 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
896 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
901 if (-d
"/sys/block/$bdev/holders") {
902 my $multipathdev = dir_glob_regex
("/sys/block/$bdev/holders", '[A-Za-z]\S*');
903 $bdev = $multipathdev if $multipathdev;
906 my $blockdev = $stable_paths->{$bdev};
907 return if !$blockdev;
909 my $size = file_read_firstline
("/sys/block/$bdev/size");
912 my $volid = "$channel.$id.$lun.$blockdev";
914 $res->{$target}->{$volid} = {
916 'size' => int($size / 2),
917 'vmid' => 0, # not assigned to any vm
918 'channel' => int($channel),
923 #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
931 # library implementation
934 PVE
::JSONSchema
::register_format
('pve-storage-id', \
&parse_storage_id
);
935 sub parse_storage_id
{
936 my ($storeid, $noerr) = @_;
938 if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
939 return undef if $noerr;
940 die "storage ID '$storeid' contains illegal characters\n";
945 PVE
::JSONSchema
::register_standard_option
('pve-storage-id', {
946 description
=> "The storage identifier.",
947 type
=> 'string', format
=> 'pve-storage-id',
950 PVE
::JSONSchema
::register_format
('pve-storage-vgname', \
&parse_lvm_name
);
952 my ($name, $noerr) = @_;
954 if ($name !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
955 return undef if $noerr;
956 die "lvm name '$name' contains illegal characters\n";
965 die "VMID '$vmid' contains illegal characters\n" if $vmid !~ m/^\d+$/;
970 PVE
::JSONSchema
::register_format
('pve-volume-id', \
&parse_volume_id
);
971 sub parse_volume_id
{
972 my ($volid, $noerr) = @_;
974 if ($volid =~ m/^([a-z][a-z0-9\-\_\.]*[a-z0-9]):(.+)$/i) {
975 return wantarray ?
($1, $2) : $1;
977 return undef if $noerr;
978 die "unable to parse volume ID '$volid'\n";
984 if ($name =~ m!^([^/\s]+\.(raw|qcow2|vmdk))$!) {
988 die "unable to parse volume filename '$name'\n";
991 sub parse_volname_dir
{
994 if ($volname =~ m!^(\d+)/(\S+)$!) {
995 my ($vmid, $name) = ($1, $2);
996 parse_name_dir
($name);
997 return ('image', $name, $vmid);
998 } elsif ($volname =~ m!^iso/([^/]+\.[Ii][Ss][Oo])$!) {
1000 } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.gz)$!) {
1001 return ('vztmpl', $1);
1003 die "unable to parse directory volume name '$volname'\n";
1006 sub parse_volname_lvm
{
1007 my $volname = shift;
1009 parse_lvm_name
($volname);
1011 if ($volname =~ m/^(vm-(\d+)-\S+)$/) {
1015 die "unable to parse lvm volume name '$volname'\n";
1018 sub parse_volname_iscsi
{
1019 my $volname = shift;
1021 if ($volname =~ m!^\d+\.\d+\.\d+\.(\S+)$!) {
1026 die "unable to parse iscsi volume name '$volname'\n";
1029 # try to map a filesystem path to a volume identifier
1030 sub path_to_volume_id
{
1031 my ($cfg, $path) = @_;
1033 my $ids = $cfg->{ids
};
1035 my ($sid, $volname) = parse_volume_id
($path, 1);
1037 if ($ids->{$sid} && (my $type = $ids->{$sid}->{type
})) {
1038 if ($type eq 'dir' || $type eq 'nfs') {
1039 my ($vtype, $name, $vmid) = parse_volname_dir
($volname);
1040 return ($vtype, $path);
1046 $path = abs_path
($path);
1048 foreach my $sid (keys %$ids) {
1049 my $type = $ids->{$sid}->{type
};
1050 next if !($type eq 'dir' || $type eq 'nfs');
1052 my $imagedir = $ids->{$sid}->{path
} . "/images";
1053 my $isodir = get_iso_dir
($cfg, $sid);
1054 my $tmpldir = get_vztmpl_dir
($cfg, $sid);
1056 if ($path =~ m!^$imagedir/(\d+)/([^/\s]+)$!) {
1059 return ('image', "$sid:$vmid/$name");
1060 } elsif ($path =~ m!^$isodir/([^/]+\.[Ii][Ss][Oo])$!) {
1062 return ('iso', "$sid:iso/$name");
1063 } elsif ($path =~ m!^$tmpldir/([^/]+\.tar\.gz)$!) {
1065 return ('vztmpl', "$sid:vztmpl/$name");
1069 # can't map path to volume id
1074 my ($cfg, $volid) = @_;
1076 my ($storeid, $volname) = parse_volume_id
($volid);
1078 my $scfg = storage_config
($cfg, $storeid);
1083 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1084 my ($vtype, $name, $vmid) = parse_volname_dir
($volname);
1087 my $imagedir = get_image_dir
($cfg, $storeid, $vmid);
1088 my $isodir = get_iso_dir
($cfg, $storeid);
1089 my $tmpldir = get_vztmpl_dir
($cfg, $storeid);
1091 if ($vtype eq 'image') {
1092 $path = "$imagedir/$name";
1093 } elsif ($vtype eq 'iso') {
1094 $path = "$isodir/$name";
1095 } elsif ($vtype eq 'vztmpl') {
1096 $path = "$tmpldir/$name";
1098 die "should not be reached";
1101 } elsif ($scfg->{type
} eq 'lvm') {
1103 my $vg = $scfg->{vgname
};
1105 my ($name, $vmid) = parse_volname_lvm
($volname);
1108 $path = "/dev/$vg/$name";
1110 } elsif ($scfg->{type
} eq 'iscsi') {
1111 my $byid = parse_volname_iscsi
($volname);
1112 $path = "/dev/disk/by-id/$byid";
1114 die "unknown storage type '$scfg->{type}'";
1117 return wantarray ?
($path, $owner) : $path;
1120 sub storage_migrate
{
1121 my ($cfg, $volid, $target_host, $target_storeid, $target_volname) = @_;
1123 my ($storeid, $volname) = parse_volume_id
($volid);
1124 $target_volname = $volname if !$target_volname;
1126 my $scfg = storage_config
($cfg, $storeid);
1128 # no need to migrate shared content
1129 return if $storeid eq $target_storeid && $scfg->{shared
};
1131 my $tcfg = storage_config
($cfg, $target_storeid);
1133 my $target_volid = "${target_storeid}:${target_volname}";
1135 my $errstr = "unable to migrate '$volid' to '${target_volid}' on host '$target_host'";
1137 # blowfish is a fast block cipher, much faster then 3des
1138 my $sshoptions = "-c blowfish -o 'BatchMode=yes'";
1139 my $ssh = "/usr/bin/ssh $sshoptions";
1141 local $ENV{RSYNC_RSH
} = $ssh;
1143 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1144 if ($tcfg->{type
} eq 'dir' || $tcfg->{type
} eq 'nfs') {
1146 my $src = path
($cfg, $volid);
1147 my $dst = path
($cfg, $target_volid);
1149 my $dirname = dirname
($dst);
1151 if ($tcfg->{shared
}) { # we can do a local copy
1153 run_command
(['/bin/mkdir', '-p', $dirname]);
1155 run_command
(['/bin/cp', $src, $dst]);
1159 run_command
(['/usr/bin/ssh', "root\@${target_host}",
1160 '/bin/mkdir', '-p', $dirname]);
1162 # we use rsync with --sparse, so we can't use --inplace,
1163 # so we remove file on the target if it already exists to
1165 my ($size, $format) = file_size_info
($src);
1166 if ($format && ($format eq 'raw') && $size) {
1167 run_command
(['/usr/bin/ssh', "root\@${target_host}",
1172 my $cmd = ['/usr/bin/rsync', '--progress', '--sparse', '--whole-file',
1173 $src, "root\@${target_host}:$dst"];
1177 run_command
($cmd, outfunc
=> sub {
1180 if ($line =~ m/^\s*(\d+\s+(\d+)%\s.*)$/) {
1181 if ($2 > $percent) {
1183 print "rsync status: $1\n";
1196 die "$errstr - target type '$tcfg->{type}' not implemented\n";
1200 die "$errstr - source type '$scfg->{type}' not implemented\n";
1205 my ($cfg, $storeid, $vmid, $fmt, $name, $size) = @_;
1207 die "no storage id specified\n" if !$storeid;
1209 parse_storage_id
($storeid);
1211 my $scfg = storage_config
($cfg, $storeid);
1213 die "no VMID specified\n" if !$vmid;
1215 $vmid = parse_vmid
($vmid);
1217 my $defformat = storage_default_format
($cfg, $storeid);
1219 $fmt = $defformat if !$fmt;
1221 activate_storage
($cfg, $storeid);
1223 # lock shared storage
1224 return cluster_lock_storage
($storeid, $scfg->{shared
}, undef, sub {
1226 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1228 my $imagedir = get_image_dir
($cfg, $storeid, $vmid);
1234 for (my $i = 1; $i < 100; $i++) {
1235 my @gr = <$imagedir/vm-$vmid-disk
-$i.*>;
1237 $name = "vm-$vmid-disk-$i.$fmt";
1243 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
1246 my (undef, $tmpfmt) = parse_name_dir
($name);
1248 die "illegal name '$name' - wrong extension for format ('$tmpfmt != '$fmt')\n"
1251 my $path = "$imagedir/$name";
1253 die "disk image '$path' already exists\n" if -f
$path;
1255 run_command
("/usr/bin/qemu-img create -f $fmt '$path' ${size}K",
1256 errmsg
=> "unable to create image");
1258 return "$storeid:$vmid/$name";
1260 } elsif ($scfg->{type
} eq 'lvm') {
1262 die "unsupported format '$fmt'" if $fmt ne 'raw';
1264 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
1265 if $name && $name !~ m/^vm-$vmid-/;
1267 my $vgs = lvm_vgs
();
1269 my $vg = $scfg->{vgname
};
1271 die "no such volume gruoup '$vg'\n" if !defined ($vgs->{$vg});
1273 my $free = int ($vgs->{$vg}->{free
});
1275 die "not enough free space ($free < $size)\n" if $free < $size;
1278 my $lvs = lvm_lvs
($vg);
1280 for (my $i = 1; $i < 100; $i++) {
1281 my $tn = "vm-$vmid-disk-$i";
1282 if (!defined ($lvs->{$vg}->{$tn})) {
1289 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
1292 my $cmd = ['/sbin/lvcreate', '--addtag', "pve-vm-$vmid", '--size', "${size}k", '--name', $name, $vg];
1296 return "$storeid:$name";
1298 } elsif ($scfg->{type
} eq 'iscsi') {
1299 die "can't allocate space in iscsi storage\n";
1301 die "unknown storage type '$scfg->{type}'";
1307 my ($cfg, $volid) = @_;
1309 my ($storeid, $volname) = parse_volume_id
($volid);
1311 my $scfg = storage_config
($cfg, $storeid);
1313 activate_storage
($cfg, $storeid);
1315 # lock shared storage
1316 cluster_lock_storage
($storeid, $scfg->{shared
}, undef, sub {
1318 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1319 my $path = path
($cfg, $volid);
1322 warn "disk image '$path' does not exists\n";
1326 } elsif ($scfg->{type
} eq 'lvm') {
1328 my $vg = $scfg->{vgname
};
1330 my $cmd = ['/sbin/lvremove', '-f', "$vg/$volname"];
1333 } elsif ($scfg->{type
} eq 'iscsi') {
1334 die "can't free space in iscsi storage\n";
1336 die "unknown storage type '$scfg->{type}'";
1341 # lvm utility functions
1346 die "no device specified" if !$device;
1350 my $cmd = ['/usr/bin/file', '-L', '-s', $device];
1351 run_command
($cmd, outfunc
=> sub {
1353 $has_label = 1 if $line =~ m/LVM2/;
1356 return undef if !$has_label;
1358 $cmd = ['/sbin/pvs', '--separator', ':', '--noheadings', '--units', 'k',
1359 '--unbuffered', '--nosuffix', '--options',
1360 'pv_name,pv_size,vg_name,pv_uuid', $device];
1363 run_command
($cmd, outfunc
=> sub {
1366 $line = trim
($line);
1368 my ($pvname, $size, $vgname, $uuid) = split (':', $line);
1370 die "found multiple pvs entries for device '$device'\n"
1384 sub clear_first_sector
{
1387 if (my $fh = IO
::File-
>new ($dev, "w")) {
1394 sub lvm_create_volume_group
{
1395 my ($device, $vgname, $shared) = @_;
1397 my $res = lvm_pv_info
($device);
1399 if ($res->{vgname
}) {
1400 return if $res->{vgname
} eq $vgname; # already created
1401 die "device '$device' is already used by volume group '$res->{vgname}'\n";
1404 clear_first_sector
($device); # else pvcreate fails
1406 # we use --metadatasize 250k, which reseults in "pe_start = 512"
1407 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
1408 my $cmd = ['/sbin/pvcreate', '--metadatasize', '250k', $device];
1412 $cmd = ['/sbin/vgcreate', $vgname, $device];
1413 # push @$cmd, '-c', 'y' if $shared; # we do not use this yet
1420 my $cmd = ['/sbin/vgs', '--separator', ':', '--noheadings', '--units', 'b',
1421 '--unbuffered', '--nosuffix', '--options',
1422 'vg_name,vg_size,vg_free'];
1425 run_command
($cmd, outfunc
=> sub {
1428 $line = trim
($line);
1430 my ($name, $size, $free) = split (':', $line);
1432 $vgs->{$name} = { size
=> int ($size), free
=> int ($free) };
1441 my $cmd = ['/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b',
1442 '--unbuffered', '--nosuffix', '--options',
1443 'vg_name,lv_name,lv_size,uuid,tags'];
1445 push @$cmd, $vgname if $vgname;
1448 run_command
($cmd, outfunc
=> sub {
1451 $line = trim
($line);
1453 my ($vg, $name, $size, $uuid, $tags) = split (':', $line);
1455 return if $name !~ m/^vm-(\d+)-/;
1459 foreach my $tag (split (/,/, $tags)) {
1460 if ($tag =~ m/^pve-vm-(\d+)$/) {
1467 if ($owner ne $nid) {
1468 warn "owner mismatch name = $name, owner = $owner\n";
1471 $lvs->{$vg}->{$name} = { format
=> 'raw', size
=> $size,
1472 uuid
=> $uuid, tags
=> $tags,
1480 #install iso or openvz template ($tt = <iso|vztmpl>)
1481 # we simply overwrite when file already exists
1482 sub install_template
{
1483 my ($cfg, $storeid, $tt, $srcfile, $destfile) = @_;
1485 my $scfg = storage_config
($cfg, $storeid);
1487 my $type = $scfg->{type
};
1489 die "invalid storage type '$type'" if !($type eq 'dir' || $type eq 'nfs');
1494 die "file '$destfile' has no '.iso' extension\n"
1495 if $destfile !~ m![^/]+\.[Ii][Ss][Oo]$!;
1496 die "storage '$storeid' does not support 'iso' content\n"
1497 if !$scfg->{content
}->{iso
};
1498 $path = get_iso_dir
($cfg, $storeid);
1499 } elsif ($tt eq 'vztmpl') {
1500 die "file '$destfile' has no '.tar.gz' extension\n"
1501 if $destfile !~ m![^/]+\.tar\.gz$!;
1502 die "storage '$storeid' does not support 'vztmpl' content\n"
1503 if !$scfg->{content
}->{vztmpl
};
1504 $path = get_vztmpl_dir
($cfg, $storeid);
1506 die "unknown template type '$tt'";
1509 activate_storage
($cfg, $storeid);
1511 my $dest = "$path/$destfile";
1513 my $cmd = ['cp', $srcfile, $dest];
1515 eval { run_command
($cmd); };
1524 #list iso or openvz template ($tt = <iso|vztmpl|backup>)
1526 my ($cfg, $storeid, $tt) = @_;
1528 die "unknown template type '$tt'\n" if !($tt eq 'iso' || $tt eq 'vztmpl' || $tt eq 'backup');
1530 my $ids = $cfg->{ids
};
1532 storage_check_enabled
($cfg, $storeid) if ($storeid);
1538 foreach my $sid (keys %$ids) {
1539 next if $storeid && $storeid ne $sid;
1541 my $scfg = $ids->{$sid};
1542 my $type = $scfg->{type
};
1544 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1546 next if $tt eq 'iso' && !$scfg->{content
}->{iso
};
1547 next if $tt eq 'vztmpl' && !$scfg->{content
}->{vztmpl
};
1548 next if $tt eq 'backup' && !$scfg->{content
}->{backup
};
1550 activate_storage
($cfg, $sid);
1552 if ($type eq 'dir' || $type eq 'nfs') {
1556 $path = get_iso_dir
($cfg, $sid);
1557 } elsif ($tt eq 'vztmpl') {
1558 $path = get_vztmpl_dir
($cfg, $sid);
1559 } elsif ($tt eq 'backup') {
1560 $path = $scfg->{path
};
1562 die "unknown template type '$tt'\n";
1565 foreach my $fn (<$path/*>) {
1570 next if $fn !~ m!/([^/]+\.[Ii][Ss][Oo])$!;
1572 $info = { volid
=> "$sid:iso/$1", format
=> 'iso' };
1574 } elsif ($tt eq 'vztmpl') {
1575 next if $fn !~ m!/([^/]+\.tar\.gz)$!;
1577 $info = { volid
=> "$sid:vztmpl/$1", format
=> 'tgz' };
1579 } elsif ($tt eq 'backup') {
1580 next if $fn !~ m!/([^/]+\.(tar|tgz))$!;
1582 $info = { volid
=> "$sid:backup/$1", format
=> $2 };
1585 $info->{size
} = -s
$fn;
1587 push @{$res->{$sid}}, $info;
1592 @{$res->{$sid}} = sort {lc($a->{volid
}) cmp lc ($b->{volid
}) } @{$res->{$sid}} if $res->{$sid};
1598 sub file_size_info
{
1599 my ($filename, $timeout) = @_;
1601 my $cmd = ['/usr/bin/qemu-img', 'info', $filename];
1608 run_command
($cmd, timeout
=> $timeout, outfunc
=> sub {
1611 if ($line =~ m/^file format:\s+(\S+)\s*$/) {
1613 } elsif ($line =~ m/^virtual size:\s\S+\s+\((\d+)\s+bytes\)$/) {
1615 } elsif ($line =~ m/^disk size:\s+(\d+(.\d+)?)([KMGT])\s*$/) {
1619 $used *= 1024 if $u eq 'K';
1620 $used *= (1024*1024) if $u eq 'M';
1621 $used *= (1024*1024*1024) if $u eq 'G';
1622 $used *= (1024*1024*1024*1024) if $u eq 'T';
1629 return wantarray ?
($size, $format, $used) : $size;
1633 my ($cfg, $storeid, $vmid, $vollist) = @_;
1635 my $ids = $cfg->{ids
};
1637 storage_check_enabled
($cfg, $storeid) if ($storeid);
1641 # prepare/activate/refresh all storages
1645 my $storage_list = [];
1647 foreach my $volid (@$vollist) {
1648 my ($sid, undef) = parse_volume_id
($volid);
1649 next if !defined ($ids->{$sid});
1650 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1651 push @$storage_list, $sid;
1652 $stypes->{$ids->{$sid}->{type
}} = 1;
1655 foreach my $sid (keys %$ids) {
1656 next if $storeid && $storeid ne $sid;
1657 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1658 push @$storage_list, $sid;
1659 $stypes->{$ids->{$sid}->{type
}} = 1;
1663 activate_storage_list
($cfg, $storage_list);
1665 my $lvs = $stypes->{lvm
} ? lvm_lvs
() : {};
1667 my $iscsi_devices = iscsi_device_list
() if $stypes->{iscsi
};
1671 foreach my $sid (keys %$ids) {
1673 next if $storeid ne $sid;
1674 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1676 my $scfg = $ids->{$sid};
1677 my $type = $scfg->{type
};
1679 if ($type eq 'dir' || $type eq 'nfs') {
1681 my $path = $scfg->{path
};
1683 my $fmts = join ('|', keys %{$default_config->{$type}->{format
}->[0]});
1685 foreach my $fn (<$path/images/[0-9][0-9]*/*>) {
1687 next if $fn !~ m!^(/.+/images/(\d+)/([^/]+\.($fmts)))$!;
1692 my $volid = "$sid:$owner/$name";
1695 my $found = grep { $_ eq $volid } @$vollist;
1698 next if defined ($vmid) && ($owner ne $vmid);
1701 my ($size, $format, $used) = file_size_info
($fn);
1703 if ($format && $size) {
1704 push @{$res->{$sid}}, {
1705 volid
=> $volid, format
=> $format,
1706 size
=> $size, vmid
=> $owner, used
=> $used };
1711 } elsif ($type eq 'lvm') {
1713 my $vgname = $scfg->{vgname
};
1715 if (my $dat = $lvs->{$vgname}) {
1717 foreach my $volname (keys %$dat) {
1719 my $owner = $dat->{$volname}->{vmid
};
1721 my $volid = "$sid:$volname";
1724 my $found = grep { $_ eq $volid } @$vollist;
1727 next if defined ($vmid) && ($owner ne $vmid);
1730 my $info = $dat->{$volname};
1731 $info->{volid
} = $volid;
1733 push @{$res->{$sid}}, $info;
1737 } elsif ($type eq 'iscsi') {
1739 # we have no owner for iscsi devices
1741 my $target = $scfg->{target
};
1743 if (my $dat = $iscsi_devices->{$target}) {
1745 foreach my $volname (keys %$dat) {
1747 my $volid = "$sid:$volname";
1750 my $found = grep { $_ eq $volid } @$vollist;
1753 next if !($storeid && ($storeid eq $sid));
1756 my $info = $dat->{$volname};
1757 $info->{volid
} = $volid;
1759 push @{$res->{$sid}}, $info;
1767 @{$res->{$sid}} = sort {lc($a->{volid
}) cmp lc ($b->{volid
}) } @{$res->{$sid}} if $res->{$sid};
1773 sub nfs_is_mounted
{
1774 my ($server, $export, $mountpoint, $mountdata) = @_;
1776 my $source = "$server:$export";
1778 $mountdata = read_proc_mounts
() if !$mountdata;
1780 if ($mountdata =~ m/^$source\s$mountpoint\snfs/m) {
1788 my ($server, $export, $mountpoint, $options) = @_;
1790 my $source = "$server:$export";
1792 my $cmd = ['/bin/mount', '-t', 'nfs', $source, $mountpoint];
1794 push @$cmd, '-o', $options;
1802 my $filename = "/sys/kernel/uevent_seqnum";
1805 if (my $fh = IO
::File-
>new ($filename, "r")) {
1807 if ($line =~ m/^(\d+)$/) {
1815 sub __activate_storage_full
{
1816 my ($cfg, $storeid, $session) = @_;
1818 my $scfg = storage_check_enabled
($cfg, $storeid);
1820 return if $session->{activated
}->{$storeid};
1822 if (!$session->{mountdata
}) {
1823 $session->{mountdata
} = read_proc_mounts
();
1826 if (!$session->{uevent_seqnum
}) {
1827 $session->{uevent_seqnum
} = uevent_seqnum
();
1830 my $mountdata = $session->{mountdata
};
1832 my $type = $scfg->{type
};
1834 if ($type eq 'dir' || $type eq 'nfs') {
1836 my $path = $scfg->{path
};
1838 if ($type eq 'nfs') {
1839 my $server = $scfg->{server
};
1840 my $export = $scfg->{export
};
1842 if (!nfs_is_mounted
($server, $export, $path, $mountdata)) {
1844 # NOTE: only call mkpath when not mounted (avoid hang
1845 # when NFS server is offline
1849 die "unable to activate storage '$storeid' - " .
1850 "directory '$path' does not exist\n" if ! -d
$path;
1852 nfs_mount
($server, $export, $path, $scfg->{options
});
1859 die "unable to activate storage '$storeid' - " .
1860 "directory '$path' does not exist\n" if ! -d
$path;
1863 my $imagedir = get_image_dir
($cfg, $storeid);
1864 my $isodir = get_iso_dir
($cfg, $storeid);
1865 my $tmpldir = get_vztmpl_dir
($cfg, $storeid);
1867 if (defined($scfg->{content
})) {
1868 mkpath
$imagedir if $scfg->{content
}->{images
} &&
1870 mkpath
$isodir if $scfg->{content
}->{iso
} &&
1872 mkpath
$tmpldir if $scfg->{content
}->{vztmpl
} &&
1876 } elsif ($type eq 'lvm') {
1878 if ($scfg->{base
}) {
1879 my ($baseid, undef) = parse_volume_id
($scfg->{base
});
1880 __activate_storage_full
($cfg, $baseid, $session);
1883 if (!$session->{vgs
}) {
1884 $session->{vgs
} = lvm_vgs
();
1887 # In LVM2, vgscans take place automatically;
1888 # this is just to be sure
1889 if ($session->{vgs
} && !$session->{vgscaned
} &&
1890 !$session->{vgs
}->{$scfg->{vgname
}}) {
1891 $session->{vgscaned
} = 1;
1892 my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes'];
1893 eval { run_command
($cmd, outfunc
=> sub {}); };
1897 my $cmd = ['/sbin/vgchange', '-aly', $scfg->{vgname
}];
1898 run_command
($cmd, outfunc
=> sub {});
1900 } elsif ($type eq 'iscsi') {
1902 return if !check_iscsi_support
(1);
1904 $session->{iscsi_sessions
} = iscsi_session_list
()
1905 if !$session->{iscsi_sessions
};
1907 my $iscsi_sess = $session->{iscsi_sessions
}->{$scfg->{target
}};
1908 if (!defined ($iscsi_sess)) {
1909 eval { iscsi_login
($scfg->{target
}, $scfg->{portal
}); };
1912 # make sure we get all devices
1913 iscsi_session_rescan
($iscsi_sess);
1920 my $newseq = uevent_seqnum
();
1922 # only call udevsettle if there are events
1923 if ($newseq > $session->{uevent_seqnum
}) {
1925 system ("$UDEVADM settle --timeout=$timeout"); # ignore errors
1926 $session->{uevent_seqnum
} = $newseq;
1929 $session->{activated
}->{$storeid} = 1;
1932 sub activate_storage_list
{
1933 my ($cfg, $storeid_list, $session) = @_;
1935 $session = {} if !$session;
1937 foreach my $storeid (@$storeid_list) {
1938 __activate_storage_full
($cfg, $storeid, $session);
1942 sub activate_storage
{
1943 my ($cfg, $storeid) = @_;
1947 __activate_storage_full
($cfg, $storeid, $session);
1950 sub activate_volumes
{
1951 my ($cfg, $vollist) = @_;
1953 my $storagehash = {};
1954 foreach my $volid (@$vollist) {
1955 my ($storeid, undef) = parse_volume_id
($volid);
1956 $storagehash->{$storeid} = 1;
1959 activate_storage_list
($cfg, [keys %$storagehash]);
1961 foreach my $volid (@$vollist) {
1962 my ($storeid, $volname) = parse_volume_id
($volid);
1964 my $scfg = storage_config
($cfg, $storeid);
1966 my $path = path
($cfg, $volid);
1968 if ($scfg->{type
} eq 'lvm') {
1969 my $cmd = ['/sbin/lvchange', '-aly', $path];
1970 eval { run_command
($cmd); };
1974 # check is volume exists
1975 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1976 die "volume '$volid' does not exist\n" if ! -f
$path;
1978 die "volume '$volid' does not exist\n" if ! -b
$path;
1983 sub deactivate_volumes
{
1984 my ($cfg, $vollist) = @_;
1986 my $lvs = lvm_lvs
();
1988 foreach my $volid (@$vollist) {
1989 my ($storeid, $volname) = parse_volume_id
($volid);
1991 my $scfg = storage_config
($cfg, $storeid);
1993 if ($scfg->{type
} eq 'lvm') {
1994 my ($name) = parse_volname_lvm
($volname);
1996 if ($lvs->{$scfg->{vgname
}}->{$name}) {
1997 my $path = path
($cfg, $volid);
1998 my $cmd = ['/sbin/lvchange', '-aln', $path];
1999 eval { run_command
($cmd); };
2006 sub deactivate_storage
{
2007 my ($cfg, $storeid) = @_;
2011 my $scfg = storage_config
($cfg, $storeid);
2013 my $type = $scfg->{type
};
2015 if ($type eq 'dir') {
2017 } elsif ($type eq 'nfs') {
2018 my $mountdata = read_proc_mounts
();
2019 my $server = $scfg->{server
};
2020 my $export = $scfg->{export
};
2021 my $path = $scfg->{path
};
2023 my $cmd = ['/bin/umount', $path];
2025 run_command
($cmd) if nfs_is_mounted
($server, $export, $path, $mountdata);
2026 } elsif ($type eq 'lvm') {
2027 my $cmd = ['/sbin/vgchange', '-aln', $scfg->{vgname
}];
2029 } elsif ($type eq 'iscsi') {
2030 my $portal = $scfg->{portal
};
2031 my $target = $scfg->{target
};
2033 my $iscsi_sessions = iscsi_session_list
();
2034 iscsi_logout
($target, $portal)
2035 if defined ($iscsi_sessions->{$target});
2043 my ($cfg, $content) = @_;
2045 my $ids = $cfg->{ids
};
2051 foreach my $storeid (keys %$ids) {
2053 next if $content && !$ids->{$storeid}->{content
}->{$content};
2055 next if !storage_check_enabled
($cfg, $storeid, undef, 1);
2057 my $type = $ids->{$storeid}->{type
};
2059 $info->{$storeid} = {
2064 content
=> content_hash_to_string
($ids->{$storeid}->{content
}),
2068 $stypes->{$type} = 1;
2070 push @$slist, $storeid;
2075 my $iscsi_sessions = {};
2078 if ($stypes->{lvm
}) {
2079 $session->{vgs
} = lvm_vgs
();
2080 $vgs = $session->{vgs
};
2082 if ($stypes->{nfs
}) {
2083 $mountdata = read_proc_mounts
();
2084 $session->{mountdata
} = $mountdata;
2086 if ($stypes->{iscsi
}) {
2087 $iscsi_sessions = iscsi_session_list
();
2088 $session->{iscsi_sessions
} = $iscsi_sessions;
2091 eval { activate_storage_list
($cfg, $slist, $session); };
2093 foreach my $storeid (keys %$ids) {
2094 my $scfg = $ids->{$storeid};
2096 next if !$info->{$storeid};
2098 my $type = $scfg->{type
};
2100 if ($type eq 'dir' || $type eq 'nfs') {
2102 my $path = $scfg->{path
};
2104 if ($type eq 'nfs') {
2105 my $server = $scfg->{server
};
2106 my $export = $scfg->{export
};
2108 next if !nfs_is_mounted
($server, $export, $path, $mountdata);
2112 my $res = PVE
::Tools
::df
($path, $timeout);
2114 next if !$res || !$res->{total
};
2116 $info->{$storeid}->{total
} = $res->{total
};
2117 $info->{$storeid}->{avail
} = $res->{avail
};
2118 $info->{$storeid}->{used
} = $res->{used
};
2119 $info->{$storeid}->{active
} = 1;
2121 } elsif ($type eq 'lvm') {
2123 my $vgname = $scfg->{vgname
};
2128 if (defined ($vgs->{$vgname})) {
2129 $total = $vgs->{$vgname}->{size
};
2130 $free = $vgs->{$vgname}->{free
};
2132 $info->{$storeid}->{total
} = $total;
2133 $info->{$storeid}->{avail
} = $free;
2134 $info->{$storeid}->{used
} = $total - $free;
2135 $info->{$storeid}->{active
} = 1;
2138 } elsif ($type eq 'iscsi') {
2140 $info->{$storeid}->{total
} = 0;
2141 $info->{$storeid}->{avail
} = 0;
2142 $info->{$storeid}->{used
} = 0;
2143 $info->{$storeid}->{active
} =
2144 defined ($iscsi_sessions->{$scfg->{target
}});
2157 my $packed_ip = gethostbyname($server);
2158 if (defined $packed_ip) {
2159 return inet_ntoa
($packed_ip);
2165 my ($server_in) = @_;
2168 if (!($server = resolv_server
($server_in))) {
2169 die "unable to resolve address for server '${server_in}'\n";
2172 my $cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
2175 run_command
($cmd, outfunc
=> sub {
2178 # note: howto handle white spaces in export path??
2179 if ($line =~ m!^(/\S+)\s+(.+)$!) {
2188 my ($portal, $noerr) = @_;
2190 if ($portal =~ m/^([^:]+)(:(\d+))?$/) {
2194 if (my $ip = resolv_server
($server)) {
2196 return $port ?
"$server:$port" : $server;
2199 return undef if $noerr;
2201 raise_param_exc
({ portal
=> "unable to resolve portal address '$portal'" });
2204 # idea is from usbutils package (/usr/bin/usb-devices) script
2205 sub __scan_usb_device
{
2206 my ($res, $devpath, $parent, $level) = @_;
2208 return if ! -d
$devpath;
2209 return if $level && $devpath !~ m/^.*[-.](\d+)$/;
2210 my $port = $level ?
int($1 - 1) : 0;
2212 my $busnum = int(file_read_firstline
("$devpath/busnum"));
2213 my $devnum = int(file_read_firstline
("$devpath/devnum"));
2220 speed
=> file_read_firstline
("$devpath/speed"),
2221 class => hex(file_read_firstline
("$devpath/bDeviceClass")),
2222 vendid
=> file_read_firstline
("$devpath/idVendor"),
2223 prodid
=> file_read_firstline
("$devpath/idProduct"),
2227 my $usbpath = $devpath;
2228 $usbpath =~ s
|^.*/\d
+\
-||;
2229 $d->{usbpath
} = $usbpath;
2232 my $product = file_read_firstline
("$devpath/product");
2233 $d->{product
} = $product if $product;
2235 my $manu = file_read_firstline
("$devpath/manufacturer");
2236 $d->{manufacturer
} = $manu if $manu;
2238 my $serial => file_read_firstline
("$devpath/serial");
2239 $d->{serial
} = $serial if $serial;
2243 foreach my $subdev (<$devpath/$busnum-*>) {
2244 next if $subdev !~ m
|/$busnum-[0-9]+(\
.[0-9]+)*$|;
2245 __scan_usb_device
($res, $subdev, $devnum, $level + 1);
2254 foreach my $device (</sys/bus
/usb
/devices
/usb
*>) {
2255 __scan_usb_device
($devlist, $device, 0, 0);
2262 my ($portal_in) = @_;
2265 if (!($portal = resolv_portal
($portal_in))) {
2266 die "unable to parse/resolve portal address '${portal_in}'\n";
2269 return iscsi_discovery
($portal);
2272 sub storage_default_format
{
2273 my ($cfg, $storeid) = @_;
2275 my $scfg = storage_config
($cfg, $storeid);
2277 my $def = $default_config->{$scfg->{type
}};
2279 my $def_format = 'raw';
2280 my $valid_formats = [ $def_format ];
2282 if (defined ($def->{format
})) {
2283 $def_format = $scfg->{format
} || $def->{format
}->[1];
2284 $valid_formats = [ sort keys %{$def->{format
}->[0]} ];
2287 return wantarray ?
($def_format, $valid_formats) : $def_format;
2290 sub vgroup_is_used
{
2291 my ($cfg, $vgname) = @_;
2293 foreach my $storeid (keys %{$cfg->{ids
}}) {
2294 my $scfg = storage_config
($cfg, $storeid);
2295 if ($scfg->{type
} eq 'lvm' && $scfg->{vgname
} eq $vgname) {
2303 sub target_is_used
{
2304 my ($cfg, $target) = @_;
2306 foreach my $storeid (keys %{$cfg->{ids
}}) {
2307 my $scfg = storage_config
($cfg, $storeid);
2308 if ($scfg->{type
} eq 'iscsi' && $scfg->{target
} eq $target) {
2316 sub volume_is_used
{
2317 my ($cfg, $volid) = @_;
2319 foreach my $storeid (keys %{$cfg->{ids
}}) {
2320 my $scfg = storage_config
($cfg, $storeid);
2321 if ($scfg->{base
} && $scfg->{base
} eq $volid) {
2329 sub storage_is_used
{
2330 my ($cfg, $storeid) = @_;
2332 foreach my $sid (keys %{$cfg->{ids
}}) {
2333 my $scfg = storage_config
($cfg, $sid);
2334 next if !$scfg->{base
};
2335 my ($st) = parse_volume_id
($scfg->{base
});
2336 return 1 if $st && $st eq $storeid;
2343 my ($list, $func) = @_;
2347 foreach my $sid (keys %$list) {
2348 foreach my $info (@{$list->{$sid}}) {
2349 my $volid = $info->{volid
};
2350 my ($sid1, $volname) = parse_volume_id
($volid, 1);
2351 if ($sid1 && $sid1 eq $sid) {
2352 &$func ($volid, $sid, $info);
2354 warn "detected strange volid '$volid' in volume list for '$sid'\n";