Initial version

This commit is contained in:
djk 1998-06-15 23:20:05 +00:00
parent 11143767eb
commit 2e16209416
10 changed files with 1009 additions and 0 deletions

62
perl/DXConnect.pm Normal file
View File

@ -0,0 +1,62 @@
#
# module to manage connection lists & data
#
package DXConnect;
require Exporter;
@ISA = qw(Exporter);
%connects = undef;
# create a new connection object [$obj = Connect->new($call, $msg_conn_obj, $user_obj)]
sub new
{
my ($pkg, $call, $conn, $user) = @_;
my $self = {};
die "trying to create a duplicate Connect for call $call\n" if $connects{$call};
$self->{call} = $call;
$self->{conn} = $conn;
$self->{user} = $user;
$self->{t} = time;
$self->{state} = 0;
bless $self, $pkg;
return $connects{$call} = $self;
}
# obtain a connection object by callsign [$obj = Connect->get($call)]
sub get
{
my ($pkg, $call) = @_;
return $connect{$call};
}
# obtain all the connection objects
sub get_all
{
my ($pkg) = @_;
return values(%connects);
}
# obtain a connection object by searching for its connection reference
sub get_by_cnum
{
my ($pkg, $conn) = @_;
my $self;
foreach $self (values(%connects)) {
return $self if ($self->{conn} == $conn);
}
return undef;
}
# get rid of a connection object [$obj->del()]
sub del
{
my $self = shift;
delete $connects{$self->{call}};
}
1;
__END__;

98
perl/DXUser.pm Normal file
View File

@ -0,0 +1,98 @@
#
# DX cluster user routines
#
package DXUser;
require Exporter;
@ISA = qw(Exporter);
use MLDBM;
use Fcntl;
%u = undef;
$dbm = undef;
$filename = undef;
#
# initialise the system
#
sub init
{
my ($pkg, $fn) = @_;
die "need a filename in User\n" if !$fn;
$dbm = tie %u, MLDBM, $fn, O_CREAT|O_RDWR, 0666 or die "can't open user file: $fn ($!)\n";
$filename = $fn;
}
#
# close the system
#
sub finish
{
$dbm = undef;
untie %u;
}
#
# new - create a new user
#
sub new
{
my ($call) = @_;
die "can't create existing call $call in User\n!" if $u{$call};
my $self = {};
$self->{call} = $call;
bless $self;
$u{call} = $self;
}
#
# get - get an existing user
#
sub get
{
my ($call) = @_;
return $u{$call};
}
#
# put - put a user
#
sub put
{
my $self = shift;
my $call = $self->{call};
$u{$call} = $self;
}
#
# del - delete a user
#
sub del
{
my $self = shift;
my $call = $self->{call};
delete $u{$call};
}
#
# close - close down a user
#
sub close
{
my $self = shift;
$self->{lastin} = time;
$self->put();
}
1;
__END__

23
perl/DXUtil.pm Normal file
View File

@ -0,0 +1,23 @@
#
# various utilities which are exported globally
#
package DXUtil;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(atime
);
@month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
sub atime
{
my ($sec,$min,$hour,$mday,$mon,$year) = gmtime(time);
$year += 1900;
my $buf = sprintf "%02d%s%04d\@%02d:%02d:%02d", $mday, $month[$mon], $year, $hour, $min, $sec;
return $buf;
}

71
perl/DXVars.pm Normal file
View File

@ -0,0 +1,71 @@
#
# The system variables - those indicated will need to be changed to suit your
# circumstances (and callsign)
#
package main;
require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw($mycall $myname $mynormalcall $mylatitude $mylongtitude $mylocator
$myqth $myemail $myprot
$clusterport $clusteraddr $debugfn
$def_hopcount $root $data $system $cmd
$userfn
);
# this really does need to change for your system!!!!
$mycall = "GB7TLH";
# your name
$myname = "Dirk";
# Your 'normal' callsign
$mynormalcall = "G1TLH";
# Your latitude (+)ve = North (-)ve = South in degrees and decimal degrees
$mylatitude = +52.68584579;
# Your Longtitude (+)ve = East, (-)ve = West in degrees and decimal degrees
$mylongtitude = +0.94518260;
# Your locator (yes I know I can calculate it - eventually)
$mylocator = "JO02LQ";
# Your QTH (roughly)
$myqth = "East Dereham, Norfolk";
# Your e-mail address
$myemail = "djk@tobit.co.uk";
# the tcp address of the cluster and so does this !!!
$clusteraddr = "dirk1.tobit.co.uk";
# the port number of the cluster (just leave this, unless it REALLY matters to you)
$clusterport = 27754;
# cluster debug file
$debugfn = "/tmp/debug_cluster";
# the version of DX cluster (tm) software I am masquerading as
$myprot = "5447";
# default hopcount to use - note this will override any incoming hop counts, if they are greater
$def_hopcount = 7;
# root of directory tree for this system
$root = "/spider";
# data files live in
$data = "$root/data";
# system files live in
$system = "$root/sys";
# command files live in
$cmd = "$root/cmd";
# where the user data lives
$userfn = "$data/users";

