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