]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage.pm
use dir_glob_* from PVE::Tools
[pve-storage.git] / PVE / Storage.pm
1 package PVE::Storage;
2
3 use strict;
4 use POSIX;
5 use IO::Select;
6 use IO::Dir;
7 use IO::File;
8 use Fcntl ':flock';
9 use File::stat;
10 use File::Basename;
11 use File::Path;
12 use IPC::Open2;
13 use Cwd 'abs_path';
14 use Getopt::Long qw(GetOptionsFromArray);
15 use Socket;
16 use Digest::SHA;
17 use Net::Ping;
18
19 use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
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);
22 use PVE::JSONSchema;
23 use PVE::INotify;
24 use PVE::RPCEnvironment;
25
26 my $ISCSIADM = '/usr/bin/iscsiadm';
27 my $UDEVADM = '/sbin/udevadm';
28
29 $ISCSIADM = undef if ! -X $ISCSIADM;
30
31 # fixme: always_call_parser => 1 ??
32 cfs_register_file ('storage.cfg',
33 \&parse_config,
34 \&write_config);
35
36 # generic utility function
37
38 sub config {
39 return cfs_read_file("storage.cfg");
40 }
41
42 sub check_iscsi_support {
43 my $noerr = shift;
44
45 if (!$ISCSIADM) {
46 my $msg = "no iscsi support - please install open-iscsi";
47 if ($noerr) {
48 warn "warning: $msg\n";
49 return 0;
50 }
51
52 die "error: $msg\n";
53 }
54
55 return 1;
56 }
57
58 sub load_stable_scsi_paths {
59
60 my $stable_paths = {};
61
62 my $stabledir = "/dev/disk/by-id";
63
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;
74 }
75 }
76 }
77 $dh->close;
78 }
79 return $stable_paths;
80 }
81
82 sub read_proc_mounts {
83
84 local $/; # enable slurp mode
85
86 my $data = "";
87 if (my $fd = IO::File->new ("/proc/mounts", "r")) {
88 $data = <$fd>;
89 close ($fd);
90 }
91
92 return $data;
93 }
94
95 # PVE::Storage utility functions
96
97 sub lock_storage_config {
98 my ($code, $errmsg) = @_;
99
100 cfs_lock_file("storage.cfg", undef, $code);
101 my $err = $@;
102 if ($err) {
103 $errmsg ? die "$errmsg: $err" : die $err;
104 }
105 }
106
107 my $confvars = {
108 path => 'path',
109 shared => 'bool',
110 disable => 'bool',
111 saferemove => 'bool',
112 format => 'format',
113 content => 'content',
114 server => 'server',
115 export => 'path',
116 vgname => 'vgname',
117 base => 'volume',
118 portal => 'portal',
119 target => 'target',
120 nodes => 'nodes',
121 options => 'options',
122 maxfiles => 'natural',
123 };
124
125 my $required_config = {
126 dir => ['path'],
127 nfs => ['path', 'server', 'export'],
128 lvm => ['vgname'],
129 iscsi => ['portal', 'target'],
130 };
131
132 my $fixed_config = {
133 dir => ['path'],
134 nfs => ['path', 'server', 'export'],
135 lvm => ['vgname', 'base'],
136 iscsi => ['portal', 'target'],
137 };
138
139 my $default_config = {
140 dir => {
141 path => 1,
142 nodes => 0,
143 shared => 0,
144 disable => 0,
145 maxfiles => 0,
146 content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, none => 1 },
147 { images => 1, rootdir => 1 }],
148 format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
149 },
150
151 nfs => {
152 path => 1,
153 nodes => 0,
154 disable => 0,
155 server => 1,
156 export => 1,
157 options => 0,
158 maxfiles => 0,
159 content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1},
160 { images => 1 }],
161 format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
162 },
163
164 lvm => {
165 vgname => 1,
166 nodes => 0,
167 shared => 0,
168 disable => 0,
169 saferemove => 0,
170 content => [ {images => 1}, { images => 1 }],
171 base => 1,
172 },
173
174 iscsi => {
175 portal => 1,
176 target => 1,
177 nodes => 0,
178 disable => 0,
179 content => [ {images => 1, none => 1}, { images => 1 }],
180 },
181 };
182
183 sub valid_content_types {
184 my ($stype) = @_;
185
186 my $def = $default_config->{$stype};
187
188 return {} if !$def;
189
190 return $def->{content}->[0];
191 }
192
193 sub content_hash_to_string {
194 my $hash = shift;
195
196 my @cta;
197 foreach my $ct (keys %$hash) {
198 push @cta, $ct if $hash->{$ct};
199 }
200
201 return join(',', @cta);
202 }
203
204 PVE::JSONSchema::register_format('pve-storage-path', \&verify_path);
205 sub verify_path {
206 my ($path, $noerr) = @_;
207
208 # fixme: exclude more shell meta characters?
209 # we need absolute paths
210 if ($path !~ m|^/[^;\(\)]+|) {
211 return undef if $noerr;
212 die "value does not look like a valid absolute path\n";
213 }
214 return $path;
215 }
216
217 PVE::JSONSchema::register_format('pve-storage-server', \&verify_server);
218 sub verify_server {
219 my ($server, $noerr) = @_;
220
221 # fixme: use better regex ?
222 # IP or DNS name
223 if ($server !~ m/^[[:alnum:]\-\.]+$/) {
224 return undef if $noerr;
225 die "value does not look like a valid server name or IP address\n";
226 }
227 return $server;
228 }
229
230 PVE::JSONSchema::register_format('pve-storage-portal', \&verify_portal);
231 sub verify_portal {
232 my ($portal, $noerr) = @_;
233
234 # IP with optional port
235 if ($portal !~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/) {
236 return undef if $noerr;
237 die "value does not look like a valid portal address\n";
238 }
239 return $portal;
240 }
241
242 PVE::JSONSchema::register_format('pve-storage-portal-dns', \&verify_portal_dns);
243 sub verify_portal_dns {
244 my ($portal, $noerr) = @_;
245
246 # IP or DNS name with optional port
247 if ($portal !~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[[:alnum:]\-\.]+)(:\d+)?$/) {
248 return undef if $noerr;
249 die "value does not look like a valid portal address\n";
250 }
251 return $portal;
252 }
253
254 PVE::JSONSchema::register_format('pve-storage-content', \&verify_content);
255 sub verify_content {
256 my ($ct, $noerr) = @_;
257
258 my $valid_content = valid_content_types('dir'); # dir includes all types
259
260 if (!$valid_content->{$ct}) {
261 return undef if $noerr;
262 die "invalid content type '$ct'\n";
263 }
264
265 return $ct;
266 }
267
268 PVE::JSONSchema::register_format('pve-storage-format', \&verify_format);
269 sub verify_format {
270 my ($fmt, $noerr) = @_;
271
272 if ($fmt !~ m/(raw|qcow2|vmdk)/) {
273 return undef if $noerr;
274 die "invalid format '$fmt'\n";
275 }
276
277 return $fmt;
278 }
279
280 PVE::JSONSchema::register_format('pve-storage-options', \&verify_options);
281 sub verify_options {
282 my ($value, $noerr) = @_;
283
284 # mount options (see man fstab)
285 if ($value !~ m/^\S+$/) {
286 return undef if $noerr;
287 die "invalid options '$value'\n";
288 }
289
290 return $value;
291 }
292
293 sub check_type {
294 my ($stype, $ct, $key, $value, $storeid, $noerr) = @_;
295
296 my $def = $default_config->{$stype};
297
298 if (!$def) { # should not happen
299 return undef if $noerr;
300 die "unknown storage type '$stype'\n";
301 }
302
303 if (!defined($def->{$key})) {
304 return undef if $noerr;
305 die "unexpected property\n";
306 }
307
308 if (!defined ($value)) {
309 return undef if $noerr;
310 die "got undefined value\n";
311 }
312
313 if ($value =~ m/[\n\r]/) {
314 return undef if $noerr;
315 die "property contains a line feed\n";
316 }
317
318 if ($ct eq 'bool') {
319 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
320 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
321 return undef if $noerr;
322 die "type check ('boolean') failed - got '$value'\n";
323 } elsif ($ct eq 'options') {
324 return verify_options($value, $noerr);
325 } elsif ($ct eq 'path') {
326 return verify_path($value, $noerr);
327 } elsif ($ct eq 'server') {
328 return verify_server($value, $noerr);
329 } elsif ($ct eq 'vgname') {
330 return parse_lvm_name ($value, $noerr);
331 } elsif ($ct eq 'portal') {
332 return verify_portal($value, $noerr);
333 } elsif ($ct eq 'natural') {
334 return int($value) if $value =~ m/^\d+$/;
335 return undef if $noerr;
336 die "type check ('natural') failed - got '$value'\n";
337 } elsif ($ct eq 'nodes') {
338 my $res = {};
339
340 foreach my $node (PVE::Tools::split_list($value)) {
341 if (PVE::JSONSchema::pve_verify_node_name($node, $noerr)) {
342 $res->{$node} = 1;
343 }
344 }
345
346 # no node restrictions for local storage
347 if ($storeid && $storeid eq 'local' && scalar(keys(%$res))) {
348 return undef if $noerr;
349 die "storage '$storeid' does not allow node restrictions\n";
350 }
351
352 return $res;
353 } elsif ($ct eq 'target') {
354 return $value;
355 } elsif ($ct eq 'string') {
356 return $value;
357 } elsif ($ct eq 'format') {
358 my $valid_formats = $def->{format}->[0];
359
360 if (!$valid_formats->{$value}) {
361 return undef if $noerr;
362 die "storage does not support format '$value'\n";
363 }
364
365 return $value;
366
367 } elsif ($ct eq 'content') {
368 my $valid_content = $def->{content}->[0];
369
370 my $res = {};
371
372 foreach my $c (PVE::Tools::split_list($value)) {
373 if (!$valid_content->{$c}) {
374 return undef if $noerr;
375 die "storage does not support content type '$c'\n";
376 }
377 $res->{$c} = 1;
378 }
379
380 if ($res->{none} && scalar (keys %$res) > 1) {
381 return undef if $noerr;
382 die "unable to combine 'none' with other content types\n";
383 }
384
385 return $res;
386 } elsif ($ct eq 'volume') {
387 return $value if parse_volume_id ($value, $noerr);
388 }
389
390 return undef if $noerr;
391 die "type check not implemented - internal error\n";
392 }
393
394 sub parse_config {
395 my ($filename, $raw) = @_;
396
397 my $ids = {};
398
399 my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : '');
400
401 my $pri = 0;
402
403 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
404 my $line = $1;
405
406 next if $line =~ m/^\#/;
407 next if $line =~ m/^\s*$/;
408
409 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
410 my $storeid = $2;
411 my $type = $1;
412 my $ignore = 0;
413
414 if (!PVE::JSONSchema::parse_storage_id($storeid, 1)) {
415 $ignore = 1;
416 warn "ignoring storage '$storeid' - (illegal characters)\n";
417 } elsif (!$default_config->{$type}) {
418 $ignore = 1;
419 warn "ignoring storage '$storeid' (unsupported type '$type')\n";
420 } else {
421 $ids->{$storeid}->{type} = $type;
422 $ids->{$storeid}->{priority} = $pri++;
423 }
424
425 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
426 $line = $1;
427
428 next if $line =~ m/^\#/;
429 last if $line =~ m/^\s*$/;
430
431 next if $ignore; # skip
432
433 if ($line =~ m/^\s+(\S+)(\s+(.*\S))?\s*$/) {
434 my ($k, $v) = ($1, $3);
435 if (my $ct = $confvars->{$k}) {
436 $v = 1 if $ct eq 'bool' && !defined($v);
437 eval {
438 $ids->{$storeid}->{$k} = check_type ($type, $ct, $k, $v, $storeid);
439 };
440 warn "storage '$storeid' - unable to parse value of '$k': $@" if $@;
441 } else {
442 warn "storage '$storeid' - unable to parse value of '$k'\n";
443 }
444
445 } else {
446 warn "storage '$storeid' - ignore config line: $line\n";
447 }
448 }
449 } else {
450 warn "ignore config line: $line\n";
451 }
452 }
453
454 # make sure we have a reasonable 'local:' storage
455 # openvz expects things to be there
456 if (!$ids->{local} || $ids->{local}->{type} ne 'dir' ||
457 $ids->{local}->{path} ne '/var/lib/vz') {
458 $ids->{local} = {
459 type => 'dir',
460 priority => $pri++,
461 path => '/var/lib/vz',
462 content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1},
463 };
464 }
465
466 # we always need this for OpenVZ
467 $ids->{local}->{content}->{rootdir} = 1;
468 $ids->{local}->{content}->{vztmpl} = 1;
469 delete ($ids->{local}->{disable});
470
471 # remove node restrictions for local storage
472 delete($ids->{local}->{nodes});
473
474 foreach my $storeid (keys %$ids) {
475 my $d = $ids->{$storeid};
476
477 my $req_keys = $required_config->{$d->{type}};
478 foreach my $k (@$req_keys) {
479 if (!defined ($d->{$k})) {
480 warn "ignoring storage '$storeid' - missing value " .
481 "for required option '$k'\n";
482 delete $ids->{$storeid};
483 next;
484 }
485 }
486
487 my $def = $default_config->{$d->{type}};
488
489 if ($def->{content}) {
490 $d->{content} = $def->{content}->[1] if !$d->{content};
491 }
492
493 if ($d->{type} eq 'iscsi' || $d->{type} eq 'nfs') {
494 $d->{shared} = 1;
495 }
496 }
497
498 my $cfg = { ids => $ids, digest => $digest};
499
500 return $cfg;
501 }
502
503 sub parse_options {
504 my ($storeid, $stype, $param, $create) = @_;
505
506 my $settings = { type => $stype };
507
508 die "unknown storage type '$stype'\n"
509 if !$default_config->{$stype};
510
511 foreach my $opt (keys %$param) {
512 my $value = $param->{$opt};
513
514 my $ct = $confvars->{$opt};
515 if (defined($value)) {
516 eval {
517 $settings->{$opt} = check_type ($stype, $ct, $opt, $value, $storeid);
518 };
519 raise_param_exc({ $opt => $@ }) if $@;
520 } else {
521 raise_param_exc({ $opt => "got undefined value" });
522 }
523 }
524
525 if ($create) {
526 my $req_keys = $required_config->{$stype};
527 foreach my $k (@$req_keys) {
528
529 if ($stype eq 'nfs' && !$settings->{path}) {
530 $settings->{path} = "/mnt/pve/$storeid";
531 }
532
533 # check if we have a value for all required options
534 if (!defined ($settings->{$k})) {
535 raise_param_exc({ $k => "property is missing and it is not optional" });
536 }
537 }
538 } else {
539 my $fixed_keys = $fixed_config->{$stype};
540 foreach my $k (@$fixed_keys) {
541
542 # only allow to change non-fixed values
543
544 if (defined ($settings->{$k})) {
545 raise_param_exc({$k => "can't change value (fixed parameter)"});
546 }
547 }
548 }
549
550 return $settings;
551 }
552
553 sub cluster_lock_storage {
554 my ($storeid, $shared, $timeout, $func, @param) = @_;
555
556 my $res;
557 if (!$shared) {
558 my $lockid = "pve-storage-$storeid";
559 my $lockdir = "/var/lock/pve-manager";
560 mkdir $lockdir;
561 $res = PVE::Tools::lock_file("$lockdir/$lockid", $timeout, $func, @param);
562 die $@ if $@;
563 } else {
564 $res = PVE::Cluster::cfs_lock_storage($storeid, $timeout, $func, @param);
565 die $@ if $@;
566 }
567 return $res;
568 }
569
570 sub storage_config {
571 my ($cfg, $storeid, $noerr) = @_;
572
573 die "no storage id specified\n" if !$storeid;
574
575 my $scfg = $cfg->{ids}->{$storeid};
576
577 die "storage '$storeid' does not exists\n" if (!$noerr && !$scfg);
578
579 return $scfg;
580 }
581
582 sub storage_check_node {
583 my ($cfg, $storeid, $node, $noerr) = @_;
584
585 my $scfg = storage_config ($cfg, $storeid);
586
587 if ($scfg->{nodes}) {
588 $node = PVE::INotify::nodename() if !$node || ($node eq 'localhost');
589 if (!$scfg->{nodes}->{$node}) {
590 die "storage '$storeid' is not available on node '$node'\n" if !$noerr;
591 return undef;
592 }
593 }
594
595 return $scfg;
596 }
597
598 sub storage_check_enabled {
599 my ($cfg, $storeid, $node, $noerr) = @_;
600
601 my $scfg = storage_config ($cfg, $storeid);
602
603 if ($scfg->{disable}) {
604 die "storage '$storeid' is disabled\n" if !$noerr;
605 return undef;
606 }
607
608 return storage_check_node($cfg, $storeid, $node, $noerr);
609 }
610
611 sub storage_ids {
612 my ($cfg) = @_;
613
614 my $ids = $cfg->{ids};
615
616 my @sa = sort {$ids->{$a}->{priority} <=> $ids->{$b}->{priority}} keys %$ids;
617
618 return @sa;
619 }
620
621 sub assert_if_modified {
622 my ($cfg, $digest) = @_;
623
624 if ($digest && ($cfg->{digest} ne $digest)) {
625 die "detected modified storage configuration - try again\n";
626 }
627 }
628
629 sub sprint_config_line {
630 my ($k, $v) = @_;
631
632 my $ct = $confvars->{$k};
633
634 if ($ct eq 'bool') {
635 return $v ? "\t$k\n" : '';
636 } elsif ($ct eq 'nodes') {
637 my $nlist = join(',', keys(%$v));
638 return $nlist ? "\tnodes $nlist\n" : '';
639 } elsif ($ct eq 'content') {
640 my $clist = content_hash_to_string($v);
641 if ($clist) {
642 return "\t$k $clist\n";
643 } else {
644 return "\t$k none\n";
645 }
646 } else {
647 return "\t$k $v\n";
648 }
649 }
650
651 sub write_config {
652 my ($filename, $cfg) = @_;
653
654 my $out = '';
655
656 my $ids = $cfg->{ids};
657
658 my $maxpri = 0;
659 foreach my $storeid (keys %$ids) {
660 my $pri = $ids->{$storeid}->{priority};
661 $maxpri = $pri if $pri && $pri > $maxpri;
662 }
663 foreach my $storeid (keys %$ids) {
664 if (!defined ($ids->{$storeid}->{priority})) {
665 $ids->{$storeid}->{priority} = ++$maxpri;
666 }
667 }
668
669 foreach my $storeid (sort {$ids->{$a}->{priority} <=> $ids->{$b}->{priority}} keys %$ids) {
670 my $scfg = $ids->{$storeid};
671 my $type = $scfg->{type};
672 my $def = $default_config->{$type};
673
674 die "unknown storage type '$type'\n" if !$def;
675
676 my $data = "$type: $storeid\n";
677
678 $data .= "\tdisable\n" if $scfg->{disable};
679
680 my $done_hash = { disable => 1};
681 foreach my $k (@{$required_config->{$type}}) {
682 $done_hash->{$k} = 1;
683 my $v = $ids->{$storeid}->{$k};
684 die "storage '$storeid' - missing value for required option '$k'\n"
685 if !defined ($v);
686 $data .= sprint_config_line ($k, $v);
687 }
688
689 foreach my $k (keys %$def) {
690 next if defined ($done_hash->{$k});
691 my $v = $ids->{$storeid}->{$k};
692 next if !defined($v);
693 $data .= sprint_config_line ($k, $v);
694 }
695
696 $out .= "$data\n";
697 }
698
699 return $out;
700 }
701
702 sub get_image_dir {
703 my ($cfg, $storeid, $vmid) = @_;
704
705 my $path = $cfg->{ids}->{$storeid}->{path};
706 return $vmid ? "$path/images/$vmid" : "$path/images";
707 }
708
709 sub get_private_dir {
710 my ($cfg, $storeid, $vmid) = @_;
711
712 my $path = $cfg->{ids}->{$storeid}->{path};
713 return $vmid ? "$path/private/$vmid" : "$path/private";
714 }
715
716 sub get_iso_dir {
717 my ($cfg, $storeid) = @_;
718
719 my $isodir = $cfg->{ids}->{$storeid}->{path};
720 $isodir .= '/template/iso';
721
722 return $isodir;
723 }
724
725 sub get_vztmpl_dir {
726 my ($cfg, $storeid) = @_;
727
728 my $tmpldir = $cfg->{ids}->{$storeid}->{path};
729 $tmpldir .= '/template/cache';
730
731 return $tmpldir;
732 }
733
734 sub get_backup_dir {
735 my ($cfg, $storeid) = @_;
736
737 my $dir = $cfg->{ids}->{$storeid}->{path};
738 $dir .= '/dump';
739
740 return $dir;
741 }
742
743 # iscsi utility functions
744
745 sub iscsi_session_list {
746
747 check_iscsi_support ();
748
749 my $cmd = [$ISCSIADM, '--mode', 'session'];
750
751 my $res = {};
752
753 run_command($cmd, outfunc => sub {
754 my $line = shift;
755
756 if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)\s*$/) {
757 my ($session, $target) = ($1, $2);
758 # there can be several sessions per target (multipath)
759 push @{$res->{$target}}, $session;
760
761 }
762 });
763
764 return $res;
765 }
766
767 sub iscsi_test_portal {
768 my ($portal) = @_;
769
770 my ($server, $port) = split(':', $portal);
771 my $p = Net::Ping->new("tcp", 2);
772 $p->port_number($port || 3260);
773 return $p->ping($server);
774 }
775
776 sub iscsi_discovery {
777 my ($portal) = @_;
778
779 check_iscsi_support ();
780
781 my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets',
782 '--portal', $portal];
783
784 my $res = {};
785
786 return $res if !iscsi_test_portal($portal); # fixme: raise exception here?
787
788 run_command($cmd, outfunc => sub {
789 my $line = shift;
790
791 if ($line =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+)\,\S+\s+(\S+)\s*$/) {
792 my $portal = $1;
793 my $target = $2;
794 # one target can have more than one portal (multipath).
795 push @{$res->{$target}}, $portal;
796 }
797 });
798
799 return $res;
800 }
801
802 sub iscsi_login {
803 my ($target, $portal_in) = @_;
804
805 check_iscsi_support ();
806
807 eval { iscsi_discovery ($portal_in); };
808 warn $@ if $@;
809
810 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login'];
811 run_command($cmd);
812 }
813
814 sub iscsi_logout {
815 my ($target, $portal) = @_;
816
817 check_iscsi_support ();
818
819 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--logout'];
820 run_command($cmd);
821 }
822
823 my $rescan_filename = "/var/run/pve-iscsi-rescan.lock";
824
825 sub iscsi_session_rescan {
826 my $session_list = shift;
827
828 check_iscsi_support ();
829
830 my $rstat = stat ($rescan_filename);
831
832 if (!$rstat) {
833 if (my $fh = IO::File->new ($rescan_filename, "a")) {
834 utime undef, undef, $fh;
835 close ($fh);
836 }
837 } else {
838 my $atime = $rstat->atime;
839 my $tdiff = time() - $atime;
840 # avoid frequent rescans
841 return if !($tdiff < 0 || $tdiff > 10);
842 utime undef, undef, $rescan_filename;
843 }
844
845 foreach my $session (@$session_list) {
846 my $cmd = [$ISCSIADM, '--mode', 'session', '-r', $session, '-R'];
847 eval { run_command($cmd, outfunc => sub {}); };
848 warn $@ if $@;
849 }
850 }
851
852 sub iscsi_device_list {
853
854 my $res = {};
855
856 my $dirname = '/sys/class/iscsi_session';
857
858 my $stable_paths = load_stable_scsi_paths();
859
860 dir_glob_foreach ($dirname, 'session(\d+)', sub {
861 my ($ent, $session) = @_;
862
863 my $target = file_read_firstline ("$dirname/$ent/targetname");
864 return if !$target;
865
866 my (undef, $host) = dir_glob_regex ("$dirname/$ent/device", 'target(\d+):.*');
867 return if !defined($host);
868
869 dir_glob_foreach ("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
870 my ($tmp, $channel, $id, $lun) = @_;
871
872 my $type = file_read_firstline ("/sys/bus/scsi/devices/$tmp/type");
873 return if !defined($type) || $type ne '0'; # list disks only
874
875 my $bdev;
876 if (-d "/sys/bus/scsi/devices/$tmp/block") { # newer kernels
877 (undef, $bdev) = dir_glob_regex ("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
878 } else {
879 (undef, $bdev) = dir_glob_regex ("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
880 }
881 return if !$bdev;
882
883 #check multipath
884 if (-d "/sys/block/$bdev/holders") {
885 my $multipathdev = dir_glob_regex ("/sys/block/$bdev/holders", '[A-Za-z]\S*');
886 $bdev = $multipathdev if $multipathdev;
887 }
888
889 my $blockdev = $stable_paths->{$bdev};
890 return if !$blockdev;
891
892 my $size = file_read_firstline ("/sys/block/$bdev/size");
893 return if !$size;
894
895 my $volid = "$channel.$id.$lun.$blockdev";
896
897 $res->{$target}->{$volid} = {
898 'format' => 'raw',
899 'size' => int($size * 512),
900 'vmid' => 0, # not assigned to any vm
901 'channel' => int($channel),
902 'id' => int($id),
903 'lun' => int($lun),
904 };
905
906 #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
907 });
908
909 });
910
911 return $res;
912 }
913
914 # library implementation
915
916 PVE::JSONSchema::register_format('pve-storage-vgname', \&parse_lvm_name);
917 sub parse_lvm_name {
918 my ($name, $noerr) = @_;
919
920 if ($name !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
921 return undef if $noerr;
922 die "lvm name '$name' contains illegal characters\n";
923 }
924
925 return $name;
926 }
927
928 sub parse_vmid {
929 my $vmid = shift;
930
931 die "VMID '$vmid' contains illegal characters\n" if $vmid !~ m/^\d+$/;
932
933 return int($vmid);
934 }
935
936 PVE::JSONSchema::register_format('pve-volume-id', \&parse_volume_id);
937 sub parse_volume_id {
938 my ($volid, $noerr) = @_;
939
940 if ($volid =~ m/^([a-z][a-z0-9\-\_\.]*[a-z0-9]):(.+)$/i) {
941 return wantarray ? ($1, $2) : $1;
942 }
943 return undef if $noerr;
944 die "unable to parse volume ID '$volid'\n";
945 }
946
947 sub parse_name_dir {
948 my $name = shift;
949
950 if ($name =~ m!^([^/\s]+\.(raw|qcow2|vmdk))$!) {
951 return ($1, $2);
952 }
953
954 die "unable to parse volume filename '$name'\n";
955 }
956
957 sub parse_volname_dir {
958 my $volname = shift;
959
960 if ($volname =~ m!^(\d+)/(\S+)$!) {
961 my ($vmid, $name) = ($1, $2);
962 parse_name_dir ($name);
963 return ('image', $name, $vmid);
964 } elsif ($volname =~ m!^iso/([^/]+\.[Ii][Ss][Oo])$!) {
965 return ('iso', $1);
966 } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.gz)$!) {
967 return ('vztmpl', $1);
968 } elsif ($volname =~ m!^rootdir/(\d+)$!) {
969 return ('rootdir', $1, $1);
970 } elsif ($volname =~ m!^backup/([^/]+(\.(tar|tar\.gz|tar\.lzo|tgz)))$!) {
971 my $fn = $1;
972 if ($fn =~ m/^vzdump-(openvz|qemu)-(\d+)-.+/) {
973 return ('backup', $fn, $2);
974 }
975 return ('backup', $fn);
976 }
977 die "unable to parse directory volume name '$volname'\n";
978 }
979
980 sub parse_volname_lvm {
981 my $volname = shift;
982
983 parse_lvm_name ($volname);
984
985 if ($volname =~ m/^(vm-(\d+)-\S+)$/) {
986 return ($1, $2);
987 }
988
989 die "unable to parse lvm volume name '$volname'\n";
990 }
991
992 sub parse_volname_iscsi {
993 my $volname = shift;
994
995 if ($volname =~ m!^\d+\.\d+\.\d+\.(\S+)$!) {
996 my $byid = $1;
997 return $byid;
998 }
999
1000 die "unable to parse iscsi volume name '$volname'\n";
1001 }
1002
1003 # try to map a filesystem path to a volume identifier
1004 sub path_to_volume_id {
1005 my ($cfg, $path) = @_;
1006
1007 my $ids = $cfg->{ids};
1008
1009 my ($sid, $volname) = parse_volume_id ($path, 1);
1010 if ($sid) {
1011 if ($ids->{$sid} && (my $type = $ids->{$sid}->{type})) {
1012 if ($type eq 'dir' || $type eq 'nfs') {
1013 my ($vtype, $name, $vmid) = parse_volname_dir ($volname);
1014 return ($vtype, $path);
1015 }
1016 }
1017 return ('');
1018 }
1019
1020 $path = abs_path ($path);
1021
1022 foreach my $sid (keys %$ids) {
1023 my $type = $ids->{$sid}->{type};
1024 next if !($type eq 'dir' || $type eq 'nfs');
1025
1026 my $imagedir = get_image_dir($cfg, $sid);
1027 my $isodir = get_iso_dir($cfg, $sid);
1028 my $tmpldir = get_vztmpl_dir($cfg, $sid);
1029 my $backupdir = get_backup_dir($cfg, $sid);
1030 my $privatedir = get_private_dir($cfg, $sid);
1031
1032 if ($path =~ m!^$imagedir/(\d+)/([^/\s]+)$!) {
1033 my $vmid = $1;
1034 my $name = $2;
1035 return ('image', "$sid:$vmid/$name");
1036 } elsif ($path =~ m!^$isodir/([^/]+\.[Ii][Ss][Oo])$!) {
1037 my $name = $1;
1038 return ('iso', "$sid:iso/$name");
1039 } elsif ($path =~ m!^$tmpldir/([^/]+\.tar\.gz)$!) {
1040 my $name = $1;
1041 return ('vztmpl', "$sid:vztmpl/$name");
1042 } elsif ($path =~ m!^$privatedir/(\d+)$!) {
1043 my $vmid = $1;
1044 return ('rootdir', "$sid:rootdir/$vmid");
1045 } elsif ($path =~ m!^$backupdir/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!) {
1046 my $name = $1;
1047 return ('iso', "$sid:backup/$name");
1048 }
1049 }
1050
1051 # can't map path to volume id
1052 return ('');
1053 }
1054
1055 sub path {
1056 my ($cfg, $volid) = @_;
1057
1058 my ($storeid, $volname) = parse_volume_id ($volid);
1059
1060 my $scfg = storage_config ($cfg, $storeid);
1061
1062 my $path;
1063 my $owner;
1064 my $vtype = 'image';
1065
1066 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
1067 my ($name, $vmid);
1068 ($vtype, $name, $vmid) = parse_volname_dir ($volname);
1069 $owner = $vmid;
1070
1071 my $imagedir = get_image_dir($cfg, $storeid, $vmid);
1072 my $isodir = get_iso_dir($cfg, $storeid);
1073 my $tmpldir = get_vztmpl_dir($cfg, $storeid);
1074 my $backupdir = get_backup_dir($cfg, $storeid);
1075 my $privatedir = get_private_dir($cfg, $storeid);
1076
1077 if ($vtype eq 'image') {
1078 $path = "$imagedir/$name";
1079 } elsif ($vtype eq 'iso') {
1080 $path = "$isodir/$name";
1081 } elsif ($vtype eq 'vztmpl') {
1082 $path = "$tmpldir/$name";
1083 } elsif ($vtype eq 'rootdir') {
1084 $path = "$privatedir/$name";
1085 } elsif ($vtype eq 'backup') {
1086 $path = "$backupdir/$name";
1087 } else {
1088 die "should not be reached";
1089 }
1090
1091 } elsif ($scfg->{type} eq 'lvm') {
1092
1093 my $vg = $scfg->{vgname};
1094
1095 my ($name, $vmid) = parse_volname_lvm ($volname);
1096 $owner = $vmid;
1097
1098 $path = "/dev/$vg/$name";
1099
1100 } elsif ($scfg->{type} eq 'iscsi') {
1101 my $byid = parse_volname_iscsi ($volname);
1102 $path = "/dev/disk/by-id/$byid";
1103 } else {
1104 die "unknown storage type '$scfg->{type}'";
1105 }
1106
1107 return wantarray ? ($path, $owner, $vtype) : $path;
1108 }
1109
1110 sub storage_migrate {
1111 my ($cfg, $volid, $target_host, $target_storeid, $target_volname) = @_;
1112
1113 my ($storeid, $volname) = parse_volume_id ($volid);
1114 $target_volname = $volname if !$target_volname;
1115
1116 my $scfg = storage_config ($cfg, $storeid);
1117
1118 # no need to migrate shared content
1119 return if $storeid eq $target_storeid && $scfg->{shared};
1120
1121 my $tcfg = storage_config ($cfg, $target_storeid);
1122
1123 my $target_volid = "${target_storeid}:${target_volname}";
1124
1125 my $errstr = "unable to migrate '$volid' to '${target_volid}' on host '$target_host'";
1126
1127 # blowfish is a fast block cipher, much faster then 3des
1128 my $sshoptions = "-c blowfish -o 'BatchMode=yes'";
1129 my $ssh = "/usr/bin/ssh $sshoptions";
1130
1131 local $ENV{RSYNC_RSH} = $ssh;
1132
1133 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
1134 if ($tcfg->{type} eq 'dir' || $tcfg->{type} eq 'nfs') {
1135
1136 my $src = path ($cfg, $volid);
1137 my $dst = path ($cfg, $target_volid);
1138
1139 my $dirname = dirname ($dst);
1140
1141 if ($tcfg->{shared}) { # we can do a local copy
1142
1143 run_command(['/bin/mkdir', '-p', $dirname]);
1144
1145 run_command(['/bin/cp', $src, $dst]);
1146
1147 } else {
1148
1149 run_command(['/usr/bin/ssh', "root\@${target_host}",
1150 '/bin/mkdir', '-p', $dirname]);
1151
1152 # we use rsync with --sparse, so we can't use --inplace,
1153 # so we remove file on the target if it already exists to
1154 # save space
1155 my ($size, $format) = file_size_info($src);
1156 if ($format && ($format eq 'raw') && $size) {
1157 run_command(['/usr/bin/ssh', "root\@${target_host}",
1158 'rm', '-f', $dst],
1159 outfunc => sub {});
1160 }
1161
1162 my $cmd = ['/usr/bin/rsync', '--progress', '--sparse', '--whole-file',
1163 $src, "root\@${target_host}:$dst"];
1164
1165 my $percent = -1;
1166
1167 run_command($cmd, outfunc => sub {
1168 my $line = shift;
1169
1170 if ($line =~ m/^\s*(\d+\s+(\d+)%\s.*)$/) {
1171 if ($2 > $percent) {
1172 $percent = $2;
1173 print "rsync status: $1\n";
1174 *STDOUT->flush();
1175 }
1176 } else {
1177 print "$line\n";
1178 *STDOUT->flush();
1179 }
1180 });
1181 }
1182
1183
1184 } else {
1185
1186 die "$errstr - target type '$tcfg->{type}' not implemented\n";
1187 }
1188
1189 } else {
1190 die "$errstr - source type '$scfg->{type}' not implemented\n";
1191 }
1192 }
1193
1194 sub vdisk_alloc {
1195 my ($cfg, $storeid, $vmid, $fmt, $name, $size) = @_;
1196
1197 die "no storage id specified\n" if !$storeid;
1198
1199 PVE::JSONSchema::parse_storage_id($storeid);
1200
1201 my $scfg = storage_config($cfg, $storeid);
1202
1203 die "no VMID specified\n" if !$vmid;
1204
1205 $vmid = parse_vmid ($vmid);
1206
1207 my $defformat = storage_default_format ($cfg, $storeid);
1208
1209 $fmt = $defformat if !$fmt;
1210
1211 activate_storage ($cfg, $storeid);
1212
1213 # lock shared storage
1214 return cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
1215
1216 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
1217
1218 my $imagedir = get_image_dir ($cfg, $storeid, $vmid);
1219
1220 mkpath $imagedir;
1221
1222 if (!$name) {
1223
1224 for (my $i = 1; $i < 100; $i++) {
1225 my @gr = <$imagedir/vm-$vmid-disk-$i.*>;
1226 if (!scalar(@gr)) {
1227 $name = "vm-$vmid-disk-$i.$fmt";
1228 last;
1229 }
1230 }
1231 }
1232
1233 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
1234 if !$name;
1235
1236 my (undef, $tmpfmt) = parse_name_dir ($name);
1237
1238 die "illegal name '$name' - wrong extension for format ('$tmpfmt != '$fmt')\n"
1239 if $tmpfmt ne $fmt;
1240
1241 my $path = "$imagedir/$name";
1242
1243 die "disk image '$path' already exists\n" if -e $path;
1244
1245 run_command("/usr/bin/qemu-img create -f $fmt '$path' ${size}K",
1246 errmsg => "unable to create image");
1247
1248 return "$storeid:$vmid/$name";
1249
1250 } elsif ($scfg->{type} eq 'lvm') {
1251
1252 die "unsupported format '$fmt'" if $fmt ne 'raw';
1253
1254 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
1255 if $name && $name !~ m/^vm-$vmid-/;
1256
1257 my $vgs = lvm_vgs ();
1258
1259 my $vg = $scfg->{vgname};
1260
1261 die "no such volume gruoup '$vg'\n" if !defined ($vgs->{$vg});
1262
1263 my $free = int ($vgs->{$vg}->{free});
1264
1265 die "not enough free space ($free < $size)\n" if $free < $size;
1266
1267 if (!$name) {
1268 my $lvs = lvm_lvs ($vg);
1269
1270 for (my $i = 1; $i < 100; $i++) {
1271 my $tn = "vm-$vmid-disk-$i";
1272 if (!defined ($lvs->{$vg}->{$tn})) {
1273 $name = $tn;
1274 last;
1275 }
1276 }
1277 }
1278
1279 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
1280 if !$name;
1281
1282 my $cmd = ['/sbin/lvcreate', '-aly', '--addtag', "pve-vm-$vmid", '--size', "${size}k", '--name', $name, $vg];
1283
1284 run_command($cmd, errmsg => "lvcreate '$vg/pve-vm-$vmid' error");
1285
1286 return "$storeid:$name";
1287
1288 } elsif ($scfg->{type} eq 'iscsi') {
1289 die "can't allocate space in iscsi storage\n";
1290 } else {
1291 die "unknown storage type '$scfg->{type}'";
1292 }
1293 });
1294 }
1295
1296 sub vdisk_free {
1297 my ($cfg, $volid) = @_;
1298
1299 my ($storeid, $volname) = parse_volume_id ($volid);
1300
1301 my $scfg = storage_config ($cfg, $storeid);
1302
1303 activate_storage ($cfg, $storeid);
1304
1305 # we need to zero out LVM data for security reasons
1306 # and to allow thin provisioning
1307
1308 my $vg;
1309
1310 # lock shared storage
1311 cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
1312
1313 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
1314 my $path = path ($cfg, $volid);
1315
1316 if (! -f $path) {
1317 warn "disk image '$path' does not exists\n";
1318 } else {
1319 unlink $path;
1320 }
1321 } elsif ($scfg->{type} eq 'lvm') {
1322
1323 if ($scfg->{saferemove}) {
1324 # avoid long running task, so we only rename here
1325 $vg = $scfg->{vgname};
1326 my $cmd = ['/sbin/lvrename', $vg, $volname, "del-$volname"];
1327 run_command($cmd, errmsg => "lvrename '$vg/$volname' error");
1328 } else {
1329 my $tmpvg = $scfg->{vgname};
1330 my $cmd = ['/sbin/lvremove', '-f', "$tmpvg/$volname"];
1331 run_command($cmd, errmsg => "lvremove '$tmpvg/$volname' error");
1332 }
1333
1334 } elsif ($scfg->{type} eq 'iscsi') {
1335 die "can't free space in iscsi storage\n";
1336 } else {
1337 die "unknown storage type '$scfg->{type}'";
1338 }
1339 });
1340
1341 return if !$vg;
1342
1343 my $zero_out_worker = sub {
1344 print "zero-out data on image $volname\n";
1345 my $cmd = ['dd', "if=/dev/zero", "of=/dev/$vg/del-$volname", "bs=1M"];
1346 eval { run_command($cmd, errmsg => "zero out failed"); };
1347 warn $@ if $@;
1348
1349 cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
1350 my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"];
1351 run_command($cmd, errmsg => "lvremove '$vg/del-$volname' error");
1352 });
1353 print "successfully removed volume $volname\n";
1354 };
1355
1356 my $rpcenv = PVE::RPCEnvironment::get();
1357 my $authuser = $rpcenv->get_user();
1358
1359 $rpcenv->fork_worker('imgdel', undef, $authuser, $zero_out_worker);
1360 }
1361
1362 # lvm utility functions
1363
1364 sub lvm_pv_info {
1365 my ($device) = @_;
1366
1367 die "no device specified" if !$device;
1368
1369 my $has_label = 0;
1370
1371 my $cmd = ['/usr/bin/file', '-L', '-s', $device];
1372 run_command($cmd, outfunc => sub {
1373 my $line = shift;
1374 $has_label = 1 if $line =~ m/LVM2/;
1375 });
1376
1377 return undef if !$has_label;
1378
1379 $cmd = ['/sbin/pvs', '--separator', ':', '--noheadings', '--units', 'k',
1380 '--unbuffered', '--nosuffix', '--options',
1381 'pv_name,pv_size,vg_name,pv_uuid', $device];
1382
1383 my $pvinfo;
1384 run_command($cmd, outfunc => sub {
1385 my $line = shift;
1386
1387 $line = trim($line);
1388
1389 my ($pvname, $size, $vgname, $uuid) = split (':', $line);
1390
1391 die "found multiple pvs entries for device '$device'\n"
1392 if $pvinfo;
1393
1394 $pvinfo = {
1395 pvname => $pvname,
1396 size => $size,
1397 vgname => $vgname,
1398 uuid => $uuid,
1399 };
1400 });
1401
1402 return $pvinfo;
1403 }
1404
1405 sub clear_first_sector {
1406 my ($dev) = shift;
1407
1408 if (my $fh = IO::File->new ($dev, "w")) {
1409 my $buf = 0 x 512;
1410 syswrite $fh, $buf;
1411 $fh->close();
1412 }
1413 }
1414
1415 sub lvm_create_volume_group {
1416 my ($device, $vgname, $shared) = @_;
1417
1418 my $res = lvm_pv_info ($device);
1419
1420 if ($res->{vgname}) {
1421 return if $res->{vgname} eq $vgname; # already created
1422 die "device '$device' is already used by volume group '$res->{vgname}'\n";
1423 }
1424
1425 clear_first_sector ($device); # else pvcreate fails
1426
1427 # we use --metadatasize 250k, which reseults in "pe_start = 512"
1428 # so pe_start is aligned on a 128k boundary (advantage for SSDs)
1429 my $cmd = ['/sbin/pvcreate', '--metadatasize', '250k', $device];
1430
1431 run_command($cmd, errmsg => "pvcreate '$device' error");
1432
1433 $cmd = ['/sbin/vgcreate', $vgname, $device];
1434 # push @$cmd, '-c', 'y' if $shared; # we do not use this yet
1435
1436 run_command($cmd, errmsg => "vgcreate $vgname $device error");
1437 }
1438
1439 sub lvm_vgs {
1440
1441 my $cmd = ['/sbin/vgs', '--separator', ':', '--noheadings', '--units', 'b',
1442 '--unbuffered', '--nosuffix', '--options',
1443 'vg_name,vg_size,vg_free'];
1444
1445 my $vgs = {};
1446 eval {
1447 run_command($cmd, outfunc => sub {
1448 my $line = shift;
1449
1450 $line = trim($line);
1451
1452 my ($name, $size, $free) = split (':', $line);
1453
1454 $vgs->{$name} = { size => int ($size), free => int ($free) };
1455 });
1456 };
1457 my $err = $@;
1458
1459 # just warn (vgs return error code 5 if clvmd does not run)
1460 # but output is still OK (list without clustered VGs)
1461 warn $err if $err;
1462
1463 return $vgs;
1464 }
1465
1466 sub lvm_lvs {
1467 my ($vgname) = @_;
1468
1469 my $cmd = ['/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b',
1470 '--unbuffered', '--nosuffix', '--options',
1471 'vg_name,lv_name,lv_size,uuid,tags'];
1472
1473 push @$cmd, $vgname if $vgname;
1474
1475 my $lvs = {};
1476 run_command($cmd, outfunc => sub {
1477 my $line = shift;
1478
1479 $line = trim($line);
1480
1481 my ($vg, $name, $size, $uuid, $tags) = split (':', $line);
1482
1483 return if $name !~ m/^vm-(\d+)-/;
1484 my $nid = $1;
1485
1486 my $owner;
1487 foreach my $tag (split (/,/, $tags)) {
1488 if ($tag =~ m/^pve-vm-(\d+)$/) {
1489 $owner = $1;
1490 last;
1491 }
1492 }
1493
1494 if ($owner) {
1495 if ($owner ne $nid) {
1496 warn "owner mismatch name = $name, owner = $owner\n";
1497 }
1498
1499 $lvs->{$vg}->{$name} = { format => 'raw', size => $size,
1500 uuid => $uuid, tags => $tags,
1501 vmid => $owner };
1502 }
1503 });
1504
1505 return $lvs;
1506 }
1507
1508 #list iso or openvz template ($tt = <iso|vztmpl|backup>)
1509 sub template_list {
1510 my ($cfg, $storeid, $tt) = @_;
1511
1512 die "unknown template type '$tt'\n" if !($tt eq 'iso' || $tt eq 'vztmpl' || $tt eq 'backup');
1513
1514 my $ids = $cfg->{ids};
1515
1516 storage_check_enabled($cfg, $storeid) if ($storeid);
1517
1518 my $res = {};
1519
1520 # query the storage
1521
1522 foreach my $sid (keys %$ids) {
1523 next if $storeid && $storeid ne $sid;
1524
1525 my $scfg = $ids->{$sid};
1526 my $type = $scfg->{type};
1527
1528 next if !storage_check_enabled($cfg, $sid, undef, 1);
1529
1530 next if $tt eq 'iso' && !$scfg->{content}->{iso};
1531 next if $tt eq 'vztmpl' && !$scfg->{content}->{vztmpl};
1532 next if $tt eq 'backup' && !$scfg->{content}->{backup};
1533
1534 activate_storage ($cfg, $sid);
1535
1536 if ($type eq 'dir' || $type eq 'nfs') {
1537
1538 my $path;
1539 if ($tt eq 'iso') {
1540 $path = get_iso_dir($cfg, $sid);
1541 } elsif ($tt eq 'vztmpl') {
1542 $path = get_vztmpl_dir($cfg, $sid);
1543 } elsif ($tt eq 'backup') {
1544 $path = get_backup_dir($cfg, $sid);
1545 } else {
1546 die "unknown template type '$tt'\n";
1547 }
1548
1549 foreach my $fn (<$path/*>) {
1550
1551 my $info;
1552
1553 if ($tt eq 'iso') {
1554 next if $fn !~ m!/([^/]+\.[Ii][Ss][Oo])$!;
1555
1556 $info = { volid => "$sid:iso/$1", format => 'iso' };
1557
1558 } elsif ($tt eq 'vztmpl') {
1559 next if $fn !~ m!/([^/]+\.tar\.gz)$!;
1560
1561 $info = { volid => "$sid:vztmpl/$1", format => 'tgz' };
1562
1563 } elsif ($tt eq 'backup') {
1564 next if $fn !~ m!/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!;
1565
1566 $info = { volid => "$sid:backup/$1", format => $2 };
1567 }
1568
1569 $info->{size} = -s $fn;
1570
1571 push @{$res->{$sid}}, $info;
1572 }
1573
1574 }
1575
1576 @{$res->{$sid}} = sort {lc($a->{volid}) cmp lc ($b->{volid}) } @{$res->{$sid}} if $res->{$sid};
1577 }
1578
1579 return $res;
1580 }
1581
1582 sub file_size_info {
1583 my ($filename, $timeout) = @_;
1584
1585 my $cmd = ['/usr/bin/qemu-img', 'info', $filename];
1586
1587 my $format;
1588 my $size = 0;
1589 my $used = 0;
1590
1591 eval {
1592 run_command($cmd, timeout => $timeout, outfunc => sub {
1593 my $line = shift;
1594
1595 if ($line =~ m/^file format:\s+(\S+)\s*$/) {
1596 $format = $1;
1597 } elsif ($line =~ m/^virtual size:\s\S+\s+\((\d+)\s+bytes\)$/) {
1598 $size = int($1);
1599 } elsif ($line =~ m/^disk size:\s+(\d+(.\d+)?)([KMGT])\s*$/) {
1600 $used = $1;
1601 my $u = $3;
1602
1603 $used *= 1024 if $u eq 'K';
1604 $used *= (1024*1024) if $u eq 'M';
1605 $used *= (1024*1024*1024) if $u eq 'G';
1606 $used *= (1024*1024*1024*1024) if $u eq 'T';
1607
1608 $used = int($used);
1609 }
1610 });
1611 };
1612
1613 return wantarray ? ($size, $format, $used) : $size;
1614 }
1615
1616 sub vdisk_list {
1617 my ($cfg, $storeid, $vmid, $vollist) = @_;
1618
1619 my $ids = $cfg->{ids};
1620
1621 storage_check_enabled($cfg, $storeid) if ($storeid);
1622
1623 my $res = {};
1624
1625 # prepare/activate/refresh all storages
1626
1627 my $stypes = {};
1628
1629 my $storage_list = [];
1630 if ($vollist) {
1631 foreach my $volid (@$vollist) {
1632 my ($sid, undef) = parse_volume_id ($volid);
1633 next if !defined ($ids->{$sid});
1634 next if !storage_check_enabled($cfg, $sid, undef, 1);
1635 push @$storage_list, $sid;
1636 $stypes->{$ids->{$sid}->{type}} = 1;
1637 }
1638 } else {
1639 foreach my $sid (keys %$ids) {
1640 next if $storeid && $storeid ne $sid;
1641 next if !storage_check_enabled($cfg, $sid, undef, 1);
1642 push @$storage_list, $sid;
1643 $stypes->{$ids->{$sid}->{type}} = 1;
1644 }
1645 }
1646
1647 activate_storage_list ($cfg, $storage_list);
1648
1649 my $lvs = $stypes->{lvm} ? lvm_lvs () : {};
1650
1651 my $iscsi_devices = iscsi_device_list() if $stypes->{iscsi};
1652
1653 # query the storage
1654
1655 foreach my $sid (keys %$ids) {
1656 if ($storeid) {
1657 next if $storeid ne $sid;
1658 next if !storage_check_enabled($cfg, $sid, undef, 1);
1659 }
1660 my $scfg = $ids->{$sid};
1661 my $type = $scfg->{type};
1662
1663 if ($type eq 'dir' || $type eq 'nfs') {
1664
1665 my $path = $scfg->{path};
1666
1667 my $fmts = join ('|', keys %{$default_config->{$type}->{format}->[0]});
1668
1669 foreach my $fn (<$path/images/[0-9][0-9]*/*>) {
1670
1671 next if $fn !~ m!^(/.+/images/(\d+)/([^/]+\.($fmts)))$!;
1672 $fn = $1; # untaint
1673
1674 my $owner = $2;
1675 my $name = $3;
1676 my $volid = "$sid:$owner/$name";
1677
1678 if ($vollist) {
1679 my $found = grep { $_ eq $volid } @$vollist;
1680 next if !$found;
1681 } else {
1682 next if defined ($vmid) && ($owner ne $vmid);
1683 }
1684
1685 my ($size, $format, $used) = file_size_info ($fn);
1686
1687 if ($format && $size) {
1688 push @{$res->{$sid}}, {
1689 volid => $volid, format => $format,
1690 size => $size, vmid => $owner, used => $used };
1691 }
1692
1693 }
1694
1695 } elsif ($type eq 'lvm') {
1696
1697 my $vgname = $scfg->{vgname};
1698
1699 if (my $dat = $lvs->{$vgname}) {
1700
1701 foreach my $volname (keys %$dat) {
1702
1703 my $owner = $dat->{$volname}->{vmid};
1704
1705 my $volid = "$sid:$volname";
1706
1707 if ($vollist) {
1708 my $found = grep { $_ eq $volid } @$vollist;
1709 next if !$found;
1710 } else {
1711 next if defined ($vmid) && ($owner ne $vmid);
1712 }
1713
1714 my $info = $dat->{$volname};
1715 $info->{volid} = $volid;
1716
1717 push @{$res->{$sid}}, $info;
1718 }
1719 }
1720
1721 } elsif ($type eq 'iscsi') {
1722
1723 # we have no owner for iscsi devices
1724
1725 my $target = $scfg->{target};
1726
1727 if (my $dat = $iscsi_devices->{$target}) {
1728
1729 foreach my $volname (keys %$dat) {
1730
1731 my $volid = "$sid:$volname";
1732
1733 if ($vollist) {
1734 my $found = grep { $_ eq $volid } @$vollist;
1735 next if !$found;
1736 } else {
1737 next if !($storeid && ($storeid eq $sid));
1738 }
1739
1740 my $info = $dat->{$volname};
1741 $info->{volid} = $volid;
1742
1743 push @{$res->{$sid}}, $info;
1744 }
1745 }
1746
1747 } else {
1748 die "implement me";
1749 }
1750
1751 @{$res->{$sid}} = sort {lc($a->{volid}) cmp lc ($b->{volid}) } @{$res->{$sid}} if $res->{$sid};
1752 }
1753
1754 return $res;
1755 }
1756
1757 sub nfs_is_mounted {
1758 my ($server, $export, $mountpoint, $mountdata) = @_;
1759
1760 my $source = "$server:$export";
1761
1762 $mountdata = read_proc_mounts() if !$mountdata;
1763
1764 if ($mountdata =~ m|^$source/?\s$mountpoint\snfs|m) {
1765 return $mountpoint;
1766 }
1767
1768 return undef;
1769 }
1770
1771 sub nfs_mount {
1772 my ($server, $export, $mountpoint, $options) = @_;
1773
1774 my $source = "$server:$export";
1775
1776 my $cmd = ['/bin/mount', '-t', 'nfs', $source, $mountpoint];
1777 if ($options) {
1778 push @$cmd, '-o', $options;
1779 }
1780
1781 run_command($cmd, errmsg => "mount error");
1782 }
1783
1784 sub uevent_seqnum {
1785
1786 my $filename = "/sys/kernel/uevent_seqnum";
1787
1788 my $seqnum = 0;
1789 if (my $fh = IO::File->new ($filename, "r")) {
1790 my $line = <$fh>;
1791 if ($line =~ m/^(\d+)$/) {
1792 $seqnum = int ($1);
1793 }
1794 close ($fh);
1795 }
1796 return $seqnum;
1797 }
1798
1799 sub __activate_storage_full {
1800 my ($cfg, $storeid, $session) = @_;
1801
1802 my $scfg = storage_check_enabled($cfg, $storeid);
1803
1804 return if $session->{activated}->{$storeid};
1805
1806 if (!$session->{mountdata}) {
1807 $session->{mountdata} = read_proc_mounts();
1808 }
1809
1810 if (!$session->{uevent_seqnum}) {
1811 $session->{uevent_seqnum} = uevent_seqnum ();
1812 }
1813
1814 my $mountdata = $session->{mountdata};
1815
1816 my $type = $scfg->{type};
1817
1818 if ($type eq 'dir' || $type eq 'nfs') {
1819
1820 my $path = $scfg->{path};
1821
1822 if ($type eq 'nfs') {
1823 my $server = $scfg->{server};
1824 my $export = $scfg->{export};
1825
1826 if (!nfs_is_mounted ($server, $export, $path, $mountdata)) {
1827
1828 # NOTE: only call mkpath when not mounted (avoid hang
1829 # when NFS server is offline
1830
1831 mkpath $path;
1832
1833 die "unable to activate storage '$storeid' - " .
1834 "directory '$path' does not exist\n" if ! -d $path;
1835
1836 nfs_mount ($server, $export, $path, $scfg->{options});
1837 }
1838
1839 } else {
1840
1841 mkpath $path;
1842
1843 die "unable to activate storage '$storeid' - " .
1844 "directory '$path' does not exist\n" if ! -d $path;
1845 }
1846
1847 my $imagedir = get_image_dir($cfg, $storeid);
1848 my $isodir = get_iso_dir($cfg, $storeid);
1849 my $tmpldir = get_vztmpl_dir($cfg, $storeid);
1850 my $backupdir = get_backup_dir($cfg, $storeid);
1851 my $privatedir = get_private_dir($cfg, $storeid);
1852
1853 if (defined($scfg->{content})) {
1854 mkpath $imagedir if $scfg->{content}->{images} &&
1855 $imagedir ne $path;
1856 mkpath $isodir if $scfg->{content}->{iso} &&
1857 $isodir ne $path;
1858 mkpath $tmpldir if $scfg->{content}->{vztmpl} &&
1859 $tmpldir ne $path;
1860 mkpath $privatedir if $scfg->{content}->{rootdir} &&
1861 $privatedir ne $path;
1862 mkpath $backupdir if $scfg->{content}->{backup} &&
1863 $backupdir ne $path;
1864 }
1865
1866 } elsif ($type eq 'lvm') {
1867
1868 if ($scfg->{base}) {
1869 my ($baseid, undef) = parse_volume_id ($scfg->{base});
1870 __activate_storage_full ($cfg, $baseid, $session);
1871 }
1872
1873 if (!$session->{vgs}) {
1874 $session->{vgs} = lvm_vgs();
1875 }
1876
1877 # In LVM2, vgscans take place automatically;
1878 # this is just to be sure
1879 if ($session->{vgs} && !$session->{vgscaned} &&
1880 !$session->{vgs}->{$scfg->{vgname}}) {
1881 $session->{vgscaned} = 1;
1882 my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes'];
1883 eval { run_command($cmd, outfunc => sub {}); };
1884 warn $@ if $@;
1885 }
1886
1887 # we do not acticate any volumes here ('vgchange -aly')
1888 # instead, volumes are activate individually later
1889
1890 } elsif ($type eq 'iscsi') {
1891
1892 return if !check_iscsi_support(1);
1893
1894 $session->{iscsi_sessions} = iscsi_session_list()
1895 if !$session->{iscsi_sessions};
1896
1897 my $iscsi_sess = $session->{iscsi_sessions}->{$scfg->{target}};
1898 if (!defined ($iscsi_sess)) {
1899 eval { iscsi_login ($scfg->{target}, $scfg->{portal}); };
1900 warn $@ if $@;
1901 } else {
1902 # make sure we get all devices
1903 iscsi_session_rescan ($iscsi_sess);
1904 }
1905
1906 } else {
1907 die "implement me";
1908 }
1909
1910 my $newseq = uevent_seqnum ();
1911
1912 # only call udevsettle if there are events
1913 if ($newseq > $session->{uevent_seqnum}) {
1914 my $timeout = 30;
1915 system ("$UDEVADM settle --timeout=$timeout"); # ignore errors
1916 $session->{uevent_seqnum} = $newseq;
1917 }
1918
1919 $session->{activated}->{$storeid} = 1;
1920 }
1921
1922 sub activate_storage_list {
1923 my ($cfg, $storeid_list, $session) = @_;
1924
1925 $session = {} if !$session;
1926
1927 foreach my $storeid (@$storeid_list) {
1928 __activate_storage_full ($cfg, $storeid, $session);
1929 }
1930 }
1931
1932 sub activate_storage {
1933 my ($cfg, $storeid) = @_;
1934
1935 my $session = {};
1936
1937 __activate_storage_full ($cfg, $storeid, $session);
1938 }
1939
1940 sub activate_volumes {
1941 my ($cfg, $vollist, $exclusive) = @_;
1942
1943 return if !($vollist && scalar(@$vollist));
1944
1945 my $lvm_activate_mode = $exclusive ? 'ey' : 'ly';
1946
1947 my $storagehash = {};
1948 foreach my $volid (@$vollist) {
1949 my ($storeid, undef) = parse_volume_id ($volid);
1950 $storagehash->{$storeid} = 1;
1951 }
1952
1953 activate_storage_list ($cfg, [keys %$storagehash]);
1954
1955 foreach my $volid (@$vollist) {
1956 my ($storeid, $volname) = parse_volume_id ($volid);
1957
1958 my $scfg = storage_config ($cfg, $storeid);
1959
1960 my $path = path ($cfg, $volid);
1961
1962 if ($scfg->{type} eq 'lvm') {
1963 my $cmd = ['/sbin/lvchange', "-a$lvm_activate_mode", $path];
1964 run_command($cmd, errmsg => "can't activate LV '$volid'");
1965 }
1966
1967 # check is volume exists
1968 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
1969 die "volume '$volid' does not exist\n" if ! -e $path;
1970 } else {
1971 die "volume '$volid' does not exist\n" if ! -b $path;
1972 }
1973 }
1974 }
1975
1976 sub deactivate_volumes {
1977 my ($cfg, $vollist) = @_;
1978
1979 return if !($vollist && scalar(@$vollist));
1980
1981 my $lvs = lvm_lvs ();
1982
1983 my @errlist = ();
1984 foreach my $volid (@$vollist) {
1985 my ($storeid, $volname) = parse_volume_id ($volid);
1986
1987 my $scfg = storage_config ($cfg, $storeid);
1988
1989 if ($scfg->{type} eq 'lvm') {
1990 my ($name) = parse_volname_lvm ($volname);
1991
1992 if ($lvs->{$scfg->{vgname}}->{$name}) {
1993 my $path = path ($cfg, $volid);
1994 my $cmd = ['/sbin/lvchange', '-aln', $path];
1995 eval { run_command($cmd, errmsg => "can't deactivate LV '$volid'"); };
1996 if (my $err = $@) {
1997 warn $err;
1998 push @errlist, $volid;
1999 }
2000 }
2001 }
2002 }
2003
2004 die "volume deativation failed: " . join(' ', @errlist)
2005 if scalar(@errlist);
2006 }
2007
2008 sub deactivate_storage {
2009 my ($cfg, $storeid) = @_;
2010
2011 my $iscsi_sessions;
2012
2013 my $scfg = storage_config ($cfg, $storeid);
2014
2015 my $type = $scfg->{type};
2016
2017 if ($type eq 'dir') {
2018 # nothing to do
2019 } elsif ($type eq 'nfs') {
2020 my $mountdata = read_proc_mounts();
2021 my $server = $scfg->{server};
2022 my $export = $scfg->{export};
2023 my $path = $scfg->{path};
2024
2025 my $cmd = ['/bin/umount', $path];
2026
2027 run_command($cmd, errmsg => 'umount error')
2028 if nfs_is_mounted ($server, $export, $path, $mountdata);
2029
2030 } elsif ($type eq 'lvm') {
2031 my $cmd = ['/sbin/vgchange', '-aln', $scfg->{vgname}];
2032 run_command($cmd, errmsg => "can't deactivate VG '$scfg->{vgname}'");
2033 } elsif ($type eq 'iscsi') {
2034 my $portal = $scfg->{portal};
2035 my $target = $scfg->{target};
2036
2037 my $iscsi_sessions = iscsi_session_list();
2038 iscsi_logout ($target, $portal)
2039 if defined ($iscsi_sessions->{$target});
2040
2041 } else {
2042 die "implement me";
2043 }
2044 }
2045
2046 sub storage_info {
2047 my ($cfg, $content) = @_;
2048
2049 my $ids = $cfg->{ids};
2050
2051 my $info = {};
2052 my $stypes = {};
2053
2054 my $slist = [];
2055 foreach my $storeid (keys %$ids) {
2056
2057 next if $content && !$ids->{$storeid}->{content}->{$content};
2058
2059 next if !storage_check_enabled($cfg, $storeid, undef, 1);
2060
2061 my $type = $ids->{$storeid}->{type};
2062
2063 $info->{$storeid} = {
2064 type => $type,
2065 total => 0,
2066 avail => 0,
2067 used => 0,
2068 shared => $ids->{$storeid}->{shared} ? 1 : 0,
2069 content => content_hash_to_string($ids->{$storeid}->{content}),
2070 active => 0,
2071 };
2072
2073 $stypes->{$type} = 1;
2074
2075 push @$slist, $storeid;
2076 }
2077
2078 my $session = {};
2079 my $mountdata = '';
2080 my $iscsi_sessions = {};
2081 my $vgs = {};
2082
2083 if ($stypes->{lvm}) {
2084 $session->{vgs} = lvm_vgs();
2085 $vgs = $session->{vgs};
2086 }
2087 if ($stypes->{nfs}) {
2088 $mountdata = read_proc_mounts();
2089 $session->{mountdata} = $mountdata;
2090 }
2091 if ($stypes->{iscsi}) {
2092 $iscsi_sessions = iscsi_session_list();
2093 $session->{iscsi_sessions} = $iscsi_sessions;
2094 }
2095
2096 eval { activate_storage_list ($cfg, $slist, $session); };
2097
2098 foreach my $storeid (keys %$ids) {
2099 my $scfg = $ids->{$storeid};
2100
2101 next if !$info->{$storeid};
2102
2103 my $type = $scfg->{type};
2104
2105 if ($type eq 'dir' || $type eq 'nfs') {
2106
2107 my $path = $scfg->{path};
2108
2109 if ($type eq 'nfs') {
2110 my $server = $scfg->{server};
2111 my $export = $scfg->{export};
2112
2113 next if !nfs_is_mounted ($server, $export, $path, $mountdata);
2114 }
2115
2116 my $timeout = 2;
2117 my $res = PVE::Tools::df($path, $timeout);
2118
2119 next if !$res || !$res->{total};
2120
2121 $info->{$storeid}->{total} = $res->{total};
2122 $info->{$storeid}->{avail} = $res->{avail};
2123 $info->{$storeid}->{used} = $res->{used};
2124 $info->{$storeid}->{active} = 1;
2125
2126 } elsif ($type eq 'lvm') {
2127
2128 my $vgname = $scfg->{vgname};
2129
2130 my $total = 0;
2131 my $free = 0;
2132
2133 if (defined ($vgs->{$vgname})) {
2134 $total = $vgs->{$vgname}->{size};
2135 $free = $vgs->{$vgname}->{free};
2136
2137 $info->{$storeid}->{total} = $total;
2138 $info->{$storeid}->{avail} = $free;
2139 $info->{$storeid}->{used} = $total - $free;
2140 $info->{$storeid}->{active} = 1;
2141 }
2142
2143 } elsif ($type eq 'iscsi') {
2144
2145 $info->{$storeid}->{total} = 0;
2146 $info->{$storeid}->{avail} = 0;
2147 $info->{$storeid}->{used} = 0;
2148 $info->{$storeid}->{active} =
2149 defined ($iscsi_sessions->{$scfg->{target}});
2150
2151 } else {
2152 die "implement me";
2153 }
2154 }
2155
2156 return $info;
2157 }
2158
2159 sub resolv_server {
2160 my ($server) = @_;
2161
2162 my $packed_ip = gethostbyname($server);
2163 if (defined $packed_ip) {
2164 return inet_ntoa($packed_ip);
2165 }
2166 return undef;
2167 }
2168
2169 sub scan_nfs {
2170 my ($server_in) = @_;
2171
2172 my $server;
2173 if (!($server = resolv_server ($server_in))) {
2174 die "unable to resolve address for server '${server_in}'\n";
2175 }
2176
2177 my $cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
2178
2179 my $res = {};
2180 run_command($cmd, outfunc => sub {
2181 my $line = shift;
2182
2183 # note: howto handle white spaces in export path??
2184 if ($line =~ m!^(/\S+)\s+(.+)$!) {
2185 $res->{$1} = $2;
2186 }
2187 });
2188
2189 return $res;
2190 }
2191
2192 sub resolv_portal {
2193 my ($portal, $noerr) = @_;
2194
2195 if ($portal =~ m/^([^:]+)(:(\d+))?$/) {
2196 my $server = $1;
2197 my $port = $3;
2198
2199 if (my $ip = resolv_server($server)) {
2200 $server = $ip;
2201 return $port ? "$server:$port" : $server;
2202 }
2203 }
2204 return undef if $noerr;
2205
2206 raise_param_exc({ portal => "unable to resolve portal address '$portal'" });
2207 }
2208
2209 # idea is from usbutils package (/usr/bin/usb-devices) script
2210 sub __scan_usb_device {
2211 my ($res, $devpath, $parent, $level) = @_;
2212
2213 return if ! -d $devpath;
2214 return if $level && $devpath !~ m/^.*[-.](\d+)$/;
2215 my $port = $level ? int($1 - 1) : 0;
2216
2217 my $busnum = int(file_read_firstline("$devpath/busnum"));
2218 my $devnum = int(file_read_firstline("$devpath/devnum"));
2219
2220 my $d = {
2221 port => $port,
2222 level => $level,
2223 busnum => $busnum,
2224 devnum => $devnum,
2225 speed => file_read_firstline("$devpath/speed"),
2226 class => hex(file_read_firstline("$devpath/bDeviceClass")),
2227 vendid => file_read_firstline("$devpath/idVendor"),
2228 prodid => file_read_firstline("$devpath/idProduct"),
2229 };
2230
2231 if ($level) {
2232 my $usbpath = $devpath;
2233 $usbpath =~ s|^.*/\d+\-||;
2234 $d->{usbpath} = $usbpath;
2235 }
2236
2237 my $product = file_read_firstline("$devpath/product");
2238 $d->{product} = $product if $product;
2239
2240 my $manu = file_read_firstline("$devpath/manufacturer");
2241 $d->{manufacturer} = $manu if $manu;
2242
2243 my $serial => file_read_firstline("$devpath/serial");
2244 $d->{serial} = $serial if $serial;
2245
2246 push @$res, $d;
2247
2248 foreach my $subdev (<$devpath/$busnum-*>) {
2249 next if $subdev !~ m|/$busnum-[0-9]+(\.[0-9]+)*$|;
2250 __scan_usb_device($res, $subdev, $devnum, $level + 1);
2251 }
2252
2253 };
2254
2255 sub scan_usb {
2256
2257 my $devlist = [];
2258
2259 foreach my $device (</sys/bus/usb/devices/usb*>) {
2260 __scan_usb_device($devlist, $device, 0, 0);
2261 }
2262
2263 return $devlist;
2264 }
2265
2266 sub scan_iscsi {
2267 my ($portal_in) = @_;
2268
2269 my $portal;
2270 if (!($portal = resolv_portal ($portal_in))) {
2271 die "unable to parse/resolve portal address '${portal_in}'\n";
2272 }
2273
2274 return iscsi_discovery($portal);
2275 }
2276
2277 sub storage_default_format {
2278 my ($cfg, $storeid) = @_;
2279
2280 my $scfg = storage_config ($cfg, $storeid);
2281
2282 my $def = $default_config->{$scfg->{type}};
2283
2284 my $def_format = 'raw';
2285 my $valid_formats = [ $def_format ];
2286
2287 if (defined ($def->{format})) {
2288 $def_format = $scfg->{format} || $def->{format}->[1];
2289 $valid_formats = [ sort keys %{$def->{format}->[0]} ];
2290 }
2291
2292 return wantarray ? ($def_format, $valid_formats) : $def_format;
2293 }
2294
2295 sub vgroup_is_used {
2296 my ($cfg, $vgname) = @_;
2297
2298 foreach my $storeid (keys %{$cfg->{ids}}) {
2299 my $scfg = storage_config ($cfg, $storeid);
2300 if ($scfg->{type} eq 'lvm' && $scfg->{vgname} eq $vgname) {
2301 return 1;
2302 }
2303 }
2304
2305 return undef;
2306 }
2307
2308 sub target_is_used {
2309 my ($cfg, $target) = @_;
2310
2311 foreach my $storeid (keys %{$cfg->{ids}}) {
2312 my $scfg = storage_config ($cfg, $storeid);
2313 if ($scfg->{type} eq 'iscsi' && $scfg->{target} eq $target) {
2314 return 1;
2315 }
2316 }
2317
2318 return undef;
2319 }
2320
2321 sub volume_is_used {
2322 my ($cfg, $volid) = @_;
2323
2324 foreach my $storeid (keys %{$cfg->{ids}}) {
2325 my $scfg = storage_config ($cfg, $storeid);
2326 if ($scfg->{base} && $scfg->{base} eq $volid) {
2327 return 1;
2328 }
2329 }
2330
2331 return undef;
2332 }
2333
2334 sub storage_is_used {
2335 my ($cfg, $storeid) = @_;
2336
2337 foreach my $sid (keys %{$cfg->{ids}}) {
2338 my $scfg = storage_config ($cfg, $sid);
2339 next if !$scfg->{base};
2340 my ($st) = parse_volume_id ($scfg->{base});
2341 return 1 if $st && $st eq $storeid;
2342 }
2343
2344 return undef;
2345 }
2346
2347 sub foreach_volid {
2348 my ($list, $func) = @_;
2349
2350 return if !$list;
2351
2352 foreach my $sid (keys %$list) {
2353 foreach my $info (@{$list->{$sid}}) {
2354 my $volid = $info->{volid};
2355 my ($sid1, $volname) = parse_volume_id ($volid, 1);
2356 if ($sid1 && $sid1 eq $sid) {
2357 &$func ($volid, $sid, $info);
2358 } else {
2359 warn "detected strange volid '$volid' in volume list for '$sid'\n";
2360 }
2361 }
2362 }
2363 }
2364
2365 1;