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