329
perl/Msg.pm Normal file
View File

@ -0,0 +1,329 @@
#
# This has been taken from the 'Advanced Perl Programming' book by Sriram Srinivasan
#
# I am presuming that the code is distributed on the same basis as perl itself.
#
# I have modified it to suit my devious purposes (Dirk Koopman G1TLH)
#
package Msg;
require Exporter;
@ISA = qw(Exporter);
use strict;
use IO::Select;
use IO::Socket;
use Carp;
use vars qw (%rd_callbacks %wt_callbacks $rd_handles $wt_handles);
%rd_callbacks = ();
%wt_callbacks = ();
$rd_handles = IO::Select->new();
$wt_handles = IO::Select->new();
my $blocking_supported = 0;
BEGIN {
# Checks if blocking is supported
eval {
require POSIX; POSIX->import(qw (F_SETFL O_NONBLOCK EAGAIN));
};
$blocking_supported = 1 unless $@;
}
#-----------------------------------------------------------------
# Send side routines
sub connect {
my ($pkg, $to_host, $to_port,$rcvd_notification_proc) = @_;
# Create a new internet socket
my $sock = IO::Socket::INET->new (
PeerAddr => $to_host,
PeerPort => $to_port,
Proto => 'tcp',
Reuse => 1);
return undef unless $sock;
# Create a connection end-point object
my $conn = {
sock => $sock,
rcvd_notification_proc => $rcvd_notification_proc,
};
if ($rcvd_notification_proc) {
my $callback = sub {_rcv($conn, 0)};
set_event_handler ($sock, "read" => $callback);
}
return bless $conn, $pkg;
}
sub disconnect {
my $conn = shift;
my $sock = delete $conn->{sock};
return unless defined($sock);
set_event_handler ($sock, "read" => undef, "write" => undef);
close($sock);
}
sub send_now {
my ($conn, $msg) = @_;
_enqueue ($conn, $msg);
$conn->_send (1); # 1 ==> flush
}
sub send_later {
my ($conn, $msg) = @_;
_enqueue($conn, $msg);
my $sock = $conn->{sock};
return unless defined($sock);
set_event_handler ($sock, "write" => sub {$conn->_send(0)});
}
sub _enqueue {
my ($conn, $msg) = @_;
# prepend length (encoded as network long)
my $len = length($msg);
$msg = pack ('N', $len) . $msg;
push (@{$conn->{queue}}, $msg);
}
sub _send {
my ($conn, $flush) = @_;
my $sock = $conn->{sock};
return unless defined($sock);
my ($rq) = $conn->{queue};
# If $flush is set, set the socket to blocking, and send all
# messages in the queue - return only if there's an error
# If $flush is 0 (deferred mode) make the socket non-blocking, and
# return to the event loop only after every message, or if it
# is likely to block in the middle of a message.
$flush ? $conn->set_blocking() : $conn->set_non_blocking();
my $offset = (exists $conn->{send_offset}) ? $conn->{send_offset} : 0;
while (@$rq) {
my $msg = $rq->[0];
my $bytes_to_write = length($msg) - $offset;
my $bytes_written = 0;
while ($bytes_to_write) {
$bytes_written = syswrite ($sock, $msg,
$bytes_to_write, $offset);
if (!defined($bytes_written)) {
if (_err_will_block($!)) {
# Should happen only in deferred mode. Record how
# much we have already sent.
$conn->{send_offset} = $offset;
# Event handler should already be set, so we will
# be called back eventually, and will resume sending
return 1;
} else { # Uh, oh
$conn->handle_send_err($!);
return 0; # fail. Message remains in queue ..
}
}
$offset += $bytes_written;
$bytes_to_write -= $bytes_written;
}
delete $conn->{send_offset};
$offset = 0;
shift @$rq;
last unless $flush; # Go back to select and wait
# for it to fire again.
}
# Call me back if queue has not been drained.
if (@$rq) {
set_event_handler ($sock, "write" => sub {$conn->_send(0)});
} else {
set_event_handler ($sock, "write" => undef);
}
1; # Success
}
sub _err_will_block {
if ($blocking_supported) {
return ($_[0] == EAGAIN());
}
return 0;
}
sub set_non_blocking { # $conn->set_blocking
if ($blocking_supported) {
# preserve other fcntl flags
my $flags = fcntl ($_[0], F_GETFL(), 0);
fcntl ($_[0], F_SETFL(), $flags | O_NONBLOCK());
}
}
sub set_blocking {
if ($blocking_supported) {
my $flags = fcntl ($_[0], F_GETFL(), 0);
$flags &= ~O_NONBLOCK(); # Clear blocking, but preserve other flags
fcntl ($_[0], F_SETFL(), $flags);
}
}
sub handle_send_err {
# For more meaningful handling of send errors, subclass Msg and
# rebless $conn.
my ($conn, $err_msg) = @_;
warn "Error while sending: $err_msg \n";
set_event_handler ($conn->{sock}, "write" => undef);
}
#-----------------------------------------------------------------
# Receive side routines
my ($g_login_proc,$g_pkg);
my $main_socket = 0;
sub new_server {
@_ == 4 || die "Msg->new_server (myhost, myport, login_proc)\n";
my ($pkg, $my_host, $my_port, $login_proc) = @_;
$main_socket = IO::Socket::INET->new (
LocalAddr => $my_host,
LocalPort => $my_port,
Listen => 5,
Proto => 'tcp',
Reuse => 1);
die "Could not create socket: $! \n" unless $main_socket;
set_event_handler ($main_socket, "read" => \&_new_client);
$g_login_proc = $login_proc; $g_pkg = $pkg;
}
sub rcv_now {
my ($conn) = @_;
my ($msg, $err) = _rcv ($conn, 1); # 1 ==> rcv now
return wantarray ? ($msg, $err) : $msg;
}
sub _rcv { # Complement to _send
my ($conn, $rcv_now) = @_; # $rcv_now complement of $flush
# Find out how much has already been received, if at all
my ($msg, $offset, $bytes_to_read, $bytes_read);
my $sock = $conn->{sock};
return unless defined($sock);
if (exists $conn->{msg}) {
$msg = $conn->{msg};
$offset = length($msg) - 1; # sysread appends to it.
$bytes_to_read = $conn->{bytes_to_read};
delete $conn->{'msg'}; # have made a copy
} else {
# The typical case ...
$msg = ""; # Otherwise -w complains
$offset = 0 ;
$bytes_to_read = 0 ; # Will get set soon
}
# We want to read the message length in blocking mode. Quite
# unlikely that we'll get blocked too long reading 4 bytes
if (!$bytes_to_read) { # Get new length
my $buf;
$conn->set_blocking();
$bytes_read = sysread($sock, $buf, 4);
if ($! || ($bytes_read != 4)) {
goto FINISH;
}
$bytes_to_read = unpack ('N', $buf);
}
$conn->set_non_blocking() unless $rcv_now;
while ($bytes_to_read) {
$bytes_read = sysread ($sock, $msg, $bytes_to_read, $offset);
if (defined ($bytes_read)) {
if ($bytes_read == 0) {
last;
}
$bytes_to_read -= $bytes_read;
$offset += $bytes_read;
} else {
if (_err_will_block($!)) {
# Should come here only in non-blocking mode
$conn->{msg} = $msg;
$conn->{bytes_to_read} = $bytes_to_read;
return ; # .. _rcv will be called later
# when socket is readable again
} else {
last;
}
}
}
FINISH:
if (length($msg) == 0) {
$conn->disconnect();
}
if ($rcv_now) {
return ($msg, $!);
} else {
&{$conn->{rcvd_notification_proc}}($conn, $msg, $!);
}
}
sub _new_client {
my $sock = $main_socket->accept();
my $conn = bless {
'sock' => $sock,
'state' => 'connected'
}, $g_pkg;
my $rcvd_notification_proc =
&$g_login_proc ($conn, $sock->peerhost(), $sock->peerport());
if ($rcvd_notification_proc) {
$conn->{rcvd_notification_proc} = $rcvd_notification_proc;
my $callback = sub {_rcv($conn,0)};
set_event_handler ($sock, "read" => $callback);
} else { # Login failed
$conn->disconnect();
}
}
#----------------------------------------------------
# Event loop routines used by both client and server
sub set_event_handler {
shift unless ref($_[0]); # shift if first arg is package name
my ($handle, %args) = @_;
my $callback;
if (exists $args{'write'}) {
$callback = $args{'write'};
if ($callback) {
$wt_callbacks{$handle} = $callback;
$wt_handles->add($handle);
} else {
delete $wt_callbacks{$handle};
$wt_handles->remove($handle);
}
}
if (exists $args{'read'}) {
$callback = $args{'read'};
if ($callback) {
$rd_callbacks{$handle} = $callback;
$rd_handles->add($handle);
} else {
delete $rd_callbacks{$handle};
$rd_handles->remove($handle);
}
}
}
sub event_loop {
my ($pkg, $loop_count, $timeout) = @_; # event_loop(1) to process events once
my ($conn, $r, $w, $rset, $wset);
while (1) {
# Quit the loop if no handles left to process
last unless ($rd_handles->count() || $wt_handles->count());
($rset, $wset) =
IO::Select->select ($rd_handles, $wt_handles, undef, $timeout);
foreach $r (@$rset) {
&{$rd_callbacks{$r}} ($r) if exists $rd_callbacks{$r};
}
foreach $w (@$wset) {
&{$wt_callbacks{$w}}($w) if exists $wt_callbacks{$w};
}
if (defined($loop_count)) {
last unless --$loop_count;
}
}
}
1;
__END__

