Printing status displays in Perl scripts

While developing a Perl script this weekend to summarize Solaris zone usage, I wanted to display some type of status while my script did it’s thing. My friend Clay came up with a cool way to do this, and I thought I would share it here in case others needed to do something similar. Here is the code:

#!/usr/bin/perl -w

# Initialize the index.
my $index=0;

# The characters to use as spinners.
my @spinners = ("-", "\\", "|", "/");
my $totalspinners =  $#spinners + 1;

# Turn on autoflush.
$| = 1;

print "Factoring primes:  ";

while (1) {
    print "\\b$spinners[$index++ % $totalspinners]";
    sleep 1;
}

# If you were to exit the loop, turn off autoflush.
$| = 0;

There may be a better way to do this (adding an additional module isn’t an option), but I have yet to find it. Niiiiiice!

Building Perl modules for Solaris

This week I needed to install a few Perl modules on a Solaris 10 host. I didn’t want to download and install a fourth perl interpreter (Solaris 10 comes with 5.6.1, 5.8.3 and 5.8.4 for some reason), since Solaris 10 comes with a relatively recent version of Perl (5.8.4). To build the module in question (DBD::mysql), I downloaded the module from CPAN, verified that the MD5 checksum was correct, and used the following steps to compile the module:

$ perl Makefile.PL

$ make

$ make install

The ‘make Makefile.PL’ completed succesfully, but the make failed with the following errors:

$ make

