#!/usr/bin/perl package LaBrea::Tarpit::Util; # # 5-17-02, michael@bizsystems.com # use strict; #use diagnostics; use vars qw($VERSION @ISA @EXPORT_OK); use AutoLoader 'AUTOLOAD'; use Fcntl qw(:DEFAULT :flock); $VERSION = do { my @r = (q$Revision: 0.06 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw ( cache_is_valid update_cache upd_cache daemon2_cache page_is_current share_open ex_open close_file http_date script_name reap_kids labrea_whoami ); # autoload declarations sub cache_is_valid; sub update_cache; sub upd_cache; sub daemon2_cache; sub share_open; sub ex_open; sub close_file; sub http_date; sub script_name; sub page_is_current; sub reap_kids; sub labrea_whoami; sub DESTROY {}; 1; __END__ =head1 NAME LaBrea::Tarpit::Util =head1 SYNOPSIS use LaBrea::Tarpit::Util qw( .... ); $rv = cache_is_valid(*HANDLE,\%look_n_feel,$short); $rv = update_cache(\%look_n_feel,\$html,\$short); ($modtime,$update)=daemon2_cache($cache,$src,$age); $modtime = page_is_current($cache_time,$page); $rv = share_open(*LOCK,*FILE,$filename,$nblock,$umask); $rv = ex_open(*LOCK,*FILE,$filename,$func,$nblock,$umask); $rv = close_file(*LOCK,*FILE) $time_string = http_date(time); $name = script_name($depth); $alive = reap_kids(\%kids); deprecated in this module =head1 DESCRIPTION - LaBrea::Tarpit::Util A collection of utility programs used by other modules and applications of LaBrea::Tarpit =over 2 =item $rv=cache_is_valid(*HANDLE,\%look_n_feel,$short); input: HANDLE \look_n_feel flag, true = check short cache false = standard returns: size of file, HANDLE open if cache valid false, cache requires update dispose: close HANDLE; =cut # returns true if cache ready, otherwise false # cache is not locked, it is updated atomicaly # # input: *HANDLE,\%look_n_feel, short_flag # returns: size of file, HANDLE open, if cache valid # false, cache requires update # sub cache_is_valid { my ($FH,$lnf,$f) = @_; return undef unless exists $lnf->{html_cache_file} && exists $lnf->{html_expire} && $lnf->{html_expire} > 0 && ($f = ($f) ? $lnf->{html_cache_file}.'.short' : $lnf->{html_cache_file}) && -e $f && -r $f; my ($size,$mtime) = (stat($f))[7,9]; return undef unless $mtime + $lnf->{html_expire} > time && open($FH,$f); return $size; } =item $rv = update_cache(\%look_n_feel,\$html,\$short); Write new cache file with contents of optional $html and/or $short The filename for the short cache is taken from $look_n_feel{html_cache_file} . '.short' returns: true on success false if failed =cut sub update_cache { my ($lnf,$htm,$sht) = @_; return undef unless exists $lnf->{html_cache_file}; @_ = ($lnf->{html_cache_file},'',$htm,$sht); goto &upd_cache; } =item $rv=upd_cache($filename,$pagename,$html,$short); This is the way B should have worked the first time, sigh.... Update a cache for a page and short report. Write new cache file with contents of optional $html and/or $short The filename for the short cache is taken from $filename . '.short' The page file name is taken from the $filename stub $filename.$pagename i.e. $filename = mycache $pagename = page2 eq => mycache.page2 returns: true on success false if failed =cut sub upd_cache { my($f,$pn,$htm,$sht) = @_; return undef unless $htm || $sht; # must want to do something $pn = ($pn) ? '.'.$pn : ''; # insert dot or make null local (*LOCK,*FH,*SH); return undef unless $f.$pn && # open new file non-blocking with exclusive lock ex_open(*LOCK,*FH,$f.$pn.'.tmp',-1,1); if ( $htm ) { # html present print FH $$htm; if ($sht && # short report present too open(SH,'>'.$f.$pn.'.short.tmp' )) { $_ = select SH; $| = 1; select $_; print SH $$sht; close SH; rename # atomic update $f.$pn.'.short.tmp', $f.'.short'; } close_file(*LOCK,*FH); # atomic update, return true on success rename # atomic update $f.$pn.'.tmp', $f.$pn; } elsif ( $sht ) { # unconditional 'else' print FH $$sht; close_file(*LOCK,*FH); rename # atomic update $f.$pn.'.tmp', $f.'.short'; } else { close_file(*LOCK,*FH); # should not get here return undef; } 1; } =item ($modtime,$update)=daemon2_cache($cache,$src,$age); Return the last modified time of the cache file, update cache if older than $age seconds. Set $@ on error; input: cache file, src file, or hash->{d_host} ->{d_port} ->{d_timeout} age in seconds timeout in seconds [default 60] returns: (mod time, 0), no update (mod time, 1), updated or () on failure =cut # $debug is the alarm time of the eval sub daemon2_cache { my ($cf,$sf,$age,$debug) = @_; require LaBrea::NetIO; import LaBrea::NetIO qw (daemon_handler); $age = 0 unless $age; local(*LOCK,*IN,*OUT); my $update = 0; my $time = time; my @return; my $timeout = (ref $sf eq 'HASH' && !exists $sf->{file} && $sf->{d_timeout}) ? $sf->{d_timeout} : 180; $timeout = $debug if $debug; local $SIG{ALRM} = sub { die "remote connect timeout"; }; eval { die 'missing output cache file' unless $cf; alarm $timeout; while (1) { my $cmt = (-e $cf) ? (stat($cf))[9] : 0; # cache last modified time unless ($cmt + $age < $time) { @return = ($cmt,$update); last; } my $nblock = ! $debug; # will block if debug if ( ex_open(*LOCK,*OUT,$cf.'.tmp',-1,$nblock) ) { # attempt non blocking open my $subref; unless ($subref = daemon_handler(*IN,$sf)) { @return = (); close_file(*LOCK,*OUT); last; } print IN "standard\n" if ref $sf eq 'HASH' && !exists $sf->{file}; while ($_ = &$subref) { print OUT $_; } close OUT; close IN; rename $cf.'.tmp', $cf; # atomic update close LOCK; $update = 1; } else { sleep 1; # another process is updating, wait } } # end while alarm 0; }; # end eval @return = () if $@; # oops return (wantarray) ? @return : $return[0]; } =item $modtime=page_is_current($cache_time,$page); Check to see if page is current input: cache time, path to page file returns: mtime of file or false on failure =cut sub page_is_current { my ($ct,$page) = @_; my $mtime; return (-e $page && $ct <= ($mtime =(stat($page))[9])) ? $mtime : 0; } =item $rv=share_open(*LOCK,*FILE,$filename,$nblock,$umask); Open a file for shared (read only) access. input: LOCK handle, FILE handle, filename, non-blocking, umask (default 0117) returns: true on success dispose by: close FILE; close LOCK; This is a READ ONLY OPERATION =cut sub share_open { my ($LOCK, $fh, $fn, $nblock, $umask) = @_; $nblock = ($nblock) ? LOCK_NB : 0; $umask = 0117 unless $umask; umask $umask; return undef unless sysopen $LOCK, $fn . '.flock', O_RDWR|O_CREAT|O_TRUNC; # die(&me . ': could not open file shared ' . $fn . '.flock'); unless (flock($LOCK,LOCK_SH|$nblock)) { close $LOCK; return undef; } return 1 if sysopen $fh, $fn, O_RDONLY|O_CREAT; # die(&me . ': could not open file shared ' . $fn); close $LOCK; return undef; } =item $rv=ex_open(*LOCK,*FILE,$filename,$func,$nblock,$umask); Open a file for exclusive access. input: LOCK handle, FILE handle, filename, function, non-blocking, umask (default 0117) returns: true on success function: 1 append false or [^\d] rw access -1 new/truncate rw access nblock: false blocking access true non-blocking access dispose by: close FILE; close LOCK; =cut sub ex_open { my ($LOCK, $fh, $fn, $func, $nblock, $umask) = @_; $nblock = ($nblock) ? LOCK_NB : 0; $umask = 0117 unless $umask; umask $umask; return undef unless sysopen $LOCK, $fn . '.flock', O_RDWR|O_CREAT|O_TRUNC; # die(&me . ': could not open file exclusive ' . $fn . '.flock'); unless (flock($LOCK,LOCK_EX|$nblock)) { close $LOCK; return undef; } if ( $func ) { if ( $func =~ /[^\d]/ || $func < 0 ) { #print STDERR "open NEW $fn\n"; $func = O_RDWR|O_CREAT|O_TRUNC; } else { #print STDERR "open APPEND $fn\n"; $func = O_RDWR|O_APPEND|O_CREAT; } } else { # use sysopen FILEHANDLE,FILENAME,MODE,PERMS #print STDERR "open RDRW $fn\n"; $func = O_RDWR|O_CREAT; } unless (sysopen $fh, $fn, $func) { close $LOCK; return undef; } my $tmp = select $fh; $| = 1; select $tmp; return 1; } =item $rv = close_file(*LOCK,*FILE); close file and lock file =cut sub close_file { my ($fl, $fh) = @_; close $fh; close $fl; # returns true on success } =item $time_string = http_date($time); Returns time string in HTTP date format, same as... Apache::Util::ht_time(time, "%a, %d %b %Y %T %Z",1)); i.e. Sat, 13 Apr 2002 17:36:42 GMT =cut sub http_date { my($time) = @_; my($sec,$min,$hr,$mday,$mon,$yr,$wday) = gmtime($time); return (qw(Sun Mon Tue Wed Thu Fri Sat))[$wday] . ', ' . # "%a, " sprintf("%02d ",$mday) . # "%d " (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$mon] . ' ' . # "%b " ($yr + 1900) . ' ' . # "%Y " sprintf("%02d:%02d:%02d ",$hr,$min,$sec) . # "%T " 'GMT'; # "%Z" } =item $name = script_name($depth); Returns the name of the calling script. (no path, just the name) input: depth of call stack (default = 0) returns: name of calling script =cut sub script_name { my $depth = $_[0] || 0; (caller($depth))[1] =~ m|([^/]+)$|; return $1;} =item $mod_ver = labrea_whoami; Returns a string of the form: $mod_ver = 'Tarpit 1.00 Util 0.04'; showing all the LaBrea modules loaded and their version numbers. The version numbers follow their respective module name, space separated. =cut sub labrea_whoami { @_ = sort grep ( /^LaBrea/ && /\.pm$/ && ($_ = $`),keys %INC); my $whoami = ''; foreach (@_) { $_ =~ s#/#::#g; $_ =~ /([^:]+)$/; $_ = '$'.$_.'::VERSION'; $whoami .= $1 . ' ' . (eval "$_") . ' '; } chop $whoami; return $whoami; } =item $alive = reap_kids(\%kids); Deprecated in this module, available for backwards compatibility only. See: LaBrea::NetIO::reap_kids =back =cut sub reap_kids { require LaBrea::NetIO; goto &LaBrea::NetIO::reap_kids; } =head1 EXPORT_OK cache_is_valid daemon2_cache close_file ex_open http_date labrea_whoami page_is_current script_name share_open update_cache upd_cache reap_kids =head1 COPYRIGHT Copyright 2002, Michael Robinton & BizSystems 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =head1 AUTHOR Michael Robinton, michael@bizsystems.com =head1 SEE ALSO perl(1), LaBrea::Tarpit(3), LaBrea::Codes(3), LaBrea::Tarpit::Report(3), LaBrea::Tarpit::Get(3), LaBrea::Tarpit::Util(3) =cut 1;