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