Gheek.net

March 30, 2011

Escape equences in Perl

Filed under: perl — lancevermilion @ 9:40 am

Here are some handy escape sequences for use in Perl when you are printing stuff to the screen.

print  "\e[H";              # Put the cursor on the first line
print  "\e[J";              # Clear from cursor to end of screen
print  "\e[H\e[J";          # Clear entire screen (just a combination of the above)
print  "\e[K";              # Clear to end of current line.
print  "\e[m";              # Turn off character attributes (eg. colors)
printf "\e[%dm", $N;        # Set color to $N (for values of 30-37, or 100-107)
printf "\e[%d;%dH", $R, $C; # Put cursor at row $R, column $C (good for "drawing")

This is a short excerpt from Perl.org – Perl Regular Expressions Reference

  \a          Alarm (beep)
   \e         Escape
   \f         Formfeed
   \n         Newline
   \r         Carriage return
   \t         Tab
   \037       Any octal ASCII value
   \x7f       Any hexadecimal ASCII value
   \x{263a}   A wide hexadecimal value
   \cx        Control-x
   \N{name}   A named character
   \N{U+263D} A Unicode character by hex ordinal
   \l         Lowercase next character
   \u         Titlecase next character
   \L         Lowercase until \E
   \U         Uppercase until \E
   \Q         Disable pattern metacharacters until \E
   \E         End modification
Advertisements

Monitor itpables activity in real time

Filed under: iptables, perl — lancevermilion @ 9:28 am

Here is a way to monitor iptables in realtime without needing an external program(s) or module(s). This is done using Perl and parsing the output from the iptables list utility.

A note from the original author:

To be effective, the xterm window needs to be at least as high as the output, else scrolling will ruin the visual effect. Also, the code may be iptables version-dependent, as it matches certain keywords for the formatting. Once started, it can be stopped with a ctl-C, which will restore some of the display settings

I can not take credit for writing. The original source for this is available at Perlmonks.org with the topic of Real-time Iptables Monitor and written by Dr. Mu.

The code:

#!/usr/bin/perl
use strict;
use warnings;
my @types = qw/nat mangle filter/;
$SIG{INT} = sub{print "\e[?25h\e[u"; exit}; 
print "\e[40;37m\e[2J\e[?25l";
while (1) {
  print "\e[0;0H";
  my %output = map {$_ => scalar `/sbin/iptables -t $_ -L -v -Z`} @types;
  foreach my $type (@types) {
    print "\e[01;34m------", uc($type), '-' x (73 - length($type)), "\n";
    $output{$type} =~ s/ pkts[^\n]*\n(\n|Zeroing)/$1/gs;
    foreach my $line (split /\n/, $output{$type}) {
      next if $line =~ m/^Zeroing/ || $line eq '';
      print $line =~ m/^\s*(\d+)/ || $line =~ m/(\d+) packets/
        ? ($1 > 0 
          ? ($line =~ m/DROP|DENY|REJECT/
            ? "\e[01;40;31m" 
            : "\e[01;40;32m")
          : "\e[00;40;37m")
        : "\e[00;40;33m";
      print "\e[K$line\e[01;40;37m\n"
    }
  }
  print "\e[s";
  sleep 1
}

Sample output:

