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