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