Removed /usr/local from CDPATH
[clearscm.git] / lib / Utils.pm
index a94edd1..ea189a1 100644 (file)
@@ -63,19 +63,26 @@ use base 'Exporter';
 use POSIX qw (setsid);
 use File::Spec;
 use Carp;
+use Term::ReadKey;
 
 use OSDep;
 use Display;
 
+our $pipe;
+
 our @EXPORT = qw (
+  CheckParms
   EnterDaemonMode
   Execute
   GetChildren
+  GetPassword
   InArray
+  LoadAvg
   PageOutput
   PipeOutput
   PipeOutputArray
   ReadFile
+  RequiredFields
   RedirectOutput
   StartPipe
   Stats
@@ -83,7 +90,30 @@ our @EXPORT = qw (
   Usage
 );
 
-sub EnterDaemonMode (;$$$) {
+sub _restoreTerm() {
+  # In case the user hits Ctrl-C
+  print "\nControl-C\n";
+
+  ReadMode 'normal';
+
+  exit;
+} # _restoreTerm
+
+sub CheckParms($$) {
+  my ($requiredFields, $rec) = @_;
+
+  my $msg = RequiredFields($requiredFields, $rec);
+
+  my $function   = (caller(1))[3];
+  my $calledFrom = (caller(2))[3];
+  my $lineNbr    = (caller(2))[2];
+
+  croak "Internal error: $function called from $calledFrom:$lineNbr\n\nThe field $msg" if $msg;
+
+  return;
+} # CheckParms
+
+sub EnterDaemonMode(;$$$) {
   my ($logfile, $errorlog, $pidfile) = @_;
 
 =pod
@@ -130,14 +160,7 @@ Returns:
   $errorlog ||= $NULL;
 
   my $file;
-  
-  if ($pidfile) {
-    $pidfile =  File::Spec->rel2abs ($pidfile); 
 
-    open $file, '>', $pidfile
-      or warning "Unable to open pidfile $pidfile for writing - $!";  
-  } # if
-  
   # Redirect STDIN to $NULL
   open STDIN, '<', $NULL
     or error "Can't read $NULL ($!)", 1;
@@ -151,7 +174,7 @@ Returns:
     or error "Can't write to $errorlog ($!)", 1;
 
   # Change the current directory to /
-  my $ROOT = $ARCH eq "windows" ? "C:\\" : "/";
+  my $ROOT = $ARCHITECTURE eq "windows" ? "C:\\" : "/";
   chdir $ROOT
     or error "Can't chdir to $ROOT ($!), 1";
 
@@ -164,22 +187,27 @@ Returns:
 
   # Now the parent exits
   exit if $pid;
-  
+
   # Write pidfile if specified
   if ($pidfile) {
+    $pidfile =  File::Spec->rel2abs ($pidfile); 
+
+    open $file, '>', $pidfile
+      or warning "Unable to open pidfile $pidfile for writing - $!";  
+
     print $file "$$\n";
-    
+
     close $file; 
   } # if
-  
+
   # Set process to be session leader
   setsid ()
     or error "Can't start a new session ($!)", 1;
-    
+
   return;
 } # EnterDaemonMode
 
-sub Execute ($) {
+sub Execute($) {
   my ($cmd) = @_;
 
 =pod
@@ -222,23 +250,17 @@ STDOUT then do so in the $command passed in.
 
 =cut
 
-  # Save $SIG{CHLD} so we can set it to 'DEFAULT' and then restore it later.
-  # Helps when you are doing process handling.
-  my $sigchld = $SIG{CHLD};
-
   local $SIG{CHLD} = 'DEFAULT';
 
   my @output = `$cmd`;
   my $status = $?;
-  
-  local $SIG{CHLD} = $sigchld;
 
   chomp @output;
 
-  return ($status, @output);
+  return wantarray ? ($status, @output) : $status;
 } # Execute
 
-sub GetChildren (;$) {
+sub GetChildren(;$) {
   my ($pid) = @_;
 
 =pod
@@ -287,7 +309,7 @@ Returns:
 
   chomp @output;
 
-  foreach (@output) {
+  for (@output) {
     # Skip the pstree process and the parent process - we want only
     # our children.
     next if /pstree/ or /\($pid\)/;
@@ -295,12 +317,93 @@ Returns:
     if (/\((\d+)\)/) {
       push @children, $1;
     } # if
-  } # foreach
+  } # for
 
   return @children;
 } # GetChildren
 
-sub InArray ($@) {
+sub GetPassword(;$) {
+  my ($prompt) = @_;
+
+=pod
+
+=head2 GetPassword (;$prompt)
+
+Prompt for a password
+
+Parameters:
+
+=for html <blockquote>
+
+=over
+
+=item $prompt
+
+Prompt string to use (Default: "Password:")
+
+=back
+
+=for html </blockquote>
+
+Returns:
+
+=for html <blockquote>
+
+=over
+
+=item $password
+
+=back
+
+=for html </blockquote>
+
+=cut  
+
+  $prompt ||= 'Password';
+
+  my $password = '';
+
+  local $| = 1;
+
+  print "$prompt:";
+
+  $SIG{INT} = \&_restoreTerm;
+
+  ReadMode 'cbreak';
+
+  while () {
+    my $key;
+
+    while (not defined ($key = ReadKey -1)) { }
+
+    if ($key =~ /(\r|\n)/) {
+       print "\n";
+
+       last;
+    } # if
+
+    # Handle backspaces
+    if ($key eq chr(127)) {
+      unless ($password eq '') {
+        chop $password;
+
+        print "\b \b";
+      } # unless
+    } else {
+      print '*';
+
+      $password .= $key;
+    } # if
+  } # while
+
+  ReadMode 'restore'; # Reset tty mode before exiting.
+
+  $SIG{INT} = 'DEFAULT';
+
+  return $password;
+} # GetPassword
+
+sub InArray($@) {
   my ($item, @array) = @_;
 
 =pod
@@ -341,16 +444,67 @@ Returns:
 
 =cut
 
-  foreach (@array) {
+  for (@array) {
     return $TRUE if $item eq $_;
-  } # foreach
+  } # for
 
   return $FALSE;
 } # InArray
 
-our $pipe;
+sub LoadAvg () {
+
+=pod
+
+=head2 LoadAvg ()
+
+Return an array of the 1, 5, and 15 minute load averages.
+
+Parameters:
+
+=for html <blockquote>
+
+=over
+
+=item none
+
+=back
 
-sub StartPipe ($;$) {
+=for html </blockquote>
+
+Returns:
+
+=for html <blockquote>
+
+=over
+
+=item An array of the 1, 5, and 15 minute load averages in a list context.
+In a scalar context just the 1 minute load average.
+
+=back
+
+=for html </blockquote>
+
+=cut
+  # TODO: Make it work on Windows...
+  return if $^O =~ /win/i;
+
+  open my $loadAvg, '<', '/proc/loadavg'
+    or croak "Unable to open /proc/loadavg\n";
+
+  my $load = <$loadAvg>;
+
+  close $loadAvg;
+
+  my @loadAvgs = split /\s/, $load;
+
+  if (wantarray) {
+    return @loadAvgs;
+  } else {
+    return $loadAvgs[0]; # This is the 1 minute average
+  }
+} # LoadAvg
+
+sub StartPipe($;$) {
   my ($to, $existingPipe) = @_;
 
 =pod
@@ -393,10 +547,10 @@ Returns:
 
   if ($existingPipe) {
     close $existingPipe;
-    
+
     open $existingPipe, '|-', $to
       or error "Unable to open pipe - $!", 1;
-      
+
     return $existingPipe;
   } else {
     open $pipe, '|-', $to
@@ -406,7 +560,7 @@ Returns:
   } # if
 } # StartPipe
 
-sub PipeOutputArray ($@) {
+sub PipeOutputArray($@) {
   my ($to, @output) = @_;
 
 =pod
@@ -424,7 +578,7 @@ Parameters:
 =item $to
 
 String representing the other end of the pipe to pipe @output to
+
 =item @output
 
 Output to pipe
@@ -447,19 +601,19 @@ Returns:
 
 =cut
 
-  open my $pipe, "|$to" 
+  open my $pipe, '|-', $to 
     or error "Unable to open pipe - $!", 1;
 
-  foreach (@output) {
+  for (@output) {
     chomp;
 
     print $pipe "$_\n";
-  } # foreach
+  } # for
 
   return close $pipe;
 } # PipeOutputArray
 
-sub PipeOutput ($;$) {
+sub PipeOutput($;$) {
   my ($line, $topipe) = @_;
 
 =pod
@@ -509,7 +663,7 @@ Returns:
   return;
 } # PipeOutput
 
-sub StopPipe (;$) {
+sub StopPipe(;$) {
   my ($pipeToStop) = @_;
 
 =pod
@@ -549,11 +703,13 @@ Returns:
   $pipeToStop ||= $pipe;
 
   close $pipeToStop if $pipeToStop;
+
+  return;
 } # StopPipe
 
-sub PageOutput (@) {
+sub PageOutput(@) {
   my (@output) = @_;
-  
+
 =pod
 
 =head2 PageOutput (@ouput)
@@ -592,15 +748,15 @@ Returns:
     PipeOutputArray $ENV{PAGER}, @output;
   } else {
     print "$_\n"
-      foreach (@output);
+      for (@output);
   } # if
-  
+
   return;
 } # PageOutput
 
-sub RedirectOutput ($$@) {
+sub RedirectOutput($$@) {
   my ($to, $mode, @output) = @_;
-  
+
 =pod
 
 =head2 RedirectOutput ($to, @ouput)
@@ -645,15 +801,17 @@ Returns:
   open my $out, $mode, $to
     or croak "Unable to open $to for writing - $!";
 
-  foreach (@output) {
+  for (@output) {
     chomp;
     print $out "$_\n";
-  } # foreach
+  } # for
+
+  close $out;
   
-  return; 
+  return;
 } # RedirectOutput
 
-sub ReadFile ($) {
+sub ReadFile($) {
   my ($filename) = @_;
 
 =pod
@@ -696,32 +854,32 @@ Returns:
 
   open my $file, '<', $filename
     or error "Unable to open $filename ($!)", 1;
-    
+
   if (wantarray) {
     local $/ = "\n";
 
     my @lines = <$file>;
-  
+
     close $file
       or error "Unable to close $filename ($!)", 1;
-  
+
     my @cleansed_lines;
-  
-    foreach (@lines) {
+
+    for (@lines) {
       chomp;
       chop if /\r/;
       push @cleansed_lines, $_ if !/^#/; # Discard comment lines
-    } # foreach
-  
+    } # for
+
     return @cleansed_lines;
   } else {
-    local $/;
-    
+    local $/ = undef;
+
     return <$file>;
   } # if
 } # ReadFile
 
-sub Stats ($;$) {
+sub Stats($;$) {
   my ($total, $log) = @_;
 
 =pod
@@ -743,7 +901,8 @@ and the values of the hash will be the counters.
 
 =item $log
 
-Logger object to log stats to (if specified)
+Logger object to log stats to (if specified). Note: if the Logger object has 
+errors or warnings then they will be automatically included in the output.
 
 =back
 
@@ -764,8 +923,13 @@ Returns:
 =cut
 
   my $msg = "$FindBin::Script Run Statistics:";
-  
-  if (scalar keys %$total) {
+
+  if ($log and ref $log eq 'Logger') {
+    $total->{errors}   = $log->{errors};
+    $total->{warnings} = $log->{warnings};
+  } # if
+
+  if (keys %$total) {
     # Display statistics (if any)
     if ($log) {
       $log->msg ($msg);
@@ -773,21 +937,21 @@ Returns:
       display $msg; 
     } # if
 
-    foreach (sort keys %$total) {
-      $msg = $$total{$_} . "\t $_";
-      
+    for (sort keys %$total) {
+      $msg = $total->{$_} . "\t $_";
+
       if ($log) {
-        $log->msg ($$total{$_} . "\t $_");
+        $log->msg ($total->{$_} . "\t $_");
       } else {
         display $msg;
       } # if
-    } # foreach
+    } # for
   } # if
-  
+
   return;
 } # Stats
 
-sub Usage (;$) {
+sub Usage(;$) {
   my ($msg) = @_;
 
 =pod
@@ -824,14 +988,74 @@ Returns:
 
 =cut
 
-  display $msg
-    if $msg;
+  display $msg if $msg;
 
   system "perldoc $0";
 
   exit 1;
 } # Usage
 
+sub RequiredFields($$) {
+
+=pod
+
+=head2 RequiredFields($total, $log)
+
+Check if a list of fields are contained in a hash
+
+Parameters:
+
+=for html <blockquote>
+
+=over
+
+=item $fields
+
+Array reference to a list of field names that are required
+
+=item $rec
+
+Hash reference whose key values we are checking
+
+=back
+
+=for html </blockquote>
+
+Returns:
+
+=for html <blockquote>
+
+=over
+
+=item Message
+
+Returns either an empty string or a string naming the first missing required
+field
+
+=back
+
+=for html </blockquote>
+
+=cut
+
+  my ($fields, $rec) = @_;
+
+  for my $fieldname (@$fields) {
+    my $found = 0;
+
+    for (keys %$rec) {
+      if ($fieldname eq $_) {
+        $found = 1;
+        last;
+      } # if
+    } # for
+
+    return "$fieldname is required" unless $found;
+  } # for
+
+  return;
+} # RequiredFields
+
 END {
   StopPipe;
 } # END