111
perl/client.pl Executable file
View File

@ -0,0 +1,111 @@
#!/usr/bin/perl
#
# A thing that implements dxcluster 'protocol'
#
# This is a perl module/program that sits on the end of a dxcluster
# 'protocol' connection and deals with anything that might come along.
#
# this program is called by ax25d and gets raw ax25 text on its input
#
# Copyright (c) 1998 Dirk Koopman G1TLH
#
#
use Msg;
use DXVars;
$mode = 1; # 1 - \n = \r as EOL, 2 - \n = \n, 0 - transparent
$call = ""; # the callsign being used
@stdoutq = (); # the queue of stuff to send out to the user
$conn = 0; # the connection object for the cluster
$lastbit = ""; # the last bit of an incomplete input line
# cease communications
sub cease
{
my $sendz = shift;
if (defined $conn && $sendz) {
$conn->send_now("Z$call|bye...\n");
}
exit(0);
}
# terminate program from signal
sub sig_term
{
cease(1);
}
# handle incoming messages
sub rec_socket
{
my ($con, $msg, $err) = @_;
if (defined $err && $err) {
cease(1);
}
if (defined $msg) {
my ($sort, $call, $line) = $msg =~ /^(\w)(\S+)|(.*)$/;
if ($sort eq 'D') {
my $nl = ($mode == 1) ? "\r" : "\n";
$nl = "" if $mode == 0;
$line =~ s/\n/\r/o if $mode == 1;
print $line, $nl;
} elsif ($sort eq 'M') {
$mode = $line; # set new mode from cluster
} elsif ($sort eq 'Z') { # end, disconnect, go, away .....
cease(0);
}
}
}
sub rec_stdin
{
my ($fh) = @_;
my $buf;
my @lines;
my $r;
my $first;
my $dangle = 0;
$r = sysread($fh, $buf, 1024);
# print "sys: $r $buf";
if ($r > 0) {
if ($mode) {
$buf =~ s/\r/\n/o if $mode == 1;
$dangle = !($buf =~ /\n$/);
@lines = split /\n/, $buf;
if ($dangle) { # pull off any dangly bits
$buf = pop @lines;
} else {
$buf = "";
}
$first = shift @lines;
unshift @lines, ($lastbit . $first) if ($first);
foreach $first (@lines) {
$conn->send_now("D$call|$first");
}
$lastbit = $buf;
} else {
$conn->send_now("D$call|$buf");
}
} elsif ($r == 0) {
cease(1);
}
}
$call = uc $ARGV[0];
die "client.pl <call> [<mode>]\r\n" if (!$call);
$mode = $ARGV[1] if (@ARGV > 1);
select STDOUT; $| = 1;
$SIG{'INT'} = \&sig_term;
$SIG{'TERM'} = \&sig_term;
$SIG{'HUP'} = \&sig_term;
$conn = Msg->connect("$clusteraddr", $clusterport, \&rec_socket);
$conn->send_now("A$call|start");
Msg->set_event_handler(\*STDIN, "read" => \&rec_stdin);
Msg->event_loop();

