Whamcloud - gitweb
LU-19098 hsm: don't print progname twice with lhsmtool
[fs/lustre-release.git] / lustre / scripts / ldev
1 #!/usr/bin/perl
2 #
3 # ldev - parser for /etc/ldev.conf
4 #
5 use strict;
6 use File::Basename;
7 use Getopt::Long qw/ :config posix_default no_ignore_case/;
8
9 $ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin";
10
11 my $prog = basename($0);
12
13 my $usage = <<EOF;
14 Usage: $prog [OPTIONS]...
15
16 Parse ldev.conf and answer the following queries:
17
18   -h, --help          Display this help.
19   -c, --config FILE   Set path to config file.
20   -H, --hostname NAME Use NAME instead of local hostname for queries.
21   -p, --partner       Print hostname of failover partner.
22   -l, --local         Print labels for local devices.
23   -f, --foreign       Print labels for foreign devices.
24   -a, --all           Print labels for local and foreign devices.
25   -F, --filesys NAME  Print labels for file system NAME.
26   -s, --sanity        Sanity check config on this node.
27   -d, --device LABEL  Print storage device of LABEL.
28   -j, --journal LABEL Print journal device of LABEL if it exists.
29   -r, --raidtab LABEL Print raidtab of LABEL if it exists.
30   -t, --type LABEL    Print device type of LABEL, i.e. "zfs" or "md".
31   -z, --zpool LABEL   Print zpool containing LABEL.
32   -R, --role ROLE     Filter output based on role, i.e. mdt, ost, mgs.
33   CMD [ARGS] ...      Run CMD in parallel for each device substituting:
34                       %f=fsname  %d=device  %i=dec-index %n=main-nid %l=label
35                       %t=srvtype %j=journal %I=hex-index %N=fail-nid %m=mgs-nid
36                       %H=hostname %b=backing-fs
37                       May be used in combination with -l, -f, -a, -F options.
38 EOF
39
40 my %eparse = (
41    elabel_uniq  =>    "label used more than once",
42    epairwise    =>    "local and foreign host not mapped to each other",
43    efieldcount  =>    "line has less than the minimum number of fields (4)",
44    ekeyval      =>    "malformed id=name",
45 );
46
47 my %conf = ();
48
49 my $global_mgs = "";
50
51 #
52 # Main
53 #
54
55 parse_cmdline ();
56
57 parse_config ();
58
59 sanity ()         if $conf{sanity};
60 exec_cmd ()       if $conf{execcmd};
61 query_partner ()  if $conf{partner};
62 query_local ()    if $conf{local};
63 query_foreign ()  if $conf{foreign};
64 query_all ()      if $conf{all};
65 query_device ()   if $conf{device};
66 query_journal ()  if $conf{journal};
67 query_raidtab ()  if $conf{raidtab};
68 query_type ()     if $conf{type};
69 query_zpool ()    if $conf{zpool};
70 query_filesys ()  if $conf{fsname};
71
72 exit(0);
73
74 #
75 # Subroutines
76 #
77
78 sub parse_cmdline
79 {
80     my $help = 0;
81     my $host = "";
82
83     $conf{partner} = 0;
84     $conf{all} = 0;
85     $conf{local} = 0;
86     $conf{foreign} = 0;
87     $conf{config} = "/etc/ldev.conf";
88     $conf{nidsfile} = "/etc/nids";
89     $conf{hostname} = `uname -n`; chomp $conf{hostname};
90     $conf{device} = "";
91     $conf{sanity} = 0;
92     $conf{execcmd} = "";
93     $conf{journal} = "";
94     $conf{role} = "";
95     $conf{fsname} = "";
96
97     my $rc = GetOptions (
98         "help|h!"         => \$help,
99         "partner|p!"      => \$conf{partner},
100         "all|a!"          => \$conf{all},
101         "local|l!"        => \$conf{local},
102         "foreign|f!"      => \$conf{foreign},
103         "config|c=s"      => \$conf{config},
104         "nidsfile|n=s"    => \$conf{nidsfile},
105         "hostname|H=s"    => \$conf{hostname},
106         "sanity|s!"       => \$conf{sanity},
107         "device|d=s"      => \$conf{device},
108         "journal|j=s"     => \$conf{journal},
109         "raidtab|r=s"     => \$conf{raidtab},
110         "type|t=s"        => \$conf{type},
111         "zpool|z=s"       => \$conf{zpool},
112         "role|R=s"        => \$conf{role},
113         "filesys|F=s"     => \$conf{fsname},
114     );
115
116     usage() if $help || !$rc;
117
118     log_fatal ("cannot read config file\n") if (! -r $conf{config});
119
120     my $filters = 0;
121     $filters++ if ($conf{local});
122     $filters++ if ($conf{foreign});
123     $filters++ if ($conf{all});
124     $filters++ if ($conf{fsname});
125     log_fatal ("Only one of -l, -f, -a, or -F may be used.\n") if ($filters > 1);
126
127     if (@ARGV) {
128         $conf{execcmd} = " " . join " ", @ARGV;
129     }
130
131     parse_nids () if ($conf{execcmd} =~ /(%n|%N|%m)/);
132 }
133
134 sub parse_config
135 {
136     my $line = 0;
137     my %l2f = ();
138     my %label2local = ();
139     my %label2dev = ();
140     my %label2journal = ();
141     my %label2raidtab = ();
142     my %label2type = ();
143     my %label2zpool = ();
144     my %filesys2mgs = ();
145     my %label2hostname = ();
146     my @local_labels = ();
147     my @foreign_labels = ();
148     my @fs_labels = ();
149
150     open (CONF, "< $conf{config}") or log_fatal ("$conf{config}: $!\n");
151
152     while (<CONF>) {
153         my $type;
154         $line++;
155         s/#.*//;
156         s/(\s)*$//;
157         next if (/^(\s)*$/);
158         chomp;
159         my ($local, $foreign, $label, $dev, $j, $raidtab) = split;
160         if ($dev !~ /^\// && $dev =~ /^([^:]+):(.+)$/) {
161             $type = $1;
162             $dev = $2;
163         }
164
165         eparse_line ($line, "efieldcount") if (!defined $dev);
166         eparse_line ($line, "epairwise") if (exists $l2f{$local}
167                                          && $l2f{$local} ne $foreign);
168         $l2f{$local} = $foreign;
169
170         eparse_line ($line, "elabel_uniq") if (exists $label2dev{$label}
171                                          || exists $label2local{$label});
172
173         $label2dev{$label} = $dev;
174         $label2local{$label} = $local;
175         $label2journal{$label} = $j if defined $j;
176         $label2raidtab{$label} = $raidtab if defined $raidtab;
177         $label2hostname{$label}=$local;
178         if (defined $type) {
179             $label2type{$label} = $type;
180             if ($type eq "zfs" && $dev =~ m{^([^/]+)/[^/]+$}) {
181                 $label2zpool{$label} = $1;
182             }
183         }
184
185         my $filesys;
186         my $nodetype;
187         if ($label eq "MGS") {
188             $filesys = "";
189             $nodetype = $label;
190         } else {
191             /(\w+)-(OST|MDT|MGS)([0-9a-fA-F]{4})/, $label;
192             $filesys = $1;
193             $nodetype = $2;
194         }
195
196         if ($nodetype eq "MGS" or $label eq "MGS") {
197             $filesys2mgs{$filesys} = $label;
198         }
199
200         if ($label eq "MGS") {
201             $global_mgs = $label;
202         }
203
204         next if $conf{role} and lc $conf{role} ne lc $nodetype;
205
206         if ($local eq $conf{hostname}) {
207             push @local_labels, $label;
208         } elsif ($foreign eq $conf{hostname}) {
209             push @foreign_labels, $label;
210         }
211
212         if ($conf{fsname} and $filesys eq $conf{fsname}) {
213             push @fs_labels, $label;
214         }
215     }
216     close CONF;
217
218     foreach (keys %l2f) {
219         my $foreign = $l2f{$_};
220         next if ($foreign eq "-");
221         eparse_str ($_, "epairwise")
222                     unless (!exists $l2f{$foreign} or $l2f{$foreign} eq $_);
223     }
224
225     @{$conf{local_labels}} = @local_labels;
226     @{$conf{foreign_labels}} = @foreign_labels;
227     @{$conf{fs_labels}} = @fs_labels;
228     %{$conf{l2f}} = %l2f;
229     %{$conf{label2dev}} = %label2dev;
230     %{$conf{label2local}} = %label2local;
231     %{$conf{label2journal}} = %label2journal;
232     %{$conf{label2raidtab}} = %label2raidtab;
233     %{$conf{label2type}} = %label2type;
234     %{$conf{label2zpool}} = %label2zpool;
235     %{$conf{filesys2mgs}} = %filesys2mgs;
236     %{$conf{label2hostname}} = %label2hostname;
237 }
238
239 sub parse_nids ()
240 {
241     my $line = 0;
242     my %host2nid = ();
243     my %nid2host = ();
244
245     open (NIDS, "< $conf{nidsfile}") or log_fatal ("$conf{nidsfile}: $!\n");
246
247     while (<NIDS>) {
248         $line++;
249         s/#.*//;
250         next if (/^(\s)*$/);
251         chomp;
252         my ($host, $nid, $morenids) = split (/\s+/, $_, 3);
253         if (!defined $nid) {
254             log_fatal ("$conf{nidsfile} line $line: incomplete line\n");
255         }
256         $host2nid{$host} = $nid;
257         $nid2host{$nid} = $host;
258         map { $nid2host{$_} = $host; } split (/\s+/, $morenids);
259     }
260     close NIDS;
261
262     %{$conf{host2nid}} = %host2nid;
263     %{$conf{nid2host}} = %nid2host;
264 }
265
266 sub query_partner
267 {
268     my %l2f = %{$conf{l2f}};
269     my $hostname = $conf{hostname};
270     if (exists $l2f{$hostname} && $l2f{$hostname} ne "-") {
271         print "$l2f{$hostname}\n";
272     }
273 }
274
275 sub query_local
276 {
277     map { print "$_\n"; } @{$conf{local_labels}};
278 }
279
280 sub query_foreign
281 {
282     map { print "$_\n"; } @{$conf{foreign_labels}};
283 }
284
285 sub query_filesys
286 {
287     map { print "$_\n"; } @{$conf{fs_labels}};
288 }
289
290 sub query_all
291 {
292     query_local ();
293     query_foreign ();
294 }
295
296 sub query_device
297 {
298     my %label2dev = %{$conf{label2dev}};
299
300     if (exists $label2dev{$conf{device}}) {
301         print "$label2dev{$conf{device}}\n";
302     }
303 }
304
305 sub query_raidtab
306 {
307     my %label2raidtab = %{$conf{label2raidtab}};
308
309     if (exists $label2raidtab{$conf{raidtab}}) {
310         print "$label2raidtab{$conf{raidtab}}\n";
311     }
312 }
313
314 sub query_journal
315 {
316     my %label2journal = %{$conf{label2journal}};
317
318     if (exists $label2journal{$conf{journal}} &&
319        $label2journal{$conf{journal}} ne "-") {
320         print "$label2journal{$conf{journal}}\n";
321     }
322 }
323
324 sub query_type
325 {
326     my %label2type = %{$conf{label2type}};
327
328     if (exists $label2type{$conf{type}}) {
329         print "$label2type{$conf{type}}\n";
330     }
331 }
332
333 sub query_zpool
334 {
335     my %label2zpool = %{$conf{label2zpool}};
336
337     if (exists $label2zpool{$conf{zpool}}) {
338         print "$label2zpool{$conf{zpool}}\n";
339     }
340 }
341
342 sub dd_test
343 {
344     my ($dpath) = @_;
345     my $retval = 0;
346     my $bs =      `blockdev --getss   $dpath 2>/dev/null`; chomp $bs;
347     my $max512  = `blockdev --getsize $dpath 2>/dev/null`; chomp $max512;
348     if ($? == 0 && $bs > 0 && $max512 > 0) {
349         my $maxb = ($max512 / $bs) * 512;
350         my $count = 10 * 1024 * 1024 / $bs;  # read first 10mb
351         my $dev = `readlink -f $dpath`; chomp $dev;
352         $count = $maxb if ($count > $maxb);
353         `dd if=$dev of=/dev/null bs=$bs count=$count >/dev/null 2>&1`;
354         $retval = ($? == 0);
355     }
356     return $retval;
357 }
358
359 sub sanity
360 {
361     my $exit_val = 0;
362
363     my @local_labels = @{$conf{local_labels}};
364     my @foreign_labels = @{$conf{foreign_labels}};
365     my %label2dev = %{$conf{label2dev}};
366     my %label2journal = %{$conf{label2journal}};
367
368     foreach (@local_labels, @foreign_labels) {
369         my $lpath = "/dev/disk/by-label/$_";
370         my $dpath = $label2dev{$_};
371         my $jpath = $label2journal{$_};
372         my $label = $_;
373         if (! -e $lpath) {
374             log_error ("$lpath does not exist\n");
375             $exit_val = 1;
376         }
377         if (! -e $dpath) {
378             log_error ("$dpath does not exist\n");
379             $exit_val = 1;
380         } elsif (!dd_test ($dpath)) {
381             log_error ("$dpath failed dd test\n");
382             $exit_val = 1;
383         }
384         if (`readlink -f $lpath` ne `readlink -f $dpath`) {
385             log_error ("$lpath and $dpath point to different things\n");
386             $exit_val = 1;
387         }
388         if ($jpath) {
389             if (! -e $jpath) {
390                 log_error ("$jpath (journal for $label) does not exist\n");
391                 $exit_val = 1;
392             } elsif (!dd_test ($jpath)) {
393                 log_error ("$jpath failed dd test\n");
394                 $exit_val = 1;
395             }
396         }
397     }
398     exit($exit_val);
399 }
400
401 sub par_exec
402 {
403     my @pids = ();
404     my %pid2label = ();
405     my %pid2cmd = ();
406     my $pid;
407     my $result = 0;
408
409     my $tmpfile = `mktemp \${TMPDIR:-/tmp}/ldev.XXXXXXXXXX`; chomp $tmpfile;
410     log_fatal ("failed to create $tmpfile\n") if (! -e $tmpfile);
411
412     foreach (@_) {
413         my ($label, $cmd) = split (/\s+/, $_, 2);
414         my ($basecmd) = split (/\s+/, $cmd);
415         if (($pid = fork)) {       # parent
416             $pid2label{$pid} = $label;
417             $pid2cmd{$pid} = $basecmd;
418         } elsif (defined $pid) {   # child
419             #print STDERR "$label: running $cmd\n";
420             exec "($cmd 2>&1 || rm -f $tmpfile) | sed -e 's/^/$label: /'";
421             print STDERR "$label: exec $basecmd: $!\n"; unlink $tmpfile;
422         } else {                   # error
423             log_fatal ("label: fork: $!\n"); unlink $tmpfile;
424         }
425     }
426     while (($pid = wait) != -1) {
427         #print STDERR "$pid2label{$pid}: completed\n";
428     }
429
430     # sentinel is intact, so there were no errors
431     if (-e $tmpfile) {
432         unlink $tmpfile;
433         $result = 1;
434     }
435
436     return $result;
437 }
438
439 sub exec_cmd
440 {
441     my @labels = ();
442     my @cmds = ();
443     my %label2dev = %{$conf{label2dev}};
444     my %label2type = %{$conf{label2type}};
445     my %label2journal = %{$conf{label2journal}};
446     my %filesys2mgs = %{$conf{filesys2mgs}};
447     my %label2hostname = %{$conf{label2hostname}};
448     my %l2f = %{$conf{l2f}};
449     my ($nid, $failnid);
450
451     if ($conf{execcmd} =~ /%n/) {
452         my %host2nid = %{$conf{host2nid}};
453         if (!defined $host2nid{$conf{hostname}}) {
454             log_fatal ("%n used but no nid defined for this host\n");
455         }
456         $nid = $host2nid{$conf{hostname}};
457     }
458     if ($conf{execcmd} =~ /%N/) {
459         if (!defined $l2f{$conf{hostname}}) {
460             log_fatal ("%N used but foreign host is undefined\n");
461         }
462         my %host2nid = %{$conf{host2nid}};
463         if (!defined $host2nid{$l2f{$conf{hostname}}}) {
464             log_fatal ("%N used but foreign nid is undefined\n");
465         }
466         $failnid = $host2nid{$l2f{$conf{hostname}}};
467     }
468
469     if ($conf{foreign}) {
470         @labels = @{$conf{foreign_labels}};
471     } elsif ($conf{all}) {
472         @labels = (@{$conf{local_labels}}, @{$conf{foreign_labels}});
473     } elsif ($conf{fsname}) {
474         @labels = (@labels, @{$conf{fs_labels}});
475         push(@labels, $global_mgs) if ($global_mgs);
476     } else {
477         @labels = @{$conf{local_labels}};
478     }
479
480     foreach (@labels) {
481         /(\w+)-(OST|MDT|MGS)([0-9a-fA-F]{4})/;
482
483         my $fsname = $1;
484         my $type = $2; $type =~ tr/A-Z/a-z/;
485         my $hexindex = $3;
486         my $decindex = hex($3);
487         my $label = $_;
488         my $cmd = $conf{execcmd};
489         my $device = $label2dev{$_};
490         if ($conf{execcmd} =~ /%j/ && !defined $label2journal{$_}) {
491             log_fatal ("%j used but no journal defined for $_\n");
492         }
493         my $journal = $label2journal{$_};
494         my $fstype = $label2type{$_};
495         if (!defined $fstype or $fstype ne "zfs") {
496             $fstype = "ldiskfs";
497         }
498         my $hostname = $label2hostname{$_};
499         my $mgsnid;
500         if ($cmd =~ /%m/) {
501             my $mgs;
502             my $mgs_host;
503
504             if (exists $filesys2mgs{$fsname}) {
505                 $mgs = $filesys2mgs{$fsname};
506             } elsif ($global_mgs) {
507                 $mgs = $global_mgs;
508             } else {
509                  log_fatal ("$fsname has no MGS defined\n");
510             }
511
512             if (exists $label2hostname{$mgs}) {
513                 $mgs_host = $label2hostname{$mgs};
514             } else {
515                  log_fatal ("$mgs has no hostname defined\n");
516             }
517
518             my %host2nid = %{$conf{host2nid}};
519             if (!exists $host2nid{$mgs_host}) {
520                  log_fatal ("$mgs and $mgs_host have no NID defined\n");
521             }
522             $mgsnid = $host2nid{$mgs_host};
523         }
524
525         $cmd =~ s/%f/$fsname/g;  # %f = fsname
526         $cmd =~ s/%t/$type/g;    # %t = server type
527         $cmd =~ s/%I/$hexindex/g;# %I = index (hex)
528         $cmd =~ s/%i/$decindex/g;# %i = index (dec)
529         $cmd =~ s/%l/$label/g;   # %l = label
530         $cmd =~ s/%d/$device/g;  # %d = device
531         $cmd =~ s/%j/$journal/g; # %j = journal device
532         $cmd =~ s/%n/$nid/g;     # %n = nid
533         $cmd =~ s/%N/$failnid/g; # %N = failnid
534         $cmd =~ s/%m/$mgsnid/g;  # %m = MGS nid
535         $cmd =~ s/%b/$fstype/g;  # %b = backing file system type
536         $cmd =~ s/%H/$hostname/g;# %H = hostname
537
538         push @cmds, "$_ $cmd";
539     }
540
541     par_exec (@cmds) or log_fatal ("parallel command execution failed\n");
542     exit 0;
543 }
544
545 sub usage
546 {
547     print STDERR "$usage";
548     exit 0;
549 }
550
551 sub log_msg     { print STDERR "$prog: ", @_; }
552 sub log_error   { log_msg ("Error: ", @_) }
553 sub log_fatal   { log_msg ("Fatal: ", @_); exit 1; }
554 sub eparse_line { log_fatal ("$conf{config} line $_[0]: $eparse{$_[1]}\n"); }
555 sub eparse_str  { log_fatal ("$conf{config}: $_[0]: $eparse{$_[1]}\n"); }