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