/scripts/dshbak

https://code.google.com/ · Perl · 361 lines · 166 code · 48 blank · 147 comment · 27 complexity · 743b7e4d62e3008812328f4840b1b2ec MD5 · raw file

  1. #!/usr/bin/perl -w
  2. #############################################################################
  3. # $Id$
  4. #############################################################################
  5. #
  6. # Copyright (C) 2001-2006 The Regents of the University of California.
  7. # Copyright (C) 2007-2011 Lawrence Livermore National Security, LLC.
  8. # Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
  9. # Written by Jim Garlick <garlick@llnl.gov>.
  10. # UCRL-CODE-2003-005.
  11. #
  12. # This file is part of Pdsh, a parallel remote shell program.
  13. # For details, see <http://www.llnl.gov/linux/pdsh/>.
  14. #
  15. # Pdsh is free software; you can redistribute it and/or modify it under
  16. # the terms of the GNU General Public License as published by the Free
  17. # Software Foundation; either version 2 of the License, or (at your option)
  18. # any later version.
  19. #
  20. # Pdsh is distributed in the hope that it will be useful, but WITHOUT ANY
  21. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  22. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  23. # details.
  24. #
  25. # You should have received a copy of the GNU General Public License along
  26. # with Pdsh; if not, write to the Free Software Foundation, Inc.,
  27. # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  28. #
  29. #############################################################################
  30. require 5.003;
  31. use strict;
  32. use Getopt::Std;
  33. use File::Basename qw/ basename /;
  34. use File::Path;
  35. use constant GETOPTS_ARGS => "chfd:";
  36. use vars map { "\$opt_$_" } split(/:*/, GETOPTS_ARGS);
  37. #############################################################################
  38. my $prog = basename $0;
  39. my $usage = <<EOF;
  40. Usage: $prog [OPTION]...
  41. -h Display this help message
  42. -c Coalesce identical output from hosts
  43. -d DIR Send output to files in DIR, one file per host
  44. -f With -d, force creation of DIR
  45. EOF
  46. #
  47. # Save the desired output type in output_fn, which
  48. # can be do_output_normal, do_output_per_file, or do_output_coalesced.
  49. #
  50. my $output_fn = \&do_output_normal;
  51. #############################################################################
  52. getopts(GETOPTS_ARGS) or usage();
  53. #############################################################################
  54. #
  55. # Process args:
  56. #
  57. $opt_h and usage(0);
  58. if ($opt_c) {
  59. &log_fatal ("Do not specify both -c and -d\n") if ($opt_d);
  60. $output_fn = \&do_output_coalesced;
  61. }
  62. if ($opt_d) {
  63. if ($opt_f and not -d $opt_d) {
  64. eval { mkpath ($opt_d) };
  65. &log_fatal ("Failed to create $opt_d: $@\n") if ($@);
  66. }
  67. -d $opt_d or &log_fatal ("Output directory $opt_d does not exist\n");
  68. $output_fn = \&do_output_per_file;
  69. }
  70. &log_fatal ("Option -f may only be used with -d\n") if ($opt_f && !$opt_d);
  71. #############################################################################
  72. #
  73. # Grab all lines of input and produce output:
  74. #
  75. my %lines = &process_lines ();
  76. &$output_fn ($_) for (sortn (keys %lines));
  77. exit 0;
  78. #############################################################################
  79. #
  80. # Functions:
  81. #
  82. #
  83. sub log_msg { print STDERR "$prog: ", @_; }
  84. sub log_fatal { &log_msg ("Fatal: ", @_); exit 1; }
  85. #
  86. # Read lines of stdin produced from pdsh and push onto a hash
  87. # per host prefix.
  88. #
  89. sub process_lines
  90. {
  91. my %lines = ();
  92. #
  93. # Stdin consists of lines of the form "hostname: output...".
  94. # Store these in a hash, keyed by hostname, of lists of lines.
  95. #
  96. while (<>) {
  97. my ($tag, $data) = m/^\s*(\S+?)\s*: ?(.*\n)$/;
  98. # Ignore lines that aren't prefixed with a hostname:
  99. next unless $tag;
  100. push(@{$lines{$tag}}, $data);
  101. }
  102. return %lines;
  103. }
  104. #
  105. # Print the standard dshbak header
  106. #
  107. sub print_header
  108. {
  109. my $div = "----------------\n";
  110. print $div, join (",", @_), "\n", $div
  111. }
  112. #
  113. # Normal output function
  114. #
  115. sub do_output_normal
  116. {
  117. my ($tag) = @_;
  118. &print_header ($tag);
  119. print @{$lines{$tag}};
  120. }
  121. #
  122. # Put each host output into separate files in directory
  123. # specified by $opt_d.
  124. #
  125. sub do_output_per_file
  126. {
  127. my ($tag) = @_;
  128. my $file = "$opt_d/$tag";
  129. open (OUTPUT, ">$file") ||
  130. &log_fatal ("Failed to open output file '$file': $!\n");
  131. print OUTPUT @{$lines{$tag}};
  132. }
  133. #
  134. # Print identical output only once, tagged with the list of
  135. # hosts producing matching data.
  136. #
  137. sub do_output_coalesced
  138. {
  139. my ($tag) = @_;
  140. my @identical = ();
  141. #
  142. # Ignore any deleted tags, lines from these hosts has already
  143. # been printed:
  144. #
  145. return if not defined ($lines{$tag});
  146. #
  147. # Look for other hosts with identical output:
  148. #
  149. for my $tag2 (keys %lines) {
  150. next if ($tag2 eq $tag);
  151. next unless (cmp_list ($lines{$tag}, $lines{$tag2}));
  152. #
  153. # Output is identical -- stash the tag of this match and
  154. # delete it from further processing:
  155. #
  156. push (@identical, $tag2);
  157. delete ($lines{$tag2});
  158. }
  159. &print_header (compress (sort (@identical, $tag)));
  160. print @{$lines{$tag}};
  161. }
  162. #
  163. # Compare two lists-o-strings
  164. # \@l1 (IN) list1
  165. # \@l2 (IN) list2
  166. # RETURN 1 if match, 0 if not
  167. #
  168. sub cmp_list
  169. {
  170. my ($l1, $l2) = @_;
  171. my ($i, $retval);
  172. $retval = 1;
  173. if ($#{$l1} != $#{$l2}) {
  174. return 0;
  175. }
  176. for ($i = 0; $i <= $#{$l1} && $retval == 1; $i++) {
  177. if (!defined(${$l2}[$i]) || ${$l1}[$i] ne ${$l2}[$i]) {
  178. $retval = 0;
  179. }
  180. }
  181. return $retval;
  182. }
  183. sub usage
  184. {
  185. my ($rc) = $@ ? $@ : 0;
  186. printf STDERR $usage;
  187. exit $rc;
  188. }
  189. #
  190. # Try to compress a list of hosts into a host range
  191. #
  192. sub compress
  193. {
  194. my %suffixes = ();
  195. my @list = ();
  196. # Each suffix key points to a list of hostnames with corresponding
  197. # suffix stripped off.
  198. push (@{$suffixes{$$_[1]}}, $$_[0])
  199. for map { [/(.*?\d*)(\D*)$/] } sortn (@_);
  200. #
  201. # For each suffix, run compress on hostnames without suffix, then
  202. # reapply suffix name.
  203. for my $suffix (keys %suffixes) {
  204. map { push (@list, "$_$suffix") }
  205. compress_inner (@{$suffixes{$suffix}});
  206. }
  207. local $"=",";
  208. return wantarray ? @list : "@list";
  209. }
  210. sub compress_inner
  211. {
  212. my %rng = comp(@_);
  213. my @list = ();
  214. local $"=",";
  215. @list = map { $_ .
  216. (@{$rng{$_}}>1 || ${$rng{$_}}[0] =~ /-/ ?
  217. "[@{$rng{$_}}]" :
  218. "@{$rng{$_}}"
  219. )
  220. } sort keys %rng;
  221. return wantarray ? @list : "@list";
  222. }
  223. #
  224. # Return the zeropadded width of $n, where the zero-padded
  225. # width is the minimum format width a number with the given
  226. # zero-padding. That is, no zero-padding is 1, because 0-9
  227. # have a minimum width of 1, "01" has a width of 2, 010 has
  228. # a width of 3 and so on.
  229. #
  230. sub zeropadwidth
  231. {
  232. my ($n) = @_;
  233. #
  234. # zeropad width is the length of $n if there are any leading
  235. # zeros and the number is not zero itself.
  236. #
  237. return length $n if (($n =~ /^0/) and ($n ne "0"));
  238. #
  239. # If no leading zeros (or $n == 0) then the width is always '1'
  240. #
  241. return 1;
  242. }
  243. sub comp
  244. {
  245. my (%i) = ();
  246. my (%s) = ();
  247. # turn off warnings here to avoid perl complaints about
  248. # uninitialized values for members of %i and %s
  249. local ($^W) = 0;
  250. for my $host (sortn (@_)) {
  251. my ($p, $n) = $host =~ /(.*?)(\d*)$/;
  252. my $zp = &zeropadwidth ($n);
  253. #
  254. # $s{$p} is a reference to an array of arrays
  255. # that indicate individual range elements of
  256. # the form [ N_start, N_end]. If only one element
  257. # is present then the range element is a singleton.
  258. #
  259. # $i{$p}{$zp}${n} tracks the index of prefix $p and suffix $n
  260. # with zero-padding $zp into the @{$s{$p}} array.
  261. #
  262. # Need to check if $n-1 exists in the $s{$p} array, but the
  263. # zero-padded width must be compatible. e.g.. "9" and "09"
  264. # are compatible with 10, but not with 010.
  265. #
  266. my $idx = $i{$p}{$zp}{$n-1};
  267. #
  268. # If the current zeropad is 1, and the length of $n is > 1,
  269. # then we check for a previous number with either zp == 1 or
  270. # zp == length. This catches 09-10, 099-100, etc .
  271. #
  272. if (!defined $idx && $zp == 1) {
  273. $idx = $i{$p}{length $n}{$n-1};
  274. }
  275. if (defined $idx) {
  276. #
  277. # $n - 1 is already in array, so update END:
  278. #
  279. $s{$p}[$idx][1] = "$n";
  280. $i{$p}{$zp}{$n-0} = $idx;
  281. }
  282. else {
  283. #
  284. # Otherwise, we create a new single entry
  285. # and update $i{} (Use $n-0 to force a number)
  286. #
  287. push (@{$s{$p}}, [ $n ]);
  288. $i{$p}{$zp}{$n-0} = $#{$s{$p}};
  289. }
  290. }
  291. #
  292. #
  293. # Now return $s{} as a hash of prefixes with a list of range elemts:
  294. # e.g. $s{"host"} = [ "1-10", "25", "100-101" ]
  295. #
  296. for my $key (keys %s) {
  297. @{$s{$key}} =
  298. map { $#$_>0 ? "$$_[0]-$$_[$#$_]" : "$$_[0]" } @{$s{$key}};
  299. }
  300. return %s;
  301. }
  302. # sortn:
  303. #
  304. # sort a group of alphanumeric strings by the last group of digits on
  305. # those strings, if such exists (good for numerically suffixed host lists)
  306. #
  307. sub sortn
  308. {
  309. map {$$_[0]} sort {($$a[1]||0)<=>($$b[1]||0)} map {[$_,/(\d*)$/]} @_;
  310. }