]>
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);
24 use PVE
::RPCEnvironment
;
26 my $ISCSIADM = '/usr/bin/iscsiadm';
27 my $UDEVADM = '/sbin/udevadm';
29 $ISCSIADM = undef if ! -X
$ISCSIADM;
31 # fixme: always_call_parser => 1 ??
32 cfs_register_file
('storage.cfg',
36 # generic utility function
39 return cfs_read_file
("storage.cfg");
42 sub check_iscsi_support
{
46 my $msg = "no iscsi support - please install open-iscsi";
48 warn "warning: $msg\n";
58 sub load_stable_scsi_paths
{
60 my $stable_paths = {};
62 my $stabledir = "/dev/disk/by-id";
64 if (my $dh = IO
::Dir-
>new($stabledir)) {
65 while (defined(my $tmp = $dh->read)) {
66 # exclude filenames with part in name (same disk but partitions)
67 # use only filenames with scsi(with multipath i have the same device
68 # with dm-uuid-mpath , dm-name and scsi in name)
69 if($tmp !~ m/-part\d+$/ && $tmp =~ m/^scsi-/) {
70 my $path = "$stabledir/$tmp";
71 my $bdevdest = readlink($path);
72 if ($bdevdest && $bdevdest =~ m
|^../../([^/]+)|) {
73 $stable_paths->{$1}=$tmp;
83 my ($dir, $regex) = @_;
85 my $dh = IO
::Dir-
>new ($dir);
86 return wantarray ?
() : undef if !$dh;
88 while (defined(my $tmp = $dh->read)) {
89 if (my @res = $tmp =~ m/^($regex)$/) {
91 return wantarray ?
@res : $tmp;
96 return wantarray ?
() : undef;
99 sub dir_glob_foreach
{
100 my ($dir, $regex, $func) = @_;
102 my $dh = IO
::Dir-
>new ($dir);
104 while (defined(my $tmp = $dh->read)) {
105 if (my @res = $tmp =~ m/^($regex)$/) {
112 sub read_proc_mounts
{
114 local $/; # enable slurp mode
117 if (my $fd = IO
::File-
>new ("/proc/mounts", "r")) {
125 # PVE::Storage utility functions
127 sub lock_storage_config
{
128 my ($code, $errmsg) = @_;
130 cfs_lock_file
("storage.cfg", undef, $code);
133 $errmsg ?
die "$errmsg: $err" : die $err;
141 saferemove
=> 'bool',
143 content
=> 'content',
151 options
=> 'options',
152 maxfiles
=> 'natural',
155 my $required_config = {
157 nfs
=> ['path', 'server', 'export'],
159 iscsi
=> ['portal', 'target'],
164 nfs
=> ['path', 'server', 'export'],
165 lvm
=> ['vgname', 'base'],
166 iscsi
=> ['portal', 'target'],
169 my $default_config = {
176 content
=> [ { images
=> 1, rootdir
=> 1, vztmpl
=> 1, iso
=> 1, backup
=> 1, none
=> 1 },
177 { images
=> 1, rootdir
=> 1 }],
178 format
=> [ { raw
=> 1, qcow2
=> 1, vmdk
=> 1 } , 'raw' ],
189 content
=> [ { images
=> 1, rootdir
=> 1, vztmpl
=> 1, iso
=> 1, backup
=> 1},
191 format
=> [ { raw
=> 1, qcow2
=> 1, vmdk
=> 1 } , 'raw' ],
200 content
=> [ {images
=> 1}, { images
=> 1 }],
209 content
=> [ {images
=> 1, none
=> 1}, { images
=> 1 }],
213 sub valid_content_types
{
216 my $def = $default_config->{$stype};
220 return $def->{content
}->[0];
223 sub content_hash_to_string
{
227 foreach my $ct (keys %$hash) {
228 push @cta, $ct if $hash->{$ct};
231 return join(',', @cta);
234 PVE
::JSONSchema
::register_format
('pve-storage-path', \
&verify_path
);
236 my ($path, $noerr) = @_;
238 # fixme: exclude more shell meta characters?
239 # we need absolute paths
240 if ($path !~ m
|^/[^;\
(\
)]+|) {
241 return undef if $noerr;
242 die "value does not look like a valid absolute path\n";
247 PVE
::JSONSchema
::register_format
('pve-storage-server', \
&verify_server
);
249 my ($server, $noerr) = @_;
251 # fixme: use better regex ?
253 if ($server !~ m/^[[:alnum:]\-\.]+$/) {
254 return undef if $noerr;
255 die "value does not look like a valid server name or IP address\n";
260 PVE
::JSONSchema
::register_format
('pve-storage-portal', \
&verify_portal
);
262 my ($portal, $noerr) = @_;
264 # IP with optional port
265 if ($portal !~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/) {
266 return undef if $noerr;
267 die "value does not look like a valid portal address\n";
272 PVE
::JSONSchema
::register_format
('pve-storage-portal-dns', \
&verify_portal_dns
);
273 sub verify_portal_dns
{
274 my ($portal, $noerr) = @_;
276 # IP or DNS name with optional port
277 if ($portal !~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[[:alnum:]\-\.]+)(:\d+)?$/) {
278 return undef if $noerr;
279 die "value does not look like a valid portal address\n";
284 PVE
::JSONSchema
::register_format
('pve-storage-content', \
&verify_content
);
286 my ($ct, $noerr) = @_;
288 my $valid_content = valid_content_types
('dir'); # dir includes all types
290 if (!$valid_content->{$ct}) {
291 return undef if $noerr;
292 die "invalid content type '$ct'\n";
298 PVE
::JSONSchema
::register_format
('pve-storage-format', \
&verify_format
);
300 my ($fmt, $noerr) = @_;
302 if ($fmt !~ m/(raw|qcow2|vmdk)/) {
303 return undef if $noerr;
304 die "invalid format '$fmt'\n";
310 PVE
::JSONSchema
::register_format
('pve-storage-options', \
&verify_options
);
312 my ($value, $noerr) = @_;
314 # mount options (see man fstab)
315 if ($value !~ m/^\S+$/) {
316 return undef if $noerr;
317 die "invalid options '$value'\n";
324 my ($stype, $ct, $key, $value, $storeid, $noerr) = @_;
326 my $def = $default_config->{$stype};
328 if (!$def) { # should not happen
329 return undef if $noerr;
330 die "unknown storage type '$stype'\n";
333 if (!defined($def->{$key})) {
334 return undef if $noerr;
335 die "unexpected property\n";
338 if (!defined ($value)) {
339 return undef if $noerr;
340 die "got undefined value\n";
343 if ($value =~ m/[\n\r]/) {
344 return undef if $noerr;
345 die "property contains a line feed\n";
349 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
350 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
351 return undef if $noerr;
352 die "type check ('boolean') failed - got '$value'\n";
353 } elsif ($ct eq 'options') {
354 return verify_options
($value, $noerr);
355 } elsif ($ct eq 'path') {
356 return verify_path
($value, $noerr);
357 } elsif ($ct eq 'server') {
358 return verify_server
($value, $noerr);
359 } elsif ($ct eq 'vgname') {
360 return parse_lvm_name
($value, $noerr);
361 } elsif ($ct eq 'portal') {
362 return verify_portal
($value, $noerr);
363 } elsif ($ct eq 'natural') {
364 return int($value) if $value =~ m/^\d+$/;
365 return undef if $noerr;
366 die "type check ('natural') failed - got '$value'\n";
367 } elsif ($ct eq 'nodes') {
370 foreach my $node (PVE
::Tools
::split_list
($value)) {
371 if (PVE
::JSONSchema
::pve_verify_node_name
($node, $noerr)) {
376 # no node restrictions for local storage
377 if ($storeid && $storeid eq 'local' && scalar(keys(%$res))) {
378 return undef if $noerr;
379 die "storage '$storeid' does not allow node restrictions\n";
383 } elsif ($ct eq 'target') {
385 } elsif ($ct eq 'string') {
387 } elsif ($ct eq 'format') {
388 my $valid_formats = $def->{format
}->[0];
390 if (!$valid_formats->{$value}) {
391 return undef if $noerr;
392 die "storage does not support format '$value'\n";
397 } elsif ($ct eq 'content') {
398 my $valid_content = $def->{content
}->[0];
402 foreach my $c (PVE
::Tools
::split_list
($value)) {
403 if (!$valid_content->{$c}) {
404 return undef if $noerr;
405 die "storage does not support content type '$c'\n";
410 if ($res->{none
} && scalar (keys %$res) > 1) {
411 return undef if $noerr;
412 die "unable to combine 'none' with other content types\n";
416 } elsif ($ct eq 'volume') {
417 return $value if parse_volume_id
($value, $noerr);
420 return undef if $noerr;
421 die "type check not implemented - internal error\n";
425 my ($filename, $raw) = @_;
429 my $digest = Digest
::SHA
::sha1_hex
(defined($raw) ?
$raw : '');
433 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
436 next if $line =~ m/^\#/;
437 next if $line =~ m/^\s*$/;
439 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
444 if (!PVE
::JSONSchema
::parse_storage_id
($storeid, 1)) {
446 warn "ignoring storage '$storeid' - (illegal characters)\n";
447 } elsif (!$default_config->{$type}) {
449 warn "ignoring storage '$storeid' (unsupported type '$type')\n";
451 $ids->{$storeid}->{type
} = $type;
452 $ids->{$storeid}->{priority
} = $pri++;
455 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
458 next if $line =~ m/^\#/;
459 last if $line =~ m/^\s*$/;
461 next if $ignore; # skip
463 if ($line =~ m/^\s+(\S+)(\s+(.*\S))?\s*$/) {
464 my ($k, $v) = ($1, $3);
465 if (my $ct = $confvars->{$k}) {
466 $v = 1 if $ct eq 'bool' && !defined($v);
468 $ids->{$storeid}->{$k} = check_type
($type, $ct, $k, $v, $storeid);
470 warn "storage '$storeid' - unable to parse value of '$k': $@" if $@;
472 warn "storage '$storeid' - unable to parse value of '$k'\n";
476 warn "storage '$storeid' - ignore config line: $line\n";
480 warn "ignore config line: $line\n";
484 # make sure we have a reasonable 'local:' storage
485 # openvz expects things to be there
486 if (!$ids->{local} || $ids->{local}->{type
} ne 'dir' ||
487 $ids->{local}->{path
} ne '/var/lib/vz') {
491 path
=> '/var/lib/vz',
492 content
=> { images
=> 1, rootdir
=> 1, vztmpl
=> 1, iso
=> 1},
496 # we always need this for OpenVZ
497 $ids->{local}->{content
}->{rootdir
} = 1;
498 $ids->{local}->{content
}->{vztmpl
} = 1;
499 delete ($ids->{local}->{disable
});
501 # remove node restrictions for local storage
502 delete($ids->{local}->{nodes
});
504 foreach my $storeid (keys %$ids) {
505 my $d = $ids->{$storeid};
507 my $req_keys = $required_config->{$d->{type
}};
508 foreach my $k (@$req_keys) {
509 if (!defined ($d->{$k})) {
510 warn "ignoring storage '$storeid' - missing value " .
511 "for required option '$k'\n";
512 delete $ids->{$storeid};
517 my $def = $default_config->{$d->{type
}};
519 if ($def->{content
}) {
520 $d->{content
} = $def->{content
}->[1] if !$d->{content
};
523 if ($d->{type
} eq 'iscsi' || $d->{type
} eq 'nfs') {
528 my $cfg = { ids
=> $ids, digest
=> $digest};
534 my ($storeid, $stype, $param, $create) = @_;
536 my $settings = { type
=> $stype };
538 die "unknown storage type '$stype'\n"
539 if !$default_config->{$stype};
541 foreach my $opt (keys %$param) {
542 my $value = $param->{$opt};
544 my $ct = $confvars->{$opt};
545 if (defined($value)) {
547 $settings->{$opt} = check_type
($stype, $ct, $opt, $value, $storeid);
549 raise_param_exc
({ $opt => $@ }) if $@;
551 raise_param_exc
({ $opt => "got undefined value" });
556 my $req_keys = $required_config->{$stype};
557 foreach my $k (@$req_keys) {
559 if ($stype eq 'nfs' && !$settings->{path
}) {
560 $settings->{path
} = "/mnt/pve/$storeid";
563 # check if we have a value for all required options
564 if (!defined ($settings->{$k})) {
565 raise_param_exc
({ $k => "property is missing and it is not optional" });
569 my $fixed_keys = $fixed_config->{$stype};
570 foreach my $k (@$fixed_keys) {
572 # only allow to change non-fixed values
574 if (defined ($settings->{$k})) {
575 raise_param_exc
({$k => "can't change value (fixed parameter)"});
583 sub cluster_lock_storage
{
584 my ($storeid, $shared, $timeout, $func, @param) = @_;
588 my $lockid = "pve-storage-$storeid";
589 my $lockdir = "/var/lock/pve-manager";
591 $res = PVE
::Tools
::lock_file
("$lockdir/$lockid", $timeout, $func, @param);
594 $res = PVE
::Cluster
::cfs_lock_storage
($storeid, $timeout, $func, @param);
601 my ($cfg, $storeid, $noerr) = @_;
603 die "no storage id specified\n" if !$storeid;
605 my $scfg = $cfg->{ids
}->{$storeid};
607 die "storage '$storeid' does not exists\n" if (!$noerr && !$scfg);
612 sub storage_check_node
{
613 my ($cfg, $storeid, $node, $noerr) = @_;
615 my $scfg = storage_config
($cfg, $storeid);
617 if ($scfg->{nodes
}) {
618 $node = PVE
::INotify
::nodename
() if !$node || ($node eq 'localhost');
619 if (!$scfg->{nodes
}->{$node}) {
620 die "storage '$storeid' is not available on node '$node'\n" if !$noerr;
628 sub storage_check_enabled
{
629 my ($cfg, $storeid, $node, $noerr) = @_;
631 my $scfg = storage_config
($cfg, $storeid);
633 if ($scfg->{disable
}) {
634 die "storage '$storeid' is disabled\n" if !$noerr;
638 return storage_check_node
($cfg, $storeid, $node, $noerr);
644 my $ids = $cfg->{ids
};
646 my @sa = sort {$ids->{$a}->{priority
} <=> $ids->{$b}->{priority
}} keys %$ids;
651 sub assert_if_modified
{
652 my ($cfg, $digest) = @_;
654 if ($digest && ($cfg->{digest
} ne $digest)) {
655 die "detected modified storage configuration - try again\n";
659 sub sprint_config_line
{
662 my $ct = $confvars->{$k};
665 return $v ?
"\t$k\n" : '';
666 } elsif ($ct eq 'nodes') {
667 my $nlist = join(',', keys(%$v));
668 return $nlist ?
"\tnodes $nlist\n" : '';
669 } elsif ($ct eq 'content') {
670 my $clist = content_hash_to_string
($v);
672 return "\t$k $clist\n";
674 return "\t$k none\n";
682 my ($filename, $cfg) = @_;
686 my $ids = $cfg->{ids
};
689 foreach my $storeid (keys %$ids) {
690 my $pri = $ids->{$storeid}->{priority
};
691 $maxpri = $pri if $pri && $pri > $maxpri;
693 foreach my $storeid (keys %$ids) {
694 if (!defined ($ids->{$storeid}->{priority
})) {
695 $ids->{$storeid}->{priority
} = ++$maxpri;
699 foreach my $storeid (sort {$ids->{$a}->{priority
} <=> $ids->{$b}->{priority
}} keys %$ids) {
700 my $scfg = $ids->{$storeid};
701 my $type = $scfg->{type
};
702 my $def = $default_config->{$type};
704 die "unknown storage type '$type'\n" if !$def;
706 my $data = "$type: $storeid\n";
708 $data .= "\tdisable\n" if $scfg->{disable
};
710 my $done_hash = { disable
=> 1};
711 foreach my $k (@{$required_config->{$type}}) {
712 $done_hash->{$k} = 1;
713 my $v = $ids->{$storeid}->{$k};
714 die "storage '$storeid' - missing value for required option '$k'\n"
716 $data .= sprint_config_line
($k, $v);
719 foreach my $k (keys %$def) {
720 next if defined ($done_hash->{$k});
721 my $v = $ids->{$storeid}->{$k};
722 next if !defined($v);
723 $data .= sprint_config_line
($k, $v);
733 my ($cfg, $storeid, $vmid) = @_;
735 my $path = $cfg->{ids
}->{$storeid}->{path
};
736 return $vmid ?
"$path/images/$vmid" : "$path/images";
739 sub get_private_dir
{
740 my ($cfg, $storeid, $vmid) = @_;
742 my $path = $cfg->{ids
}->{$storeid}->{path
};
743 return $vmid ?
"$path/private/$vmid" : "$path/private";
747 my ($cfg, $storeid) = @_;
749 my $isodir = $cfg->{ids
}->{$storeid}->{path
};
750 $isodir .= '/template/iso';
756 my ($cfg, $storeid) = @_;
758 my $tmpldir = $cfg->{ids
}->{$storeid}->{path
};
759 $tmpldir .= '/template/cache';
765 my ($cfg, $storeid) = @_;
767 my $dir = $cfg->{ids
}->{$storeid}->{path
};
773 # iscsi utility functions
775 sub iscsi_session_list
{
777 check_iscsi_support
();
779 my $cmd = [$ISCSIADM, '--mode', 'session'];
783 run_command
($cmd, outfunc
=> sub {
786 if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)\s*$/) {
787 my ($session, $target) = ($1, $2);
788 # there can be several sessions per target (multipath)
789 push @{$res->{$target}}, $session;
797 sub iscsi_test_portal
{
800 my ($server, $port) = split(':', $portal);
801 my $p = Net
::Ping-
>new("tcp", 2);
802 $p->port_number($port || 3260);
803 return $p->ping($server);
806 sub iscsi_discovery
{
809 check_iscsi_support
();
811 my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets',
812 '--portal', $portal];
816 return $res if !iscsi_test_portal
($portal); # fixme: raise exception here?
818 run_command
($cmd, outfunc
=> sub {
821 if ($line =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+)\,\S+\s+(\S+)\s*$/) {
824 # one target can have more than one portal (multipath).
825 push @{$res->{$target}}, $portal;
833 my ($target, $portal_in) = @_;
835 check_iscsi_support
();
837 eval { iscsi_discovery
($portal_in); };
840 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login'];
845 my ($target, $portal) = @_;
847 check_iscsi_support
();
849 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--logout'];
853 my $rescan_filename = "/var/run/pve-iscsi-rescan.lock";
855 sub iscsi_session_rescan
{
856 my $session_list = shift;
858 check_iscsi_support
();
860 my $rstat = stat ($rescan_filename);
863 if (my $fh = IO
::File-
>new ($rescan_filename, "a")) {
864 utime undef, undef, $fh;
868 my $atime = $rstat->atime;
869 my $tdiff = time() - $atime;
870 # avoid frequent rescans
871 return if !($tdiff < 0 || $tdiff > 10);
872 utime undef, undef, $rescan_filename;
875 foreach my $session (@$session_list) {
876 my $cmd = [$ISCSIADM, '--mode', 'session', '-r', $session, '-R'];
877 eval { run_command
($cmd, outfunc
=> sub {}); };
882 sub iscsi_device_list
{
886 my $dirname = '/sys/class/iscsi_session';
888 my $stable_paths = load_stable_scsi_paths
();
890 dir_glob_foreach
($dirname, 'session(\d+)', sub {
891 my ($ent, $session) = @_;
893 my $target = file_read_firstline
("$dirname/$ent/targetname");
896 my (undef, $host) = dir_glob_regex
("$dirname/$ent/device", 'target(\d+):.*');
897 return if !defined($host);
899 dir_glob_foreach
("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
900 my ($tmp, $channel, $id, $lun) = @_;
902 my $type = file_read_firstline
("/sys/bus/scsi/devices/$tmp/type");
903 return if !defined($type) || $type ne '0'; # list disks only
906 if (-d
"/sys/bus/scsi/devices/$tmp/block") { # newer kernels
907 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
909 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
914 if (-d
"/sys/block/$bdev/holders") {
915 my $multipathdev = dir_glob_regex
("/sys/block/$bdev/holders", '[A-Za-z]\S*');
916 $bdev = $multipathdev if $multipathdev;
919 my $blockdev = $stable_paths->{$bdev};
920 return if !$blockdev;
922 my $size = file_read_firstline
("/sys/block/$bdev/size");
925 my $volid = "$channel.$id.$lun.$blockdev";
927 $res->{$target}->{$volid} = {
929 'size' => int($size * 512),
930 'vmid' => 0, # not assigned to any vm
931 'channel' => int($channel),
936 #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
944 # library implementation
946 PVE
::JSONSchema
::register_format
('pve-storage-vgname', \
&parse_lvm_name
);
948 my ($name, $noerr) = @_;
950 if ($name !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
951 return undef if $noerr;
952 die "lvm name '$name' contains illegal characters\n";
961 die "VMID '$vmid' contains illegal characters\n" if $vmid !~ m/^\d+$/;
966 PVE
::JSONSchema
::register_format
('pve-volume-id', \
&parse_volume_id
);
967 sub parse_volume_id
{
968 my ($volid, $noerr) = @_;
970 if ($volid =~ m/^([a-z][a-z0-9\-\_\.]*[a-z0-9]):(.+)$/i) {
971 return wantarray ?
($1, $2) : $1;
973 return undef if $noerr;
974 die "unable to parse volume ID '$volid'\n";
980 if ($name =~ m!^([^/\s]+\.(raw|qcow2|vmdk))$!) {
984 die "unable to parse volume filename '$name'\n";
987 sub parse_volname_dir
{
990 if ($volname =~ m!^(\d+)/(\S+)$!) {
991 my ($vmid, $name) = ($1, $2);
992 parse_name_dir
($name);
993 return ('image', $name, $vmid);
994 } elsif ($volname =~ m!^iso/([^/]+\.[Ii][Ss][Oo])$!) {
996 } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.gz)$!) {
997 return ('vztmpl', $1);
998 } elsif ($volname =~ m!^rootdir/(\d+)$!) {
999 return ('rootdir', $1, $1);
1000 } elsif ($volname =~ m!^backup/([^/]+(\.(tar|tar\.gz|tar\.lzo|tgz)))$!) {
1002 if ($fn =~ m/^vzdump-(openvz|qemu)-(\d+)-.+/) {
1003 return ('backup', $fn, $2);
1005 return ('backup', $fn);
1007 die "unable to parse directory volume name '$volname'\n";
1010 sub parse_volname_lvm
{
1011 my $volname = shift;
1013 parse_lvm_name
($volname);
1015 if ($volname =~ m/^(vm-(\d+)-\S+)$/) {
1019 die "unable to parse lvm volume name '$volname'\n";
1022 sub parse_volname_iscsi
{
1023 my $volname = shift;
1025 if ($volname =~ m!^\d+\.\d+\.\d+\.(\S+)$!) {
1030 die "unable to parse iscsi volume name '$volname'\n";
1033 # try to map a filesystem path to a volume identifier
1034 sub path_to_volume_id
{
1035 my ($cfg, $path) = @_;
1037 my $ids = $cfg->{ids
};
1039 my ($sid, $volname) = parse_volume_id
($path, 1);
1041 if ($ids->{$sid} && (my $type = $ids->{$sid}->{type
})) {
1042 if ($type eq 'dir' || $type eq 'nfs') {
1043 my ($vtype, $name, $vmid) = parse_volname_dir
($volname);
1044 return ($vtype, $path);
1050 $path = abs_path
($path);
1052 foreach my $sid (keys %$ids) {
1053 my $type = $ids->{$sid}->{type
};
1054 next if !($type eq 'dir' || $type eq 'nfs');
1056 my $imagedir = get_image_dir
($cfg, $sid);
1057 my $isodir = get_iso_dir
($cfg, $sid);
1058 my $tmpldir = get_vztmpl_dir
($cfg, $sid);
1059 my $backupdir = get_backup_dir
($cfg, $sid);
1060 my $privatedir = get_private_dir
($cfg, $sid);
1062 if ($path =~ m!^$imagedir/(\d+)/([^/\s]+)$!) {
1065 return ('image', "$sid:$vmid/$name");
1066 } elsif ($path =~ m!^$isodir/([^/]+\.[Ii][Ss][Oo])$!) {
1068 return ('iso', "$sid:iso/$name");
1069 } elsif ($path =~ m!^$tmpldir/([^/]+\.tar\.gz)$!) {
1071 return ('vztmpl', "$sid:vztmpl/$name");
1072 } elsif ($path =~ m!^$privatedir/(\d+)$!) {
1074 return ('rootdir', "$sid:rootdir/$vmid");
1075 } elsif ($path =~ m!^$backupdir/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!) {
1077 return ('iso', "$sid:backup/$name");
1081 # can't map path to volume id
1086 my ($cfg, $volid) = @_;
1088 my ($storeid, $volname) = parse_volume_id
($volid);
1090 my $scfg = storage_config
($cfg, $storeid);
1094 my $vtype = 'image';
1096 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1098 ($vtype, $name, $vmid) = parse_volname_dir
($volname);
1101 my $imagedir = get_image_dir
($cfg, $storeid, $vmid);
1102 my $isodir = get_iso_dir
($cfg, $storeid);
1103 my $tmpldir = get_vztmpl_dir
($cfg, $storeid);
1104 my $backupdir = get_backup_dir
($cfg, $storeid);
1105 my $privatedir = get_private_dir
($cfg, $storeid);
1107 if ($vtype eq 'image') {
1108 $path = "$imagedir/$name";
1109 } elsif ($vtype eq 'iso') {
1110 $path = "$isodir/$name";
1111 } elsif ($vtype eq 'vztmpl') {
1112 $path = "$tmpldir/$name";
1113 } elsif ($vtype eq 'rootdir') {
1114 $path = "$privatedir/$name";
1115 } elsif ($vtype eq 'backup') {
1116 $path = "$backupdir/$name";
1118 die "should not be reached";
1121 } elsif ($scfg->{type
} eq 'lvm') {
1123 my $vg = $scfg->{vgname
};
1125 my ($name, $vmid) = parse_volname_lvm
($volname);
1128 $path = "/dev/$vg/$name";
1130 } elsif ($scfg->{type
} eq 'iscsi') {
1131 my $byid = parse_volname_iscsi
($volname);
1132 $path = "/dev/disk/by-id/$byid";
1134 die "unknown storage type '$scfg->{type}'";
1137 return wantarray ?
($path, $owner, $vtype) : $path;
1140 sub storage_migrate
{
1141 my ($cfg, $volid, $target_host, $target_storeid, $target_volname) = @_;
1143 my ($storeid, $volname) = parse_volume_id
($volid);
1144 $target_volname = $volname if !$target_volname;
1146 my $scfg = storage_config
($cfg, $storeid);
1148 # no need to migrate shared content
1149 return if $storeid eq $target_storeid && $scfg->{shared
};
1151 my $tcfg = storage_config
($cfg, $target_storeid);
1153 my $target_volid = "${target_storeid}:${target_volname}";
1155 my $errstr = "unable to migrate '$volid' to '${target_volid}' on host '$target_host'";
1157 # blowfish is a fast block cipher, much faster then 3des
1158 my $sshoptions = "-c blowfish -o 'BatchMode=yes'";
1159 my $ssh = "/usr/bin/ssh $sshoptions";
1161 local $ENV{RSYNC_RSH
} = $ssh;
1163 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1164 if ($tcfg->{type
} eq 'dir' || $tcfg->{type
} eq 'nfs') {
1166 my $src = path
($cfg, $volid);
1167 my $dst = path
($cfg, $target_volid);
1169 my $dirname = dirname
($dst);
1171 if ($tcfg->{shared
}) { # we can do a local copy
1173 run_command
(['/bin/mkdir', '-p', $dirname]);
1175 run_command
(['/bin/cp', $src, $dst]);
1179 run_command
(['/usr/bin/ssh', "root\@${target_host}",
1180 '/bin/mkdir', '-p', $dirname]);
1182 # we use rsync with --sparse, so we can't use --inplace,
1183 # so we remove file on the target if it already exists to
1185 my ($size, $format) = file_size_info
($src);
1186 if ($format && ($format eq 'raw') && $size) {
1187 run_command
(['/usr/bin/ssh', "root\@${target_host}",
1192 my $cmd = ['/usr/bin/rsync', '--progress', '--sparse', '--whole-file',
1193 $src, "root\@${target_host}:$dst"];
1197 run_command
($cmd, outfunc
=> sub {
1200 if ($line =~ m/^\s*(\d+\s+(\d+)%\s.*)$/) {
1201 if ($2 > $percent) {
1203 print "rsync status: $1\n";
1216 die "$errstr - target type '$tcfg->{type}' not implemented\n";
1220 die "$errstr - source type '$scfg->{type}' not implemented\n";
1225 my ($cfg, $storeid, $vmid, $fmt, $name, $size) = @_;
1227 die "no storage id specified\n" if !$storeid;
1229 PVE
::JSONSchema
::parse_storage_id
($storeid);
1231 my $scfg = storage_config
($cfg, $storeid);
1233 die "no VMID specified\n" if !$vmid;
1235 $vmid = parse_vmid
($vmid);
1237 my $defformat = storage_default_format
($cfg, $storeid);
1239 $fmt = $defformat if !$fmt;
1241 activate_storage
($cfg, $storeid);
1243 # lock shared storage
1244 return cluster_lock_storage
($storeid, $scfg->{shared
}, undef, sub {
1246 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1248 my $imagedir = get_image_dir
($cfg, $storeid, $vmid);
1254 for (my $i = 1; $i < 100; $i++) {
1255 my @gr = <$imagedir/vm-$vmid-disk
-$i.*>;
1257 $name = "vm-$vmid-disk-$i.$fmt";
1263 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
1266 my (undef, $tmpfmt) = parse_name_dir
($name);
1268 die "illegal name '$name' - wrong extension for format ('$tmpfmt != '$fmt')\n"
1271 my $path = "$imagedir/$name";
1273 die "disk image '$path' already exists\n" if -e
$path;
1275 run_command
("/usr/bin/qemu-img create -f $fmt '$path' ${size}K",
1276 errmsg
=> "unable to create image");
1278 return "$storeid:$vmid/$name";
1280 } elsif ($scfg->{type
} eq 'lvm') {
1282 die "unsupported format '$fmt'" if $fmt ne 'raw';
1284 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
1285 if $name && $name !~ m/^vm-$vmid-/;
1287 my $vgs = lvm_vgs
();
1289 my $vg = $scfg->{vgname
};
1291 die "no such volume gruoup '$vg'\n" if !defined ($vgs->{$vg});
1293 my $free = int ($vgs->{$vg}->{free
});
1295 die "not enough free space ($free < $size)\n" if $free < $size;
1298 my $lvs = lvm_lvs
($vg);
1300 for (my $i = 1; $i < 100; $i++) {
1301 my $tn = "vm-$vmid-disk-$i";
1302 if (!defined ($lvs->{$vg}->{$tn})) {
1309 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
1312 my $cmd = ['/sbin/lvcreate', '-aly', '--addtag', "pve-vm-$vmid", '--size', "${size}k", '--name', $name, $vg];
1314 run_command
($cmd, errmsg
=> "lvcreate '$vg/pve-vm-$vmid' error");
1316 return "$storeid:$name";
1318 } elsif ($scfg->{type
} eq 'iscsi') {
1319 die "can't allocate space in iscsi storage\n";
1321 die "unknown storage type '$scfg->{type}'";
1327 my ($cfg, $volid) = @_;
1329 my ($storeid, $volname) = parse_volume_id
($volid);
1331 my $scfg = storage_config
($cfg, $storeid);
1333 activate_storage
($cfg, $storeid);
1335 # we need to zero out LVM data for security reasons
1336 # and to allow thin provisioning
1340 # lock shared storage
1341 cluster_lock_storage
($storeid, $scfg->{shared
}, undef, sub {
1343 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1344 my $path = path
($cfg, $volid);
1347 warn "disk image '$path' does not exists\n";
1351 } elsif ($scfg->{type
} eq 'lvm') {
1353 if ($scfg->{saferemove
}) {
1354 # avoid long running task, so we only rename here
1355 $vg = $scfg->{vgname
};
1356 my $cmd = ['/sbin/lvrename', $vg, $volname, "del-$volname"];
1357 run_command
($cmd, errmsg
=> "lvrename '$vg/$volname' error");
1359 my $tmpvg = $scfg->{vgname
};
1360 my $cmd = ['/sbin/lvremove', '-f', "$tmpvg/$volname"];
1361 run_command
($cmd, errmsg
=> "lvremove '$tmpvg/$volname' error");
1364 } elsif ($scfg->{type
} eq 'iscsi') {
1365 die "can't free space in iscsi storage\n";
1367 die "unknown storage type '$scfg->{type}'";
1373 my $zero_out_worker = sub {
1374 print "zero-out data on image $volname\n";
1375 my $cmd = ['dd', "if=/dev/zero", "of=/dev/$vg/del-$volname", "bs=1M"];
1376 eval { run_command
($cmd, errmsg
=> "zero out failed"); };
1379 cluster_lock_storage
($storeid, $scfg->{shared
}, undef, sub {
1380 my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"];
1381 run_command
($cmd, errmsg
=> "lvremove '$vg/del-$volname' error");
1383 print "successfully removed volume $volname\n";
1386 my $rpcenv = PVE
::RPCEnvironment
::get
();
1387 my $authuser = $rpcenv->get_user();
1389 $rpcenv->fork_worker('imgdel', undef, $authuser, $zero_out_worker);
1392 # lvm utility functions
1397 die "no device specified" if !$device;
1401 my $cmd = ['/usr/bin/file', '-L', '-s', $device];
1402 run_command
($cmd, outfunc
=> sub {
1404 $has_label = 1 if $line =~ m/LVM2/;
1407 return undef if !$has_label;
1409 $cmd = ['/sbin/pvs', '--separator', ':', '--noheadings', '--units', 'k',
1410 '--unbuffered', '--nosuffix', '--options',
1411 'pv_name,pv_size,vg_name,pv_uuid', $device];
1414 run_command
($cmd, outfunc
=> sub {
1417 $line = trim
($line);
1419 my ($pvname, $size, $vgname, $uuid) = split (':', $line);
1421 die "found multiple pvs entries for device '$device'\n"
1435 sub clear_first_sector
{
1438 if (my $fh = IO
::File-
>new ($dev, "w")) {
1445 sub lvm_create_volume_group
{
1446 my ($device, $vgname, $shared) = @_;
1448 my $res = lvm_pv_info
($device);
1450 if ($res->{vgname
}) {
1451 return if $res->{vgname
} eq $vgname; # already created
1452 die "device '$device' is already used by volume group '$res->{vgname}'\n";
1455 clear_first_sector
($device); # else pvcreate fails
1457 # we use --metadatasize 250k, which reseults in "pe_start = 512"
1458 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
1459 my $cmd = ['/sbin/pvcreate', '--metadatasize', '250k', $device];
1461 run_command
($cmd, errmsg
=> "pvcreate '$device' error");
1463 $cmd = ['/sbin/vgcreate', $vgname, $device];
1464 # push @$cmd, '-c', 'y' if $shared; # we do not use this yet
1466 run_command
($cmd, errmsg
=> "vgcreate $vgname $device error");
1471 my $cmd = ['/sbin/vgs', '--separator', ':', '--noheadings', '--units', 'b',
1472 '--unbuffered', '--nosuffix', '--options',
1473 'vg_name,vg_size,vg_free'];
1477 run_command
($cmd, outfunc
=> sub {
1480 $line = trim
($line);
1482 my ($name, $size, $free) = split (':', $line);
1484 $vgs->{$name} = { size
=> int ($size), free
=> int ($free) };
1489 # just warn (vgs return error code 5 if clvmd does not run)
1490 # but output is still OK (list without clustered VGs)
1499 my $cmd = ['/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b',
1500 '--unbuffered', '--nosuffix', '--options',
1501 'vg_name,lv_name,lv_size,uuid,tags'];
1503 push @$cmd, $vgname if $vgname;
1506 run_command
($cmd, outfunc
=> sub {
1509 $line = trim
($line);
1511 my ($vg, $name, $size, $uuid, $tags) = split (':', $line);
1513 return if $name !~ m/^vm-(\d+)-/;
1517 foreach my $tag (split (/,/, $tags)) {
1518 if ($tag =~ m/^pve-vm-(\d+)$/) {
1525 if ($owner ne $nid) {
1526 warn "owner mismatch name = $name, owner = $owner\n";
1529 $lvs->{$vg}->{$name} = { format
=> 'raw', size
=> $size,
1530 uuid
=> $uuid, tags
=> $tags,
1538 #list iso or openvz template ($tt = <iso|vztmpl|backup>)
1540 my ($cfg, $storeid, $tt) = @_;
1542 die "unknown template type '$tt'\n" if !($tt eq 'iso' || $tt eq 'vztmpl' || $tt eq 'backup');
1544 my $ids = $cfg->{ids
};
1546 storage_check_enabled
($cfg, $storeid) if ($storeid);
1552 foreach my $sid (keys %$ids) {
1553 next if $storeid && $storeid ne $sid;
1555 my $scfg = $ids->{$sid};
1556 my $type = $scfg->{type
};
1558 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1560 next if $tt eq 'iso' && !$scfg->{content
}->{iso
};
1561 next if $tt eq 'vztmpl' && !$scfg->{content
}->{vztmpl
};
1562 next if $tt eq 'backup' && !$scfg->{content
}->{backup
};
1564 activate_storage
($cfg, $sid);
1566 if ($type eq 'dir' || $type eq 'nfs') {
1570 $path = get_iso_dir
($cfg, $sid);
1571 } elsif ($tt eq 'vztmpl') {
1572 $path = get_vztmpl_dir
($cfg, $sid);
1573 } elsif ($tt eq 'backup') {
1574 $path = get_backup_dir
($cfg, $sid);
1576 die "unknown template type '$tt'\n";
1579 foreach my $fn (<$path/*>) {
1584 next if $fn !~ m!/([^/]+\.[Ii][Ss][Oo])$!;
1586 $info = { volid
=> "$sid:iso/$1", format
=> 'iso' };
1588 } elsif ($tt eq 'vztmpl') {
1589 next if $fn !~ m!/([^/]+\.tar\.gz)$!;
1591 $info = { volid
=> "$sid:vztmpl/$1", format
=> 'tgz' };
1593 } elsif ($tt eq 'backup') {
1594 next if $fn !~ m!/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!;
1596 $info = { volid
=> "$sid:backup/$1", format
=> $2 };
1599 $info->{size
} = -s
$fn;
1601 push @{$res->{$sid}}, $info;
1606 @{$res->{$sid}} = sort {lc($a->{volid
}) cmp lc ($b->{volid
}) } @{$res->{$sid}} if $res->{$sid};
1612 sub file_size_info
{
1613 my ($filename, $timeout) = @_;
1615 my $cmd = ['/usr/bin/qemu-img', 'info', $filename];
1622 run_command
($cmd, timeout
=> $timeout, outfunc
=> sub {
1625 if ($line =~ m/^file format:\s+(\S+)\s*$/) {
1627 } elsif ($line =~ m/^virtual size:\s\S+\s+\((\d+)\s+bytes\)$/) {
1629 } elsif ($line =~ m/^disk size:\s+(\d+(.\d+)?)([KMGT])\s*$/) {
1633 $used *= 1024 if $u eq 'K';
1634 $used *= (1024*1024) if $u eq 'M';
1635 $used *= (1024*1024*1024) if $u eq 'G';
1636 $used *= (1024*1024*1024*1024) if $u eq 'T';
1643 return wantarray ?
($size, $format, $used) : $size;
1647 my ($cfg, $storeid, $vmid, $vollist) = @_;
1649 my $ids = $cfg->{ids
};
1651 storage_check_enabled
($cfg, $storeid) if ($storeid);
1655 # prepare/activate/refresh all storages
1659 my $storage_list = [];
1661 foreach my $volid (@$vollist) {
1662 my ($sid, undef) = parse_volume_id
($volid);
1663 next if !defined ($ids->{$sid});
1664 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1665 push @$storage_list, $sid;
1666 $stypes->{$ids->{$sid}->{type
}} = 1;
1669 foreach my $sid (keys %$ids) {
1670 next if $storeid && $storeid ne $sid;
1671 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1672 push @$storage_list, $sid;
1673 $stypes->{$ids->{$sid}->{type
}} = 1;
1677 activate_storage_list
($cfg, $storage_list);
1679 my $lvs = $stypes->{lvm
} ? lvm_lvs
() : {};
1681 my $iscsi_devices = iscsi_device_list
() if $stypes->{iscsi
};
1685 foreach my $sid (keys %$ids) {
1687 next if $storeid ne $sid;
1688 next if !storage_check_enabled
($cfg, $sid, undef, 1);
1690 my $scfg = $ids->{$sid};
1691 my $type = $scfg->{type
};
1693 if ($type eq 'dir' || $type eq 'nfs') {
1695 my $path = $scfg->{path
};
1697 my $fmts = join ('|', keys %{$default_config->{$type}->{format
}->[0]});
1699 foreach my $fn (<$path/images/[0-9][0-9]*/*>) {
1701 next if $fn !~ m!^(/.+/images/(\d+)/([^/]+\.($fmts)))$!;
1706 my $volid = "$sid:$owner/$name";
1709 my $found = grep { $_ eq $volid } @$vollist;
1712 next if defined ($vmid) && ($owner ne $vmid);
1715 my ($size, $format, $used) = file_size_info
($fn);
1717 if ($format && $size) {
1718 push @{$res->{$sid}}, {
1719 volid
=> $volid, format
=> $format,
1720 size
=> $size, vmid
=> $owner, used
=> $used };
1725 } elsif ($type eq 'lvm') {
1727 my $vgname = $scfg->{vgname
};
1729 if (my $dat = $lvs->{$vgname}) {
1731 foreach my $volname (keys %$dat) {
1733 my $owner = $dat->{$volname}->{vmid
};
1735 my $volid = "$sid:$volname";
1738 my $found = grep { $_ eq $volid } @$vollist;
1741 next if defined ($vmid) && ($owner ne $vmid);
1744 my $info = $dat->{$volname};
1745 $info->{volid
} = $volid;
1747 push @{$res->{$sid}}, $info;
1751 } elsif ($type eq 'iscsi') {
1753 # we have no owner for iscsi devices
1755 my $target = $scfg->{target
};
1757 if (my $dat = $iscsi_devices->{$target}) {
1759 foreach my $volname (keys %$dat) {
1761 my $volid = "$sid:$volname";
1764 my $found = grep { $_ eq $volid } @$vollist;
1767 next if !($storeid && ($storeid eq $sid));
1770 my $info = $dat->{$volname};
1771 $info->{volid
} = $volid;
1773 push @{$res->{$sid}}, $info;
1781 @{$res->{$sid}} = sort {lc($a->{volid
}) cmp lc ($b->{volid
}) } @{$res->{$sid}} if $res->{$sid};
1787 sub nfs_is_mounted
{
1788 my ($server, $export, $mountpoint, $mountdata) = @_;
1790 my $source = "$server:$export";
1792 $mountdata = read_proc_mounts
() if !$mountdata;
1794 if ($mountdata =~ m
|^$source/?\s$mountpoint\snfs|m
) {
1802 my ($server, $export, $mountpoint, $options) = @_;
1804 my $source = "$server:$export";
1806 my $cmd = ['/bin/mount', '-t', 'nfs', $source, $mountpoint];
1808 push @$cmd, '-o', $options;
1811 run_command
($cmd, errmsg
=> "mount error");
1816 my $filename = "/sys/kernel/uevent_seqnum";
1819 if (my $fh = IO
::File-
>new ($filename, "r")) {
1821 if ($line =~ m/^(\d+)$/) {
1829 sub __activate_storage_full
{
1830 my ($cfg, $storeid, $session) = @_;
1832 my $scfg = storage_check_enabled
($cfg, $storeid);
1834 return if $session->{activated
}->{$storeid};
1836 if (!$session->{mountdata
}) {
1837 $session->{mountdata
} = read_proc_mounts
();
1840 if (!$session->{uevent_seqnum
}) {
1841 $session->{uevent_seqnum
} = uevent_seqnum
();
1844 my $mountdata = $session->{mountdata
};
1846 my $type = $scfg->{type
};
1848 if ($type eq 'dir' || $type eq 'nfs') {
1850 my $path = $scfg->{path
};
1852 if ($type eq 'nfs') {
1853 my $server = $scfg->{server
};
1854 my $export = $scfg->{export
};
1856 if (!nfs_is_mounted
($server, $export, $path, $mountdata)) {
1858 # NOTE: only call mkpath when not mounted (avoid hang
1859 # when NFS server is offline
1863 die "unable to activate storage '$storeid' - " .
1864 "directory '$path' does not exist\n" if ! -d
$path;
1866 nfs_mount
($server, $export, $path, $scfg->{options
});
1873 die "unable to activate storage '$storeid' - " .
1874 "directory '$path' does not exist\n" if ! -d
$path;
1877 my $imagedir = get_image_dir
($cfg, $storeid);
1878 my $isodir = get_iso_dir
($cfg, $storeid);
1879 my $tmpldir = get_vztmpl_dir
($cfg, $storeid);
1880 my $backupdir = get_backup_dir
($cfg, $storeid);
1881 my $privatedir = get_private_dir
($cfg, $storeid);
1883 if (defined($scfg->{content
})) {
1884 mkpath
$imagedir if $scfg->{content
}->{images
} &&
1886 mkpath
$isodir if $scfg->{content
}->{iso
} &&
1888 mkpath
$tmpldir if $scfg->{content
}->{vztmpl
} &&
1890 mkpath
$privatedir if $scfg->{content
}->{rootdir
} &&
1891 $privatedir ne $path;
1892 mkpath
$backupdir if $scfg->{content
}->{backup
} &&
1893 $backupdir ne $path;
1896 } elsif ($type eq 'lvm') {
1898 if ($scfg->{base
}) {
1899 my ($baseid, undef) = parse_volume_id
($scfg->{base
});
1900 __activate_storage_full
($cfg, $baseid, $session);
1903 if (!$session->{vgs
}) {
1904 $session->{vgs
} = lvm_vgs
();
1907 # In LVM2, vgscans take place automatically;
1908 # this is just to be sure
1909 if ($session->{vgs
} && !$session->{vgscaned
} &&
1910 !$session->{vgs
}->{$scfg->{vgname
}}) {
1911 $session->{vgscaned
} = 1;
1912 my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes'];
1913 eval { run_command
($cmd, outfunc
=> sub {}); };
1917 # we do not acticate any volumes here ('vgchange -aly')
1918 # instead, volumes are activate individually later
1920 } elsif ($type eq 'iscsi') {
1922 return if !check_iscsi_support
(1);
1924 $session->{iscsi_sessions
} = iscsi_session_list
()
1925 if !$session->{iscsi_sessions
};
1927 my $iscsi_sess = $session->{iscsi_sessions
}->{$scfg->{target
}};
1928 if (!defined ($iscsi_sess)) {
1929 eval { iscsi_login
($scfg->{target
}, $scfg->{portal
}); };
1932 # make sure we get all devices
1933 iscsi_session_rescan
($iscsi_sess);
1940 my $newseq = uevent_seqnum
();
1942 # only call udevsettle if there are events
1943 if ($newseq > $session->{uevent_seqnum
}) {
1945 system ("$UDEVADM settle --timeout=$timeout"); # ignore errors
1946 $session->{uevent_seqnum
} = $newseq;
1949 $session->{activated
}->{$storeid} = 1;
1952 sub activate_storage_list
{
1953 my ($cfg, $storeid_list, $session) = @_;
1955 $session = {} if !$session;
1957 foreach my $storeid (@$storeid_list) {
1958 __activate_storage_full
($cfg, $storeid, $session);
1962 sub activate_storage
{
1963 my ($cfg, $storeid) = @_;
1967 __activate_storage_full
($cfg, $storeid, $session);
1970 sub activate_volumes
{
1971 my ($cfg, $vollist, $exclusive) = @_;
1973 return if !($vollist && scalar(@$vollist));
1975 my $lvm_activate_mode = $exclusive ?
'ey' : 'ly';
1977 my $storagehash = {};
1978 foreach my $volid (@$vollist) {
1979 my ($storeid, undef) = parse_volume_id
($volid);
1980 $storagehash->{$storeid} = 1;
1983 activate_storage_list
($cfg, [keys %$storagehash]);
1985 foreach my $volid (@$vollist) {
1986 my ($storeid, $volname) = parse_volume_id
($volid);
1988 my $scfg = storage_config
($cfg, $storeid);
1990 my $path = path
($cfg, $volid);
1992 if ($scfg->{type
} eq 'lvm') {
1993 my $cmd = ['/sbin/lvchange', "-a$lvm_activate_mode", $path];
1994 run_command
($cmd, errmsg
=> "can't activate LV '$volid'");
1997 # check is volume exists
1998 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
1999 die "volume '$volid' does not exist\n" if ! -e
$path;
2001 die "volume '$volid' does not exist\n" if ! -b
$path;
2006 sub deactivate_volumes
{
2007 my ($cfg, $vollist) = @_;
2009 return if !($vollist && scalar(@$vollist));
2011 my $lvs = lvm_lvs
();
2014 foreach my $volid (@$vollist) {
2015 my ($storeid, $volname) = parse_volume_id
($volid);
2017 my $scfg = storage_config
($cfg, $storeid);
2019 if ($scfg->{type
} eq 'lvm') {
2020 my ($name) = parse_volname_lvm
($volname);
2022 if ($lvs->{$scfg->{vgname
}}->{$name}) {
2023 my $path = path
($cfg, $volid);
2024 my $cmd = ['/sbin/lvchange', '-aln', $path];
2025 eval { run_command
($cmd, errmsg
=> "can't deactivate LV '$volid'"); };
2028 push @errlist, $volid;
2034 die "volume deativation failed: " . join(' ', @errlist)
2035 if scalar(@errlist);
2038 sub deactivate_storage
{
2039 my ($cfg, $storeid) = @_;
2043 my $scfg = storage_config
($cfg, $storeid);
2045 my $type = $scfg->{type
};
2047 if ($type eq 'dir') {
2049 } elsif ($type eq 'nfs') {
2050 my $mountdata = read_proc_mounts
();
2051 my $server = $scfg->{server
};
2052 my $export = $scfg->{export
};
2053 my $path = $scfg->{path
};
2055 my $cmd = ['/bin/umount', $path];
2057 run_command
($cmd, errmsg
=> 'umount error')
2058 if nfs_is_mounted
($server, $export, $path, $mountdata);
2060 } elsif ($type eq 'lvm') {
2061 my $cmd = ['/sbin/vgchange', '-aln', $scfg->{vgname
}];
2062 run_command
($cmd, errmsg
=> "can't deactivate VG '$scfg->{vgname}'");
2063 } elsif ($type eq 'iscsi') {
2064 my $portal = $scfg->{portal
};
2065 my $target = $scfg->{target
};
2067 my $iscsi_sessions = iscsi_session_list
();
2068 iscsi_logout
($target, $portal)
2069 if defined ($iscsi_sessions->{$target});
2077 my ($cfg, $content) = @_;
2079 my $ids = $cfg->{ids
};
2085 foreach my $storeid (keys %$ids) {
2087 next if $content && !$ids->{$storeid}->{content
}->{$content};
2089 next if !storage_check_enabled
($cfg, $storeid, undef, 1);
2091 my $type = $ids->{$storeid}->{type
};
2093 $info->{$storeid} = {
2098 shared
=> $ids->{$storeid}->{shared
} ?
1 : 0,
2099 content
=> content_hash_to_string
($ids->{$storeid}->{content
}),
2103 $stypes->{$type} = 1;
2105 push @$slist, $storeid;
2110 my $iscsi_sessions = {};
2113 if ($stypes->{lvm
}) {
2114 $session->{vgs
} = lvm_vgs
();
2115 $vgs = $session->{vgs
};
2117 if ($stypes->{nfs
}) {
2118 $mountdata = read_proc_mounts
();
2119 $session->{mountdata
} = $mountdata;
2121 if ($stypes->{iscsi
}) {
2122 $iscsi_sessions = iscsi_session_list
();
2123 $session->{iscsi_sessions
} = $iscsi_sessions;
2126 eval { activate_storage_list
($cfg, $slist, $session); };
2128 foreach my $storeid (keys %$ids) {
2129 my $scfg = $ids->{$storeid};
2131 next if !$info->{$storeid};
2133 my $type = $scfg->{type
};
2135 if ($type eq 'dir' || $type eq 'nfs') {
2137 my $path = $scfg->{path
};
2139 if ($type eq 'nfs') {
2140 my $server = $scfg->{server
};
2141 my $export = $scfg->{export
};
2143 next if !nfs_is_mounted
($server, $export, $path, $mountdata);
2147 my $res = PVE
::Tools
::df
($path, $timeout);
2149 next if !$res || !$res->{total
};
2151 $info->{$storeid}->{total
} = $res->{total
};
2152 $info->{$storeid}->{avail
} = $res->{avail
};
2153 $info->{$storeid}->{used
} = $res->{used
};
2154 $info->{$storeid}->{active
} = 1;
2156 } elsif ($type eq 'lvm') {
2158 my $vgname = $scfg->{vgname
};
2163 if (defined ($vgs->{$vgname})) {
2164 $total = $vgs->{$vgname}->{size
};
2165 $free = $vgs->{$vgname}->{free
};
2167 $info->{$storeid}->{total
} = $total;
2168 $info->{$storeid}->{avail
} = $free;
2169 $info->{$storeid}->{used
} = $total - $free;
2170 $info->{$storeid}->{active
} = 1;
2173 } elsif ($type eq 'iscsi') {
2175 $info->{$storeid}->{total
} = 0;
2176 $info->{$storeid}->{avail
} = 0;
2177 $info->{$storeid}->{used
} = 0;
2178 $info->{$storeid}->{active
} =
2179 defined ($iscsi_sessions->{$scfg->{target
}});
2192 my $packed_ip = gethostbyname($server);
2193 if (defined $packed_ip) {
2194 return inet_ntoa
($packed_ip);
2200 my ($server_in) = @_;
2203 if (!($server = resolv_server
($server_in))) {
2204 die "unable to resolve address for server '${server_in}'\n";
2207 my $cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
2210 run_command
($cmd, outfunc
=> sub {
2213 # note: howto handle white spaces in export path??
2214 if ($line =~ m!^(/\S+)\s+(.+)$!) {
2223 my ($portal, $noerr) = @_;
2225 if ($portal =~ m/^([^:]+)(:(\d+))?$/) {
2229 if (my $ip = resolv_server
($server)) {
2231 return $port ?
"$server:$port" : $server;
2234 return undef if $noerr;
2236 raise_param_exc
({ portal
=> "unable to resolve portal address '$portal'" });
2239 # idea is from usbutils package (/usr/bin/usb-devices) script
2240 sub __scan_usb_device
{
2241 my ($res, $devpath, $parent, $level) = @_;
2243 return if ! -d
$devpath;
2244 return if $level && $devpath !~ m/^.*[-.](\d+)$/;
2245 my $port = $level ?
int($1 - 1) : 0;
2247 my $busnum = int(file_read_firstline
("$devpath/busnum"));
2248 my $devnum = int(file_read_firstline
("$devpath/devnum"));
2255 speed
=> file_read_firstline
("$devpath/speed"),
2256 class => hex(file_read_firstline
("$devpath/bDeviceClass")),
2257 vendid
=> file_read_firstline
("$devpath/idVendor"),
2258 prodid
=> file_read_firstline
("$devpath/idProduct"),
2262 my $usbpath = $devpath;
2263 $usbpath =~ s
|^.*/\d
+\
-||;
2264 $d->{usbpath
} = $usbpath;
2267 my $product = file_read_firstline
("$devpath/product");
2268 $d->{product
} = $product if $product;
2270 my $manu = file_read_firstline
("$devpath/manufacturer");
2271 $d->{manufacturer
} = $manu if $manu;
2273 my $serial => file_read_firstline
("$devpath/serial");
2274 $d->{serial
} = $serial if $serial;
2278 foreach my $subdev (<$devpath/$busnum-*>) {
2279 next if $subdev !~ m
|/$busnum-[0-9]+(\
.[0-9]+)*$|;
2280 __scan_usb_device
($res, $subdev, $devnum, $level + 1);
2289 foreach my $device (</sys/bus
/usb
/devices
/usb
*>) {
2290 __scan_usb_device
($devlist, $device, 0, 0);
2297 my ($portal_in) = @_;
2300 if (!($portal = resolv_portal
($portal_in))) {
2301 die "unable to parse/resolve portal address '${portal_in}'\n";
2304 return iscsi_discovery
($portal);
2307 sub storage_default_format
{
2308 my ($cfg, $storeid) = @_;
2310 my $scfg = storage_config
($cfg, $storeid);
2312 my $def = $default_config->{$scfg->{type
}};
2314 my $def_format = 'raw';
2315 my $valid_formats = [ $def_format ];
2317 if (defined ($def->{format
})) {
2318 $def_format = $scfg->{format
} || $def->{format
}->[1];
2319 $valid_formats = [ sort keys %{$def->{format
}->[0]} ];
2322 return wantarray ?
($def_format, $valid_formats) : $def_format;
2325 sub vgroup_is_used
{
2326 my ($cfg, $vgname) = @_;
2328 foreach my $storeid (keys %{$cfg->{ids
}}) {
2329 my $scfg = storage_config
($cfg, $storeid);
2330 if ($scfg->{type
} eq 'lvm' && $scfg->{vgname
} eq $vgname) {
2338 sub target_is_used
{
2339 my ($cfg, $target) = @_;
2341 foreach my $storeid (keys %{$cfg->{ids
}}) {
2342 my $scfg = storage_config
($cfg, $storeid);
2343 if ($scfg->{type
} eq 'iscsi' && $scfg->{target
} eq $target) {
2351 sub volume_is_used
{
2352 my ($cfg, $volid) = @_;
2354 foreach my $storeid (keys %{$cfg->{ids
}}) {
2355 my $scfg = storage_config
($cfg, $storeid);
2356 if ($scfg->{base
} && $scfg->{base
} eq $volid) {
2364 sub storage_is_used
{
2365 my ($cfg, $storeid) = @_;
2367 foreach my $sid (keys %{$cfg->{ids
}}) {
2368 my $scfg = storage_config
($cfg, $sid);
2369 next if !$scfg->{base
};
2370 my ($st) = parse_volume_id
($scfg->{base
});
2371 return 1 if $st && $st eq $storeid;
2378 my ($list, $func) = @_;
2382 foreach my $sid (keys %$list) {
2383 foreach my $info (@{$list->{$sid}}) {
2384 my $volid = $info->{volid
};
2385 my ($sid1, $volname) = parse_volume_id
($volid, 1);
2386 if ($sid1 && $sid1 eq $sid) {
2387 &$func ($volid, $sid, $info);
2389 warn "detected strange volid '$volid' in volume list for '$sid'\n";