138
perl/cluster.pl Executable file
View File

@ -0,0 +1,138 @@
#!/usr/bin/perl
#
# A thing that implements dxcluster 'protocol'
#
# This is a perl module/program that sits on the end of a dxcluster
# 'protocol' connection and deals with anything that might come along.
#
# this program is called by ax25d and gets raw ax25 text on its input
#
# Copyright (c) 1998 Dirk Koopman G1TLH
#
#
use Msg;
use DXVars;
use DXUtil;
use DXConnect;
use DXUser;
package main;
@inqueue = undef; # the main input queue, an array of hashes
# handle out going messages
sub send_now
{
my ($conn, $sort, $call, $line) = @_;
print DEBUG "$t > $sort $call $line\n" if defined DEBUG;
print "> $sort $call $line\n";
$conn->send_now("$sort$call|$line");
}
sub send_later
{
my ($conn, $sort, $call, $line) = @_;
print DEBUG "$t > $sort $call $line\n" if defined DEBUG;
print "> $sort $call $line\n";
$conn->send_later("$sort$call|$line");
}
# handle disconnections
sub disconnect
{
my $dxconn = shift;
my ($user) = $dxconn->{user};
my ($conn) = $dxconn->{conn};
$user->close() if defined $user;
$conn->disconnect();
$dxconn->del();
}
# handle incoming messages
sub rec
{
my ($conn, $msg, $err) = @_;
my $dxconn = DXConnect->get_by_cnum($conn); # get the dxconnnect object for this message
if (defined $err && $err) {
disconnect($dxconn);
return;
}
if (defined $msg) {
my $self = bless {}, "inqueue";
$self->{dxconn} = $dxconn;
$self->{data} = $msg;
push @inqueue, $self;
}
}
sub login
{
return \&rec;
}
# cease running this program, close down all the connections nicely
sub cease
{
my $dxconn;
foreach $dxconn (DXConnect->get_all()) {
disconnect($dxconn);
}
}
# this is where the input queue is dealt with and things are dispatched off to other parts of
# the cluster
sub process_inqueue
{
my $self = shift @inqueue;
return if !$self;
my $data = $self->{data};
my $dxconn = $self->{dxconn};
my ($sort, $call, $line) = $data =~ /^(\w)(\S+)|(.*)$/;
# do the really sexy console interface bit! (Who is going to do the TK interface then?)
print DEBUG atime, " < $sort $call $line\n" if defined DEBUG;
print "< $sort $call $line\n";
# handle A records
if ($sort eq 'A') {
if ($dxconn) { # there should not be one of these, disconnect
}
my $user = DXUser->get($call); # see if we have one of these
}
}
#############################################################
#
# The start of the main line of code
#
#############################################################
# open the debug file, set various FHs to be unbuffered
open(DEBUG, ">>$debugfn") or die "can't open $debugfn($!)\n";
select DEBUG; $| = 1;
select STDOUT; $| = 1;
# initialise User file system
DXUser->init($userfn);
# start listening for incoming messages/connects
Msg->new_server("$clusteraddr", $clusterport, \&login);
# prime some signals
$SIG{'INT'} = \&cease;
$SIG{'TERM'} = \&cease;
$SIG{'HUP'} = 'IGNORE';
# this, such as it is, is the main loop!
for (;;) {
Msg->event_loop(1, 0.001);
process_inqueue();
}