------NAT----------------------------------------------------------------------
Chain PREROUTING (policy ACCEPT 173 packets, 22610 bytes)
Chain POSTROUTING (policy ACCEPT 107 packets, 7820 bytes)
Chain OUTPUT (policy ACCEPT 107 packets, 7820 bytes)
------MANGLE-------------------------------------------------------------------
Chain PREROUTING (policy ACCEPT 740 packets, 61407 bytes)
Chain INPUT (policy ACCEPT 709 packets, 58383 bytes)
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 959 packets, 457K bytes)
Chain POSTROUTING (policy ACCEPT 960 packets, 457K bytes)
------FILTER-------------------------------------------------------------------
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    8   722 RH-Firewall-1-INPUT  all  --  any    any     anywhere             anywhere            
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RH-Firewall-1-INPUT  all  --  any    any     anywhere             anywhere            
Chain OUTPUT (policy ACCEPT 31157 packets, 13M bytes)
Chain RH-Firewall-1-INPUT (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  lo     any     anywhere             anywhere            
    0     0 ACCEPT     icmp --  any    any     anywhere             anywhere            icmp any 
    0     0 ACCEPT     esp  --  any    any     anywhere             anywhere            
    0     0 ACCEPT     ah   --  any    any     anywhere             anywhere            
    2   394 ACCEPT     udp  --  any    any     anywhere             224.0.0.251         udp dpt:mdns 
    0     0 ACCEPT     udp  --  any    any     anywhere             anywhere            udp dpt:ipp 
    0     0 ACCEPT     tcp  --  any    any     anywhere             anywhere            tcp dpt:ipp 
    6   328 ACCEPT     all  --  any    any     anywhere             anywhere            state RELATED,ESTABLISHED 
    0     0 ACCEPT     tcp  --  any    any     anywhere             anywhere            state NEW tcp dpt:http 
    0     0 ACCEPT     tcp  --  any    any     anywhere             anywhere            state NEW tcp dpt:ssh 
    0     0 REJECT     all  --  any    any     anywhere             anywhere            reject-with icmp-host-prohibited 

March 29, 2011

How to fork a child in Perl

Filed under: perl — lancevermilion @ 6:13 pm

I had the need to have a timer running while I waited for a process to stop running.

The Code:

FORK:
{
  if ($pid=fork)
  {
    #Parent
    print "Parent PID: $$\n";
    print "Child  PID: $pid\n";
    print "\e[K($$) Attempting to stop Tomcat.\n";
    `/etc/init.d/tomcat5 stop`;
    $PROCPID = `/sbin/pidof java`;
    chomp($PROCPID);
    if ( $PROCPID )
    {
      print "\e[K($$) Tomcat COULD NOT be stopped.\n";
    }
    else
    {
      print "\e[K($$) Tomcat should be stopped.\n";
    }
    print "\e[K($$) Exiting.\n";
  }
  elsif ( defined($pid) )
  {
    #Child
      until ($stopped == 1)
      {
        $cnt++;
        sleep 1;
        $PROCPID = `/sbin/pidof java`;
        chomp($PROCPID);
        if ( $PROCPID )
        {
          print "\e[K($$) Been waiting $cnt seconds for Tomcat to exit.\r";
        }
        else
        {
          print "\e[K($$) Had to wait $cnt seconds for Tomcat to exit.\n";
          print "\e[K($$) Tomcat SHOULD be stopped.\n";
          $stopped = 1;
        }
      }
    print "\e[K($$) Exiting.\n";
    exit;
  }
  elsif ( $! == EAGAIN )
  {
    # EAGAIN is the supposedly recoverable fork error
    sleep 5;
    redo FORK;
  }
  else
  {
    die "Can't fork: $!\n";
  }
}
waitpid($pid, 0);

Sample output:
The timer counts from 1 second till the last second when the process is determined to be stopped.

Parent PID: 7048
Child  PID: 7049
(7048) Attempting to stop Tomcat.
(7048) Tomcat should be stopped.
(7048) Exiting.
(7049) Had to wait 32 seconds for Tomcat to exit.
(7049) Tomcat SHOULD be stopped.
(7049) Exiting.

Here is the code explained.
This is just a label so we can easily use the redo function.

FORK:

If we successfully fork then save the PID of the child to $pid.

  if ($pid=fork)

Test if the process is running. If it is not running then $PROCPID will not result in a test of TRUE.

    $PROCPID = `/sbin/pidof java`;
    chomp($PROCPID);
    if ( $PROCPID )

Everywhere you see \e[K it means to issue a Control + K. In other words on a Linux command line that will clear the line where the cursor is.

 print "\e[K($$) Tomcat COULD NOT be stopped.\n";

If a child PID does exist then give the child instructions for what you want it to do.

 elsif ( defined($pid) )

Here we are going to loop through until the $PROCPID has returned FALSE. This then means the process is is no longer running.

      until ($stopped == 1)
      {
        $cnt++;
        sleep 1;
        $PROCPID = `/sbin/pidof java`;
        chomp($PROCPID);
        if ( $PROCPID )
        {
          print "\e[K($$) Been waiting $cnt seconds for Tomcat to exit.\r";
        }
        else
        {
          print "\e[K($$) Had to wait $cnt seconds for Tomcat to exit.\n";
          print "\e[K($$) Tomcat SHOULD be stopped.\n";
          $stopped = 1;
        }
    print "\e[K($$) Exiting.\n";
    exit;

This simply means do not exist the script until all children have returned.

waitpid($pid, 0);

Usable Sample Script
Sample Code as a subroutine, using strict, and variable declaration that support start or stop options.

use strict;
sub frk
{
  my $proc = shift;
  my $psw = shift;
  my $action = shift;
  FORK:
  {
    if (my $pid=fork)
    {
      #Parent
      print "Parent PID: $$\n";
      print "Child  PID: $pid\n";
      print "\e[K($$) Attempting to $action $proc.\n";
      `$psw $action`;
      my $PROCPID = `/sbin/pidof $proc`;
      chomp($PROCPID);
      if ( $PROCPID )
      {
        print "\e[K($$) $proc COULD NOT $action.\n" if ( $action eq 'stop' );
        print "\e[K($$) $proc should be $action.\n" if ( $action eq 'start' );
      }
      else
      {
        print "\e[K($$) $proc should be $action.\n" if ( $action eq 'stop' );
        print "\e[K($$) $proc COULD NOT $action.\n" if ( $action eq 'start' );
      }
      print "\e[K($$) Exiting.\n";
      return $pid;
    }
    elsif ( defined($pid) )
    {
      #Child
        my $leaveloop = 0;
        my $cnt = 0;
        until ($leaveloop == 1)
        {
          $cnt++;
          sleep 1;
          my $PROCPID = `/sbin/pidof $proc`;
          chomp($PROCPID);
          if ( $PROCPID )
          {
            print "\e[K($$) Been waiting $cnt seconds for $proc to $action.\r" if ( $action eq 'stop' );
            print "\e[K($$) Had to wait $cnt seconds for $proc to $action.\n" if ( $action eq 'start' );
            print "\e[K($$) $proc SHOULD be $action.\n" if ( $action eq 'start' );
            $leaveloop = 1 if ( $action eq 'start' );
          }
          else
          {
            print "\e[K($$) Had to wait $cnt seconds for $proc to $action.\n" if ( $action eq 'stop' );
            print "\e[K($$) $proc SHOULD be $action.\n" if ( $action eq 'stop' );
            $leaveloop = 1 if ( $action eq 'stop' );
            print "\e[K($$) Been waiting $cnt seconds for $proc to $action.\r" if ( $action eq 'start' );
          }
        }
      print "\e[K($$) Exiting.\n";
      exit;
    }
    elsif ( $! == 'EAGAIN' )
    {
      # EAGAIN is the supposedly recoverable fork error
      sleep 5;
      redo FORK;
    }
    else
    {
      die "Can't fork: $!\n";
    }
  }
}

my $pid;
my $proc = 'java';
my $psw = '/etc/init.d/tomcat5';
my $action = $ARGV[0];
chomp($action);

$pid = frk($proc,$psw,$action);

waitpid($pid, 0);

Sample output for start

Parent PID: 11595
Child  PID: 11596
(11595) Attempting to start java.
(11595) java should be start.
(11595) Exiting.
(11596) Had to wait 1 seconds for java to start.
(11596) java SHOULD be start.
(11596) Exiting.

Sample output for stop

Parent PID: 12167
Child  PID: 12168
(12167) Attempting to stop java.
(12167) java should be stop.
(12167) Exiting.
(12168) Had to wait 32 seconds for java to stop.
(12168) java SHOULD be stop.
(12168) Exiting.

Simple Text Menu using Perl

Filed under: perl — lancevermilion @ 8:54 am

I had the desire to have a menu created from an array which I passed to a sub routine. So I searched around and found a nice one on perlmonks.org but it was based on array refs and creating a multi-dimensional array. This was way overkill for what I wanted and needed. Here is the link to that script as it is still a very nice script.
Simple Text menu
Here is a link to another script I found on there that was pretty good for a more permanent menu system.
Building a Perl Menu

How to create a simple menu using a Perl sub-routine.

sub menu
{
    # The array passed to this sub-routine must be passed
    # as a Array Reference.
    my $tmpRefArray = shift;
    # Convert the Array Reference back to an array
    my @m = @{$tmpRefArray};
    # If you want to insert a static title you can do it here.
    #unshift @m, 'Insert menu title here';
    # Remove all newlines throughout the entire array.
    chomp(@m);
    my $choice;
    while (1)
    {
      # Comment out this line if you don't want a title.
      # Otherwise the first element in the array must be the title.
      print "$m[0]\n";
      print map { "\t$_. $m[$_]\n" } (1..$#m);
      print "Choose (1-$#m)> ";
      chomp ($choice = <STDIN>);
      last if ( ($choice > 0) && ($choice <= $#m ));
      print "You chose '$choice'.  That is not a valid option.\n\n";
    }
    return "$m[$choice]\n";
}

You might notice how it says the array needs to be passed as an Array Reference.
I have provided some links below to help out with Reference/DeReference in Perl.
Dereferencing in Perl by perlmeme.org
perlreftut by perldoc.perl.org
Programming Perl 3rd Edition – Chapter 9. Data Structures
Perl Hash Howto by abatko @ cs.mcgill.ca

# A quick way to build an array for something important.
my @procsvcwrappers = `find /etc/init.d/*`;
# Insert a title as the first element in the Array (aka $procsvcwrappers[0])
unshift @procsvcwrappers, 'Choose a Process Service Wrapper:';
# Pass the array as an Array Reference 
# and
# Save the value returned to $procsvcwrapper.
my $procsvcwrapper = menu(\@procsvcwrappers);
print "You selected Process Service Wrapper \"$procsvcwrapper\".\n";

Here is a sample of the output.

Choose a Process Service Wrapper:
        1. /etc/init.d/acpid
        2. /etc/init.d/anacron
        3. /etc/init.d/apmd
        4. /etc/init.d/atd
        5. /etc/init.d/auditd
        6. /etc/init.d/autofs
        7. /etc/init.d/avahi-daemon
        8. /etc/init.d/avahi-dnsconfd
        9. /etc/init.d/bluetooth
        10. /etc/init.d/conman
        11. /etc/init.d/cpuspeed
        12. /etc/init.d/crond
        13. /etc/init.d/cups
        14. /etc/init.d/cups-config-daemon
<snip>
        78. /etc/init.d/wpa_supplicant
        79. /etc/init.d/xfs
        80. /etc/init.d/xinetd
        81. /etc/init.d/ypbind
        82. /etc/init.d/yum-updatesd
Choose (1-82)> 10
You selected Process Service Wrapper "/etc/init.d/conman".

March 24, 2011

How to find the rpm file used to install the original RPM.

Filed under: linux, perl, shell scripts — lancevermilion @ 5:22 pm

I have run into a problem over the years where my linux system(s) is not on a network accessible segment but rather in a completely firewalled (i.e. POC) DMZ and only available via sneaker net.

This poses issues when I need to upgrade the OS (i.e. CentOS 5.2 to CentOS 5.4) and make sure the same versions of 3rd party software gets reinstalled on the newer version of OS. To try and solve the 3rd party software issue I have kept pretty detailed logs (software versions on which systems) of what is installed but every now and again I forget to update my list of installed 3rd party software. So I use the following steps to figure out what software must be reinstalled.

The first step I have done is check to see what RPMs are installed and in the RPM database. This is nice to have but it doesn’t tell me the complete rpm that was used for the install.

rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}

The second step I compare the output from the RPM database against the log (/var/log/rpmpkgs*) that gets written to when each RPM gets installed. This can be a very long process especially if you have done upgrades to your system recently. I normally do this with a quick shell script. I decided it would be best to attack it with a Perl script since I am more fond of Perl scripting then shell scripting.

At some point in time the RPM Packages log gets updated. I haven’t managed to track down the timing for exactly when it gets updated. If anyone knows this please let me know.

#!/usr/bin/perl

use strict;

print "#" x 80 . "\n";
print "Starting RPM Checking Script.\n";
print "-" x 80 . "\n";
print "Please wait while the RPM database is read.\n";
my @rpmdb=`rpm -qa --queryformat '%{NAME},%{VERSION},%{RELEASE}\n'`;
print "Please wait while the RPM package log is read.\n";
my @rpmpkgs=`cat /var/log/rpmpkgs*`;
print "Done reading the RPM database and package logs.\n";
print "RPMDB entries: " . scalar(@rpmdb) ."\n";
print "RPMPKGS log entries: " . scalar(@rpmpkgs) ."\n";
print "#" x 80 . "\n";
foreach(@rpmdb)
{
  chomp;
  my $attempts = 1;
  my $match=0;
  my $tmprpmpkg="";
  my ($name, $ver, $rel) = split(/,/, $_);
  $_ =~ s/,/-/g;
  my $mod = "$name-$ver-$rel";
  my $mod1 = "$name-$ver";
  my $mod2 = "$name";
  for my $rpmpkg (@rpmpkgs)
  {
    chomp($rpmpkg);
     # use the octal value of + instead of + since it has meaning in regex
    $mod =~ s/\53/plus/g;
    $rpmpkg =~ s/\53/plus/g;
    if ($rpmpkg =~ /$mod.*/)
    {
      $match=1;
      $tmprpmpkg=$rpmpkg;
    }
  }
# Uncomment if you want to see the broader search attempts
#  print "\"$mod\" was not found, attempting to find \"$mod1\".\n" if $match < 1 ;
  if ( $match < 1 )
  {
    $attempts++;
    for my $rpmpkg (@rpmpkgs)
    {
      chomp($rpmpkg);
     # use the octal value of + instead of + since it has meaning in regex
      $mod1 =~ s/\53/plus/g;
      $rpmpkg =~ s/\53/plus/g;
      if ($rpmpkg =~ /$mod1.*/)
      {
        $match=1;
        $tmprpmpkg=$rpmpkg;
      }
    }
  }
# Uncomment if you want to see the broader search attempts
#  print "\"$mod1\" was not found, attempting to find \"$mod2\".\n" if $match < 1 ;
  if ( $match < 1 )
  {
    $attempts++;
    for my $rpmpkg (@rpmpkgs)
    {
      chomp($rpmpkg);
     # use the octal value of + instead of + since it has meaning in regex
      $mod2 =~ s/\53/plus/g;
      $rpmpkg =~ s/\53/plus/g;
      if ($rpmpkg =~ /$mod2.*/)
      {
        $match=1;
        $tmprpmpkg=$rpmpkg;
      }
    }
  }
  # Uncomment the line below if you ALSO want to see RPMs that are installed.
  # I have commented this line out initially because I first want to know what
  # RPMs do I need to chase down.
  #print "MATCH: $tmprpmpkg\n" if $match >= 1 && $attempts == 1;
  print "SIMILAR: $_ ~ $tmprpmpkg\n" if $match >= 1 && $attempts > 1;
  print "NOT_FOUND: \"$_\" is not found in \"/var/log/rpmpkgs\".\n" if $match < 1 ;
}
print "#" x 80 . "\n";

Example output. I just added gtkspell and updated tcpdump so there would be some good output. I also snipped almost every match from the list, but left a few so you get the idea of the output.

################################################################################
Starting RPM Checking Script.
--------------------------------------------------------------------------------
Please wait while the RPM database is read.
Please wait while the RPM package log is read.
Done reading the RPM database and package logs.
RPMDB entries: 780
RPMPKGS log entries: 779
################################################################################
MATCH: tzdata-2009k-1.el5.noarch.rpm
MATCH: desktop-backgrounds-basic-2.0-41.el5.centos.noarch.rpm
MATCH: rootfiles-8.1-1.1.1.noarch.rpm
MATCH: atk-1.12.2-1.fc6.i386.rpm
MATCH: audit-libs-1.7.13-2.el5.i386.rpm
<snip>
NOT_FOUND: "gtkspell-2.0.11-2.1" is not found in "/var/log/rpmpkgs".
SIMILAR: tcpdump-3.9.4-15.el5 ~ tcpdump-3.9.4-14.el5.i386.rpm
################################################################################

If you want to get almost the same output (but only for exact matches) you can use this output.

for i in `rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}\n'`;
do
  t=`grep "$i" /var/log/rpmpkgs*`;
  [[ "$?" != "0" ]] && echo "$i not found";
done

Output:

gtkspell-2.0.11-2.1 not found
tcpdump-3.9.4-15.el5 not found

March 11, 2011

Bash Scripting I18N/L10N aka Internationalization/Locale

Filed under: linux, shell scripts — lancevermilion @ 11:06 am

It was driving me crazy for the last two days why so many service wrappers (init.d) scripts would use $”..” instead of “…”. Well that is because they are allowing for I18N/L10N.

Example using I18N/L10N with a locale of Spanish

echo $"Hello Sir" # Hola señor

Instead of

echo "Hello Sir" # Hello Sir

Explanation I found that was good was here.
http://wiki.bash-hackers.org/syntax/quoting

I18N/L10N

A dollar-sign followed by a double-quoted string, for example

echo $"generating database..."

means I18N. If there is a translation available for that string, it is used instead of the given text. If not, or if the locale is C/POSIX, the dollar sign simply is ignored, which results in a normal double-quoted string. 

If the string was replaced (translated), the result is double-quoted.

In case you’re a C-programmer: The purpose of $"..." is the same as for gettext() or _().

For useful examples to localize your scripts, please see Appendix I of the Advanced Bash Scripting Guide.

Blog at WordPress.com.