#!/usr/local/bin/perl 'di '; 'ds 00 \\"'; 'ig 00 '; # # $Id: counter.pl,v 1.7 1995/08/10 16:42:21 drich Exp $ # Copyright (C) 1994 Daniel F. Rich (drich@sgi.com) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # counter - increment a counter for WWW # # This program is its own manual page, Install in man and bin. # # To install: # 1. Change $defcounterfile below to point to the default file containing # the count, or run using the -f option. This is the file that will be # used if the counter URL is not found in the config file, or of the # config file can't be opened. # Also, change $DocumentRoot to point to the base of your # document tree (this is only necessary if you are using this as # a CGI script) # # 2. This counter will now run as a CGI script, and if you wish to # do that you may ignore the rest of this step. # If you want to run the counter out of inetd, add the following lines # to the files specified (the port in the services file, 8987, will be # the port specified in the URL in step 5): # /etc/services: # counter 8987/tcp # WWW visitor counter # /etc/inetd.conf (or /usr/etc/inetd.conf) # counter stream tcp nowait www /www/bin/counter counter # The last three fields of the line above are the user to run the counter # under (*must* have write access to the file specified by # $defcounterfile), the path to the counter program, and the args to pass # to the program respectively. The first argument must be counter, and # any other arguments may be added on after that. # # 3. If you wish to have a counter for more than one document, you will # need to create a config file. By default, this is named # 'counter.conf' and is in the same directory as the counter script, # but you can override this with the -c option. # By default, the CGI version of the counter will use the image file # specified in the URL, and will append ".count" to it. # # 4. Test the script by running it from the command line. If everything # is correct, it should return an X11 bitmap containing the current # count. You will want to run it with the input redirected from # /dev/null, as it is expecting to see a document URL on standard input. # Ex. ./counter.pl < /dev/null # If you want to test it with a sample document name, just type something # like: # GET /counter.xbm HTTP/1.0 # after running the script. # To test the pixmap generation, replace the name counter.xbm with # counter.xpm in the above GET statement. # # 5. Now, to use this from your documents, just use a URL of the form: # http://your.www.host:8987/counter.xbm # where counter.xbm is replaced with the filenames in your config file. # If you only wish to count a single document, the URL above should # work fine. # For CGI usage, just use a URL of the form: # http://your.www.host/cgi-bin/counter.pl/counter.xbm?args # If you don't wish to specify any command line args, remove the "?args" # # # Written 3/8/94 by Dan Rich (drich@sgi.com) # Based on C code written by Frans van Hoesel (hoesel@chem.rug.nl) # # $Log: counter.pl,v $ # Revision 1.7 1995/08/10 16:42:21 drich # Fixes for perl4 and NCSA's httpd 1.4. # # Revision 1.6 1995/07/11 00:57:43 drich # Added support for xpm and cgi-bin execution. # # Revision 1.5 1995/01/09 19:05:37 drich # Fixed a bug in the version number code. # # Revision 1.4 1995/01/06 00:42:42 drich # Added -V flag, and documentation for the '-f' option. # Also cleaned up the code so it runs with perl5. # # Revision 1.3 1994/10/25 13:31:38 drich # Fixed a socket bug, and corrected the URL where the program is available. # # Revision 1.2 1994/10/18 20:13:29 drich # Added daemon support and additional command line flags. # Also added new documentation (man page!!) # # Revision 1.1 1994/04/18 14:48:00 drich # Initial revision # sub usage { print STDERR "usage: $0 [-dziVP] [-F fg] [-B bg] [-p port] [-f defaultcountfile | -c configfile] [-C interval] [-D level]\n"; print STDERR "\tthe -F and -B options are only valid for xpm generation\n"; exit 1; } sub debug_print { local($level,$message) = @_; return if (!defined($debug)); return if ($level > $debug); $message =~ s/\n(.)/\nDEBUG:$level:$1/g if ($message =~ /\n./); if ($nocr && ($olevel == $level)) { print STDERR "$message" if ($level <= $debug); } else { print STDERR "\n" if ($nocr); print STDERR "DEBUG:$level:$message"; } $olevel = $level; $nocr = $message !~ /\n/; } # Read in configuration file sub read_config { local($sig) = @_; if ($sig eq 'HUP') { &debug_print(1,"Caught a SIG $sig--reloading config file\n"); &syslog('info',"Caught a SIG $sig--reloading config file") && defined($syslog); } @config=(); if ( -f "$configfile" ) { &debug_print(1, "Reading config file: $configfile..."); open(CONFIG,"<$configfile") || &syslog('err',"err opening $configfile: $!"); while () { chop; s/#.*$//; next if (!$_); ($url,$counter,$options) = split(/\s+/,$_,3); $config{$url} = $counter; $options{$url} = $options; &debug_print(2," $url --> $counter"); &debug_print(2," w/zeros") if ($options{$url} =~ /zero/); &debug_print(2," inverse") if ($options{$url} =~ /inverse/); &debug_print(2,"\n"); } close(CONFIG); &debug_print(1, "done.\n"); } $SIG{'HUP'} = 'read_config'; } sub give_up { print STDERR "$_[0]\n"; &syslog('warning',"$_[0]\n") if ($syslog); exit 1; } sub main { $DocumentRoot = $ENV{'HOME'} . '/html'; $defcounterfile = "/www/counter.txt"; # Default file containing the count $defport = 8987; # Default port to run on (standalone) ($dirname,$basename) = ($0 =~ /^(.*)\/([^\/]*)$/); if (! $basename) { $dirname="."; $basename=$0; } $configfile = "$dirname/counter.conf"; #push(@INC,'/usr/local/lib/perl'); require('getopts.pl') || &give_up("$0: can\'t do getopts.pl: $@"); #require('sys/file.ph') || &give_up("$0: can\'t do sys/file.ph: $@"); #require('sys/errno.ph') || &give_up("$0: can\'t do sys/errno.ph: $@"); undef($syslog); # True if we can use syslog #require('syslog.pl') && ($syslog = 1); # If running as a cgi-bin script, args are in query_string if ($ENV{'QUERY_STRING'}) { ($args = $ENV{'QUERY_STRING'}) =~ s/\+/ -/g; $args =~ s/^\s+//; @ARGV=split(/\s+/,$args); } if ( ! &Getopts('B:c:C:dD:F:f:iPp:Vz') ) { # &usage; exit 1; } # If we are a CGI script, don't allow use of -f, -d, or -p # and set defcounterfile to PATH_INFO.count if ($ENV{'PATH_INFO'}) { undef($opt_d); undef($opt_p); $opt_f = "${DocumentRoot}$ENV{'PATH_INFO'}.count"; } $checkpoint = $opt_C; # Checkpoint interval $daemon = $opt_d; # Run as a daemon? (standalone) $debug = $opt_D; # Counter file name is specified by -f or the config file (-c) $defcounterfile = $opt_f if ($opt_f); $definverse = $opt_i; # Invert bitmap if true $port = $opt_p ? $opt_p : $defport; # Port to run on (standalone) $defleading_zero = $opt_z; # Print leading zeros on count $genxpm = $opt_P; # Generate an XPM file? $defxpmfg = $opt_F ? $opt_F : "Blue"; # Default xpm fg is Blue $defxpmbg = $opt_B ? $opt_B : "None"; # Default xpm bg is transparent # If we are running as a cgi-bin and debug is set, redirect stderr if ($ENV{'PATH_INFO'} && $debug) { close(STDERR); open(STDERR,">&STDOUT"); print "Content-type: text/plain\n\n"; } # Print version info $version="\$Revision: 1.7 $_"; $version =~ s/\$Revision: 1.7 $/\1/; # Strip the RCS version if ($debug || $opt_V) { print STDERR "WWW counter, version $version\n\n"; if (!$debug) { print STDERR "\$RCSfile: counter.pl,v $ \$Revision: 1.7 $ \$Date: 1995/08/10 16:42:21 $ \n\n"; print STDERR "Copyright (C) 1994,1995 Daniel F. Rich (drich\@corp.sgi.com)\n\n"; print STDERR "This program is free software; you can redistribute it and/or modify\n"; print STDERR "it under the terms of the GNU General Public License as published by\n"; print STDERR "the Free Software Foundation; either version 2, or (at your option)\n"; print STDERR "any later version.\n\n"; print STDERR "This program is distributed in the hope that it will be useful,\n"; print STDERR "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"; print STDERR "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"; print STDERR "GNU General Public License for more details.\n"; } } exit if ($opt_V); # Check for valid arguments if ($checkpoint && ($checkpoint !~ /^\d+$/)) { &usage; } if ($port && ($port !~ /^\d+$/)) { &usage; } &debug_print(1, "We have syslog!!\n") if ($syslog); #&openlog($basename,'cons,pid','daemon'); &read_config(0); # Start up the daemon if needed if ($daemon) { if (!$debug) { # Fork off, so we are a true deamon! unless (fork) { unless (fork) { sleep 1 until getppid == 1; if ( -e "/dev/console" ) { close(STDERR); open(STDERR,"> /dev/console"); } } else { exit 0; } } else { wait; exit 0; } } close(STDIN); # We will map this to our socket close(STDOUT); require('sys/socket.ph') || &give_up("$0: can\'t do sys/socket.ph: $@"); $sockaddr = 'S n a4 x8'; $proto = (getprotobyname('tcp'))[2]; $this = pack($sockaddr, &AF_INET, $port, "\0\0\0\0"); select(NS); $| = 1; select(stdout); socket(S, &AF_INET, &SOCK_STREAM, $proto) || &give_up("socket: $!"); bind(S,$this) || &give_up("bind: $!"); listen(S,5) || &give_up("connect: $!"); select(S); $| = 1; select(stdout); &syslog('notice',"daemon started on port $port"); } # bitmap for each digit @digits = (0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x66, 0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00, 0x00,0x00,0x00,0x30,0x38,0x30,0x30,0x30, 0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00, 0x00,0x00,0x00,0x3c,0x66,0x60,0x60,0x30, 0x18,0x0c,0x06,0x06,0x7e,0x00,0x00,0x00, 0x00,0x00,0x00,0x3c,0x66,0x60,0x60,0x38, 0x60,0x60,0x60,0x66,0x3c,0x00,0x00,0x00, 0x00,0x00,0x00,0x30,0x30,0x38,0x38,0x34, 0x34,0x32,0x7e,0x30,0x78,0x00,0x00,0x00, 0x00,0x00,0x00,0x7e,0x06,0x06,0x06,0x3e, 0x60,0x60,0x60,0x66,0x3c,0x00,0x00,0x00, 0x00,0x00,0x00,0x38,0x0c,0x06,0x06,0x3e, 0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00, 0x00,0x00,0x00,0x7e,0x66,0x60,0x60,0x30, 0x30,0x18,0x18,0x0c,0x0c,0x00,0x00,0x00, 0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x3c, 0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00, 0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x66, 0x7c,0x60,0x60,0x30,0x1c,0x00,0x00,0x00 ); # Xpm bitmap for each digit (this looks better in a really wide window) @xpmdigits = (" "," "," "," "," "," "," "," "," "," ", " "," "," "," "," "," "," "," "," "," ", " "," "," "," "," "," "," "," "," "," ", " .... "," .. "," .... "," .... "," .. "," ...... "," ... "," ...... "," .... "," .... ", " .. .. "," ... "," .. .. "," .. .. "," .. "," .. "," .. "," .. .. "," .. .. "," .. .. ", " .. .. "," .. "," .. "," .. "," ... "," .. "," .. "," .. "," .. .. "," .. .. ", " .. .. "," .. "," .. "," .. "," ... "," .. "," .. "," .. "," .. .. "," .. .. ", " .. .. "," .. "," .. "," ... "," . .. "," ..... "," ..... "," .. "," .... "," .. .. ", " .. .. "," .. "," .. "," .. "," . .. "," .. "," .. .. "," .. "," .. .. "," ..... ", " .. .. "," .. "," .. "," .. "," . .. "," .. "," .. .. "," .. "," .. .. "," .. ", " .. .. "," .. "," .. "," .. "," ...... "," .. "," .. .. "," .. "," .. .. "," .. ", " .. .. "," .. "," .. "," .. .. "," .. "," .. .. "," .. .. "," .. "," .. .. "," .. ", " .... "," .. "," ...... "," .... "," .... "," .... "," .... "," .. "," .... "," ... ", " "," "," "," "," "," "," "," "," "," ", " "," "," "," "," "," "," "," "," "," ", " "," "," "," "," "," "," "," "," "," "); $notdone = 1; while ($notdone) { # Setup loop for daemon # Wait for connection... if ($daemon) { &debug_print(1,"Waiting for connection...\n"); while (!accept(NS,S)) { # Restart if EINTR &give_up("accept: $!") if ($! != &EINTR); } open(STDOUT,">&NS"); # Map STDIN and STDOUT to the socket open(STDIN,"<&NS"); } # Get URL and set counterfile if ($ENV{'SERVER_SOFTWARE'} !~ /^NCSA/) { # NCSA doesn't have STDIN $line=; $line = "GET $ENV{'PATH_INFO'} HTTP/1.0\n" if (!$line); } else { $line = "GET $ENV{'PATH_INFO'} HTTP/1.0\n"; } ($counterurl) = ($line =~ /GET (.*) HTTP/); $genxpm = ($counterurl =~ /\.xpm$/); # Generate an xpm if in URL # Check for Netscape 1.1, and if so, turn of xpm if ($ENV{'HTTP_USER_AGENT'} =~ /Mozilla\/1.1/) { undef($genxpm); } $counterfile = defined($config{$counterurl}) ? $config{$counterurl} : $defcounterfile; $leading_zero = defined($options{$counterurl}) ? ($options{$counterurl} =~ /zero/) : $defleading_zero; $inverse = defined $options{$counterurl} ? ($options{$counterurl} =~ /inverse/) : $definverse; $xpmfg = defined $options{$counterurl} ? ($options{$counterurl} =~ /fg=(\S+)/) : $defxpmfg; $xpmbg = defined $options{$counterurl} ? ($options{$counterurl} =~ /bg=(\S+)/) : $defxpmbg; &debug_print(1,"url: $counterurl"); &debug_print(1," w/zero") if ($leading_zero); &debug_print(1,"\n"); # Read and increment the counter file # while (-f "${counterfile}.lock") { sleep 1 } while (-f "${counterfile}.lock") { sleep 1 ; ++$count_id; if($count_id > 10){last;} } open(LOCK,">${counterfile}.lock"); close(LOCK); # If counter file exists use it, otherwise create a new one if (-f "$counterfile" ) { open(COUNTERFILE,"<$counterfile") || &give_up("$0: can\'t open $counterfile: $!\n"); # flock(COUNTERFILE,&LOCK_EX) || # &give_up("$0: can\'t lock $counterfile: $!\n"); $text = ; close(COUNTERFILE); } else { $text = '0'; } $text++; $len = length($text) > 7 ? length($text) : 7; open(COUNTERFILE,">$counterfile") || &give_up("$0: can\'t open $counterfile: $!\n"); if ($leading_zero) { printf COUNTERFILE "%0${len}u\n",$text; $text = sprintf("%0${len}u",$text); } else { printf COUNTERFILE "%u\n",$text; $len = length($text); $text = sprintf("%${len}u",$text); } &debug_print(1,"count: $text\n"); # flock(COUNTERFILE,&LOCK_UN); close(COUNTERFILE); if ($checkpoint && ($text % $checkpoint == 0)) { if ($syslog) { &syslog('info',"$counterurl checkpoint: $text"); } else { open(CHECKFILE,">${counterfile}.check") || warn "$0: can\'t open ${counterfile}.check: $!\n"; print CHECKFILE "%0${len}u\n",$text; close(CHECKFILE); } } unlink("${counterfile}.lock"); # Generate an X11 bitmap or pixmap on STDOUT if ($genxpm) { $output = sprintf("/* XPM */\n"); $output .= sprintf("static char * paint_recol_bad[] = {\n"); $output .= sprintf("\"%d %d 2 1\",\n",$len*8,16); if ($inverse) { $output .= sprintf("\" \tc $xpmfg\",\n"); $output .= sprintf("\".\ts None c $xpmbg\",\n"); } else { $output .= sprintf("\" \ts None c $xpmbg\",\n"); $output .= sprintf("\".\tc $xpmfg\",\n"); } } else { $output = sprintf("#define count_width %d\n#define count_height 16\n", $len*8); $output .= sprintf("static char count_bits[] = {\n"); } for ($y=0; $y < 16; $y++) { $output .= "\"" if ($genxpm); for ($x=0; $x < $len; $x++) { if ($genxpm) { $d = substr($text,$x,1) - '0'; $output .= sprintf("%s",$xpmdigits[($y * 10) + $d]); } else { $d = substr($text,$x,1) - '0'; $output .= '0x'; if ($inverse) { $output .= sprintf("%1x",(($digits[($d * 16) + $y] >> 4) ^ 0xf) & 0xf); $output .= sprintf("%1x",($digits[($d * 16) + $y] ^ 0xf) & 0xf); } else { $output .= sprintf("%1x",($digits[($d * 16) + $y] >> 4) & 0xf); $output .= sprintf("%1x",$digits[($d * 16) + $y] & 0xf); } if ($x < $len-1) { $output .= ','; } } } $output .= "\"" if ($genxpm); if ($y==15) { $output .= '};'; } else { $output .= ','; } $output .= "\n"; } printf "Content-length: %d\n",length($output); if ($genxpm) { print "Content-type: image/x-xpixmap\n\n"; } else { print "Content-type: image/x-xbitmap\n\n"; } print "$output"; if ($daemon) { close(STDOUT); close(STDIN); close(NS); } else { undef($notdone); } } } eval {&main;}; if ( $@ ne '' ) { print "Content-type: text/plain\n\n"; print $@; } ################### BEGIN PERL/TROFF TRANSITION .00 ; 'di .nr nl 0-1 .nr % 0 '; __END__ ############## END PERL/TROFF TRANSITION .TH counter 1 "October 14, 1994" .SH NAME counter \- display a user counter for a World Wide Web document .SH SYNOPSIS .B counter [ .I -dizVP ] [ .I -C interval ] [ .I -D debuglevel ] [ .I -F|B color ] [ .I -f defaultcounterfile | .I -c configfile ] [ .I -i ] [ .I -p portnum ] .SH DESCRIPTION .B counter will display a count of users accessing a World Wide Web document. .LP The output of .B counter contains an x-bitmap or x-pixmap of the current value stored in the counterfile. This can be used to count the number of accesses to a WWW document, by using the WWW browser's caching of image files (and therefore they won't request the image a second time). .LP This can either be run as a daemon, out of inetd, or as a CGI script. When run as a CGI, command line arguments are specified using the URL query syntax, and must use the "+" character instead of "-". .LP .SH OPTIONS .TP .BI \-c file overrides the default config file \fIcounter.conf\fP. The config file specifies what file should be used to store the count for each URL that is to be counted. It contains one line for each counter, and each line is of the form: .nf .in +5 .B URLfile counterfile options .in -5 .fi \fBURLfile\fP contains just the filename from the counter bitmap URL (with a leading slash). The only \fBoptions\fP currently supported are \fIzero\fP and \fIinverse\fP, and for xpm files, \fIfg=value\fP and \fIbg=value\fP to set the foreground and background colors (setting a color to \fI"None"\fP will cause it to appear as transparent). Ex. .nf .in +5 .B /counter.xbm /www/counter.txt zero inverse .B /counter-map.xpm /www/counter-map.txt zero fg=red bg=blue .B /counter-home.xbm /www/counter-home.txt .in -5 .fi .TP .BI \-C checkpoint causes \fBcounter\fP to checkpoint the count at the interval specified. This will be done using syslog if available, otherwise, a counterfile is created with \fI.count\fP appended to the name. .TP .B \-d will cause counter to detach itself from the controlling terminal and run as a network daemon on the port specified by the \fI-p\fP flag. This will also cause any error messages to be output to the console. If the \fI-D\fP option is also used, the program will not detach itself from the terminal, but will instead output the debugging information to stderr. This option is not valid for CGI usage. .TP .BI \-D level enables debugging at the level specified. .TP .B \-f file specifies the counter file to use if either an unknown bitmap is specified, or if the config file cannot be opened. This option is not valid for CGI usage. From CGI the default counterfile is created by adding ".count" the filename specfied in the URL. .TP .B \-i causes \fBcounter\fP to output an inverted bitmap (white numbers on a black background). This affects only the default counter if a config file is used. .TP .BI \-p port specifies the port number for the daemon to run on. This option is not valid for CGI usage. .TP .B \-P will instruct counter to generate an xpm file (this is automatic if the URL contains a filename ending in .xpm. The \fI-F\fP and \fI-B\fP options are used to set the foreground and background colors respectively. The image will automatically be forced to an xbm if the remote user is running Netscape 1.1, as it doesn't support xpm properly. .TP .B \-V will print the version number of the counter script and exit. .TP .B \-z forces \fBcounter\fP to print leading zeros on the counter. This affects only the default counter if a config file is used. .SH INSTALLATION First you need to decide if you want to run the counter from inetd, or as a standalone daemon. You can only run from inetd if you have the ability to edit system files and restart the inetd daemon. However, if you are going to run the counter with a config file, it will run faster if you run it as a standalone daemon (from inetd, it must reread the config file every time the counter is accessed). .LP If you are going to run it out of inetd, you need to change two of your network configuration files. You will need to add the following line to /etc/services (the 8987 is the port number that will be specified in the URL that accesses the counter): .nf .in +5 .B counter 8987/tcp .in -5 .fi and add the following to /etc/inetd.conf: .nf .in +5 .B counter stream tcp nowait www /www/bin/counter counter .in -5 .fi \fIWww\fP must be changed to a user who has write access to the counter files. The \fI/www/bin/counter\fP path needs to be changed to where you installed this program, and you can add any of the options above to the end of the line as they will be passed to the \fBcounter\fP script (NOTE: you cannot use \fI-p\fP or \fI-d\fP if running from inetd). .LP If you wish to use the counter in more than one document, you either need to run multiple copies of this script on different ports, or use a config file as specified above. By default, this file is named \fIcounter.conf\fP, and is located in the same directory as \fBcounter\fP. This can be overridden with the \fI-c\fP option. .LP At this point, you are ready to test the script by running it from the command line. You will need to redirect STDIN to /dev/null, as that is where \fBcounter\fP expects to see the document URL. It should return an X11 bitmap of the current value in the default counter file. If you want to test \fBcounter\fP with one of the URLs in the config file, you need to pass the following as input to \fBcounter\fP (it will pause after you start it): .nf .in +5 GET /counter.xbm HTTP/1.0 .in -5 .fi replacing /counter.xbm with the file you want to test from \fIcounter.conf\fP. .LP To get this count into your documents, use a URL of the form: .in +5 .I http://your.server.host:8987/counter.xbm .in -5 where \fI8987\fP is the port \fBcounter\fP is running on, and counter.xbm is one of the names in your config file. .LP To use as a CGI program, you would use a URL of the form: .in +5 .I http://your.server.host/cgi-bin/counter.pl/counter.xbm .in -5 or with arguments: .in +5 .I http://your.server.host/cgi-bin/counter.pl/dirname/counter.xpm/+z+Fred .in -5 (which would create a counter with leading zeros and a red forground). .SH FILES .TP .B counter.txt default file which holds the user count .TP .B counter.txt.lock default lock file used to enforce file locking .TP .B counter.conf default counter configuration file .TP .B /etc/inted.conf .TP .B /etc/services .SH SEE ALSO \fBsyslog\fP(3), \fBperl\fP. .SH BUGS .LP The one known problem with \fBcounter\fP is that it will occasionally zero the count file under heavy loads. I believe this is caused by a race condition with the lock files, but have never been able to track it down. That is the main reason the checkpoint option was added to this version of \fBcounter\fP. .SH CREDITS Frans van Hoesel (hoesel@chem.rug.nl) originaly wrote a simple C counter that I ported to perl. While his program was a perfectly good solution to the counting problem, I wanted a version written in perl that would be easier for me to maintain and modify. .LP Since then, it has had a slight case of creeping-featurism, and now contains several command line flags, a config file, and a man page. .SH AVAILABILITY The latest version of .B counter is available through SGI's Silicon Surf WWW site at .IR http://www.sgi.com/counter.html. Just select the counter for a description of the program, and an option to download the latest version of this script. .SH AUTHOR .I Daniel Rich\ \ \ \