57
perl/msgdemo.pl Normal file
View File

@ -0,0 +1,57 @@
#
# testmsg.pl - Used for testing the Msg.pm module
# Invoke as testmsg.pl {-client|-server}
#
use Msg;
use strict;
my $i = 0;
sub rcvd_msg_from_server {
my ($conn, $msg, $err) = @_;
if (defined $msg) {
die "Strange... shouldn't really be coming here\n";
}
}
my $incoming_msg_count=0;
sub rcvd_msg_from_client {
my ($conn, $msg, $err) = @_;
if (defined $msg) {
++$i;
my $len = length ($msg);
print "$i ($len)\n";
}
}
sub login_proc {
# Unconditionally accept
\&rcvd_msg_from_client;
}
my $host = 'localhost';
my $port = 8080;
my $prog;
foreach $prog (@ARGV) {
if ($prog eq '-server') {
Msg->new_server($host, $port, \&login_proc);
print "Server created. Waiting for events";
Msg->event_loop();
} elsif ($prog eq '-client') {
my $conn = Msg->connect($host, $port,
\&rcvd_msg_from_server);
die "Client could not connect to $host:$port\n" unless $conn;
print "Connection successful.\n";
my $i;
my $msg = " " x 10000;
for ($i = 0; $i < 100; $i++) {
print "Sending msg $i\n";
$conn->send_now($msg);
}
$conn->disconnect();
Msg->event_loop();
}
}