cp lib/DBD/mysql.pm blib/lib/DBD/mysql.pm
cp lib/DBD/mysql/GetInfo.pm blib/lib/DBD/mysql/GetInfo.pm
cp lib/Mysql.pm blib/lib/Mysql.pm
cp lib/DBD/mysql/INSTALL.pod blib/lib/DBD/mysql/INSTALL.pod
cp lib/Mysql/Statement.pm blib/lib/Mysql/Statement.pm
cp lib/Bundle/DBD/mysql.pm blib/lib/Bundle/DBD/mysql.pm
cc -c  -I/usr/perl5/site_perl/5.8.4/i86pc-solaris-64int/auto/DBI -I/home/apps/mysql/mysql/include/mysql -DDBD_MYSQL_INSERT_ID_I
S_GOOD -g  -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_TS_ERRNO -xO3 -xspace -xildoff    -DVERSION=\"3.0006\"  -DXS_VERSION=\
"3.0006\" -KPIC "-I/usr/perl5/5.8.4/lib/i86pc-solaris-64int/CORE"   dbdimp.c
cc: unrecognized option `-KPIC'
cc: language ildoff not recognized
cc: dbdimp.c: linker input file unused because linking not done
/usr/bin/perl -p -e "s/~DRIVER~/mysql/g" /usr/perl5/site_perl/5.8.4/i86pc-solaris-64int/auto/DBI/Driver.xst > mysql.xsi
/usr/bin/perl /usr/perl5/5.8.4/lib/ExtUtils/xsubpp  -typemap /usr/perl5/5.8.4/lib/ExtUtils/typemap  mysql.xs > mysql.xsc && mv 
mysql.xsc mysql.c
Warning: duplicate function definition 'do' detected in mysql.xs, line 224
Warning: duplicate function definition 'rows' detected in mysql.xs, line 567
cc -c  -I/usr/perl5/site_perl/5.8.4/i86pc-solaris-64int/auto/DBI -I/home/apps/mysql/mysql/include/mysql -DDBD_MYSQL_INSERT_ID_I
S_GOOD -g  -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_TS_ERRNO -xO3 -xspace -xildoff    -DVERSION=\"3.0006\"  -DXS_VERSION=\
"3.0006\" -KPIC "-I/usr/perl5/5.8.4/lib/i86pc-solaris-64int/CORE"   mysql.c
cc: unrecognized option `-KPIC'
cc: language ildoff not recognized
cc: mysql.c: linker input file unused because linking not done
Running Mkbootstrap for DBD::mysql ()
chmod 644 mysql.bs
rm -f blib/arch/auto/DBD/mysql/mysql.so
LD_RUN_PATH="/home/apps/mysql/mysql/lib/mysql:/lib:/usr/lib" /usr/bin/perl myld cc  -G dbdimp.o  mysql.o  -o blib/arch/auto/DBD
/mysql/mysql.so   -L/home/apps/mysql/mysql/lib/mysql -lmysqlclient -lz -lposix4 -lcrypt -lgen -lsocket -lnsl -lm   
cc: dbdimp.o: No such file or directory
cc: mysql.o: No such file or directory
*** Error code 1
make: Fatal error: Command failed for target `blib/arch/auto/DBD/mysql/mysql.so'

Since I was building the module with gcc, the compiler and linker got a bit confused when they were passed Sun studio compiler flags (i.e., -KPIC in this example). There are two fixes for this problem. If you want to build a single module with gcc, you can edit the Makefile that was produced by ‘perl Makefile.PL’, and remove the “-KPIC” and “-xO3 -xspace -xildoff” values from the following variables:

$ egrep ‘(KPIC|O3)’ Makefile
CCCDLFLAGS = -KPIC
OPTIMIZE = -xO3 -xspace -xildoff

If you want to use gcc to build all Perl modules on a system, you can permanently* remove the Sun Studio compiler references by adjusting the “cccdlflags” and “optimize” variables in /usr/perl5/5.8.4/lib/sun4-solaris-64int/Config.pm:

$ egrep ‘(KPIC|O3)’ Config.pm
cccdlflags=’-KPIC’
optimize=’-xO3 -xspace -xildoff’

Since I don’t want to support two compiler packages, I decided to use option #2 since gcc comes on the Solaris installation CDs.

* If you edit Config.pm, you should be aware that Solaris Perl patches will overwrite this file.

Locating and printing modules with perldoc

I have recently started using perldoc to access the Perl documentation collection, and can’t believe it took me this long to do so. Perldoc has a slew of nifty options, including “-l” (print the path to a Perl module) to print the full path to a given module:

$ /usr/perl5/5.8.4/bin/perldoc -l Net::DNS

/usr/perl5/site_perl/5.8.4/sun4-solaris-64int/Net/DNS.pm

And “-m” (display the modules Perl code) to display the modules code and pod documentation (if it exists):

$ /usr/perl5/5.8.4/bin/perldoc -m Net::DNS

package Net::DNS;
#
# $Id: DNS.pm 468 2005-07-22 12:12:55Z olaf $
#
use strict;


BEGIN { 
    eval { require bytes; }
}

If you haven’t used perldoc to access the Perl documentation, I highly recommend doing so. You can get started with perldoc by running perldoc with the argument perldoc (e.g., $perldoc perldoc).

Perl module library madness!

While reading up on website performance monitoring applications last week, I came across the cricket HTTP-performance module. HTTP-performance allows you to graph the time it takes to connect to a website and to render a page. This sounded interesting, so I decided to download and install cricket. After reading through the beginners guide, I installed all of the required Perl modules, and proceeded to run the cricket compile script:

$ ./compile

Can't load '/usr/perl5/5.8.4/lib/i86pc-solaris-64int/auto/DB_File/DB_File.so' for module DB_File: ld.so.1: /usr/perl5/5.8.4/bin/perl: fatal: libdb-4.2.so: open failed: No such file or
directory at /usr/perl5/5.8.4/lib/i86pc-solaris-64int/XSLoader.pm line 68.
 at /usr/perl5/5.8.4/lib/i86pc-solaris-64int/DB_File.pm line 251
Compilation failed in require at /home/rmatteso/cricket-1.0.5/lib/ConfigTree/Node.pm line 25.
BEGIN failed--compilation aborted at /home/rmatteso/cricket-1.0.5/lib/ConfigTree/Node.pm line 25.
Compilation failed in require at ./compile line 39.
BEGIN failed--compilation aborted at ./compile line 39.I 

Ack!!!! — it looks like DB_File.so can’t find the BerkelyDB shared library:

$ ldd /usr/perl5/5.8.4/lib/i86pc-solaris-64int/auto/DB_File/DB_File.so

        libdb-4.2.so =>  (file not found)
        libc.so.1 =>     /lib/libc.so.1
        libm.so.2 =>     /lib/libm.so.2

Since I hate using LD_LIBRARY_PATH, I decided to adjust the link line to include a hard coded runtime path. To determine which variable to edit, I searched the Makefile until I came to the following line:

$ less Makefile

$(LD) $(LDDLFLAGS) $(LDFROM) $(OTHERLDFLAGS) -o $@ $(MYEXTLIB) $(PERL_ARCHIVE) \ $(LDLOADLIBS) $(PERL_ARCHIVE_AFTER) $(EXPORT_LIST) $(INST_DYNAMIC_FIX)

After locating this line, I added “-R/usr/sfw/lib:/usr/local/BerkeleyDB/lib” to the LDDLFLAGS variable and rebuilt the Perl module:

$ make

cc -c  -I/usr/local/BerkeleyDB/include  -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_TS_ERR
NO     -DVERSION="1.814"  -DXS_VERSION="1.814"  "-I/usr/perl5/5.8.4/lib/i86pc-solaris-64
int/CORE"  -D_NOT_CORE  -DmDB_Prefix_t=size_t -DmDB_Hash_t=u_int32_t version.c
cc -c  -I/usr/local/BerkeleyDB/include  -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_TS_ERR
NO     -DVERSION="1.814"  -DXS_VERSION="1.814"  "-I/usr/perl5/5.8.4/lib/i86pc-solaris-64
int/CORE"  -D_NOT_CORE  -DmDB_Prefix_t=size_t -DmDB_Hash_t=u_int32_t DB_File.c
Running Mkbootstrap for DB_File ()
chmod 644 DB_File.bs
rm -f blib/arch/auto/DB_File/DB_File.so
LD_RUN_PATH="/usr/local/BerkeleyDB/lib" 
cc  -G -R/usr/sfw/lib:/usr/local/BerkeleyDB/lib version.o DB_File.o  -o blib/arch/auto/DB_File/DB_File.so   -L/usr/local/BerkeleyDB/lib -ldb
chmod 755 blib/arch/auto/DB_File/DB_File.so
cp DB_File.bs blib/arch/auto/DB_File/DB_File.bs
chmod 644 blib/arch/auto/DB_File/DB_File.bs
Manifying blib/man3/DB_File.3

$ make install

Installing /usr/perl5/5.8.4/lib/i86pc-solaris-64int/auto/DB_File/DB_File.so
Files found in blib/arch: installing files in blib/lib into architecture dependent library tree
Installing /usr/perl5/5.8.4/man/man3/DB_File.3
Writing /usr/perl5/5.8.4/lib/i86pc-solaris-64int/auto/DB_File/.packlist
Appending installation info to /usr/perl5/5.8.4/lib/i86pc-solaris-64int/perllocal.pod

Everything seemed to work, but did the runpath get set correctly? A quick check revealed that it did:

$ dump -Lv /usr/perl5/5.8.4/lib/i86pc-solaris-64int/auto/DB_File/DB_File.so

/usr/perl5/5.8.4/lib/i86pc-solaris-64int/auto/DB_File/DB_File.so:

  **** DYNAMIC SECTION INFORMATION ****
.dynamic:
[INDEX] Tag         Value
[1]     NEEDED          libdb-4.2.so
[2]     NEEDED          libc.so.1
[3]     INIT            0xe030
[4]     FINI            0xe050
[5]     RUNPATH         /usr/sfw/lib:/usr/local/BerkeleyDB/lib
[6]     RPATH           /usr/sfw/lib:/usr/local/BerkeleyDB/lib

This was a fun experience, since it finally forced me to sit down and read through the ELF standard (great read if you are into that kind of stuff).

Resolving hostnames with Perl

I recently added name resolution support to ldap-stats.pl. This was super easy to do, and only required three lines of Perl code:

### Import the required modules
use Socket;

### Convert the IP address string to an Internet address
my $ipaddr = inet_aton($index);

### Resolve the IP address to a hostname
my $host = gethostbyaddr($ipaddr, AF_INET);

Once the conversion and resolution complete, the name will be available in the $host scalar variable. Giddie up!