48
perl/persist.c Normal file
View File

@ -0,0 +1,48 @@
/* persistent.c */
#include <EXTERN.h>
#include <perl.h>
/* 1 = clean out filename's symbol table after each request, 0 = don't */
#ifndef DO_CLEAN
# define DO_CLEAN 0
#endif
static PerlInterpreter *perl = NULL;
int main(int argc, char **argv, char **env)
{
char *embedding[] = { "", "persistent.pl"};
char *args[] = { "", DO_CLEAN, NULL };
char filename [1024];
int exitstatus = 0;
if ((perl = perl_alloc()) == NULL) {
fprintf(stderr, "no memory!");
exit(1);
}
perl_construct(perl);
exitstatus = perl_parse(perl, NULL, 2, embedding, NULL);
if(!exitstatus) {
exitstatus = perl_run(perl);
while(printf("Enter file name: ") && gets(filename)) {
/* call the subroutine, passing it the filename as an argument */
args[0] = filename;
perl_call_argv("Embed::Persistent::eval_file",
G_DISCARD | G_EVAL, args);
/* check $@ */
if(SvTRUE(GvSV(errgv)))
fprintf(stderr, "eval error: %s\n", SvPV(GvSV(errgv),na));
}
}
perl_destruct_level = 0;
perl_destruct(perl);
perl_free(perl);
exit(exitstatus);
}

72
perl/persistent.pl Normal file
View File

@ -0,0 +1,72 @@
package Embed::Persistent;
#persistent.pl
#require Devel::Symdump;
use strict;
use vars '%Cache';
sub valid_package_name {
my($string) = @_;
$string =~ s/([^A-Za-z0-9\/])/sprintf("_%2x",unpack("C",$1))/eg;
#second pass only for words starting with a digit
$string =~ s|/(\d)|sprintf("/_%2x",unpack("C",$1))|eg;
#Dress it up as a real package name
$string =~ s|/|::|g;
return "Embed" . $string;
}
#borrowed from Safe.pm
sub delete_package {
my $pkg = shift;
my ($stem, $leaf);
no strict 'refs';
$pkg = "main::$pkg\::"; # expand to full symbol table name
($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/;
my $stem_symtab = *{$stem}{HASH };
delete $stem_symtab->{$leaf };
}
sub eval_file {
my($filename, $delete) = @_;
my $package = valid_package_name($filename);
my $mtime = -M $filename;
if(defined $Cache{$package}{mtime} && $Cache{$package}{mtime } <= $mtime) {
#we have compiled this subroutine already,
#it has not been updated on disk, nothing left to do
print STDERR "already compiled $package->handler\n";
} else {
local *FH;
open FH, $filename or die "open '$filename' $!";
local($/) = undef;
my $sub = <FH>;
close FH;
#wrap the code into a subroutine inside our unique package
my $eval = qq{package $package; sub handler { $sub; }};
{
#hide our variables within this block
my($filename,$mtime,$package,$sub);
eval $eval;
}
die $@ if $@;
#cache it unless we're cleaning out each time
$Cache{$package}{mtime} = $mtime unless $delete;
}
eval {$package->handler;};
die $@ if $@;
delete_package($package) if $delete;
#take a look if you want
#print Devel::Symdump->rnew($package)->as_string, $/;
}
1;
__END__