From: Uri Guttman Date: Fri, 5 Dec 2008 01:34:29 +0000 (-0500) Subject: init commit X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=4536f6551e07988483a419d1094bdbe80a77b322;p=urisagit%2FStem.git init commit --- 4536f6551e07988483a419d1094bdbe80a77b322 diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..35743fb --- /dev/null +++ b/Build.PL @@ -0,0 +1,33 @@ +use strict; +use warnings ; + +use Config; +use File::Spec; + +use BuildStem ; + +my $is_win32 = ( $^O =~ /Win32/) ? 1 : 0 ; + +my %requires ; + +my $version_from = File::Spec->catfile( File::Spec->curdir, 'lib', 'Stem.pm' ); + +my $build = BuildStem->new( + module_name => 'Stem', + dist_version_from => $version_from, + requires => \%requires, + license => 'gpl', + dynamic_config => 1, + recursive_test_files => 1, + create_makefile_pl => 'passthrough' +); + +# since we are making a fresh Build script, delete any older stem config file +# so Build will create a new one. + +my $conf_pm_file = $build->config_pm_path() ; +unlink $conf_pm_file ; + +$build->create_build_script() ; + +exit ; diff --git a/BuildStem.pm b/BuildStem.pm new file mode 100644 index 0000000..3711f98 --- /dev/null +++ b/BuildStem.pm @@ -0,0 +1,852 @@ +package BuildStem ; + +use strict; +use warnings qw( all ); + +use Carp ; +use Config; +use File::Path ; +use File::Spec ; + +use lib 'lib' ; +use base 'Module::Build' ; + +$ENV{HARNESS_DEBUG} = 1 ; +$ENV{HARNESS_VERBOSE} = 1 ; + +# this is the common env values to control running stem stuff in the +# build directory. + +my $env = + 'PATH=blib/bin:blib/demo:$PATH PERL5LIB=blib/lib STEM_CONF_PATH=conf' ; + +my %env = ( + PATH => "blib/bin:blib/demo:$ENV{PATH}", + PERL5LIB => 'blib/lib', + STEM_CONF_PATH => 'conf', +) ; + +local( @ENV{ keys %env } ) = values %env ; + + +my ( @manifest_lines ) ; + +eval { + require Stem::InstallConfig +} ; +my $conf = \%Stem::InstallConfig::Config ; + +my $is_win32 = ( $^O =~ /Win32/) ? 1 : 0 ; + +my $default_stem_path = $is_win32 ? + '/stem' : + File::Spec->catfile( + File::Spec->rootdir, qw( usr local stem ) ) ; + +my $default_conf_path = File::Spec->catfile( $default_stem_path, 'conf' ) ; +#my $default_tail_dir = File::Spec->catfile( File::Spec->tmpdir, 'stem_tail' ); + +my %defaults = ( + bin_path => $Config{bin}, + run_stem_path => File::Spec->catfile( $Config{bin}, 'run_stem' ), + perl_path => $Config{perlpath}, + conf_path => $default_conf_path, + prefix => $Config{prefix}, +# tail_dir => $default_tail_dir, + build_demos => ! $is_win32, + install_demos => ! $is_win32, + install_ssfe => ! $is_win32, + %{$conf} +); + +################ +# these are the top level action handlers. ACTION_foo gets called when you do +# 'Build foo' on the command line +################ + +sub ACTION_build { + + my ( $self ) = @_ ; + + $self->query_for_config() ; + + $self->SUPER::ACTION_build() ; + +# $self->build_bin() ; +} + +sub ACTION_test { + + my ( $self ) = @_ ; + + local( @ENV{ keys %env } ) = values %env ; + + $self->depends_on('build'); + + $self->SUPER::ACTION_test() ; +} + +sub ACTION_install { + + my ( $self ) = @_ ; + + $self->install_config_files() ; +# $self->install_ssfe() ; + + $self->SUPER::ACTION_install() ; +} + +sub ACTION_run { + + my ( $self ) = @_ ; + + $self->depends_on('build'); + + my $run_cmd = $self->{'args'}{'cmd'} || '' ; + + $run_cmd or die "Missing cmd=name argument" ; + + my $cmd = "$env $run_cmd" ; +# print "CMD: $cmd\n" ; + + system $cmd ; +} + +sub ACTION_run_stem { + + my ( $self ) = @_ ; + + $self->depends_on('build'); + + my $conf = $self->{'args'}{'conf'} || '' ; + + $conf or die "Missing conf=name argument" ; + + my $cmd = "$env run_stem $conf" ; +# print "DEMO: $cmd\n" ; + + system $cmd ; +} + + +sub run_demo { + + my ( $self ) = @_ ; + + $self->depends_on('build'); + + my $cmd = "$env $self->{action}_demo" ; + print "DEMO: $cmd\n" ; + system $cmd ; +} + + +sub ACTION_tail { + + mkdir 'tail' ; + + unlink ; + + goto &run_demo ; +} + +*ACTION_chat = \&run_demo ; +*ACTION_chat2 = \&run_demo ; +*ACTION_inetd = \&run_demo ; + +sub ACTION_update_pod { + + my( $self ) = @_ ; + + my @manifest_sublist = $self->grep_manifest( qr/\.pm$/ ) ; + + @manifest_sublist = grep /Codec/, @manifest_sublist ; + +print join( "\n", @manifest_sublist ), "\n" ; + + system( "bin/spec2pod.pl @manifest_sublist" ) ; + + return; +} + +# grep through all matched files +# command line args: +# files= (default is all .pm files) +# re= + +sub ACTION_grep { + + my( $self ) = @_ ; + + my $args = $self->{'args'} ; + + my $file_regex = $args->{ files } || qr/\.pm$/ ; + my $grep_regex = $args->{ re } or die "need grep regex" ; + + my @manifest_sublist = $self->grep_manifest( $file_regex ) ; + + local( @ARGV ) = @manifest_sublist ; + + while( <> ) { + + next unless /$grep_regex/ ; + + print "$ARGV:$. $_" + } + continue { + + close ARGV if eof ; + } + + return; +} + +# ACTION: grep through MANIFEST +# command line args: +# files= +# +# do we need this action? +# + +sub ACTION_grep_manifest { + + my( $self ) = @_ ; + + my @manifest_sublist = $self->grep_manifest() ; + + print join( "\n", @manifest_sublist ), "\n" ; + return; +} + +# ACTION: count source lines +# command line args: +# files= (defaults to all .pm and bin files +# +# do we need this action? + +sub ACTION_lines { + + my( $self ) = @_ ; + + my $args = $self->{'args'} ; + my $file_regex = $args->{ files } || qr/\.pm$|^bin/ ; + + my @manifest_sublist = $self->grep_manifest( $file_regex ) ; + + system( "./util/lines @manifest_sublist" ) ; + + return; +} + +# build a distro and scp to stemsystems.com + +sub ACTION_ftp { + + my ( $self ) = @_ ; + + my $dist_tar = $self->dist_dir() . '.tar.gz' ; + + unlink $dist_tar ; + + $self->ACTION_dist() ; + + system "scp $dist_tar stemsystems.com:www/" ; +} + + +# this sub overrides the find_test_files method in Module::Build + +sub find_test_files { + + my ($self) = @_; + + my $test_args = $self->{ args }{ tests } ; + + my @tests = $test_args ? split( ':', $test_args ) : + $self->grep_manifest( qr/\.t$/ ) ; + + return \@tests ; +} + +sub process_script_files { + my( $self ) = @_ ; + + my @scripts = $self->grep_manifest( qr{^bin/} ) ; + +#print "SCR @scripts\n" ; + foreach my $file ( @scripts ) { + + my $bin_dir = File::Spec->catdir( + $self->blib, + $file =~ /_demo$/ ? 'demo' : 'bin' ) ; + + File::Path::mkpath( $bin_dir ); + + my $result = $self->copy_if_modified( + $file, $bin_dir, 'flatten') or next; + +#print "COPY $file\n" ; + $self->fix_run_stem($result); + $self->fix_demos($result); + $self->fix_shebang_line($result); + $self->make_executable($result); + } +} + +sub fix_run_stem { + + my( $self, $file ) = @_ ; + + return unless $file =~ m{/run_stem$} ; + + my $text = read_file( $file ) ; + + $text =~ s/'conf:.'/'$conf->{'conf_path'}'/ if $conf->{'conf_path'} ; + + write_file( $file, $text ) ; +} + +sub fix_demos { + + my( $self, $file ) = @_ ; + + return unless $file =~ /_demo$/ ; + + my $text = read_file( $file ) ; + + $conf->{xterm_path} ||= 'NOT FOUND' ; + $conf->{telnet_path} ||= 'NOT FOUND' ; + + $text =~ s[xterm][$conf->{xterm_path}]g; + $text =~ s[telnet][$conf->{telnet_path}]g; + + write_file( $file, $text ) ; +} + +# MANIFEST helper subs + +sub grep_manifest { + + my( $self, $file_regex ) = @_ ; + + $file_regex ||= $self->{ args }{ files } || qr/.*/ ; + + manifest_load() ; + + return grep( /$file_regex/, @manifest_lines ) ; +} + +sub manifest_load { + + return if @manifest_lines ; + + @manifest_lines = grep ! /^\s*$|^\s*#/, read_file( 'MANIFEST' ) ; + + chomp @manifest_lines ; + + return ; +} + +################################# + +sub query_for_config { + + my( $self ) = @_ ; + + return if $defaults{ 'config_done' } ; + + print <<'EOT'; + +Building Stem + +This script will ask you various questions in order to properly +configure, build and install Stem on your system. Whenever a question +is asked, the default answer will be shown inside [brackets]. +Pressing enter will accept the default answer. If a choice needs to be +made from a list of values, that list will be inside (parentheses). + +If you have already configured Stem in a previous build, you can put +use_defaults=1 on the Build command line and you won't be prompted for +any answers and the previous settings will be used. + +If you want to force a new build, run Build clean. + +EOT + + $self->get_path_config() ; + $self->get_demo_config() ; + + $defaults{ 'config_done' } = 1 ; + + $self->write_config_pm() ; +} + + +my $package = 'Stem::InstallConfig' ; + +sub config_pm_path { + + return File::Spec->catfile( + File::Spec->curdir, 'lib', split( /::/, $package) ) . '.pm' ; + +} + +sub write_config_pm { + + my ( $self ) = @_ ; + + my $config = Data::Dumper->Dump( + [\%defaults], + ["*${package}::Config"] + ); + + my $conf_pm_file = $self->config_pm_path() ; + + $self->add_to_cleanup( $conf_pm_file ) ; + + write_file( $conf_pm_file, <query_config_value( <<'EOT', 'perl_path' ); + +# Stem has several executable Perl programs and demonstration scripts +# and they need to have the correct path to your perl binary. + +# What is the path to perl? +# EOT + +# $self->query_config_value( <<'EOT', 'bin_path' ); + +# Those Stem executables need to be installed in a directory that is in your +# shell $PATH variable. + +# What directory will have the Stem executables? +# EOT + + $self->query_config_value( <<'EOT', 'conf_path' ); + +Stem configuration files are used to create and initialize Stem Cells +(objects). Stem needs to know the list of directories to search to +find its configurations files. + +Note that the default has a single absolute path. You can test Stem +configurations easily setting this path when executing run_stem. You +can override or modify the path time with either a shell environment +variable or on the command line of run_stem. See the documentation on +run_stem for how so do this. + +The first directory in the list is where the standard Stem +configuration files will be installed. + +Enter a list of absolute directory paths separated by ':'. + +What directories do you want to search for Stem configuration files? +EOT + + return ; +} + +sub get_demo_config { + + my( $self ) = @_ ; + +# don't even bother if win32 + + return if $is_win32 ; + +# $self->get_config_boolean( <<'EOT', 'build_demos' ); + +# Stem comes with several demonstration scripts. After building them, +# they can be run from the main directory by the Build script: ./Build +# chat, Build inetd, etc. Do you want to build the demos? +# EOT + +# return unless $defaults{build_demos}; + +# all the demos need xterm + + $self->get_xterm_path(); + $self->get_telnet_path(); + return unless -x $defaults{xterm_path} && -x $defaults{telnet_path}; + +# $self->query_config_value( <<'EOT', 'tail_dir' ); + +# The tail demo script needs a temporary working directory. Enter the +# path to a directory to use for this purpose. If it does not exist, +# this directory will be created. +# EOT + + $self->get_config_boolean( <<'EOT', 'install_ssfe' ); + +ssfe (Split Screen Front End) is a compiled program optionally used by +the Stem demonstration scripts that provides a full screen interface +with command line editing and history. It is not required to run Stem +but it makes the demonstrations easier to work with and they look much +nicer. To use ssfe add the '-s' option when you run any demonstration +script. You can also use ssfe for your own programs. Install ssfe in +some place in your \$PATH ($conf->{'bin_path'} is where Stem executables +are being installed) so it can be used by the demo scripts. The ssfe +install script will do this for you or you can do it manually after +building it. + +Do you want to install ssfe? +EOT + +} + +sub get_xterm_path { + + my( $self ) = @_ ; + + my $xterm_path; + +# unless ( $xterm_path = which_exec( 'xterm' ) ) { + +# foreach my $path ( qw( +# /usr/openwin/bin/xterm +# /usr/bin/X11/xterm +# /usr/X11R6/bin/xterm ) ) { + +# next unless -x $path; +# $xterm_path = $path ; +# last; +# } +# } + +# if ( $xterm_path ) { + +# $defaults{'xterm_path'} = $xterm_path ; +# print "xterm was found at '$xterm_path'\n"; +# return ; +# } + + $self->query_config_value( <<"EOT", 'xterm_path' ); + +xterm was not found on this system. you can't run the demo programs +without xterm. Make sure you enter a valid path to xterm or some other +terminal emulator. + +NOTE: If you don't have an xterm, you can still run the demo scripts +by hand. Run a *_demo script and see what commands it issues. Take the +part after the -e and run that command in its own terminal window. + +Enter the path to xterm (or another compatible terminal emulator) +EOT + +} + +sub get_telnet_path { + + my( $self ) = @_ ; + + my $telnet_path; + + unless ( $telnet_path = which_exec( 'telnet' ) ) { + +# enter a list of common places to find telnet. or delete this as it +# will almost always be in the path + + foreach my $path ( qw( ) ) { + + next unless -x $path; + $telnet_path = $path ; + last; + } + } + + if ( $telnet_path ) { + + $defaults{'telnet_path'} = $telnet_path ; + print "telnet was found at '$telnet_path'\n"; + return ; + } + + $self->query_config_value( <<"EOT", 'telnet_path' ); + +telnet was not found on this system. you can't run the demo programs +without telnet. Make sure you enter a valid path to telnet or some other +terminal emulator. + +NOTE: If you don't have an telnet, you can still run the demo scripts +by hand. Run a *_demo script and see what telnet commands it +issues. The run those telnet commands using your telnet or another +similar program. + +Enter the path to telnet (or another compatible terminal emulator) +EOT + +} + +sub install_config_files { + + my ( $self ) = @_ ; + + my ( $conf_path ) = split /:/, $conf->{conf_path} ; + + mkpath( $conf_path, 1, 0755 ) unless -d $conf_path ; + + my @config_files = $self->grep_manifest( qr{^conf/.+\.stem$} ) ; + + foreach my $conf_file (@config_files) { + + $conf_file =~ s{conf/}{} ; + + my $out_file = File::Spec->catfile( $conf_path, $conf_file ); + + print "Installing config file: $out_file\n"; + + my $in_file = File::Spec->catfile( + File::Spec->curdir(), 'conf', $conf_file ); + + my $conf_text = read_file($in_file); + + if ( $conf_file eq 'inetd.stem' ) { + + my $quote_serve = File::Spec->catfile( + $conf->{bin_path}, 'quote_serve' ); + + $conf_text =~ s[path\s+=>\s+'bin/quote_serve',] + [path\t\t=> '$quote_serve',]; + } +# elsif ( $conf eq 'monitor.stem' || $conf eq 'archive.stem' ) { + +# $conf_text =~ s[path'\s+=>\s+'tail] +# [path'\t\t=> '$conf->{tail_dir}]g ; +# } + + write_file( $out_file, $conf_text ); + } +} + + +sub install_ssfe { + + my ( $self ) = @_ ; + + return unless $conf->{install_stem_demos} && + $conf->{install_ssfe} ; + + print <<'EOT'; + +Installing ssfe. + +This is not a Stem install script and it will ask its own +questions. It will execute in its own xterm (whatever was configured +earlier) to keep this install's output clean. The xterm is kept open +with a long sleep call and can be exited by typing ^C. + +EOT + +######### +# UGLY +######### + + system <<'EOT'; +xterm -e /bin/sh -c 'chdir extras ; +tar zxvf sirc-2.211.tar.gz ; +chdir sirc-2.211 ; +./install ; +sleep 1000 ;' +EOT + + print "\nInstallation of ssfe is done\n\n"; +} + +######################################################### +# this sub builds the exec scripts in bin and puts them into blib/bin +# for local running or later installation + +# sub build_bin { + +# my ( $self ) = @_ ; + +# my @bin_scripts = $self->grep_manifest( qr{^bin/} ) ; + +# foreach my $bin_file ( @bin_scripts ) { + +# #print "BIN $bin_file\n" ; + +# my $bin_text = read_file( $bin_file ) ; + +# $bin_file =~ s{bin/}{} ; + +# # fix the shebang line + +# $bin_text =~ s{/usr/local/bin/perl}{$conf->{'perl_path'}} ; + +# my $bin_dir ; + +# if ( $bin_file =~ /_demo$/ ) { + +# next unless $conf->{build_demos} ; + +# $bin_dir = 'demo' ; + +# # fix the location of xterms in the demo scripts + +# $bin_text =~ s[xterm][$conf->{xterm_path}]g; +# $bin_text =~ s[telnet][$conf->{telnet_path}]g; + +# # fix the default config search path in run_stem +# } +# else { + +# $bin_dir = 'bin' ; + +# # fix the default config search path in run_stem + +# if ( $bin_file eq 'run_stem' ) { +# $bin_text =~ +# s/'conf:.'/'$conf->{'conf_path'}'/ ; +# } +# } + +# # elsif ( $bin_file eq 'tail_demo' ) { +# # $bin_text =~ s['tail']['$conf->{tail_dir}']; +# # } + +# # write the built script into the blib/ dir + +# my $out_file = File::Spec->catfile( 'blib', +# $bin_dir, +# $bin_file +# ); + +# mkdir "blib/$bin_dir" ; +# print "Building executable script: $out_file\n"; +# write_file( $out_file, $bin_text ); +# chmod 0755, $out_file; +# } +# } + +############################################################# + +# this sub searches the path for the locations of an executable + +sub which_exec { + + my ( $exec ) = @_; + + foreach my $path_dir ( split /[:;]/, $ENV{PATH} ) { + + my $exec_path = File::Spec->catfile( $path_dir, $exec ); + return $exec_path if -x $exec_path ; + } + + return; +} + +# the sub searches a list of dir paths to find the first one that +# exists with a prefix dir + +# UNUSED FOR THE MOMENT + +# sub which_dir { + +# my ( $prefix, @dirs ) = @_; + +# foreach my $subdir ( @dirs ) { + +# my $dir = File::Spec->catfile( $prefix, $subdir ); +# return $dir if -x $dir; +# } + +# return; +# } + +############################################################# + +# these subs handle querying for a user answer. it uses the key to +# find a current value in the defaults and prompt for another value +# if 'use_defaults' is set on the command line, then no prompting will be done + +sub query_config_value { + + my( $self, $query, $key ) = @_ ; + + my $default = $self->{args}{$key} ; + + $default = $defaults{ $key } unless defined $default ; + + $defaults{ $key } = ( $self->{args}{use_defaults} ) ? + $default : + $self->prompt( edit_query( $query, $default ), $default ) ; +} + +sub get_config_boolean { + + my( $self, $query, $key ) = @_ ; + + my $default = $self->{args}{$key} ; + + $default = $defaults{ $key } unless defined $default ; + $default =~ tr/01/ny/ ; + + $defaults{ $key } = ( $self->{args}{use_defaults} ) ? + $default : + $self->y_n( edit_query( $query, $default ), $default ) ; +} + +sub edit_query { + + my ( $query, $default ) = @_ ; + + chomp $query ; + + $default ||= '' ; + + my $last_line = (split /\n/, $query)[-1] ; + + if ( length( $last_line ) + 2 * length( $default ) > 70 ) { + + $query .= "\n\t" ; + } + + return $query ; +} + +# low level file i/o subs. should be replaced with File::Slurp. stem +# should depend on it + + +sub read_file { + + my ( $file_name ) = @_ ; + + local( *FH ); + + open( FH, $file_name ) || croak "Can't open $file_name $!"; + + return if wantarray; + + read FH, my $buf, -s FH; + return $buf; +} + +sub write_file { + + my( $file_name ) = shift ; + + local( *FH ) ; + + open( FH, ">$file_name" ) || croak "can't create $file_name $!" ; + + print FH @_ ; +} + +1 ; diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..2fdc692 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,189 @@ +Stem Version 0.11 +================= +Fri Dec 1 03:50:58 EST 2006 + + +* The Stem event loop is layered and can easily wrap other event + loops. Supported event loops include Event.pm, perl/tk and wxperl and + a pure Perl event Loop. See Stem::Event.pm and Stem::Event::EventPM.pm + for more. Other event loops that need wrapping include POE, Gtk and Qt. + +* There is a pure Perl event Loop so Stem now runs on windows or on any + system where Event.pm isn't installed. + +* Full support for SSL on all socket related objects and cells. + Converting from a plain socket to an SSL socket just requires adding + the SSL arguments to the Stem::Socket contruction. All Stem modules + that use sockets use Stem::Socket which in turn uses IO::Socket::SSL + (and that needs openssl). If that module is not installed, its test + will be skipped. + +* There is a Stem::UDPMsg cell that provides a UDP socket client and/or + server. + +* The new Stem::Cell::Flow module provides a way for Stem cells to + handle a mix of sync/async (local/remote) method calls. You can create + a logic flow with if/else/while command in a simple mini-language. + This allows a cell to do complex operations such as accessing a DB + (via Stem::DBI) or fetching web pages and mix that with local method + calls that crunch the data. Normally this would entail a complex state + machine or having each method know the next one to call but + Stem::Cell::Flow allows you to convert that to a much simpler logic + flow. See the test script t/cell/flow.t and the lib/Stem/Test/Flow.pm + module for more on this. It requires Parse::RecDescent to be installed + (the test will be skipped if it is not found). + +* A bunch of new tests were added: + + Event loops + Sockets (both with and without SSL) + Stem::Cell::Flow + +* There is a brand new install based on Module::Build. A Makefile.PL is + provided but it is a wrapper around the Module::Build installer. Stem + can now be installed with CPAN.pm + +* There is more pod (and more is needed). All contructor attributes are + now automatically updated to pod by the stem2pod utility. That script + also inserts pod templates for all methods. This will make it easier + to add pod and keep it up to date. + +* Cleaned up the INSTALL, README and other top level documents. + +* Many more changes than I have room to write in this file. + +Stem Version 0.10 +================= +Nov-11-2002 + +* The version number has been bumped to 0.10 because of the large number + of changes and the major improvements in reliability and speed. + +* Renamed Stem::TtyMsg to Stem::Console. Now it doesn't need any + arguments to be initialized. A Stem envioronment variable can be set + which will disable it. All the configuration files and demo scripts + have been updated to use it. The old TtyMsg module has been deleted. + +* Added Stem::Test::Echo.pm and Stem::Load::Driver.pm. They support + benchmarking of basic message passing in multiple modes. + +* Stem::Msg now only accepts the string format of message + addresses. This speeds up message creation and simplifies message + handling and delivery. + +* Stem::Class now supports attribute type checking and + conversion. Supported types include boolean, list, hash, LoL (and + friends), object and handle. + +* Stem::Conf has better error reporting. The Cell name and class are now + printed with the error. + +* Added the reply_type field to messages. This is used to make simple + state machines with messages (used in the work sequencing system). + +* Stem::Portal has been improved and now use the Stem::Packet module to + handle its buffering. + +* Added Stem::Packet and Stem::Codec::*. These modules support + serializing (marshalling) Stem data for use in pipes and files. The + codec to be used can be selected in the configuration file. This is + now used in Stem::Portal and all worker mode operations. Current codec + include Perl (Data::Dumper/eval), YAML, and Storable. + +* Split Log.pm into Log.pm and Log/Entry.pm. Moved Stem::LogTail.pm to + Stem::Log::Tail.pm. Added Stem::Log::File which handles physical log + files including rotation and archiving. + +* Added Stem::Trace.pm which gives modules a simple way to inject log + entries during program execution. It allows for creation of customized + Trace functions which can have defaults and different calling styles. + +* Split Stem::Cell.pm into multiple files to make it easier to + maintain. These include Stem::Cell::Sequence.pm, Stem::Cell::Clone.pm, + Stem::Cell::Pipe.pm and Stem::Cell::Work.pm + +* Added the Stem::DBI module which is a Stem message based front end to + DBI. This Cell can be configured with the all the SQL needed for your + application which is shared by all the Cells that use it. It can be + run in its own Stem Hub (process) thereby providing non-blocking + access to the DB from other Hubs. A farm of these proxies can be + created and managed by a WorkQueue Cell which will support parallel DB + access from one or more Hubs. + +* Added work sequencing support in Stem::Cell. This allows any Cell to + call local or remote methods and to manage their flow control. This is + an interim version that will be superseded by a new mini-language that + will be easier to code and will support more complex flow + (e.g. if/else/while). This is a very powerful feature that simplifies + complex state operations to simple flow control statements. + +* Added the Stem::WorkQueue module which allows a set of work request + messages to be distributed to a set of worker Cells. Modified + Stem::Cell to support a cell to request a new work message. + +* Created worker mode where an object (or data structure) gets + sent to a Cell via a message. The Cell can then write the object to a + socket or process or crunch it. The Cell then gets back the object + (presumably modified) and sends it back to the originator of the work + request. This is done in Stem::Cell and used in Stem::Proc, + Stem::Sock::Msg and Stem::DBI. + +* Created worker ready mode where a Cell can send out a message stating + it can receive a work message. Typically this will go to a WorkQueue + Cell. This message is sent out when the Cell starts up or after it + completes a previous work request. + +* Added Stem::Boot.pm which will run a set of external programs and + monitor them. Their output can be logged and they can be + restarted. Other options (for each program) include setting the + initial directory and which host to run it on (via ssh if desired). + The set of programs and options are loaded via a configuration file + specific to this module. + +* Added Stem::Inject.pm which has one method which will connect to + a Stem Hub and send it a single message. Then it will read back a + single message and return its data. This is meant to be used in Perl + programs that are not Stem driven and want to communicate with Stem + cells. + +* Updated the Stem Cell cookbook with more examples. It now shows how to + create class and object cells and also how to create cloneable Cells. + +Stem Version 0.06 +================= +Feb-26-2002 + +* Stem is now released under the GPL. If you want to use Stem in a + product that will be sold, contact us about acquiring a commercial + license. + +* A simple and easy to use installation script is now included. The + installation script allows you to have a Stem environment up and + running in a matter of minutes. Read the INSTALL document to learn how + to run this script. + +* There is a new set of cookbook documents with examples. These show + you how to develop new Stem Cells. + +* Complete documentation is now included for all demonstration + scripts included with Stem. + +* Stem Message Addresses can now be in a string form as well as the hash + form. This simplifies creating addresses in Stem configurations. All + the installed configurations now use this format. See the Address + design notes for more on this. + +* The Stem::Cell module has been added. It supports generic functionality for + Stem Cells, including cloning, asynchronous I/O, and pipes. It + currently is used by the Stem::Proc and Stem::SockMsg modules. All + future Cells that need those services can use this module. + +* The modules Stem::Proc and Stem::SockMsg have been rewritten to use + Stem::Cell. They are much shorter and simpler now that the common + Cell functions are handled by Stem::Cell. + +* A new module, Stem::Gather, has been added. It allows you to + synchronize multiple asynchronous events. It triggers a callback when + all of its required tokens have been 'gathered'. + +* Bug fixes and general system improvements. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..a83b226 --- /dev/null +++ b/CREDITS @@ -0,0 +1,3 @@ +People besides Uri who've contributed to Stem code and docs: + +Dave Rolsky - various code, bug fixes, docs, etc. diff --git a/Cookbook/World1.pm b/Cookbook/World1.pm new file mode 100644 index 0000000..fbd8d45 --- /dev/null +++ b/Cookbook/World1.pm @@ -0,0 +1,82 @@ +package World1; + +# This is class level cell with no constructor or alias registration. +# It has one simple command message handler + +sub hello_cmd { + + return "Hello world!\n"; +} + +=head1 Stem Cookbook - World1 + +=head1 NAME + +World1 - A minimal class level B cell. + +=head1 DESCRIPTION + +This is the simplest possible B class level cell. It contains a +single method named C. Because this method ends in C<_cmd> +it is capable of being invoked remotely via a command message and have +its return value sent back as a response message to the sender, which +in this example is the Stem::Console cell. + +=head2 COMMAND METHOD + +The following code snippet in the F class cell is the method +that will receive a hello command from a remote sender. + + package World1; + + sub hello_cmd { + + return "Hello world!\n"; + } + +B makes the creation of Command message handling methods very +I. Any return with defined data will automatically be sent back +to the sender of this command in a response type message. In the +method above we return the "Hello world!\n" string which will get printed on +the console. + +For more information on how a message is routed to its destination +cell in B please see the F. + +=head1 THE CONFIGURATION FILE + +The following B configuration file is used to bring a +World1 class level cell into existance in the B environment. + +- + class: Stem::Console +- + class: World1 + +The first entry is C, a class level cell allows a user +to manually send command messages into the B system. It is not +required for this module, but it is used in this example to send +messages to the World1 class and to print responses from it. The +second entry loads the C class. We can now refer to this class +cell as I when we want to send it a message. + +=head1 USAGE + +Execute C from the command line to run this configuration. +You will be greeted with the B> prompt. It is now +possible to send a message manually to I. Type the following +command at the B prompt: + +B + +This is standard B Console syntax, the cell address followed by +the command name. This will send a message world_cmd method in the +C class cell. That method returns a value, which is converted +into a response message addressed to Stem::Console (the originator of +the command message), and its data will be printed on the console terminal. + +B<"Hello world!"> + +=cut + +1 ; diff --git a/Cookbook/World2.pm b/Cookbook/World2.pm new file mode 100644 index 0000000..0b28496 --- /dev/null +++ b/Cookbook/World2.pm @@ -0,0 +1,106 @@ +package World2; + +# This is class level cell with no constructor or alias registration. +# It has two command message handlers, one to get the name and one to set it. + +my $name = 'UNKNOWN' ; + +sub hello_cmd { + + return "Hello world from $name\n"; +} + +sub name_cmd { + + my ( $self, $msg ) = @_ ; + + my $data = $msg->data() ; + + return unless $data ; + + $name = ${$data} ; + + return ; +} + + +=head1 Stem Cookbook - World2 + +=head1 NAME + +World2 - A minimal class level B cell with read/write data. + +=head1 DESCRIPTION + +This B class level cell is an extension of the World1 class. It +still has a method named C that will return the stored +name. The C method takes a message and set the $name to its +data. + +=head2 COMMAND METHOD + +The following code snippet in the F class +cell is the method that will receive a hello command from a +remote sender. + + package World2; + + sub hello_cmd { + + return "Hello world!\n"; + } + +B makes the creation of Command message handling methods very +I. Any return with defined data will automatically be sent back +to the sender of this command in a response type message. In the +method above we return the "Hello world!\n" string which will get printed on +the console. + +For more information on how a message is routed to its destination +cell in B please see the F. + +=head1 THE CONFIGURATION FILE + +The following B configuration file is used to bring a +World2 class level cell into existance in the B environment. + +[ + class => 'Stem::Console', +], +[ + class => 'World2', +] + +The first entry is C, class level cell allows a user to +manually send command messages into the B system. It is not +required for this module, but it is used in this example to send +messages to the World2 class and to print responses from it. The +second entry loads the C class. We can now refer to this class +cell as I when we want to send it a message. + +=head1 USAGE + +Execute C from the command line to run this configuration. +You will be greeted with the B> prompt. It is now +possible to send a message manually to I. Type the following +command at the B prompt: + +B + +This is standard B Console syntax, the cell address followed by +the command name. This will send a message world_cmd method in the +C class cell. That method returns a value, which is converted +into a response message addressed to Stem::Console (the originator of +the command message), and its data will be printed on the console terminal. + +B<"Hello world!"> + +=head1 SEE ALSO + +F + +F + +=cut + +1; diff --git a/Cookbook/World3.pm b/Cookbook/World3.pm new file mode 100644 index 0000000..d0c58a3 --- /dev/null +++ b/Cookbook/World3.pm @@ -0,0 +1,235 @@ +package World3 ; + +use strict; + +# This is the specification table that describes the attributes for +# this object. The only attribute is the name of the planet and it +# defaults to 'X' + +my $attr_spec = +[ + { + 'name' => 'planet', + 'default' => 'X', + }, +]; + +# The new method constructs the object which is returned to the +# configuration system where it will be registered. + +sub new { + + my( $class ) = shift ; + +# The call to parse_args takes the attribute specification and the +# configuration arguments and creates a cell object + + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + + return ( $self ); +} + +# This command method is similar to the one in World1 except we +# we use the object argument and return the name from that object. + +sub hello_cmd { + + my( $self ) = @_; + + return "Hello world from $self->{'planet'}!\n"; +} + +=head1 Stem Cookbook - World3 + +=head1 NAME + +World2 - A simple object level B cell. + +=head1 DESCRIPTION + +This cell is an extension of the B cell. In this example, +instead of a single class cell with a fixed response value, we now can +create multiple cells (registered objects) each with their own private +data. The world_cmd method will return the planet's name stored in the +cell. + +=head1 CREATING THE CELL + +This cell illustrates the basic way to construct objects in Stem. + +=over 4 + +=item * + +A specification table is required to describe the allowed attributes +of the object. This is a list of hashes with each hash describing one +attribute. It is usually defined in a file lexical variable commonly +named $attr_spec which is assigned an anonymous list of attribute +descriptions. The fields that describe the attributes are defined in +the F module. + +=item * + +An object constructor is called and is passed a list of key value +arguments. This class method can be called via a configuration (which +uses default name of 'new') or from any Stem code. The constructor +passes its attribute specification table and the passed arguments to +the Stem::Class::parse_args routine which returns the new object The +constructor method checks if an error happened by seeing if that +returned value is an object (ref is true) or else it must be an error +string. Any error string is returned to the caller of this +constructor. This is the standard way Stem handles errors, references +are good values and scalars (error strings) are bad. This propogation +of error strings up the call stack is consistantly used in all Stem +modules. After a successful construction of an object, the constructor +method can do additional work and then it returns the object. The +caller of the constructor will also check for an object or error +string. The common case of a configuration file constructing a Stem +object cell with register a good cell or print the error string and +die. + +=back + +=head2 ATTRIBUTE SPECIFICATION + +Object cells require an attribute specification that describes +the information we want to exist independently in each object +cell when it is created. The following is the attribute specification +used in C: + +$attr_spec = +[ + { + 'name' => 'planet', + 'default' => 'X', + + }, + +]; + +This specification indicates that this cell has an attribute +named I. It will default to the value of I if +this attribute is not specified in the configuration arguments +for this cell. Some of the attribute specification tags are I, +I, I, I, I, and I. For more +information on cell configuration please see +B and +B. + +=head2 OBJECT CONSTRUCTOR + +This is a minimal B constructor with the usual name I. you +can invoke any other method as a constructor from a configuration by +using the 'method' field: + +sub new { + + my ( $class ) = shift; + + my $self = Stem::Class::parse_args( $attr_spec, @_ ); + return $self unless ref $self ; + + return ( $self ); + +} + +To create a B object cell we call the C +routine and pass it the object cell attribute specification and the +rest of the arguments passed into this constructor. The rest of the +arguments come from the I field in the configuration for this cell. +The parse_args function then returns the newly created object to the +caller, which is usually the configuration system but it could be any +other code as well. An important observation to make here is the +B error handling technique. Errors, in B, are propagated +up the call stack bu returning an error string rather than a +reference. This is the typical Stem way of determining whether of not +an error condition had occurred. Constructors or subroutines which +normally return objects or references will return a string value as +an error message. This is always checked by the caller and will usually +be passed up the call stack until a top level subroutine handles it. + +=head1 CREATING THE CONFIGURATION FILE + +The following B configuration file is used to bring a +World2 object level cell into existance in the B environment. + +[ + class => 'Console', +], + +[ + class => 'World2', + name => 'first_planet', + args => [], + +], + +[ + class => 'World2', + name => 'second_planet', + args => [ + planet => 'venus', + + ], + +], + + +As explained in F, we create a +C cell to allow for the creation of a Stem console +to manually send command messages and display their responses. +We also create two object level C cells. +The first, we name I and it defaults to having its planet +attribute set to 'first_planet'. The second, we name I and set its +planet attribute to 'venus'. + +Using the I specifier in the cell configuration indicates +that we are creating an I cell rather than a class cell. +It indicates to the B cell creation environment that we +wish to execute the constructor of the specified class to +create an object of the class rather than using the B +module as a class itself. Using object cells allow us to instantiate +multiple objects with unique values, addressed and subsequent +behavior. + +=head1 USAGE + +Execute C from the command line to run this +configuration. You will be greeted with the B> prompt. +It is now possible to send a message manually into the system. + +Type the following at the B prompt: + +B + +This will show the status of the local B hub. You +will notice the two entries for the object cells created +by the configuration file under the object cell section. + +Now execute the same command as you did in F: + +B + +B + +B + +B + +As in F, the above triggers the C method. However, +now we are triggering the C method on separate object cells +rather than a single class cell. + + +=head1 SEE ALSO + +F + +F + +F + +=cut + + +1; diff --git a/Cookbook/World4.pm b/Cookbook/World4.pm new file mode 100644 index 0000000..2ae734e --- /dev/null +++ b/Cookbook/World4.pm @@ -0,0 +1,179 @@ +package World4 ; + +use strict; + +my $attr_spec = +[ + { + 'name' => 'planet', + 'default' => 'uranus', + }, +]; + +sub new { + + my( $class ) = shift ; + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + + return ( $self ); +} + +# based on who was the receiver of the message +# we return with the appropriate response + +sub hello_cmd { + + my( $self ) = @_; + + return "Hello world from $self->{'planet'}!\n"; +} + +sub name_cmd { + + my ( $self, $msg ) = @_ ; + + my $data = $msg->data() ; + + return unless $data ; + + $self->{'planet'} = ${$data} ; + + return ; +} + + + + +=head1 Stem Cookbook - World3 + +=head1 NAME + +World3 - Mixing class and object B cells. + +=head1 DESCRIPTION + +This is an extension of the B where +we talked about the creation of B class and object cells. In +this example, we take the idea of a class cell and an object cell +and combine them into a single B module. We then have +the ability of creating multiple cells (registered objects) +with their own private data and at the same time have a +class cell to manage a global resource. + +=head1 CREATING THE CELL + +The following lists the requirements for creating a B +object level cell: + +=over 4 + +=item * + +An attribute specification + +=item * + +An object constructor + +=item * + +A class registration + +=back + +=head2 CHANGES FROM PART 1 AND PART 2 + +Most of the code from Part 2 and Part 1 remain the same. We keep +the same attribute specification as well as the same object cell +constructor (except for a slight modification, see below). +You remember from Part 1 that we created a class level +B cell from the configuration file, + +[ + class => 'World1', + name => 'solar_system', + +] + +Because we do not have an args field, it means we are creating a +class cell. In this example, we want a class cell to be created +as a global resource only if an object cell is created. If the +class cell is supposed to manage global information for object +cells there is no need to create one if an object cell does not +exist. To get this type of behavior, we register the class cell +from within the B module rather than from the configuration +file, + + Stem::Route::register_class( __PACKAGE__, 'solar_system' ); + +This line (World3.pm, line 5) effectively registers the class cell +with the B message routing system using the package name +and a name we wish to register the cell as. + +We keep referring to the class cell as a global resource, so in +this example we create a global resource that the class cell will +manage, + + my @objects = (); + +On line 16 in World3.pm we create an array named objects that will +be used to hold a reference to each of the World3 cell objects that are +created from the configuration file (Note that this is not a +requirement for creating this module and is just used as an example. +It could have just as easily been a simple scalar, a hash, some +other kind of object, or even nothing!). + +In order to populate this array of the objects that are created from +the configuration file, we simply add them to the array when they +are created in the object cell constructor, + + push @objects, $self; + +This simply pushes the reference to the newely created World3 object cell +onto the objects array. The class cell can now be used to represent the +World3 object cells as a group. + +The next modification exists in the hello_cmd subroutine. We need a way +to distiguish whether or not a message is being sent to an object cell +as opposed to a class cell. As you might recall, the perl I function +is used to determine if a scalar refers to a reference or a normal +scalar value. If a subroutine is invoked from an object, the first +argument of the subroutine will be a reference to the object itself, +otherwise, it will be the string name of the class from which the subroutine +belongs. The following code demonstrates a new hello_cmd subroutine +that makes this distinction and performs accordingly, + +sub hello_cmd { + + my ($class) = @_; + + return "Hello world from $class->{'planet'}\n" if ref $class; + + my $response_string = ''; + foreach my $obj (@objects) { + + $response_string .= "Hello world from $obj->{'planet'}\n"; + } + + return $response_string; +} + +As you can see, we return the familiar "Hello world from $class->{'planet'}" +string, but this time we check to make sure $class is a reference before +returning the string. If it is not, we know that the hello_cmd was invoked +from a message that was intended for the class cell. If this is the case, we +concatenate a "Hello, World ..." string for each of the Hello3 object cells +that were stored in the objects array and send that string as a response +message to the sender. + +=head1 SEE ALSO + +F + +F + +F + +=cut + +1; diff --git a/Cookbook/World5.pm b/Cookbook/World5.pm new file mode 100644 index 0000000..62d3db0 --- /dev/null +++ b/Cookbook/World5.pm @@ -0,0 +1,59 @@ +package World5 ; + +use strict; + +use base 'Stem::Cell' ; + +my $attr_spec = +[ + { + 'name' => 'planet', + 'default' => 'world', + }, + { + 'name' => 'cell_attr', + 'class' => 'Stem::Cell', + }, +]; + +sub new { + + my( $class ) = shift ; + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return $self unless ref $self ; + +# Track the object in the class level hash %planets + + return ( $self ); +} + +sub triggered_cell { + + my( $self ) = @_ ; + + $self->{'planet'} = $self->cell_get_args( 'planet' ) || 'pluto' ; + + return; +} + +# based on who was the receiver of the message +# we return with the appropriate response + +sub hello_cmd { + + my( $self ) = @_; + + return "Hello world from $self->{'planet'}\n" ; +} + +=head1 Stem Cookbook - World3 + +=head1 NAME + +World5 + +=head1 DESCRIPTION + +=cut + +1; diff --git a/Cookbook/cookbook.txt b/Cookbook/cookbook.txt new file mode 100644 index 0000000..3e47f9a --- /dev/null +++ b/Cookbook/cookbook.txt @@ -0,0 +1,331 @@ +chapter: Hello world! +name: A Stem Cell Cookbook + +title: A few questions about a simple sub + +*: What can you do with this sub? +*: Can it be networked? +*: Can it receive messages? +*: Can it send messages? + +code: +package World1; + +sub world_cmd { + + return "Hello world!\n"; +} + +PAGE_END + +title: A few questions about a simple sub: Answers + +*: It can be networked under Stem +*: It can receive Stem messages +*: It can send Stem messages +*: No coding changes need to be made + +code: +package World1; + +sub world_cmd { + + return "Hello world!\n"; +} + +PAGE_END + +title: Loading World1 into Stem + +*: Stem configuration files load modules and register Cells +*: This configuration loads the console module and the World1 module +*: This is YAML (yaml.org) format and other formats are supported +*: Start it with: run_stem worlds.stem + +code: +--- #YAML:1.0 +- + class: Stem::Console +- + class: World1 + +PAGE_END + +title: Stem overview + +*: Stem is a message passing, event driven system +*: Stem Cells are Perl objects that are registered +*: Cells can be Perl classes or instantiated objects +*: Cells can send and receive messages. +*: Cell classes are loaded and Cells are created via Stem configuration files +*: Stem Hubs (processes) can support many active Cells +*: Stem Hubs can be connected in networks on one or more systems +*: Any Cell can send a message to any other reachable Cell + +PAGE_END + +title: Stem message delivery + +*: Messages are objects with address, content and related fields +*: The 'to' address of a message is used to identify the destination + Cell (Perl object) +*: The destination Cell is invoked by a method and the message is + its only argument +*: The method to be called is determined by the message type and command +*: A 'foo' command message is delivered to method 'foo_cmd' +*: A 'bar' type message is delivered to method 'bar_in' +*: Command methods can optionaly return data which is sent back in in + reply to the sender. + +PAGE_END + +title: Stem addresses + +*: Stem Addresses are name triplets: Hub, Cell, Target +*: The Hub is the name of Stem process +*: The Cell is the registered name of the Stem object or class +*: The target is the unique address of a cloned Cell +*: The Cell part of an address is required and the Hub and Target are optional +*: Addresses are written in string form in Stem configuration or from + the console. + +code: + cell + hub:cell + :cell:target + hub:cell:target + +PAGE_END + +title: Adding class level data + +*: We add class level data and a method to change it +*: The file lexical variable $name stores the planet's name +*: The method 'name_cmd' can set that name from the data in a message +*: A 'name' command message can be sent from the console or anywhere +*: The configuration file doesn't change other except for the name of the + class it loads + +code: + +package World2; +use strict ; +my $name = 'UNKNOWN' ; + +sub world_cmd { + return "Hello world from $name\n"; +} + +sub name_cmd { + + my( $class, $msg ) = @_ ; + + my $data = $msg->data() ; + + return unless $data ; + + $name = ${$data} ; + + return ; +} + +PAGE_END + +title: A basic object level Cell + +*: This module has an attribute specification so we can construct an object +*: If no planet name is passed to the constructor, it will be named 'X' +*: It also has a constructor method new() that is called from the configuration +*: The hello_cmd method now returns the object data in the planet attribute + +code: +package World3; + +use strict ; + +my $attr_spec = [ + { + 'name' => 'planet', + 'default' => 'X', + }, +] ; + +sub new { + my ( $class ) = shift ; + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return ( $self ); +} + +sub hello_cmd { + + my( $self ) = @_ ; + + return "Hello world from $self->{'planet'}\n" ; +} + +PAGE_END + +title: Configuration for object level cells + +*: We add a 'name' field which is the cell part of the address for this + object Cell +*: We add an 'args' field whose values are passed to the new() method +*: The object is constructed and registered with the selected 'name' +*: The initial value of the planet name can be set in the arguments +*: We created two object cells here using the same class but the first + uses the default planet name of 'X' and the second is named 'venus' + +code: +--- #YAML:1.0 +- + class: Stem::Console +- + class: World3 + name: planet1 + args: [] +- + class: World3 + name: planet2 + args: + planet: venus + +PAGE_END + +title: Changing object data + +*: All that is needed is a name_cmd method very similar to the one in World2 +*: It just changes the value in the cell itself +*: The configuration file needs to only change the class and cell names + +code: +package World4 ; + +use strict ; + +my $attr_spec = [ + { + 'name' => 'planet', + 'default' => 'X', + }, +] ; + +sub new { + my ( $class ) = shift ; + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return ( $self ); +} + +sub hello_cmd { + + my( $self ) = @_ ; + + return "Hello world from $self->{'planet'}\n" ; +} + +sub name_cmd { + + my ( $self, $msg ) = @_ ; + + my $data = $msg->data() ; + + return unless $data ; + + $self->{'planet'} = ${$data} ; + + return ; +} + + + + +PAGE_END + +title: Cloning object cells + +*: Cloned Cells are similar to sessions or state objects but are much + simpler to create and manage +*: Object Cells that use the cloning services of Stem::Cell are called + parent Cells +*: All cloned Cells are owned by the Parent cell +*: When a parent Cell is triggered (via a message or internal call), it + copies and registers the clone with a unique target address +*: The Stem::Cell module is inherited and it handles the 'cell_trigger' + command message +*: The specification must include a Stem::Cell class attribute + + Note that it has its own default +*: A callback to the 'triggered_cell' method is made in a newly cloned cell + +code: +package World5 ; + +use base 'Stem::Cell' ; + +use strict ; + +my $attr_spec = [ + { + 'name' => 'planet', + 'default' => 'X', + }, + { + 'name' => 'cell_attr', + 'class' => 'Stem::Cell', + }, +] ; + +sub new { + my ( $class ) = shift ; + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return ( $self ); +} + +sub hello_cmd { + + my( $self ) = @_ ; + + return "Hello world from $self->{'planet'}\n" ; +} + +sub triggered_cell { + + my( $self ) = @_ ; + + $self->{'planet'} = $self->cell_get_args( 'planet' ) || 'pluto' ; + + return; +} + + +sub name_cmd { + + my ( $self, $msg ) = @_ ; + + my $data = $msg->data() ; + + return unless $data ; + + $self->{'planet'} = ${$data} ; + + return ; +} + +PAGE_END + +title: Cloning cell configuration + +*: This is the similar to the World4 configuration but we added the + 'cell_attr' attribute and set its 'cloneable' flag to true + +code: + +- + class: World5 + name: planet5 + args: + planet: jupiter + cell_attr: + cloneable: 1 + + +PAGE_END diff --git a/Cookbook/worlds.stem b/Cookbook/worlds.stem new file mode 100644 index 0000000..7a2e7c5 --- /dev/null +++ b/Cookbook/worlds.stem @@ -0,0 +1,56 @@ +--- #YAML:1.0 +# This is YAML format Stem configuration file. It load a colsole cell +# and all the cookbook classes and registers a number of object cells +# +# Load and initialize the console class +- + class: Stem::Console +# Load the World1 class. We pass no arguments as this is a class level cell. +- + class: World1 +# Load the World2 class. We pass no arguments as this is a class level cell. +- + class: World2 +# Load the World3 class, construct an object with an empty argument list +# and register is as a Cell with the name 'planet1'. +# This Cell will use the default value for its name attribute +- + class: World3 + name: planet1 + args: [] +# Construct another World3 object with an argument list and register +# that as the Cell with the name 'planet2. +# This Cell will use the value 'venus' for its name attribute +- + class: World3 + name: planet2 + args: + planet: venus +# Load the World4 class, construct an object with an argument list and +# register that as the Cell with the name 'planet3. +# This Cell will use the value 'earth' for its name attribute +- + class: World4 + name: planet3 + args: + name: earth +# Construct another World4 object with an argument list and +# register that as the Cell with the name 'planet4. +# This Cell will use the value 'mars' for its name attribute +- + class: World4 + name: planet4 + args: + planet: mars +# Load the World5 class, construct an object with an argument list and +# register that as the Cell with the name 'system. +# This Cell will use the value 'jupiter' for its name attribute and be +# cloneable. The value in name will be the default name for all Cells +# cloned from this parent +- + class: World5 + name: planet5 + args: + planet: jupiter + cell_attr: + cloneable: 1 diff --git a/DEMO b/DEMO new file mode 100644 index 0000000..a3607c6 --- /dev/null +++ b/DEMO @@ -0,0 +1,174 @@ + + Stem Demonstration Scripts + +Stem comes with several demonstration scripts and example configuration +files which are used by them. You can optionally install the executable +demonstrations and their configuration files. Note that the actual +demonstration scripts don't do anything special to Stem, they just +create windows and execute run_stem inside them with selected +configuration files. They also create telnet connections inside other +windows which you can use to interact with Stem. You can manually create +the windows and do the same commands, these scripts are just +conveniences. In fact, a good way to learn more about Stem is to copy +and modify the configuration files used in the demonstrations and run +them yourself. + +When you run any of the demo scripts, the commands used to fork off an +xterm are printed. You can manually run those commands in your own +terminal windows if you want to experiment with or explore the Stem +configurations. If you kill the script (e.g. with ^C), all the created +xterm windows will be killed off leaving you with a cleaned up desktop. + +There are 4 demonstration scripts that come with Stem. They are briefly +described here and in more detail in other files. They all have some +common features which are also described in detail below. + + chat_demo and chat2_demo demonstrate a simple 4 user chat + server. chat_demo runs with a single Stem Hub and chat2_demo + uses 2 Hubs. Both bring up an xterm for each Stem Hub and 4 more + for the telnet sessions. Read DEMO_CHAT for the full details on + how to use this demo. + + inetd_demo emulates the inetd Unix super-daemon. It runs a + single Stem Hub in an xterm and 4 telnet sessions each in their + own xterm. The server process it runs is proc_serv in the bin + directory. You can run it directly from the command to see how + it behaves (it is a simple command/response program). Read + DEMO_INETD for the full details on how to use this demo. + + tail_demo monitors a typical log file and copies any new data it + finds there to a Stem Logical Log which writes it to a file and + optionally to other destinations. The status of the source file + is also sent to a Logical Log. Read DEMO_TAIL for the full + details on how to use this demo. + +Using the console Cell Stem::TtyMsg + +All of the demo configurations include the Stem::TtyMsg module which +allows you to enter command messages from the keyboard to a running Stem +Hub (process). This module is not required to run Stem but it is in the +demo configurations so you can interact with Stem and learn more about +it. + +It reads lines from STDIN (using the Stem::AsyncIO module so the rest of +the Stem Hub continues to run), parses them and sends out a command +messages based on the lines. It also can set key/values in the local +Hub's environment (%Stem::Vars::Env) which is used to control many Stem +operations. + +Command messages can generate response messages which will be sent back +to the TtyMsg Cell. These responses will be printed to STDOUT (again, +using the Stem::AsyncIO module). Any Cell can just send a data message +to the TtyMsg Cell (which is also registered with the class Cell name +tty) and its data will get printed. + +The rules for parsing lines input to TtyMsg are very simple. There are +three kinds of command lines: + + Direct commands + + The only direct command is 'help' which has to be the + only token on the line. It causes the help text to be + printed. + + Setting a Stem environment variable + + Key/values in the local Hub's environment can be set + with lines of the form: + + name=value + + Token has to be only word characters ([a-zA-Z0-9_]) and + the string after the = is the value (stripped of + surrounding white space). The Hub environment variable + with the name token is set to the parsed value. The + token and value are printed. + + You can also set any environment variable in any remote + Hub with the command message: + + hub:var set_env name=value + + Note that 'hub' must be the real name of that Stem Hub, + and var is already the registered class name of the + Stem::Vars class Cell. + + See below for more on entering command messages and the + env_notes document in /Design. + + Sending a Command Message + + A command message line starts with a Cell address and + then must have a command token. The rest of the line is + optional data for the command message. A minimal Cell + address is just a Cell name. It can have an optional Hub + name prefix. Also a target name can be suffixed after a + trailing :. So the only legal Cell addresses look like + this: + + cell + hub:cell + :cell:target + hub:cell:target + + If the Hub is missing the message is destined for the + local Hub and if the Cell doesn't exist here, it is + routed to the DEFAULT Hub. Read the Cell and Message + technical note for more on this. + + The next token on a message command line is the command + name and it is required. It will be the value of the + 'cmd' field in the message. The rest of the line is used + as the data field of the message. + + Some uses of command line messages are getting the + status of various Class Cells since almost all of them + have a status command. By listing all the registered + Cells you can see which ones you can send messages to. + + This will print all of the Cells in this hub. The + listing shows all object Cell and Class Cells with their + aliases. Command line messages should use the aliases + for the Cell name as the class names have colons in + them. + + This will print all of the Cells in the local hub. + + reg status + + This will print all of the Cells in the hub named remote. + + remote:reg status + + This will print all of the Portals in this hub. You can + use their hub names to send command messages to those + hubs. + + port status + + If you are running either chat demo you can change the + Switch maps which control which user get sent chat + messages. Here are some examples. The Switch Cell is + named sw, and the two Hubs in chat2_demo are named + chat_client and chat_server. Note that the 'sw' Cell is + only in the server Hub in chat2_demo, but since no 'sw' + Cell exists in the client Hub, any message sent to 'sw' + will still go to the server hub. So all of these map + commands can be issued from either Hub input and they + will work. The 'map' or 'status' token is the command + and the tokens after 'map' are data to the map + command. The first data token is the input map you are + setting and the rest of the tokens are the output maps + to send chat messages to. + + Print the current maps. + + sw status + + Change the a map to just b. + + sw map a b + + Change the d map to all users. + + sw map d a b c d diff --git a/DEMO_CHAT b/DEMO_CHAT new file mode 100644 index 0000000..d56895e --- /dev/null +++ b/DEMO_CHAT @@ -0,0 +1,252 @@ + + + Demonstration of Chat Server Emulation + +These two demonstration scripts emulate a very simple chat server with 4 +connected users. They showcase the Stem modules Stem::Switch (which +multiplexes Stem Messages) and Stem::SockMsg (a socket to message +gateway). chat_demo and chat2_demo behave the same but the former runs +as a single Stem Hub (process) and the latter as two Hubs (which can be +on separate systems - see below to experiment with that). Just like +with real chat, a user can type into their terminal and their text will +appear on the windows of other users. The Stem::Switch Cell (configured +as 'sw') acts as the chat server and it controls which users will see +the text from other users. You can change that user to user map by +issuing command messages to the 'sw' Cell (see DEMO for more on entering +command messages from the terminal). The two demo scripts are described +in detail below with sections on running, using, configuring and +experimenting with them. + +Running chat_demo + +The single Hub chat demonstration is called chat_demo and it uses the +chat.stem configuration file (found in conf/ and also where you +installed the demo Stem configurations). It is run with the simple +command: + +chat_demo + +To exit, just enter 'quit' in the chat_demo script window itself. It will +kill all the other windows and then exit. This will also happen if you +interrupt the demo script with ^C. + +If the -s option is used, then all the windows will use ssfe (split +screen front end which you can install from the Stem distribution) which +provides a command line edit and history window section and an output +section. A single Hub window will be created and then 4 smaller telnet +windows which will be connected to listen sockets in the Stem Hub. These +telnet windows are the chat users and they can type data and other users +will see the output. The telnet windows are named A, B, C and D. + +Using chat_demo + +Now type a line of text into A's window and hit return. Notice how all 4 +windows see that text. Now do the same for D. Only C will see its +text. This is controlled by the map in the Stem::Switch Cell named +'sw'. You can print out that map by sending a status command message to +that Cell. In the Hub window (named Stem) type this command: + +sw status + +You will see this printout: + + Status of switch: sw + + In Map: + + a -> a b c d + b -> a + c -> b d + d -> c + + Out Map: + + a -> A + b -> B + c -> C + d -> D + +This shows that a data message that came in with the Message target 'a' +will have its data copied to all 4 users and that 'd' will only send +text to 'c'. The Message target name is used as a key to index into the +In Map which gets a list of keys to the Out Map. The Out Map is then +indexed and a list of Cell addresses is found. Those addresses are sent +a copy of the data message. Now you should be able to predict what will +happend to text entered on B or C. Note that the internal keys are not +related to any other namespaces and are private to this Cell. The Switch +Cell's maps can be changed by command messages sent to this Cell. + +Also run this command in the Hub window: + +reg status + +This sends a status message to the Class Cell Stem::Route which has the +alias 'reg'. It returns a listing of all registered Cells with their +Cloned Cell names or Class Cell Aliases. You can run this command in any +Hub window to find the list of registered Cells. Most of the Class +Cells support and some Object Cells support status commands which can be +sent from the console. + + +Configuring chat_demo + +Look at the file conf/chat.stem. That is the configuration file used by +chat_demo. It is very simple and easy to understand. It is a Perl list +of lists structure with key/value pairs. Read the config_notes for more +on this. + +The first Cell configured is Stem::TtyMsg which supports typing in and +sending command messages. This Cell is used in all the demo +configurations. You can use it for any Stem application where you might +want to enter command messages by hand. + + [ + class => 'Stem::TtyMsg', + args => [], + ], + +Then come four Stem::SockMsg Cells named A, B, C and D. Each has a +single server socket listening on its own port. Also they are configured +(via the 'data_addr' attribute) to send their data to the same 'sw' Cell +but with the target addresse a, b, c, or d. These Cells allow +the user telnets to connect to this Hub. + +[ + class => 'Stem::SockMsg', + name => 'A', + args => [ + port => 6666, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:a' + ], + ], +], + +Finally we have the Stem::Switch Cell named 'sw' which controls the +mapping of users to users. It is just like the output from the first +status command we did above. It sets the input maps to the list of +internal target names and the output map is set to Cell addresses that +redirect the incoming messages. + +[ + class => 'Stem::Switch', + name => 'sw', + args => [ + + in_map => [ + + a => [ qw( a b c d ) ], + b => 'a', + c => [ qw( b d ) ], + d => 'c', + ], + + out_map => [ + + a => 'A', + b => 'B', + c => 'C', + d => 'D', + ], + ], +], + +Experimenting with chat_demo + +Now try to send a map command message to the 'sw' Cell. Enter this in +the Hub window: + +sw map b b c d + +and then type something into B. You should see it print on B, C, and D's +windows. You can change any of the maps. The 'map' token is the command +(as was 'status') and b is the input map name you are changing. The rest +of the tokens are the internal keys to output map. You can always print +out the map with the status command (as shown above) and verify your +changes. + +Running chat2_demo + +You run chat2_demo also by just typing the script name and its basic +behavior is just like chat_demo. The main difference is that it runs two +Stem Hubs and the four users are split with two connecting to each +Hub. So there are two configuration files named chat_server.stem and +chat_client.stem and they are in conf/ directory. When you run +chat2_demo, two Hub windows will be created with the names Chat1 and +Chat2. The two Stem Hubs are called 'server' and 'client' and those +names only reflect how they initially connect via sockets. Once they are +properly connected, they communicate in a peer to peer fashion. + +Using chat2_demo + +You can interact with chat2_demo just as you did with chat_demo. The +same user to user mapping is in effect and you can enter user text the +same way and also change the map. In fact you can enter and send all the +same command messages you did before in either Hub window and you will +see similar output. The major difference is that 2 of the output map +Cell addresses have Hub values. + +First enter the 'reg status' command in each Hub window. Notice how the +'server' Hub (window named Chat1) has the C and D Stem::SockMsg Cells +and the Stem::Switch Cell named 'sw'. The 'client' Hub (window named +Chat2) has only the A and B Stem::SockMsg Cells. This means that the +users connected to the 'client' Hub have to + +Now enter this command in each of the two Hub windows: + +port status + +That sends a 'status' command to the Class Cell Stem::Portal of the Hub. + +The 'server' Hub will print: + +Portal Status for Hub 'server' + client => Stem::Portal=HASH(0xd2978) + +This shows that this Hub can send messages to another Hub named 'client' +And the 'client' Hub will print: + +Portal Status for Hub 'client' + DEFAULT => Stem::Portal=HASH(0xd0930) + server => Stem::Portal=HASH(0xd0930) + +This shows that this Hub can send messages to another Hub named 'server' +and to one named 'DEFAULT' which is the same portal as 'server'. When a +message doesn't have a Hub name in its 'to' address and it can't be +delivered locally, it is sent to a Portal named DEFAULT if it can be +found. This is similar to the default route in IP networks. + +How chat2_demo is Configured + +Look at the files conf/chat_server.stem and conf/chat_client.stem. They +are the configuration files used by chat2_demo. They are basically +copies of chat.stem with support for two hubs and the Stem::SockMsg +Cells split between them. The new Cell addition to both is Stem::Portal +which supports send messages between Hubs. The 'server' Hub has this: + +[ + class => 'Stem::Portal', + args => ['server' => 1 ], +], + +That makes this Hub a server which listen for connections from other +Stem Hubs. The default port number is 10,000 (though this will change +soon). There is no 'host' attribute in that Stem::Portal Cell so it uses +the localhost interface by default. The 'client' Hub doesn't have a +server attribute so it is a client and it connects by default to +localhost and the port 10,000. + +[ + class => 'Stem::Portal', + args => [], +], + +Then come four Stem::SockMsg Cells with A and B in stem_client.stem and +C and D in stem_server.stem. And finally the Stem::Switch Cell named +'sw' which is only in stem_server.stem. Note that the output map for 'a' +and 'b' have the Hub name 'client' in their Cell addresses. This is +because the A and B users are connecting to the 'client' Hub and this +Stem::Switch Cell needs to know that so it can send them data. In a more +realistic chat system, these switch maps would be controlled by end user +commands and not by entering command messages. diff --git a/DEMO_INETD b/DEMO_INETD new file mode 100644 index 0000000..073cdc3 --- /dev/null +++ b/DEMO_INETD @@ -0,0 +1,231 @@ + + Demonstration of Inetd Server Emulation + +This demonstration script emulates the standard inetd super-daemon found +on all Unix systems. It showcases the Stem modules Stem::Proc (which +handle processes) and Stem::SockMsg (a socket to message gateway). This +demonstration runa a single Hub which listens for socket connections on +two ports. When a connection comes in, a new process is spawned which +interacts with the remote client that made the connection. This is +effectively what inetd does but inetd_demo has several major +advantages. StemInetd can insert filters and/or taps in the data stream, +all its connections and status changes can be logged and filtered and it +can be distributed securely across a network. The demo script is +described in detail below with sections on running, using, configuring +and experimenting with it. + +Running inetd_demo + +The single Hub inetd demonstration is called inetd_demo and it uses the +inetd.stem configuration file (found in conf/). It is run with the simple +command: + +inetd_demo + +To exit, just enter 'q' in the inetd_demo script window itself. It will +kill all the other windows and then exit. This will also happen if you +interrupt the demo script with ^C. + +If the -s option is used, then all the windows will use ssfe (split +screen front end) which provides a command line edit and history window +section and an output section. A single Hub window named Stem will be +created and then 4 smaller telnet windows which will be connected to +listen sockets in the Stem Hub. These telnet windows are the inetd users +and they can type data and interact with a simple command line server +program named proc_serv. The telnet windows are named A, B, C and D and +if you use ssfe, each will display the telnet command it ran. + +Using inetd_demo + +Now enter the help command into window A and hit return. These are the +commands you give the proc_serv application. Each of the user windows is +connected to a different running proc_serv process. You can verify this +by running the pid command in each window. Then have fun with the yow +and insult commands. Later in the experimenting section, you can change +the program to run and its options. The major difference among the user +windows is that two of them A and C are connected to the 6666 port in +the Hub and run the proc_serv in normal mode. The B and D windows are +connected to the 6667 port in the Hub and run the proc_serv with -n so +numbers the successful output lines. + +You can print out the list of all the registered Cells by sending a +status command message to the Stem:Route Class Cell which is aliased to +'reg'. In the Hub window (named Stem) type this command: + +reg status + +You will see this printout: + +Route Status for Hub '' + + Object Cells with Target names of their Cloned Cells + + A => Stem::SockMsg=HASH(0x2dd5a0) + :aaaaaa => Stem::SockMsg=HASH(0x2fda20) + :aaaaab => Stem::SockMsg=HASH(0x350ab0) + B => Stem::SockMsg=HASH(0x2ed474) + :aaaaaa => Stem::SockMsg=HASH(0x2c10ac) + :aaaaab => Stem::SockMsg=HASH(0x355040) + proc_serv => Stem::Proc=HASH(0x2fdbc4) + :aaaaac => Stem::Proc=HASH(0x3515c4) + :aaaaad => Stem::Proc=HASH(0x35554c) + :aaaaaa => Stem::Proc=HASH(0x2f881c) + :aaaaab => Stem::Proc=HASH(0x352660) + + Class Cells with their Aliases + + Stem::Conf => conf + Stem::Demo::Cmd => cmd + Stem::Hub => hub + Stem::Log => + Stem::Log::Entry => entry + Stem::Portal => port + Stem::Route => foo reg + Stem::TtyMsg => tty + Stem::Vars => var + +This shows the Parent Object Cells A, B and proc_serv each with their +own set of Cloned Cells. Below that are the loaded Class Cells. +The two Stem::SockMsg Cells have 2 telnet users connected to them and +the proc_serv Cell has cloned four Objects, each of which manages a +single process. Note that parent Cells don't do the work, they manage +the Cloned Cells which do it. + + +How inetd_demo is Configured + +Look at the file conf/inetd.stem. That is the configuration file used by +inetd_demo. It is very simple and easy to understand. It is a Perl list +of lists structure with pairs of keys and values. Read the config_notes +for more on this. + +The first Cell configured is Stem::TtyMsg which supports typing in and +sending command messages. This is done in all the demo configurations. + + [ + class => 'Stem::TtyMsg', + args => [], + ], + +Then come two Stem::SockMsg Cells named A and B. Each has a server +socket listening on its own port. Also they each will create a piped +connection to the cell named 'proc_serv'. The Cell B has one extra +attribute set, it adds the -n option when a process is spawned for it. + +[ + class => 'Stem::SockMsg', + name => 'A', + args => [ + port => 6666, + server => 1, + piped_to => 'proc_serv', + ], +], +[ + class => 'Stem::SockMsg', + name => 'B', + args => [ + port => 6667, + server => 1, + piped_to => 'proc_serv', + piped_args => [ '-n' ], + ], +], + + + +Finally we have the Stem::Proc Cell named 'proc_serv' which can clone +itself and spawn off processes. + +[ + class => 'Stem::Proc', + name => 'proc_serv', + args => [ + path => 'proc_serv', + use_stderr => 1, + piped_to => 1, + no_init_spawn => 1, + ], +], + +The 'path' attribute is the absolute path or program name to +be run. Note that this configuration assumes it will find 'proc_serv in +$PATH. The three boolean attributed tell the Cell that it should handle +output from stderr of the process, it is expecting a pipe connection +request and it should only spawn processes when it has been cloned. + + +Experimenting with inetd_demo + +These experiments are similar to the Hub and Portal ones in +DEMO_CHAT. They show you how to change the processes StemInetd runs, and +to distribute it over multiple systems over secure connections. Choose a +second system and make sure Stem is properly installed on it (NFS +mounting the tarball dir will help). + +To support a remote Hub connecting the Hub which owns the Stem::Proc +Cell, you have to add a Stem::Portal Cell to each of them. + +Make two copies of the configuration file conf/inetd.stem and call them +inetd_server.stem and inetd_client.stem + +Edit inetd_server.stem and rename the Stem::Hub configuration to +inetd_server. Also insert this Stem::Portal Cell configuration into it +replacing 'foo_host' with the server hostname: + +[ + class => 'Stem::Portal', + args => [ + 'server' => 1, + 'host' => 'foo_host' + ], +], + + +Edit inetd_client.stem and rename the Stem::Hub configuration to +inetd_client. Delete the Stem::Proc Cell configuration. Also insert +this Stem::Portal Cell configuration into it replacing 'foo_host' with +the server hostname: + +[ + class => 'Stem::Portal', + args => [ + 'host' => 'foo_host' + ], +], + + +You can create and modify as many of the Stem::SockMsg Cells as you want +on each Hub. Then in a window on the server box, do: + +run_stem inetd_server + +and on a window in the other box (called bar_host) where Stem is setup do: + +run_stem inetd_client + +You can create telnet sessions from the 'server' system that connect +to the ports of the Stem::SockMsg Cells. + +telnet localhost 6666 +telnet localhost 6667 + +And on the 'inetd_client' system, connect telnets to the ports of its +Stem::SockMsg Cells. + +telnet localhost 6666 +telnet localhost 6667 + +You can now interact with this Stem application just as you did when it +was running on one system as it did with inetd_demo. + +Instead of editing the configuration files, you could also set the +Stem::Portal host attribute by setting a command line argument or +environment variable. This command will make the 'server' Hub accept +connections from the 'foo' host interface: + +run_stem host=foo chat_server + +You can do the same for the 'client' Hub and have it connect to host +'foo'. By setting the STEM_HOST environment variable to the host name +you can get the same effect. diff --git a/DEMO_TAIL b/DEMO_TAIL new file mode 100644 index 0000000..f32dce3 --- /dev/null +++ b/DEMO_TAIL @@ -0,0 +1,251 @@ + + Demonstration of Log Tail + +This demonstration script illustrates Stem's ability to monitor log +files. It showcases the Stem modules Stem::Log (which logs messages) +and Stem::LogTail (which checks files for changes/updates). This +demonstration runs two hubs named archive and monitor. The monitor +hub watches a particular log file for changes. When a change occurs, +messages are sent to the archive hub to be logged. The archive +hub records the contents of the monitored log file (sent by the +monitor hub) and also records status messages sent by the monitor +hub. Log messages that are recorded by the archive hub can be +stored as either raw data or with custom formats. This demonstrates +a single log file being monitored, in a real world case there could be +several log files being monitored. It is easy to see in this example that +Stem can handle this with a small number of additions to its +configuration files. This can be distributed securely across a network. +This demo script is described in detail below with sections on +running, using, configuring, and experimenting with it. + +Running tail_demo + +The log tail demonstration is called tail_demo and it uses monitor.stem +and archive.stem configuration files (found in conf/). It is run with +the simple command: + +tail_demo + +To exit, just enter 'q' in the tail_demo script window itself. It will +kill all the other windows and then exit. This will also happen if you +interrupt the demo script with ^C. + +If the -s option is used, then all the windows will use ssfe (split +screen front end) which provides a command line edit and history window +section and an output section. Two hub windows named Archive and +Monitor will be created and a single shell window will be created with its +current directory set to tail/. Stem will create two log files +in the tail/ directory, bar.log and bar_status.log. bar.log is used +by the archive hub to record what ever is sent to that log file and +bar_status.log is used as a log file for status messages. +The hub windows can be used to interact with that hubs Stem environment +and the command line window can be used to put contents into a foo.log +file. The two hub windows use the standard module Stem::TtyMsg which +allows you to interact with them. In this demo they will be used to +modify the Stem environment which will affect the behavior of the +logical logs. + +Using tail_demo + +Initially, bar.log and bar_status.log will be empty files but in 10 +seconds (the tailing interval set in the monitor.stem configuration) +the status log will have a message about foo.log not being found. Run +ls -l several times in the shell (center) window to see when the status +has been logged and then read that file will + +$ cat bar_status.log + +Now type the following at the command line (in the shell window): + +$ echo 'foobar' > foo.log + +After 10 seconds (configured in the Stem configuration file) +you can look in bar.log and you will notice that there is a single line +that reads, "foobar", and in bar_status.log you will notice that there +is a status message saying that it is the first time that foo.log was +opened. And, of course, we have the line "foobar" in the monitored log +file, foo.log. + +Configuring tail_demo + +Look at the file conf/monitor.stem. That is one of the configuration files +used by tail_demo. It is very simple and easy to understand. It is a Perl list +of lists structure with key/value pairs. Read the config_notes for more +on this. + +The first Cell configured is Stem::Hub which names this hub as +'monitor'. + + [ + class => 'Stem::Hub', + name => 'monitor', + args => [], + ], + +Next comes the configuration for the Stem::Portal cell, + + [ + class => 'Stem::Portal', + args => [ + ], + ], + +It is important to note here that there are no args passed into the +portal. This means that the portal is a client portal, its default +host is set to localhost, and its default port is set to 10,000. For +more information on portals, read the portal design notes. + +The next Cell configured is Stem::TtyMsg which supports typing in and +sending command messages. This is used in all the demo configurations. + + [ + class => 'Stem::TtyMsg', + args => [], + ], + +The next cell is the application specific part in the monitor.stem +configuration, the Cell configuration for Stem::LogTail: + + [ + 'class' => 'Stem::LogTail', + 'name' => 'foo', + 'args' => [ + 'path' => 'tail/foo.log', + 'repeat_interval' => 10, + 'data_log' => 'archive:bar', + 'status_log' => 'archive:bar_status', + ], + ], + +This is the cell responsible for monitoring the indicated log file +(foo.log). It has arguments for the path to the monitored file, what +the time interval is (in seconds) to check the file for changes, the +data log, and the status log. Note that the address of the data and status +log indicates both the name of the hub that it is located at, as well as, the +name of the log cell. For more information on these configuration options +please see the tail log design notes. + +Now, lets take a look at the conf/archive.stem configuration file. This +file defines logical log files (bar and bar_status) that are used by the +monitor.stem configuration file to log the changes to foo.log. + +The first three cells that are configured are the same as the monitor +configuration, Stem::Hub, Stem::Portal, and Stem::TtyMsg. They are +for the most part identical. It is worth mentioning here that the +configuration for the Portal has its server boolean flag set to true, +indicating that this portal will be awaiting connection requests from +remote Portals anywhere on a network. + +The next three are the configurations for Stem::Log logical logs. The +first one is a typical logical log file configuration, + + [ + 'class' => 'Stem::Log', + 'args' => [ + 'name' => 'bar', + 'path' => 'tail/bar.log', + 'filters' => [ + file => 1, + forward => [ 'bar_stdout' ], + ], + ], + ], + +This is defining a logical log named "bar" that is associated with +a real log file indicated by the path, "tail/bar.log". It also +has a filters argument that allows Stem::Log::Entries to be filtered +before they are placed in the log file. The first of the filter +operations in the above configuration, 'file', indicates that the +incoming log entry should be placed in the file indicated by the 'path' +argument. Another one of these filter rules, 'forward, indicates that +the log entry is always forwarded to the log 'bar_stdout'. + + +The next Stem::Log configuration defines a logical log that conditionally +outputs its entries to STDOUT. It will write log file entries to STDOUT +if the 'bar_stdout' Stem environment variable is set to be greater than +the severity level of the log entry. + + [ + 'class' => 'Stem::Log', + 'args' => [ + + 'name' => 'bar_stdout', + 'format' => '%f [%L][%l] %T', + 'strftime' => '%D %T', + 'filters' => [ + 'env_gt_level' => 'bar_stdout', + stdout => 1, + ], + ], + ], + +This configuration specifies a format (overriding the default raw format +like bar.log) that displays the entry with timestamps, label, and level. +This allows you to customize your log entries to your liking. +To demonstrate the severity level detection, do the following at the +Stem prompt in the archive hub window (upper left), + +bar_stdout=8 + +This will ensure that if the severity level of the incoming log entry +is less than 8, it will be displayed to standard output. now, append a +line to the foo.log file in the command line window, + +echo 'hello log' >> foo.log + +You will see in the archive hub window a log message appear in stdout +in 10 seconds (according to this configuration), + +02/11/02 14:27:15 [tail][5] hello log + +This log entry is in raw format, the other Stem::Log configurations add +formats (as mentioned above). For more information on the format of the +log entries and filters please take a look at the log design notes. + +The final cell configuration is for filtering the status messages from the +LogTail Cell, + + [ + 'class' => 'Stem::Log', + 'args' => [ + + 'name' => 'bar_status', + 'path' => 'tail/bar_status.log', + 'format' => '[%f]%h:%H:%P - %T', + 'strftime' => '%T', + 'filters' => [ + file => 1, + 'env_gt_level' => 'bar_status', + tty_msg => 1, + ], + ], + ], + +As you can see, it has the same format as the previous Stem::Log +configurations. This configuration has both a logfile (tail/bar_status.log) +and the ability to display the status message if the severity level is less +than the Stem environment variable 'bar_status' to the tty message +console (indicated by the tty_msg filter operation, which is set to true). +Note that tty_msg is different than stdout, the message is being sent to +the TtyMsg module for output to the console versus just using stdout +directly. There are also other actions including custom ones. + +Let's see this in action, close the three widows created from the tail_demo +script and re-run tail_demo. do the following at the +Stem prompt in the archive hub window, + +bar_status=8 + +This will ensure that if the severity level of the incoming log entry +is less than 8, it will be displayed to standard output. now, append a +line to the foo.log file in the command line window, + +echo 'hello again log' >> foo.log + +You will see in the archive hub window a log message appear in stdout +in 10 seconds (according to this configuration), + +[21:58:04]trichards-linux.alias.net:monitor:/opt/bin/run_stem - LogTail: first open of /opt/bin/tail/foo.log + + diff --git a/Design/Stem-Mon b/Design/Stem-Mon new file mode 100644 index 0000000..9c939dc --- /dev/null +++ b/Design/Stem-Mon @@ -0,0 +1,49 @@ +This module will be on the server: +SNMP Trap Support (sending and/or receiving) +----------------- +Net::Snmp +--supports v1 and v2 +--can be made non-blocking via new...-nonblocking +--default is to block + +These modules would be run on each client based on a config file +of sorts. + +User Logins +----------- +something to read a variant of /var/log/lastlogin, wtmp,etc +we can do this because the files are usually a fixed binary format + +Drive Space Monitoring +---------------------- +File::df + +--requires: statfs() +--statfs() can be prevented by blocking (on bsdi at least) +--works with, solaris, sunos, hp/ux, osf/1, linux + + +Running process monitor +----------------------- +Proc::Processtable +--requires: File::Find, Storable +--works with: Linux, Solaris, aix, hp/ux, fbsd, irix, osf, bsdi, nbsd +---Watchdog::Process says +# This class is unreliable on Linux as +# Proc::ProcessTable::Process::cmndline() sometimes returns undef. +--hrm...does this mean Proc::Processtable is broke? + +Load monitoring +--------------- +Unix:Processors, gives info of whether a processor is online + how many processors, speed of each processor + +Sys::CpuLoad +--works with: any os that has /proc/loadavg or system call + equivalent of /usr/bin/uptime + +Log File Monitoring +------------------- +File::Tail +--I'm sure this one blocks...duh...but it can be changed +--but this is very useful for monitoring purposes (see swatch for an example) \ No newline at end of file diff --git a/Design/arch_notes b/Design/arch_notes new file mode 100644 index 0000000..cdd60c6 --- /dev/null +++ b/Design/arch_notes @@ -0,0 +1,37 @@ + Stem Architecture Notes + +Stem is a network application development toolkit and a suite of network +applications. Its goal is to transform common network tasks from +programming to configuration and to make solving difficult network tasks +much easier. + +A running Stem system on a network consists of one or more +interconnected Stem daemons which are called Hubs. Each Hub contains a +collection of modules and objects which are called Cells. Stem Cells are +capable of sending and receiving messages to any other Cell in the Stem +system. Modules and Cells are loaded or created in a Hub by using +configuration files. Configurations can be loaded and sent to any +connected Hub where they will be executed. + +Stem's core set of modules provide all of the common operations needed +including message passing, interprocess communication, asynchronous +buffered I/O, socket connections and timers. Other modules which can be +loaded and configured, perform such tasks as process creation and +management, log file management, gateways, protocol support, message +switching, etc... + +Configuring Stem Cells has been designed for simplicity and +flexibilty. All Cells use a common style of attribute/value pair +arguments with well chosen default values which make common situations +easier to configure. Many example configuration files come with Stem as +well as demonstration scripts which run working applications such as +inetd emulation, log file transferring and chat servers. + +Stem Messages are how Cells communicate with each other. Their names +are an ordered triplet: Hub, Cell and Target. Each Cell registers itself with +a unique name and optional Target name in its Hub. All Hub names in a +Stem system must be unique too, which makes the address triplet +sufficient for any Message to be directed to any Cell. + +For more detailed information on the architecture and design of Stem, +read the other technical notes. diff --git a/Design/asyncio_notes b/Design/asyncio_notes new file mode 100644 index 0000000..54d1714 --- /dev/null +++ b/Design/asyncio_notes @@ -0,0 +1,24 @@ + Stem::AsyncIO Design Notes + +The Stem::AsyncIO module provides a buffered I/O interface to sockets +and process handles. It is used by a variety of modules such as +Stem::Proc, Stem::SockMsg, Stem::Portal::Stream to do the common +function of doing their buffered I/O. + +The constructor takes an owner object and up to 3 handles: input, output +and stderr (for processes). The input and stderr handles are monitored +with read events and when data is available, it is read and a callback +is made to the owner object with the data as its argument. + +The write handle is fully buffered and the module provides non-blocking +asynchronous output to it. Data to be sent to the handle is passed in +with the write method. If there is any output data buffered, a write +event monitors the handle and triggers a callback when data can be +written to it. The callback writes as much data as possible to the write +handle. + +If the read or stderr handle is being used and it is closed (the socket +is disconnected or the process exits), this is detected and a callback +to the owner object is made. + +This module is only used internally and should not be configured. diff --git a/Design/cell_notes b/Design/cell_notes new file mode 100644 index 0000000..3335925 --- /dev/null +++ b/Design/cell_notes @@ -0,0 +1,107 @@ + + Stem Cell Design Notes + +Stem Cells are the fundamental working unit in a Stem application. +Cells are Perl objects that have 3 primary characteristics: First, they +must be registered with a Stem address. Second, they must have public +methods that can take an incoming message as an argument. And third, a +cell must be able to generate messages. There are three major types of +Stem Cells: class, object and cloned Cells. These are described in +further detail below. + +Class Cells are created by a Stem class which registers itself (using +the Stem::Route::register_class routine at module load time) and are +always registered using its class name. Class Cells are typically +created by modules which manage some global resource and don't need to +have multiple object instances created. A common reason for this is a +module which has a 'status_cmd' (or similar) method that is used to +get the status of the whole module. The Class Cell registration makes +those methods accessible via messages. Some Stem classes such as +Stem::Conf, Stem::Portal, Stem::Msg are Class Cells. Some modules can be +a Class Cell and also create Object Cells. Class Cells can optionally +be registered with aliases. The aliases make it easier to send a command +message from the terminal (using Stem::TtyMsg) to a class Cell +(Stem::Route is aliased to 'reg', Stem::Cron is aliased to 'cron'). + +Object Cells are objects that are created by a class's constructor and +are then registered with the Stem::Route::register_cell routine. The +registration takes the object and a required name (unique to this Hub). +Most often an Object Cell is created by a configuration but any module +can construct an object and register it. Since configurations can be +loaded from files and executed anywhere, Stem Cells can be configured at +any time during the existance of the current Stem system. + +Cloned Cells are only created by existing parent Object Cells. (Parent +Cells are Object Cells set up to create Cloned Cells). When the parent +Cell gets some form of trigger (typically a socket connection or a +special command message), it makes a clone of itself and does whatever +special work the cloned object needs. The parent Cell owns a Stem::Id +object which it calls to generate a unique (within this Cell) Target +name which it uses to register the cloned Cell. So the new Cell has a +unique Cell/Target name pair which can be used in messages directed at +it. In a typical case, the new Cloned Cell will send a message elsewhere +informing some other object about its existance; e.g., The Stem::SockMsg +class can be configured to clone itself when a socket connection is made and +then it will send a 'pipe_start' command message out. In an Inetd +configuration that message would be directed to a parent Stem::Proc +Object Cell which will clone itself and fork a process. This clone will +respond to the SockMsg message with its new target address, thereby +setting up a Stem pipe between the socket and process. When either the +process exits or the socket is closed, the cloned Cells are notified and +they clean up and unregister themselves. + +You can always find the current set of Cells in a Hub by sending a +'status' command message to the Class Cell Stem::Route. This is also +registered with the alias of 'reg'. So from the terminal (if your Hub +has configured in Stem::TtyMsg) you can type: + +reg status + +to get the registered Cells in this Hub or + +:hubname:reg status + +to get the Cells in a remote Hub. + + +When a Stem message is delivered in a Hub, its 'to' address is looked up +in the Hub's registry and the message is delivered to the destination +Cell via a method. A Cell must have some well known methods that handle +incoming messages. These method names have well defined formats and +uses. In general there are three groups of incoming message methods. All +of these delivery methods get passed the message itself as their sole +argument. + +The first group of delivery methods are those that handle command +messages. These are named for the command (the 'cmd' field of a command +message) with '_cmd' appended. So a foo command message will be +delivered to the foo_cmd method if it exists in the destination Cell. If +a command message method returns a value, it is automatically sent back +to the originating Cell in a response message. + +The second group handles all other message types. They are named for the +message type with a suffix of '_in'. So a 'foo' type message would be +delivered to the 'foo_in' method if it exists in the destination Cell. A +very common message type is 'data' and it gets delivered to the +'data_in' method. + +The final group has the single method 'msg_in' which is used if no other +method can handle the message. This is the default message delivery +method. You can have a Cell with just this method and it should be +designed to handle all expected message types. + +The use of specific delivery methods is not critical but it encourages +cleaner Cell design by having methods focus on doing work and not on +deciding how to handle different message types. This is in keeping with +the Stem design philosophy of doing as much common work as possible +behind the scenes, while leaving only the problem specific work to the +Cell. + +There are no design considerations for sending messages from a Cell. It +just creates a message object with the class call Stem::Msg->new and +then dispatches it. If the message doesn't have the 'from' address set, +it will default to the address of the current Cell (if it is known). If +the code that generates a new message is not a registered Cell, then you +must specify the 'from' address as one can't be deduced. + +For more on Cell addresses see registry_notes and message_notes. diff --git a/Design/config_notes b/Design/config_notes new file mode 100644 index 0000000..df1408d --- /dev/null +++ b/Design/config_notes @@ -0,0 +1,227 @@ + + Stem Object and Cell Creation and Configuration Design Notes + +All Stem cells and objects share the same API style in their constructor +methods (commonly 'new'). All parameters are passed as key/value +attributes and processed with Stem::Class::parse_args which is driven +with a simple table of field descriptions. Each field is described by an +anonymous hash with attribute/values. Each allowed field must have a +name and it also can have several optional description attributes. Here +are the supported field description attributes: + + name The value of this is the name of this + field. Obviously the field name is required. + + required This is a boolean value that says this field + is required. If it is not set in the constructor + call, an error string is returned. + + default The value for this is the default for this + field. It is used if this field is not set in + the constructor. + + class The value for this is a Stem class (Perl + package) name. It means the value of this + parameter will be parsed as the arguments to the + constructor ('new' for now) of that class. The + object returned is saved as the value of this + field. If the called constructor returns an + error string, it is returned. + + class_args The value of this is an anonymous list + of attribute/value pairs for this class + field. They are passed after the caller's + arguments and so will override any duplicate + passed in parameters. + + callback The value of this is a code reference which is + called to do custom construction of this + field. The code ref is called with the new + object, the field name and the anonymous + reference which has the field's passed in + values. + + type The value for this attribute is the type of the + field. Currently unsupported, it is meant for + stronger type checking of parameters such as + booleans. This will be supported soon. + + env The value for this attribute is a name of a Stem + environment variable. If this name is found in + %Stem::Vars::Env then the value in that + hash is used as the value for this + attribute. This overrides any value passed in + the the constructor or a default option. + NOTE: Stem environment variables can be set from + many places including the command line, the + shell environment, command messages and the terminal. + +Here is a typical constructor from the Stem::Log class. It has 3 fields +of which the first is required the other two have defaults. The +beginning of the constructor sub is shown below and that same two lines +of code is used in almost every class constructor. + + +my $field_info = [ + + { + 'name' => 'log', + 'required' => 1, + }, + { + 'name' => 'level', + 'default' => 'info', + }, + { + 'name' => 'text', + 'default' => '', + }, + +] ; + +sub new + + my $self = Stem::Class::parse_args( $field_info, @_ ) ; + return $self unless ref $self ; + + +Object Creation Error Handling + +Stem cells and objects are being created all the time and in many +ways. There is a standard way Stem constructor subs return errors. If the +object construction works, it returns the object. If there is an error, +it returns the error string. The caller must test constructor +returns with ref to see if they worked. This makes it easy to pass back +error messages to higher level objects with this code shown above. + +The first line parses the passed arguments (in @_) with a field +description table. The second line tests if an object was created. If it +was (ref $self is true), then the constructor continues. Otherwise, the +error string is just returned to the caller. So the exact low level +error is propagated up the call tree. This is used consistently in all +of the constructors so even if you have a class which has a field which +is a class (see the 'class' field type above), and it has a parse error, +that error will be passed all the way up to the highest level call +(typically in the Stem::Config module). + +Stem Cell Configuration + +Stem cells are typically created by the Stem::Conf class. The primary +source of configuration data is from a file and that is handled by the +load method. Currently config files are legal Perl and are just parsed +with string eval. (Don't yell, I know it sucks but it is simple to +bootstrap.) The next revision of this module will tighten up the +specifications of config files and create a proper parser for it. The +parser will probably use Parse::RecDescent (written by our advisor +Damian Conway who will probably write the parser for us :). The config +syntax will probably be similar to what it is now, but barewords +(actually any token without whitespace) will be allowed anywhere. Only +value strings with white space will need to be quoted. Config keywords will +always be barewords. Fat comma will be supported and [] will demark +lists of anything. There won't be any hashes since this is really just a +mass of initializations and a list is fine to initialize a hash. + +A Stem cell configuration is comprised of a list of attribute/value +pairs. You can also have a list of cell configurations in one file, +but each configuration is handled independently. Each configuration +entry has only a few options in the usual format of key/value pairs. The +first is 'class', which is required and it names the Stem class which +will be configured. The next one is 'name' and it is optional but almost +always used. It is the name that this cell will be registered as and +that is the address that will be used to send messages to this cell. The +last major option is 'args' and its value is a list of attribute/value +pairs used to initialize the cell. Which set of configuration options +is what controls how a cell is created and/or registered. + +The 'class' option is first processed and if it is not loaded, Stem will +load it for you. This can be done remotely which allows for a servlet +style of behavior, i.e. a request can come in and be a configuration or +converted to one and the required Stem class will be loaded and a cell +created. That cell can then be passed a message and respond to it. All +of that can occur at runtime on demand. + +If the 'args' option is set, then a constructor of the class is called +and it is passed the attribute/value pairs from the list value of +'args'. The constructor method is defaulted to 'new' but that can be +overridden with the 'method' option. The constructor processes its +arguments (most likely using the Stem::Class::parse_args routine +described above) and has 3 possible return values. If it returns undef, +nothing more is done by the Stem::Conf module for this configuration. If +a string is returned, that is assumed to be an error message and it is +either printed or returned to the originator of this configuration. Any +other configurations in this file (or passed in remote configuration) +are skipped. If the retun value is a ref, then it is assumed +to be an object and it can be registered with the address in the 'name' +option. + +If the 'name' option is set, that will be used to register the cell or +class itself. In most of the configuration cases, an object is created +by the class constructor with the 'args' option and it is then +registered as a cell with that name for its address. If no 'args' +option is set, then the class itself is registered under the 'name' +value and it is a class level cell. There can only be one class level +cell for any class although it could be registered under multiple names +(aliases). In addition, the value of the 'name' option is passed along +with the 'args' values to the constructor as the attribute 'reg_name'. + +Here are some example classes which are configured in several of those +ways: + +The Stem::Hub class needs to be initialized with a registration name +but has no other attributes. So its configuration has a 'name' and an +'args' option whose value is an empty list (that forces the constructor +to be called). + + [ + class => 'Stem::Hub', + name => 'server', + args => [], + ] + +The Stem::TTY::Msg class configuration doesn't use the 'name' option and +it used an empty 'args' value. So its constructor is called and it +returns its single class level object, and that is registered under its +class name. + + [ + class => 'Stem::TTY::Msg', + args => [], + ] + + +The Stem::Demo::Cmd class is a class level cell that just has a 'name' +option in its configuration and that is used to register the class +itself. + + [ + class => 'Stem::Demo::Cmd', + name => 'cmd', + ] + + +The Stem::Sock::Msg is configured in the most common way, with 'name' and +'args' options and values for the arguments. + + [ + class => 'Stem::Sock::Msg', + name => 'C', + args => [ + port => 6668, + server => 1, + data_msg => [ + to_cell => 'sw', + to_target => 'c', + ], + ], + ] + + +Normally a single configuration file is loaded by the run_stem program +at start up time. The Stem::Conf module also supports loading a +configuration file via a command message or another configuration (which +is similar to include files or Perl modules). A configuration which +loads a configuration file can evaluate in the current Hub or send it to +any Hub in the Stem system. This allows for centralized management of +Stem configurations. As an aid to this, the stem_msg program can be used +to send a 'load' command message to a Hub. + diff --git a/Design/console_notes b/Design/console_notes new file mode 100644 index 0000000..f7cfe93 --- /dev/null +++ b/Design/console_notes @@ -0,0 +1,15 @@ + + Stem::Console Design Notes + +The Stem::Console is a class Cell which provides a simple command line +interface to a Stem Hub. It parses lines typed into stdin and creates +and dispatches command type Stem Messages from them. It also can accept +and print response messages sent back by the command method in the +addressed Cell. + +This class Cell is registered under its class name and with the +nicknames console, cons, and tty. It takes no configure arguments and +it just creates an Stem::AsyncIO object to handle the console I/O. + +When you see the Stem> prompt, you can enter 'help' to see how to enter +messages and commands. diff --git a/Design/cron_notes b/Design/cron_notes new file mode 100644 index 0000000..e272f8e --- /dev/null +++ b/Design/cron_notes @@ -0,0 +1,80 @@ + + Stem Cron System Design Notes + +Stem::Cron is designed to both supplant the standard OS cron and +extending it to support more useful time filters. The key difference +from the OS cron is that Stem::Cron sends a Stem message when it is +triggered instead of running a process. This message can be addressed to +any cell on any Stem hub and so it can cause any action to occur. To +emulate the OS cron, all the message needs to do is to trigger an +addressed Stem::Proc cell to spawn a process. + + + +Stem::Cron entries are similar to the OS cron. You can select a set of +time parts (minutes, hours, dates, months, days of week) to trigger the +entry and each part is a list which filters the triggers. Each minute +(run by infinite repeat timer) the list of cron entries is scanned. If +the current broken out time matches one corresponding value in each of +the parts of the entry, then the entry is triggered and its message +is dispatched. + +The cron entry is configured with name/value pairs which are the time +parts and the set of which values to trigger on in that part. If a time +is not specified, it is assumed to match all values for that time part +(like * in crontab). These two differences already make Stem::Cron +easier to configure than crontab. In addition, each Stem::Cron entry +must have a message value. This is sent when the cron entry passes all +of its time filters and gets triggered. The message can be any type, +carry any data and be addressed to any cell in the Stem network. + +Beyond the simple time filters of crontab, Stem::Cron allows you to +specify complex date descriptions such as last date of the month, first +weekday of the month, 2nd Thursday of the month, etc. + +Another feature I am looking at is not well defined but i have a use for +it. The log filters want to have a time based toggle so for example, you +can enable/disable sending logs to a pager at night by sending it the +right messages. By attaching those messages to properly configured cron +entries, you have automated managing the times when the log filter is +enabled/disabled. But there are several open design issues. First, what +is the initial boolean state of the log time filter? Maybe we should make +another boolean attribute: time_filter_enabled => 1. Then how often do +you send the boolean toggle messages? A normal cron time range would +send messages every minute from enable to disable time (huh?). or we can +just send the disable message on one tightly defined cron entry and the +enable on another. The initial boolean state is used and that will keep +the log filter in the right state. This time controlled boolean toggle +is a useful idea we can apply elsewhere. A module just for managing +these things could be a good idea. It would, of course, use Stem::Cron, +but it could be specialized in setting the cron entries up and other +stuff. + +Right now, Stem::Cron entries are only created in configuration +files. Creating remote entries would just require sending a config via a +message to the global Cron cell. + +Stem::Cron is a high-level layer above Stem::Event timers. The major +difference is that Stem::Event only uses callbacks, whereas +Stem::Cron only sends out messages. Also, Stem::Cron has a much more +powerful and flexible API for specifying the timing of these messages. + +Besides the emulation of the standard OS cron, Stem::Cron can send +out a message at a repeated interval which has a resolution of +seconds. + +TODO + +more testing + +fancy time parts. i have developed perl code that calculates most of +those dates so it should be simple to port the logic over to stem. + +timed boolean triggers needs more design work and then coding. + +Sending remote configs has not been developed yet but should be done +soon. should be generic and go into the Stem::Config module. + + + + diff --git a/Design/debug_notes b/Design/debug_notes new file mode 100644 index 0000000..e2529b1 --- /dev/null +++ b/Design/debug_notes @@ -0,0 +1,22 @@ + Stem::Debug Design Notes + +Stem modules have a need to report error conditions and trace critical +events while being able to control when, where and how this information +is presented. The Stem::Debug module provides a way for any Stem module +to create debug/trace subs customized to that module's needs. +Stem::Debug delegates all of this backend filtering, formatting and +distribution of these messages to the Stem::Log subsystem. In effect, +Stem::Debug creates specialized front ends subs to Stem::Log. + +Using Stem::Debug is very simple. A module just does a use Stem::Debug +statement with optional key/value parameters. More than one use +statement can be made in a module and each one will create a new debug sub +inside that module's namespace. When you create this sub, you can select +its name, which logical log the message goes to, and the label and severity +levels. Also a command line argument name may be specified for filtering +even before the log entry is generated. + +The final design of the Stem::Debug is still in flux. It is not yet +ready to be used by external developers. + +This module is only used internally and should not be configured. diff --git a/Design/env_notes b/Design/env_notes new file mode 100644 index 0000000..0fc2ed8 --- /dev/null +++ b/Design/env_notes @@ -0,0 +1,63 @@ + + Stem Environment Notes + +The global hash %Stem::Vars::Env is used to store environment values for +the local Hub. These values can be set in a variety of ways: + + Shell environment variables of the form STEM_* are located and + the STEM_ prefix is removed and the rest of the name is lower + cased. This name is set to the value of the shell variable. + + The command line of run_stem is parsed for name=value tokens. The + name/value pair is set in the Stem environment. + + The Stem::TtyMsg module parses its input lines for name=value + lines (only if the line doesn't contain a leading Cell address). + White space is allowed around each part and surrounding white + space is stripped from the value. The name/value pair is set in + the local Stem environment. + + The Stem::Vars Class Cell (aliased to 'var') can take a + 'set_env' command message. The data field is parse for + name=value just like Stem::TtyMsg does. This can be used from + the terminal of any Hub running Stem::TtyMsg to set environment + variables in any Hub. + +The environment values are accessable in several ways: + + A Stem module can import %Stem::Vars::Env via a use Stem::Vars + statement. The %Env hash can be directly used. + + The Stem::Vars Class Cell supports a 'get_env' command message + which uses the data field and the environment variable name. It + returns its value via the normal command/response message + mechanism. + + The Stem::Vars Class Cell supports a 'status' command message + which returns a printout of the entire Stem environment for this + Hub. It returns that via the normal command/response message + mechanism. + +Here are some of the places and ways Stem Environment variables are +used: + + An attribute description can specify an environment variable + name (via the 'env' option). If this environment variable is + found it will be used as a default value for this attribute + overriding the default option value if it was supplied + too. A passed in value will set the attribute regardless of the + environment. + + Logical Logs use environment variables to test whether a Log + Entry is filtered. The value of the variable is compared to the + Entry severity level with any one of the normal numerical + boolean tests and it sets the filter flag accordingly. + + The Debug/Trace subsystem uses environment variables in a way + similar to the Log subsystem. When a Debug routine is created, + an enviroment name can be set and its value is used as a boolean + to determine if this call to the Debug sub will happen. + +Stem environment values are (and will be) used in many places. If you +use them in configurations or in code, be sure that the names used are +unique. diff --git a/Design/event_notes b/Design/event_notes new file mode 100644 index 0000000..49d2b93 --- /dev/null +++ b/Design/event_notes @@ -0,0 +1,38 @@ + +Events + +The low-level core of a Stem hub is an event-loop engine. This +engine provides support for the common events needed in a networking +system, including reading and writing, socket connection and +acceptance, and timers. The Stem::Event system provides a high-level +API to these services to the rest of the hub while it, in turn, +relies on a lower-level event system which is currently Event.pm, +which is available on CPAN. This design isolates the actual event +engine used from the cells which need its services. There are plans +to support other event engines including Perl/Tk, and creating one +that runs on NT/Win2K. + +Stem::Event uses the standard Stem callback style which requires +parameters designating the object and method to call when an event +has been triggered. In typical Stem fashion, the method names have +useful defaults so a Stem::Event call is made with very few +parameters. + +Read and write Stem events take a required object and filehandle as +parameters, and an optional timeout value. If the I/O is not +completed before the timeout occurs, the timeout method is called +instead of the normal I/O completion method. The connect and accept +events also require an object and the appropriate socket parameters. +The connect event can take an optional timeout which behaves +similarly to the read/write timeout. A timer event can be created +which will be triggered after a given delay and optionally repeated +at a specified interval. As you would expect in an event system, +multiple instances of all these events can be active at the same +time. + +Most cells will never directly use the Stem::Event interface as there +are higher level cells that perform commonly needed services for +them. These include Stem::AsynchIO, Stem::Socket, and Stem::Cron. +However, this does not stop any cell from directly calling these if +it needs finer control over its events. + diff --git a/Design/id_notes b/Design/id_notes new file mode 100644 index 0000000..7308e4b --- /dev/null +++ b/Design/id_notes @@ -0,0 +1,18 @@ + + Stem::Id Design Notes + +The Stem::Id module generates unique Id strings for use as names in Stem +addresses. Its most common use is by parent Cells which clone themselves +and need a unique Target. The parent Cell uses its Cell name and the new +Target to register the cloned Cell. + +A typical use is by a Stem::SockMsg Cell which creates a Stem::Id object +during its own construction. When this parent Cell accepts a socket +connection, it clones itself and needs to register this new Cell with a +unique address. The parent Cell calls the next method of its Stem::Id +object to get a unique Id which is uses for the Target address of the +cloned Cell. Then the cloned Cell is registered with the parent Cell +name and the new Target name. This address is then sent to other Cells +so they can communicate with this new Cell. + +This module is only used internally and should not be configured. diff --git a/Design/index b/Design/index new file mode 100644 index 0000000..69395f3 --- /dev/null +++ b/Design/index @@ -0,0 +1,140 @@ + + Stem Technical Notes + + +Stem Architecture + + This describes the top level architecture of Stem, in particular + what its components are (Hubs and Cells) and how they work + together. + +Stem Cells + + This describes Stem Cells, the primary components of a Stem + system. It covers both class and object type Cells, their + creation and registration and their method conventions. + +Stem Messages + + This describes the content and fields of Stem messages. These are + used to communicate between Stem Cells. + +Stem Registry and Message Addresses + + This describes how Cells are registered so they can receive + messages and the way messages are addressed. + +Cell Creation and Configuration + + Stem Cells can be created internally via a constructor or from + external configurations. This describes the common systems used + to create and register Cells. + +Logging Subsystem + + This describes the Stem logging subsystem including logical + logs, log files, log filters and how to submit log entries. + +Cron Subsystem + + This describes the Stem Cron subsystem and how it can schedule + regular Stem message submissions based on time and date. + +Stem Events + + This describes the low level Stem Event subsystem including + reading, writing and socket events. + +Security Notes + + This describes the Stem security model and its features. + + + Stem Cell and Module Notes + +These are some of the Cells and modules in the Stem library. These notes +will describe what they are, why they are needed and how they are +used. Also some examples of their use and configuration are given. For +detailed descriptions of their configuration, attributes, and methods +see their documentation (available in the source tree or ) + +NOTE to jasmine: when we get the pod generation stuff going soon, we +will make html versions of the pod docs and web them. they need to be +linked from each cell tech notes and be in a documentation subdir with a +nav bar link. this is later but i am just letting you know now. + +NOTE: also i am going to list all of the cells here, even if they don't +have a tech notes page yet. make an empty one for them. we should come +up with a template for their names, format, etc. i want to drop the +'notes' part in most cases. + + +Stem::Conf + + This module parses and executes Stem configurations from both + local files and sent from remote Stem Hubs. + +Stem::Proc + + This Cell forks off and manages external processes. They can be + monitored and all their I/O can be easily be integrated with + Stem via messages, logs and/or callbacks. + +Stem::SockMsg + + This Cell connects to and accepts standard Internet + sockets. They can be monitored and all their I/O can be easily + be integrated with Stem via messages, logs and/or callbacks. + +Stem::Cron + + This module creates and manages scheduled message submissions. + +Stem::Log + + This module supports the Stem logging subsystem. It encompasses + the Stem::Log::File, Stem::Log::Filter and Stem::Log::Entry + classes. + +Stem::LogTail + + This Cell monitors an active external log file. It sends new + data into the Stem logging subsystem on periodic basis or on + demand. + +Stem::Switch + + This Cell is a general purpose Stem Message multiplexor. + Messages directed to this Cell can be + +Stem::Debug + + This class is used to create standard and customized debug and + trace subs in any Stem module. The used + +Stem::Id + + This class is used to create ID's for dynamically created + Cells that need unique addresses. + +Stem::Portal + + This class creates and manages connections between Stem Hubs. It + converts Stem Messages to/from a stream format for transmission + over the connection. + +Stem::AsyncIO + + This class is used by other Cells to create and manage buffered + I/O streams to sockets and process handles. + +Stem::Socket + + This class is the low level API to create socket connections and + accepted sockets. + +Stem::TtyMsg + + This class Cell is used to provide a TTY interface to a running + Stem Hub. Stem command messages can be entered and sent to Cells + and their responses are printed. diff --git a/Design/log_notes b/Design/log_notes new file mode 100644 index 0000000..0aecef0 --- /dev/null +++ b/Design/log_notes @@ -0,0 +1,133 @@ + + Stem Logging Design + +The Stem logging subsystem is designed to be very flexible, powerful and +yet simple to use. Log data comes into the system via a Log Entry which +is submitted to Logical Logs. Entries can be submitted to multiple +Logical Logs which can be local to the current Hub or on remote +Hubs. Each Logical Log processes the Entry which can be filtered, +redirected and written to physical log files. Logical Log filter rules +can match the text or label with regular expressions, test the range or +value of the level, check the time of day or do any boolean combination +of those. If an Entry passes a set of rules, then it is passed to a set +of actions which can execute a wide range of operations upon it +including printing the entry to a file, sending it via email or to a +pager, printing it to stderr, or the entry can be forwarded to other +Logical Logs. The full set of filter rules and actions are described +below. + +Log Entries are constructed with the 'new' method of the +Stem::Log::Entry class. The caller can set the entry's text, label, and +a severity level and the timestamp is automatically stored in the +entry. If any Logical Logs are specified the entry is submitted to +them. In any case, the entry object is returned and it can be submitted +to Logical Logs with its submit method. The class Stem::Log::Entry is +registered as a Cell so Log Entries that are forwarded from remote Hubs +can be handled by this class. Log Entries can be created by created and +submitted by code anywhere in Stem. Many Cells can be configured to +submit Log Entries which contain data or status information. The +Debug/Trace subsystem also generates Log Entries as do any monitoring +modules such as Stem::LogTail. + + +A Logical Log is constructed by the 'new' method of the Stem::Log +class. They are typically created by external configurations but some +modules create them internally for their own use. Each Logical Log on a +Hub must have a unique name and that is the name used to submit Log +Entries. Remote Logical Logs are referred to by a string of the form +'Hub:LogName'. Any place where you can specify a Logical Log name, you +can also use a remote Log name. + +When a Log Entry is submitted to a Logical Log it gets filtered and +processed. The Logical Log is configured with optional physical file and +filter attributes. If there is no filter in a Logical Log, its default +is to print any submitted Entries to its file (if there is one). Logical +Logs don't need to have a physical file attribute as they can filter and +print their Entries to many other possible destinations (see below for a +list of actions and Entry destinations). + +The 'path' attribute of a Logical Log specifies its file. Other +attributes control the long term management of the file. They include +when to rotate the log file, the format of the timestamp suffix of the +rotated files, any compression to be performed, where to move archived +logs, eternal programs to be called to process the log file, etc. These +log file handling attributes and their code support are under development. + +The filter attribute of a Logical Log consists of a set of key/value +pairs which are called filter operations. When an Entry is submitted to +a Logical Log which has a filter, a private hash copy of all of its data +is made and a special boolean called the filter flag is set in that +hash. All of the filter operations are processed sequentially and work +with that flag. The operations can be grouped into 3 types, flag +operations, rules and actions. Flag operations directly modify the +filter flag and its behavior which is used to control the rules and +actions of this filter. Rules are boolean tests that check the submitted +entry for some condition and can set or clear the filter flag. Actions +print or forward the submitted Entry only if the filter flag is +currently true. The filter flag is initialized to true so all actions +and rules will be executed until some rule or flag operation clears it. + +Flag operations are always executed regardless of the current value of +the filter flag. The current value of the filter flag can be set, +cleared or inverted. Also the boolean operation that is used with the +rules can be selected. It defaults to 'and' which causes each rule's +boolean result to be 'and'ed with the filter flag and stored there. If +the flag operator is set to 'or', then the rule result is or'ed with the +flag and stored back into it. The boolean test of the filter flag can be +inverted with the 'invert_test' flag operation. By combining the flag +operations and the negated prefix of rules (see below) you can get any +boolean combination of rules. If you want multiple sets of rules each +with their own set of actions in a filter, just set the filter flag to +true before each set of rules and follow them by their associated +actions. If you want to execute some actions if any of a set of rules is +true, set the filter flag to false, set the flag operation to 'or' and +set the test to inverted. The next rules will execute since the test in +inverted and the flags is false. If any rule returns true, it will will +set the flag since it is 'or'ed with it. The rest of the rules will be +skipped. Then the normal_test operation should be executed. The actions +that follow will only be executed if any rule was true. + +Filter rules are only executed if the filter flag is currently true (or +false when the inverted_flag operation is in effect). Each filter rule +name can be prefixed with 'not_' which will invert the results of the +rule. There are many builtin rules which are grouped into three +categories. The first group matches either an Entry label or text with a +regular expression. The second group compares the Entry severity level +with an integer. The third group compares the Entry severity level with +a global value in the %Stem::Vars::Env hash. Those hash values can +be set on the command line, from environment variables and by code. This +allows for fine control of how Entries get filtered by level. Examples +of using that facility are to enable debug/trace calls to output to +stderr or be forwarded to a remote Logical Log. + +Filter actions, like filter rules are only executed if the filter flag +is currently true (or false when the inverted_flag operation is in +effect). But actions cannot affect the value of the filter flag and are +meant to send Log Entries to different destinations. The builtin actions +can print Log Entries to stdout, stderr or the controlling TTY. Entries +can be emailed, sent to a pager, written to the console with the wall or +write commands, or forwarded to other Logical Logs. Of course they also +can be written the to physical file associated with this Logical Log. + +WARNING: Currently forwarding loops can be created with Log filter +actions. There are plans to detect them with either storing in the Log +Entry a hop count or a history of which Logical Logs it has seen. + +Custom filter rules and actions can also be created. Any module can have +them and they are called by their name which is the value of the +'custom' operation. The difference between a custom rule and action is +that the rules return a defined boolean value while the actions return +the undefined value (a plain return does that). + +When a Log Entry needs to printed by an action (which all builtin ones +except forwarding does), it must format the Entry. This is controlled by +the 'format' attribute of the Logical Log. The format value is similar +to sprintf and uses % as a field marker. It can print the Entry text +(%T), label (%L), level (%l), timestamp (%l) and original Logical Log +name (%N) (so forwarded Log Entries can say where they came from). The +default Log Entry format is %T which will just print the text. Also the +timestamp which is normally printed as an integer (Unix Epoch time) can +be printed with the %f marker in a strftime format. The attribute which +controls the time format is 'strftime'. The default strftime format is +%C which will print the time as the command 'date' will. + diff --git a/Design/logtail_notes b/Design/logtail_notes new file mode 100644 index 0000000..bfc64be --- /dev/null +++ b/Design/logtail_notes @@ -0,0 +1,44 @@ + + Stem::LogTail Design Notes + +The Stem::LogTail module provides a service similar to the standard Unix +program tail -f. It constructs a Cell that can watch a file for changes +in it size or inode and then sends the new data to a destination. The +destination is a Logical Log created elsewhere in the Stem application +and it can be located on any Hub on the network. Also file status +changes such as not being found, first time it is opened, it was +truncated, etc. can be sent to a different Logical Log than the data. +This module is a critical part of the StemLog application which +transfers active log files from one system to another. + +The action of checking the file is triggered by a call to the tail_cmd +method. There are two main ways of triggering it, either by an internal +timer or via a command message directed at this Cell. The timer is +configured when the Cell is created and its resolution in in seconds. So +e.g. you can cause a file check to occur every 15 seconds. If a 'tail' +command message is sent to this Cell, it will also trigger a file +check. The command message is usually sent from a Stem::Cron entry +configured elsewhere. In either case new data is sent to the 'data_log' +Log and status is sent to the 'status_log' Log. + +The primary configuration parameter is the path to the file to be +checked. Also the required 'data_log', optional 'status_log' and +'repeat_interval' are set in the configuration. + +When the file is checked, the current size and inode are compared to the +previous values. If either has changed, then either data and/or status log +entries are sent. + +NOTE: The modification time of a file is not currently checked for +changes. The reason is that there is no way to determine if a file +changed (or what data has changed) if the file just has its modification +time changed (by touch or utime). This means that there is a weakness in +just checking for the file size changing. If a file were to be truncated +and then written to with the same amount of data as it had when it was +last checked, a tail operation would not report any new data. This bug +exists in both the GNU and Solaris tail programs. The only way around +this is to check for modification time changes and trust +this. Stem::LogTail will support an option for doing this instead of +checking file size but it is up to the user to assure that the file will +only change its modification time if its data actually changes. Most +logs generated by programs have this attribute. diff --git a/Design/message_notes b/Design/message_notes new file mode 100644 index 0000000..675f494 --- /dev/null +++ b/Design/message_notes @@ -0,0 +1,98 @@ + Stem Message Design Notes + +Stem Messages are how Cells communicate with each other. Messages are +simple data structures with two major sections: the address and the +content. The address contains the Cell name the message is directed to and +which Cell to send replies to. The content has the message type, command +and data. These sections are described below in further detail. + +The Message address section has multiple addresses called types. The two +supported types correspond to the common email headers and are called +'to' and 'from'. The 'to' address designates which Cell will get this +message and the 'from' address says which Cell sent this message. Other +address types which may be supported are 'reply' (which overrides the +'from' address for replies) and 'orig' which is the address of the +original Cell which created this message (useful when messages get +forwarded). Each address type contains the standard Stem Cell address +triplet of Hub/Cell/Target which are called the address parts. The Cell +name is required and the Hub and Target are optional. + +The Message content has information about this message and any data +being sent to the destination Cell. The primary attribute is 'type' +which can be set to any string, but common types are 'data', 'cmd', +'response' and 'status'. Stem modules and Cells can create any Message +types they want. If the Message is a 'cmd' type, then the 'cmd' +attribute must be set to the command. A status type Message requires the +'status' attribute be set and the 'data' or 'response' types has the +'data' attribute set (though any message type can send data). There is +also a special flag attribute called 'ack_req' which forces a 'msg_ack' +type to be sent back to the 'from' address after this message is +delivered. One important note about the 'data' attribute, it is always a +reference and never a simple Perl scalar value. You can send a scalar +value but only by setting the 'data' attribute to its reference. + +Messages are constructed with the 'new' method of the Stem::Msg +class. Any of the message attributes can be specified in that call or +accessed/modified with the accessor methods. When a Message is completed +and ready to be sent, the dispatch method is called and the message is +queued for delivery. For convenience, the Message address types and part +can be combined into one attribute name in both the constructor and +accessors. So the Cell the message is directed at can be set with +'to_cell' and the Hub it came from can be accessed with 'from_hub'. The +entire address triplet of an address type can be set or accessed just +with its type name, so the 'to' address is set or accessed with the 'to' +attribute or method. It takes or returns a hash of the address parts and +their values. + +Messages are delivered only after the current callback is finished +executing (remember all code in Stem are called as callbacks). Stem +Message delivery is the heart of Stem operations and is described in +detail here. Delivery take place in three phases, the first determining +which Hub the Message is sent to, the second, which Cell in that Hub +gets it, and the third, which method in that Cell to call for delivery. + +If the Message has a 'to_hub' address, then that Hub name is looked up +in the Portal (which are Cells which connect Hubs together) registry. If +a Portal is found, the Message is delivered to it to be sent to the +destination Hub for local delivery. A Message can be forwarded across +several Hubs before it gets delivered to its destination Cell. If the +Hub name is the same as the current Hub or there is no Hub name in the +address, the Message is delivered to a Cell in the current Hub. In the +special case where there is no Hub name and the Cell name isn't +registered in the current Hub, the Message is sent to the Portal with an +alias of DEFAULT. This is just like the default route in IP routing +tables. If there is a Hub name and the Cell is not found, then there is +an addressing error and that is logged and the Message is discarded. + +Once a Message's destination Hub is reached, it must be delivered to a +local Cell. The 'to_cell' and 'to_target' attributes are accessed from +the message and that pair is searched for in this Hub's Cell registry. +If there is no Target name in the address, it defaults to the null +string. If there is a Target and the Cell is not found, the search is +repeated with no Target name (the Target name will be used by the +destination Cell). If the Cell still is not found, an addressing error +will be logged (with the message address) and the Message is discarded. + +When the destination Cell of a Message is determined, the method to call +for delivery must be chosen. The rules for this are simple. If a Message +is a 'cmd' type, then the method name is made by taking the 'cmd' name +from that attribute and appending '_cmd' to it. So a 'foo' command +message will have its type set to 'cmd', the 'cmd' set to 'foo' and it +will be delivered to the 'foo_cmd' method. If the Message is any other +type than 'cmd' the method name is created by taking its type name and +appending '_in' to it. So a 'foo' type Message is delivered to the +method 'foo_in'. If the delivery method doesn't exist, the default +method 'msg_in' is used. If no delivery method is found, then an error +is logged and the Message is discarded. + +Command Messages have a useful feature where they can automatically +generate 'response' messages. When a 'cmd' delivery method is called and +it returns a defined value, a 'response' type Message is created using +the 'reply' Message method. This uses the 'reply' or 'from' address in +'cmd' Message as the 'to' address in the 'response' Message. Its data +field is set from the data returned from the command delivery +method. This reduces the work of common command methods to just having +to return a data value. Many Cells use this technique, e.g. the +status_cmd method in a Cell just returns the status text of it. The +delivery mechanism takes that text and creates and dispatches a +'response' method with the status text as its 'data' payload. diff --git a/Design/portal_notes b/Design/portal_notes new file mode 100644 index 0000000..5d23d08 --- /dev/null +++ b/Design/portal_notes @@ -0,0 +1,45 @@ + + Stem::Portal Design Notes + +The Stem::Portal class supports the transmission of Stem Messages +between Stem Hubs. Portals are based on sockets and are configured very +similarly to a Socket with host and port attributes. Also a Portal can +be designated a TCP/IP server or a client. Once two Hubs are connected +via their Portals, they communicate peer to peer in full duplex mode. + +Portals are sent a Message to transmit by the Stem::Msg delivery +subsystem. The Message is converted to a byte stream format and written +to the Portal socket. When a Portal reads a byte stream Message from its +socket, it converts it back to an internal Message and sends it to the +Stem::Msg class for delivery. + +When a Stem Portal connects to another, they both exchange a special +'register' type Stem Message. This message when received by a Portal is +trapped and never sent for delivery to the Stem::Msg subsystem. The +'register' message contains the Hub name of the remote Portal. This name +is then stored in a registry private to the Portal class. It is used by +the Stem::Msg delivery subsystem to look up which Portal to use to send +out a remote message. You can see the current state of the Portal +registry by sending a 'status' command message to the Portal class. This +is easily done from the terminal (if the Hub has Stem::TtyMsg +configured) with the command: + + :port status + +If you put a Hub name before the :, you will get the Portal registry +status of that Hub. + +A special alias is set up for a client Portal that is the first one +configured in a Hub. It is given the alias DEFAULT and this Portal +becomes the default destination for any Messages that aren't delivered +locally. So if a Stem Message has no Hub in its 'to' address and the +Cell name is not registered locally, it is sent to the DEFAULT Portal +and hence to the Hub it is connected to. This is very similar to the +default route in IP routers like the routed daemon. A new feature (to be +implemented soon) will be to allow the setting of the DEFAULT Portal at +configuration or run time. + +Security of Hub to Hub communications are handled by the Portal +Cells. There are two designs in progress, one which pipes over ssh to +the other Hub, and an internal encryption module which will perform the +same functions but be more efficient. diff --git a/Design/proc_notes b/Design/proc_notes new file mode 100644 index 0000000..265914c --- /dev/null +++ b/Design/proc_notes @@ -0,0 +1,34 @@ +Stem::Proc + +A major service in network management infrastructure is running and +controlling processes. Stem::Proc is a very simple to use cell which +has a wide range of useful options to control the process behavior +and its standard I/O. The required parameters to the cell specify +the program and its arguments. + +The first group of configuration parameters controls how and when the +process is actually started. A process can be started at +configuration time or initiated by a command message. The message +interface is very important as it allows process management from any +other cell, including Stem::Cron (this will allow emulation of the OS +cron running processes), Stem::SockMsg (this will allow emulation +of the OS inetd), and other similar needs for remote process +invocation. Process monitoring is achieved by handling SIGCHLD +signals and I/O handle shut-down detection. A message can be sent +upon process exiting and the process can optionally be restarted +automatically, giving you a watchdog service. + +The second group is much larger and controls the I/O behavior of the +process. You can enable/disable any subset of its +stdin/stdout/stderr handles. This is needed for processes that don't +use all of their standard I/O handles. Process I/O can be managed by +the Stem::AsyncIO class and callbacks. In addition, the cell can be +configured to transfer its I/O data to/from other cells via Stem +messages. One neat feature is the ability to buffer all output from +a process and send it via a message only upon process exit. This +emulates the running of a process in backquotes as supported by +shells and Perl. + +Processes that need to work with a terminal can optionally be run +behind a pseudo-TTY device. + diff --git a/Design/registry_notes b/Design/registry_notes new file mode 100644 index 0000000..086e3d5 --- /dev/null +++ b/Design/registry_notes @@ -0,0 +1,131 @@ + + Stem Cell Registry and Message Address Design Notes + +The heart of Stem is the messaging subsystem and the heart of that is +the registry. This is where all knowledge of how to address cells is +located. Each cell gets registered by it name and optionally its target +and messages are directed to it via its names. The decisions made by the +registry when delivering a message are described here as well as the API +and other related issues and modules. + + +Stem Message Addresses + +Stem messages are sent to registered cells by using an address triplet: +the hub name, the cell name and the target name. + +A hub is a single process running Stem. Its name must be unique among all +Stem hubs in a single connected net. A hub consists of a set of objects +and Stem cells. It contains the message registry, the core Stem system +and it will load other modules on demand. + +A Stem cell is a single object in a Stem hub which has registered itself +under a name and can receive messages via its methods. Not all objects +in Stem are cells, but all Stem cells are objects. Cells are commonly +registered by the Stem::Config system or by a parent cell spawning +targeted cells. Only one cell can be registered in a hub for a given +cell name. One unusual trick is that a whole class can register itself +as a cell by using its class name as the object and some fixed string as +the name (sometimes that is the class name as well). There can only be +one cell by that class and name but there can be aliases for any cell +name. That is used by cells which must be implemented with class level +data. + +The target is the last part of an address and is optional. A given cell +could be registered with a cell name and target and it can send and +receive messages with its own globally unique address. The cell name is +either the parent's cell name or a fixed one for the particular class +(the Stem::Log::Filter class does this). The target name is commonly +either a Stem::Id value or a name from a configuration. Another use for +the target is a cell such as Stem::Switch which uses it to address its +input/output maps. The use of the target is defined by the design of the +cell. + +Message Delivery + +The first step in delivering a message is finding out which cell it goes +to. This is done by looking up the cell that matches the hub/name/target +address in the message. This is a multistep procedure with the following +rules: + +If the hub name of the message is set and it is not the name of this +hub, locate the portal that can send to that hub and deliver the message +to that portal. Portal names are in a different namespace as regular +cells but portals can also be registered as targeted cells so they can +have commands sent to them. See more on Portals below. + +If the message has a cell name and an optional target name, the cell is +looked up in the local registry. Cells with just a cell name don't share +the namespace with cells that have cell and target names. If the cell is +found the message is delivered by a method. (See how that is chosen +below.) + +If the cell is not found locally it is sent out via a portal with the +alias DEFAULT. This portal should be connected to a hub which would know +how to direct the message to the proper destination cell. Typically a +Stem hub that is a TCP client to a more central server hub will just +have its portal to the server aliased to DEFAULT. + +If the message has the local hub name and couldn't be delivered, it is +logged and thrown away. Optionally a delivery failure message could be +sent back to the originator. But this is not the Internet and bounces +can be automatically fixed in Stem. + +NOTE: This brings up the whole subject of message routing. I have been +thinking about this issue for a while and it is not as tricky as the +Internet because of several things. First, we can cheat. Stem is +completely in charge of its routing so it can be smart about itself and +not deal with worst case situations like the net. A hub can be +configured to distribute routing information that supports the network +topology. The discovery of the network and its topology can also be +automated by a booting Stem network, even from a virgin boot. Remote +Stem hubs could be installed with minimal (and not customized) +configurations which will cause itself to connect to a server hub and +download the real configuration. This simplifies deployment of Stem to a +new set of boxes. Much more on this subject will be in another design +notes file. + + +Choosing the Cell Method + +Once the destination cell of a message is determined, you then have to +find out its best method to call to deliver that message. Stem's +messages can be delivered via a generic method (e.g. 'msg_in') which is +expected to take any type of message, or via specific methods +(e.g. 'data_in') which handle selected messages. Here are the rules for +determining the cell method to call. + +If the message type is 'cmd' with a command 'foo' and there is a cell +method 'foo_cmd', the message is delivered via that method. If a command +message is delivered via a command method and a value is returned, that +value is sent back to the 'from' address in a response message. + +For all other message types, if the Cell has a method that is the type +name with '_in' appended to it, that method is used for delivery, +e.g.; if the message type is 'data', and if the cell has a method named +'data_in', that is called with the message as its sole argument. + +If the message is not delivered by any of those special methods, it will +be delivered to the generic method 'msg_in'. This method should exist in +every cell (except those that have the special methods cover all their +message types). The method delivery lookup simplifies writing Cells by +moving the internal dispatching code from the Cell to the registry. + + + +Stem::Id is a simple module designed to manage a set of unique IDs for +its owner object, i.e.; it is used by the Stem::SockMsg modules +to register all of its accepted/connected sockets with unique targets. + +Stem::Portal is the class that send messages between hubs over +pipes. These pipes can be direct sockets or indirect through a secure +transport such as ssh or stunnel. It receives messages vis the 'send' +method which are then converted to a string form and written out the +pipe. The stringify format is currently Data::Dumper but it can be set +via the configuration of the portal to use Storable, XML or something +else. Each stringified message is prefixed with 1 or 2 lines containing +its size and format. Incoming message strings are converted back into +internal messages and then delivered locally by calling dispatch on +them. Portals can use any communications channel as long as it gets read +and write handles. This means that new security and transport protocols +can be integrated easily into the portal. diff --git a/Design/security_notes b/Design/security_notes new file mode 100644 index 0000000..7757bf8 --- /dev/null +++ b/Design/security_notes @@ -0,0 +1,18 @@ +Security + +A critical aspect of any network application these days is security. +Stem hubs communicate with each other via standard internet sockets. +This communication can be made secure by tunneling over existing +open source products such as SSH and Stunnel. Stem runs the security +program and has it connect with the remote hub and its stdin/stdout +is used for the local connection. The Stem::Proc cell is used to +manage this external security program and it provides all the I/O +support needed. The Stem::Portal cell is used to connect Stem hubs +together and it has configuration options to select the desired +security application and options. A Stem hub can listen for portal +connections using a Listen socket bound to localhost or an IP +address. By using localhost and a secure transport, a Stem hub +cannot be accessed by any unauthorized programs. + + + diff --git a/Design/sock_msg_notes b/Design/sock_msg_notes new file mode 100644 index 0000000..45395d6 --- /dev/null +++ b/Design/sock_msg_notes @@ -0,0 +1,33 @@ +Stem::Sock::Msg + +This cell is the primary way of interfacing external programs to the +Stem messaging system. It is effectively a gateway with a standard +socket on the outside and a message interface on the inside. The +socket side takes standard Stem socket parameters for configuration. +The message side has a variety of message types and options to +control buffering. + +The socket parameters are directly passed to a Stem::Socket cell, and +supports both client and server modes. Multiple instances of this +Stem::Sock::Msg cell are supported and can be created via socket +connection or acceptance. If a connected socket is externally shut +down it can optionally be reconnected automatically. + +When the socket is connected or disconnected, a status message can be +sent out. This is used to trigger the addressed cell to be made +aware of the new socket status and act accordingly. For example, in +an inetd emulation, the socket connected message would cause the +address Stem::Proc cell to start a process and logically connect it +to this Stem::Sock::Msg cell. Similarly, the socket closed message +would cause the process to shut down and the logical connection to be +broken. + +When data comes in from the socket, it is buffered and sent out via a +data message to an addressed cell. Optionally, all incoming data can +be buffered and only sent out in a single message when the socket is +closed. This reduces the need for some cells to do their own +buffering. + +As with all Stem cells, various status information can be logged to +logical logs using the Stem logging system. Which status is logged +can be controlled by the configuration of the cell. diff --git a/Design/socket_notes b/Design/socket_notes new file mode 100644 index 0000000..0fe4366 --- /dev/null +++ b/Design/socket_notes @@ -0,0 +1,19 @@ + Stem::Socket Design Notes + +The Stem::Socket module provides an interface to create connected +sockets. It can either connect to a remote socket as a client or listen +for connections as a server. Its constructor takes an owner object, an +optional host or IP address and a required port number. Also a boolean +flag designates whether is it a client or server. If no host argument is +provided, it will default to localhost. To make a server listen on any +IP address (with the wild card INADDR_ANY), pass in the empty string '' +as the host. + +When a socket connection is made (either a client or a server), the +owner object is notified by a callback and the handle of the newly +created socket is passed to it. Since a client socket can timeout when +connecting to a server, a timeout value can be set. If the connection +request times out a different method is used as the callback to the +owner object. + +This module is only used internally and should not be configured. diff --git a/Design/switch_notes b/Design/switch_notes new file mode 100644 index 0000000..9df97d3 --- /dev/null +++ b/Design/switch_notes @@ -0,0 +1,23 @@ +Stem::Switch + +The Stem::Switch cell is a very simple but powerful object which can +be used in a wide range of applications. Its primary function is to +receive a message and to copy and redirect it to a list of +destination cells. It can be used as a multiplexer in many-to-one, +one-to-many, and many-to-many configurations. Currently it is used +as the heart of the chat server demo and the Inetd demo. + +Stem::Switch has two maps: the input map is used to translate the +incoming target address to entries in the output map. Each input map +entry can have multiple output targets and that list can be set via a +configuration file at start-up time or a command message at runtime. +The output map just converts the output name to an actual cell +address and is one-to-one; it also is set via the configuration file +or by runtime command messages. In addition to the explicit command +message technique for changing the maps, a publish/subscribe +interface is supported. A given cell can announce to a Stem::Switch +cell that it will be publishing messages to a given target address in +the switch. Other cells can send a subscribe message which will +cause all messages sent to the selected publish address to be +forwarded to themselves. + diff --git a/Doc/FAQ.txt b/Doc/FAQ.txt new file mode 100644 index 0000000..2b644bb --- /dev/null +++ b/Doc/FAQ.txt @@ -0,0 +1,260 @@ +What Can I Do With Stem? + +You should probably ask, "What can't I do with Stem?" :-). Stem is not +bound by any narrow niche definition since it is a general purpose +networking toolkit. As such it can be the backbone of almost any +networked application. On the other hand Stem is designed with standard +modules that are aimed at specific application niches. Some Stem users +want a a way to accelerate network application development and others +want a simple solution to common problems. Stem can satisfy the +differing needs of both groups. + +What Are the Top Stem applications? + +The most commonly requested applications of Stem are log management, +file distribution, monitoring and remote command execution. For more on +Stem applications see the niches page. + +Why Should I Use Stem? + +You should use Stem if you are tired of reinventing the wheel for each +network application you build. Or if you don't want to be forced to rely +on a commercial monolithic network tool that everyone hates to use. Or +if you want to dramatically reduce the development and maintenance costs +of developing your new network applications. Or if you just want to use +a well designed network system that will make your profession much more +enjoyable. Please contact us if you want more reasons. + +Who Is Using Stem? + +Currently we are developing a Stem based log management system for a +Fortune 500 company. It will watch growing log files on hundreds of +systems and transfer the new log records to a central system for +analysis and archiving. This complex system will use standard Stem +modules and require only a single customized tool that creates the +configuration files. Another Internet content provider company we are +talking to is interested in a similar system. We don't know of any +products that can manage logs over a network as simply and elegantly as +Stem. + +What Existing Products Does Stem Improve On? + +Stem is not a direct improvement of any existing product. Rather it is a +coherent integration of a range of network services as used by many +products. Stem did not invent logging, monitoring, process management, +file transfers, etc, but it has put them together under a simple +architecture that meets the needs of the technical market. With Stem, +solving many common network problems doesn't require any coding at all, +just simple edits to text-based configuration files. Even when coding +*is* required, Stem allows you to develop and maintain complex network +applications much more cheaply and reliably than existing application +frameworks. + +Do I Need To Get Rid Of [Commercial App] To Use Stem? + +No, Stem can run in parallel with all of your existing applications. As long +as Stem can have access to its own TCP ports (which can be configured to +any available ones), it has no need to conflict with any of your current +systems. + +What is a Stem Hub? + +A Stem Hub is a single Perl process running Stem. It can function as a +complete stand-alone application or be connected to other Stem Hubs in a +networked application. Stem Hubs contain the Stem core system and Stem +Cells that have been created by configuration files. For more on this +read the arch_notes document +or find it in the Design directory of the tarball. + +What is a Stem Cell? + +A Stem Cell is a Perl object which has to have 3 major characteristics: + + 1. It has been registered as a Cell in this Hub with a cell name + and an optional target name. + + 2. It has to have a set of methods designated to handle messages + directed at the Cell. + + 3. It has to be able to send messages to other cells. + +What is a Stem Message? + +Stem Messages carry commands and data between Stem Cells. They are +addressed with a Hub/Cell/Target triplet. Messages can be any one of a +wide range of types including command, data, log, stderr etc. They can +carry any form of data to a maximum (to be designated later) size. See +message_notes document for more. + +How Hard Is Stem To Install/Configure? + +Stem is very easy to install. It only requires Perl 5.005_03 and one +code module Event.pm that you can get from CPAN. The rest of Stem is +comprised of pure Perl modules which need no special installation +handling. Read the INSTALL document for more on how to +install it. The quickstart instruction is simply to cd to the stem +download directory and say +./install.pl + +Much more on this can be found in the config_notes document or in +the Design directory of the tarball. + +Can Stem Do Guaranteed Messaging? + +*Stem's core design does not directly support guaranteed message +delivery. It was designed this way, as the technical market that Stem +targets does not have that strong a requirement for this feature as the +commercial/e-business markets have. Future releases of Stem will support +guaranteed delivery via a separate module that is now under development. + +Does Stem Use XML? + +Stem's messages are designed to not be specific to any format or +encoding. They can carry data structure in any format the designer wants +including XML. In fact, the message itself can also be in any format as +long as it is properly identified and a module to decode that format is +installed. Currently only the Stem internal message format is used but +as demand arises, other message formats, including XML will be +supported. One longer term goal is that message formats from many other +systems will be supported by gateway modules which will translate +between Stem and external messages. + +What Kind Of Security Does Stem Use? + +Stem doesn't directly do authentication and secure transmissions. Rather +it relies upon industry standard utilities such as SSL, ssh and stunnel to +provide secure socket connections between Stem hubs (processes). + +Can Stem Do Central Configuration Management? + +The log management project under development has this requirement. The +lists of log files on each system that need to be monitored are +maintained on the central system. Configuration files are auto-generated +from those lists and distributed to all the Stem hubs in this network +application. The same mechanism can be used to distribute configuration +files for other applications and Stem can also notify them to reload the +new files. + +Can Stem Handle My Content Distribution? + +Distributing content is similar to distributing configuration files and +will use the same mechanisms. What content is distributed to where can +all be easily controlled by Stem configurations without any additional +programming. + +Can I Extend Stem Myself To Suit Our Needs? + +Stem is designed to be modified and extended very easily. You can copy +existing modules or use design ideas from them to created new Cells for +your own needs. The high level Cell API hides the complexities of +network communication and message transfer, leaving you free to focus on +the specific design and coding of your custom Cells. + +Should I Modify Stem On My Own Or Use Stem's Development Team? + +If your requirements are simple, and you have skilled staff to do the +work, there's no reason not to develop and maintain your own Stem-based +systems. If your applications are complex applications, your staff new +to Stem, or your time-frame short, you will probably find it more +cost-effective to let Stem Systems' expert team develop your system for +you. + +Otherwise Stem Systems can assist you in the design, development and +configuration of your application. How much assistance can be provided +depends on the customer subscription deal and any other contracts. If +you do not have a subscription, our support will be limited to bug fixes +and email and you will have to maintain your modified code on your own. + +Which Operating Systems Support Stem? + +Stem runs without any modification under any Unix flavor (Solaris, +Linux, FreeBSD, etc.) Support for WinX is in the planning stage and is +expected to be released in Q3 2002. + +How Can I Contribute to Stem? + +What Does Stem's Open Source License Allow Me To do? + +Is Stem Open Source? + +We are happy to announce that as of version 0.06, Stem is now under +the GPL. + +What Is Stem Systems? + +Stem Systems is the company which is developing and maintaining Stem. It +owns the copyright to the Stem code base and is releasing it to its +customers and the open source community. Stem Systems also sells support +subscriptions to the Stem user community. + +What Is the Business Model Of Stem Systems? + +Stem itself is free to use. Users can buy support subscriptions from +Stem Systems which include Stem configuration and development +assistance. Subscribers get earlier access to releases of new Stem +versions and modules and direct support from Stem's development team. + +How Much Does a Stem Subscription Cost? + +The base price is an annual fee of $300 for each computer system (box) +which is running Stem. Volume, site and educational discounts are +available. + +Who Are Your Technical Advisors? + + + +What Are Your Resources? + +How Can I Learn Stem? + +The best way right now is to start with the technical notes and get a +basic understanding of Stem, its architecture and its nomenclature. Then +run the various demo scripts and read their configuration files. +Developers will go next to the Cell documentation which describes each +Cell's function, their attributes and how to configure it. There are no +training materials now, but we are discussing the creation of classes +with a training firm for when the demand arises. + +What Is the Future Of Stem? + +Stem will constantly be growing it Cell library and adding support for +new features. Whenever any repeated networked operation can be +identified, Stem will create a Stem Cell to perform it. Then Stem's +users can easily integrate that new Cell without programming or needing +to reinvent that wheel. + +Who Created Stem? + +Stem was created by Uri Guttman, and is the culmination of his long +career in systems architecture and development. He has combined a +quarter century of experience designing and implementing event-driven +systems with his love of Perl to produce a powerful, yet easy-to-use +framework for network applications. For more on Stem's creator, see the +Stem Team biographies. + +How (and why) was Stem conceived? + +Stem was created when its designer was contracted to a network +application development group where they were constantly putting out +fires and creating a massive ad hoc system. It was clear that they +needed a framework to give structure to their network application +design. Stem was the result. + +What Does Stem Stand For? + +Stem is not an acronym or an abbreviation, rather it is named from the +real word 'stem' which has several dictionary meanings that map well to Stem. + +v. tr. + 1. To stop or hold back by or as if by damming; as in "stem the tide" + +n. + 1. A slender stalk supporting or connecting another plant part, such + as a leaf or flower. + 2. A connecting or supporting part. + +n. + 1. A line of ancestry : STOCK; especially : a fundamental line from + which others have arisen. as in "stem cell" in biology. diff --git a/Doc/HISTORY-2001 b/Doc/HISTORY-2001 new file mode 100644 index 0000000..e0a7012 --- /dev/null +++ b/Doc/HISTORY-2001 @@ -0,0 +1,13 @@ + STEM CODE HISTORY 2001 + +$Id: HISTORY-2001,v 1.1 2001/02/07 10:09:09 uri Exp $ + +20010109: + + mengwong@pobox.com created a repository of stem-0.04. + +20010110: + amused@pobox.com verified cvs operation + uri@stemsystems.com verified cvs operation + mengwong@pobox.com added loginfo mailing to devel@stemsystems.com + diff --git a/FAQ/faq.text b/FAQ/faq.text new file mode 100644 index 0000000..f5e3d38 --- /dev/null +++ b/FAQ/faq.text @@ -0,0 +1,407 @@ +S: About M + + +Q: What is M? + +A: M is a general purpose networking toolkit and a suite of ready +to use network applications. Its goal is to transform common network +programming to configuration and make uncommon network programming much +simpler. Some M users will use its suite of applications and +modules and just configure them to perform their needed network +tasks. Others will create new M modules to perform their specific +tasks and integrate them with M's standard modules using +configurations. In both cases, M will speed up network +application development, simplify maintenance, and lower lifetime costs. + + +Q: Where can I get M? +A: M can now be downloaded by the general public. Go to the +download page to get the latest version. + + +Q: What Can I Do With M? +A: M is a general purpose networking toolkit. As such, it can +be the backbone or framework of almost any networked (distributed) +application you can imagine. M is designed with standard modules +that are aimed at specific application niches. Some M users +want a simple solution to common problems while others need a way to +accelerate network application development. Via this modular design, +M can satisfy the differing needs of both groups. + + +Q: What Are the Top M Applications? +A: The most commonly requested applications of +M are log management, file distribution, +monitoring and remote command execution. For more on +M applications, see the niches page. + +QUOTE< + Stem Monitoring. \ + Overloaded Server Appears. \ + Remediation. \ + -- U.G. \ + > + +Q: Why Should I Use M? +A: You should use M if: +
    +
  • You are tired of reinventing the wheel for each + network application you build. M provides you with all + the common services that a network application needs and + makes them very simple to use.

  • + +
  • You don't want to be forced to rely on a commercial + monolithic network tool that everyone hates to use. M is + low cost and Open Source. You can easily write new modules + to customize M to your needs.

  • + +
  • You want to dramatically reduce the development + and maintenance costs of developing your new + network applications. M tranforms common network + programming to configuration. Even if you create new M + modules, it is very simple to connect them together with + M configurations. This lowers your development time and + costs in many ways.

  • + +
  • You just want to use a well-designed network system that + will make your profession much more enjoyable. M is + architected to be easy to use at both the configuration and + coding levels. Our goal is for all M users to enjoy + working with it and reduce the frustrations and stress of + network management.

  • +
+ +

These are just a few of the many reasons why +M should be in place on your network. +If you have questions on how M will benefit +your individual needs, please contact us. + + +Q: Who Is Using M? +A: A start up firm hired M to implement a specialized +web crawler. M will be used as +the communications backbone for this multiprocessor system and +it will interconnect and manage all of its components. Some of +those components include subprocesses doing the page fetches, +site objects, html page parsers, custom filters, crawl management +and a database. + +QUOTE< + Divers search limpid pools. \ + Precious Perl is summer's find, \ + But Stem is loved more. \ + -- D.G. \ +> + +Q: How can I be notified about M updates? +A: M has a email list dedicated to M updates. To +subscribe, send an empty email to: news-subscribe@stemsystems.com. This +is a low volume list used for sending important news regarding M +(ie - new releases). + +S: M Design + +Q: Do I Need To Get Rid Of [Commercial Application] To Use M? +A: No, M can run in parallel with all of +your existing applications. As long as M +can have access to its own TCP ports (which can be configured to +any available ones), it has no need to conflict with any of your current +systems. + +QUOTE< + superglue the net \ + tie the machines together \ + stem keeps it all sane \ + -- U.G. \ +> + + +Q: What is a M Hub? +A: A M Hub is a single Perl process running +M. It can function as a complete standalone +application or be connected to other M Hubs in a +networked application. M Hubs contain the +M core system and M +Cells that have been created by configuration files. + +

For more on this, read the +architecture notes document or find it +in the Design directory of the tarball. + +Q: What is a M Cell? +A: A M Cell is a Perl object that has 3 major characteristics: + +

    +
  1. First, it is registered as a Cell in this Hub with a cell name and + an optional target name

  2. +
  3. It has a set of methods designated to handle messages directed at + the Cell

  4. +
  5. It sends messages to other cells.

  6. +
+ + +S: M Features + + +Q: What is a M Message? +A: M Messages carry commands and data between +M Cells. They are addressed with a +Hub/Cell/Target triplet. + +

Messages can be any one of a wide range of types including command, +data, log, stderr etc. They can carry any form of data to a maximum +(to be designated later) size. + +

See +message notes document for more. + +QUOTE< + Nets catch more than fish. \ + Data's trapped beneath the waves. \ + Stem frees; packets breathe. \ + -- D.G. \ +> + + +Q: How Hard Is M To Install/Configure? +A: M is very easy to install. It only requires +Perl 5.005_03 and one code module Event.pm that you can get from +CPAN. The rest of +M is comprised of pure Perl modules. + +

M has a step by step installation +script that guides you through the installation process and any +required modules that are necessary for the normal functioning of M. +It is easy to use and allows you to customize the installation to +your liking or take the default install. + +

Much more on this can be found in the +config notes +document or in the Design directory of the tarball. + + +Q: Can M Do Guaranteed Messaging? +A: M's core design does not directly support +guaranteed message delivery. It was designed this way, as the +technical market that M targets +doesn't have a strong requirement for this feature as the +commercial/e-business markets have. + +

Future releases of M will support guaranteed delivery via a +separate module that is now under development. + + + +Q: Does M Use XML? +A: M's messages are designed to not be specific +to any format or encoding. They can carry data structure in any format the +designer wants, including XML. In fact, the message itself can also be in +any format as long as it is properly identified and a module to decode that +format is installed. + +

M is currently in the process of including +YAML as its primary format for +messages, configuration files, and logs, but as demand arises other message formats +including XML will be supported. + +

One longer-term goal is that message formats from many other +systems will be supported by gateways modules, which will translate +between M and external messages. + +Q: What Kind Of Security Does M Use? +A: M doesn't do direct authentication and secure +transmissions. Currently it relies upon industry standard utilities such as ssh and +stunnel to provide the secure socket connections between +M hubs (processes). Security is a great concern to +everyone and M development is looking into the possibilities +of supporting various mechanisms and levels of security (i.e. - ssh, ssl, etc) +through configurable modules. + +QUOTE< + Information rain \ + Can drown the sleepy server, \ + But for Stem's shelter. \ + -- D.G. \ +> + + +Q: Can M Do Central Configuration Management? +A: The log management project under development has this requirement. The +lists of log files on each system that need to be monitored are +maintained on the central system. Configuration files are autogenerated +from those lists and distributed to all the M hubs in this network +application. The same mechanism can be used to distribute configuration +files for other applications and M can also notify them to reload the +new files. + +Q: Can M Handle My Content Distribution? +A: Distributing content is similar to distributing configuration files and +will use the same mechanisms. What content is distributed to where can +all be easily controlled by M configurations without any additional +programming. + + +S: Developing with M + + +Q: Can I Extend M Myself To Suit Our Needs? +A: M is designed to be modified and extended very +easily. You can copy existing modules or use design ideas from them to +created new Cells for your own needs. + +

The high level Cell API hides the complexities of +network communication and message transfer, leaving you free to focus on +the specific design and coding of your custom Cells. + + +QUOTE< + Just one message lost \ + could stop your business blooming. \ + Fear not: grasp the stem! \ + -- D.G. \ +> + +Q: Is There a M Tutorial? +A: M Development has created a cookbook of examples that demonstrates the +design of M cells from the simplest form up +to various levels of complexity. + + +Q: Should I Modify M On My Own Or Use M's Development Team? +A: If your requirements are simple, and you have skilled staff to do the +work, there's no reason not to develop and maintain your own M-based +systems. If your applications are complex applications, your staff new +to M, or your time-frame short, you will probably find it more +cost-effective to let M' expert team develop your system for +you. + +

Otherwise, M can assist you in the design, development and +configuration of your application. How much assistance can be provided +depends on the customer subscription deal and any other contracts. If +you do not have a subscription, our support will be limited to bug fixes +and email and you will have to maintain your modified code on your own. + +Q: Which Operating Systems Support M? +A: M runs runs without any modification under any Unix flavor (Solaris, +Linix, FreeBSD, etc.) Support for WinX is in the planning stage and is +expected to be released in the near future. + +Q: What Is M's License? +A: M will be released under the GNU General +Public License starting with version 0.06. Our intent is for +M to be free for non-commercial use. Commercial licenses can be +purchased through M. Please contact Stem Systems for more +information regarding commercial license. + + +S: M + + +Q: What Is M? +A: M is the company which is developing and maintaining M. It +owns the copyright to the M codebase and is releasing it to its +customers and the open source community. M also sells support +subscriptions to the M user community. + +QUOTE< + Black chips nurture life. \ + Data shoots out, seeking Spring. \ + Stem makes all fertile. \ + -- D.G. \ +> + +Q: What Is The Business Model Of M? +A: M has three business models, +

    +
  1. Development Projects
  2. +
  3. Support Subscriptions
  4. +
  5. 3rd Party products and VAR's
  6. +
+ +Q: How Much Does M Cost? +A: 0.06 will be under the +GNU General +Public License. M can be used freely for non-commercial +use. For commercial and acedemic licenses please contact us at Stem Systems for more +information. + + +Q: Who Are Your Technical Advisors? +A: Our technical advisors are listed here. + +S: Miscellaneous + + +Q: How Can I Learn M? +A: The best way right now is to start with the technical notes and get a +basic understanding of M, its architecture and +its nomenclature. Then run the various demo scripts and read their +configuration files. Developers will go next to the Cell documentation that +describes each Cell's function, their attributes and how to configure it. +Once some insight into each Cell is attained the next step would be to go +through M's cookbook +of examples put together by the developers of M. This will +show you how to extend M with your own Cells. + +

We are discussing the +creation of classes with a training firm when the demand arises. + +Q: What Is The Future Of M? +A: M will constantly be growing its Cell library and adding support for +new features. Whenever any repeated networked operations can be +identified, M will create a M Cell to +perform it. Then M's +users can easily integrate that new Cell without programming or needing +to re-invent that wheel. + +Q: Who Created M? +A: M was created by Uri Guttman, and is the +culmination of his long career in systems architecture and development. He has combined a +quarter century of experience designing and implementing event-driven +systems with his love of Perl to produce a powerful, yet easy-to-use +framework for network applications. + +

For more on M's creator, see the +M Team biographies. + +Q: How (and why) Was M Conceived? +A: M was created when its designer was contracted +to a network application development group who were constantly +putting out fires and creating a massive ad hoc system. It was clear that they needed a +framework to give structure to their network application design. +M was the result. + +QUOTE< + Coders or firemen? \ + System in conflagration. \ + From ashes rose Stem. \ + -- U.G. \ +> + + +Q: What Does M Stand For? +A: M is not an acronym or an abbreviation, rather it is named from the +real word 'stem' which has several dictionary meanings that map well to M. + +

v. tr.
+
  1. To stop or hold back by or as if by damming; as in "stem the tide"
+ +
n.
+
  1. A slender stalk supporting or connecting another plant part, such as a leaf or flower. +
  2. A connecting or supporting part. +
+ +
n.
+
  1. A line of ancestry : STOCK; especially : a fundamental line from +which others have arisen. as in "stem cell" in biology.
+ + +QUOTE< + To keep your network \ + flowering when the heat's on, \ + it needs a strong stem. \ + -- D.C. \ +> diff --git a/FAQ/faq_maker.pl b/FAQ/faq_maker.pl new file mode 100644 index 0000000..9a8bbc9 --- /dev/null +++ b/FAQ/faq_maker.pl @@ -0,0 +1,346 @@ +#!/usr/local/bin/perl -w + +use strict ; +use Carp ; + +use YAML ; + +my @markup = ( + + { + 'search' => 'M<[^<>]+>', + 'replace' => sub { + my ( $text ) = @_; + + $text =~ s|M<([^<>]+)>|$1|sg; + + $text; + }, + + }, + + { + 'search' => 'QUOTE<(.*?)>', + 'replace' => sub { + my ( $text ) = @_; + + $text =~ /QUOTE<(.*?)>/gs; + + my $before = $`; + my $after = $'; + my $quote = $1; + + $quote =~ s/\\/
/sg; + + $before . + "

" . + "
$quote" . + "
" . + $after; + }, + }, + + ); + + +my ( + @sections, + + $header_text, + + $page_title_base +); + +set_header_text() ; + +process_faq_text() ; + +process_sections() ; + +print_section_page() ; + +exit ; + + +sub process_faq_text { + + my ( $section, $quest_text, $answer_text, $curr_faq ) ; + + while( <> ) { + + next if /^\s*$/ ; + s/\n/ /; + + if ( /^([SQ]):\s*(.+)$/ ) { + + + if ( $curr_faq ) { + + + $curr_faq->{'answer'} = + markup_text( $answer_text ) ; + + $answer_text = '' ; + + unless ( $curr_faq->{'question'} && + $curr_faq->{'answer'} ) { + + + die + + "bad FAQ entry before line $. in $ARGV\n" ; + } + + push( @{$section->{'faqs'}}, $curr_faq ) ; + $curr_faq = undef ; + } + + if ( $1 eq 'S' ) { + + my $section_title = $2 ; + + push( @sections, $section ) if $section ; + + $section = { + + 'plain_title' => $section_title, + 'title' => markup_text( $section_title ), + } ; + + next ; + } + + $quest_text = $2 ; + + next ; + } + + if ( /^A:\s*(.+)$/ ) { + + $answer_text = markup_text( $1 ) ; + + $curr_faq = { + 'question' => markup_text( $quest_text ), + } ; + + $quest_text = '' ; + next ; + } + + if ( $quest_text ) { + + $quest_text .= $_ ; + next ; + } + + $answer_text .= $_ ; + } + + push( @sections, $section ) ; +} + + +sub process_sections { + + + my $sect_num = 1 ; + + foreach my $sect_ref ( @sections ) { + + + my $title = $sect_ref->{'title'} ; + + $sect_ref->{'num'} = $sect_num ; + + my $link = <$title +LINK + + $sect_ref->{'link'} = $link ; + + my $quest_num = 1 ; + + foreach my $faq_ref ( @{$sect_ref->{'faqs'}} ) { + + my $quest = $faq_ref->{'question'} ; + + my $answer = $faq_ref->{'answer'} ; + + $faq_ref->{'num'} = $quest_num ; + $faq_ref->{'index'} = "$sect_num.$quest_num" ; + + $faq_ref->{'link'} = <$quest +LINK + + $quest_num++ ; + } + + $sect_num++ ; + } +} + + +sub print_section_page { + + my $page_text = < + title => "$page_title_base" + + +Home > FAQ + +


+ +

Frequently Asked Questions

+ +
    +HTML + + foreach my $sect_ref ( @sections ) { + + my $link = $sect_ref->{'link'} ; + + $page_text .= "
  • $link" ; + + print_faq_pages( $sect_ref ) ; + } + + $page_text .= "
"; + + write_file( 'faq.html', $page_text ) ; + +} + +sub print_faq_pages { + + my ( $sect_ref ) = @_ ; + + my $quest_list ; + + my $faq_text ; + + my $plain_title = $sect_ref->{'plain_title'} ; + my $title = $sect_ref->{'title'} ; + my $sect_num = $sect_ref->{'num'} ; + + my $page_text = < + title => "$page_title_base > $plain_title" + + +Home > FAQ > $title + +
+ +

$title

+ +
+ +HTML + + + $quest_list .= < +HTML + + foreach my $faq_ref ( @{$sect_ref->{'faqs'}} ) { + + my $quest = $faq_ref->{'question'} ; + my $answer = $faq_ref->{'answer'} ; + + my $faq_num = $faq_ref->{'num'} ; + my $faq_ind = $faq_ref->{'index'} ; + + $quest_list .= <$faq_ref->{'link'} +HTML + + + $faq_text .= < + +

$quest

+
+$answer +
+ + + +
+ +HTML + + } + + $quest_list .= "" ; + + + my $section_list = '
    ' ; + + foreach my $s_ref ( @sections ) { + + $section_list .= <$s_ref->{'link'} +HTML + + if ( $s_ref == $sect_ref ) { + + $section_list .= $quest_list ; + } + + } + + $section_list .= "
" ; + + $page_text .= $section_list ; + + $page_text .= $faq_text ; + + write_file( "faq$sect_num.html", $page_text ) ; +} + + +sub set_header_text { + + $page_title_base = 'Stem Systems, Inc. > Stem > FAQ' +} + + +sub write_file { + + my( $file_name ) = shift ; + + local( *FH ) ; + + open( FH, ">$file_name" ) || carp "can't create $file_name $!" ; + + print FH @_ ; +} + + + +sub markup_text { + + my ( $text ) = @_; + + map { + + if ($text =~ /$_->{'search'}/s) { + + $text = $_->{'replace'}->($text); + } + + } @markup; + + return $text; + +} + + +__END__ + + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..70d24cd --- /dev/null +++ b/INSTALL @@ -0,0 +1,73 @@ + + Installation Of Stem + +Installing Stem is simple as you can use the classic module install +commmands: + +` perl Makefile.PL + make + make test + make install + +The installation uses Module::Build so you have to have that. You can +get it from CPAN. The Makefile actually just is a passthrough that calls +the Build program and is there for compatibility. You can bypass make +and use the Build commands directly: + +on UNIX flavors do this: + + perl Build.PL + Build + Build test + Build install + +on Windows do this: + + perl Build.PL + perl Build + perl Build test + perl Build install + +The perl Makefile.PL or perl Build.PL command will query you for a +short series of answers. These include where is perl, where to store the +Stem executable scripts, where to store the Stem configuration files, +etc. Each question will be explained in detail and has a reasonable +default value. + +Stem has a set of demonstration applications and you will be asked if +you want to install them. The driver scripts use xterm to +bring up multiple windows so you can interact with the +demonstrations. The Build.PL script will find xterms on most UNIX +flavors that have X on them. OSX doesn't come with X by default but you +can install the X cdrom if you want. + +NOTE: The xterm program is NOT required to run the demostrations. You +can run the scripts and the commands which create the xterms will be +printed. If you don't have xterms, they will fail but you can copy the +Stem command (the part after the -e) and run them in terminal windows +that you bring up yourself. This works on windows and OSX which doesn't +have X installed. Just run those Stem commands in the order they are +printed and each one in its own terminal window. + +Another option for the demo scripts is called ssfe (split screen front +end). It is a general purpose C (UNIX only) utility that runs any +command and provides command line editing and history. It is a nice +little utility and it make running the demos a bit nicer. It is bundled +in a IRC application call sirc and if you ask for it to be installed, +the whole sirc package (just ssfe and sirc) will be built and +installed. This build/install will be run in its own xterm. When it is +done installing, it will sleep for a while. You can kill the xterm +window or ^C in it and the rest of the Stem installation will continue. + +The installation answers you give will be stored in the module +Stem::InstallConfig for use by any Stem application. This module is used +by Build.PL to override the default installation answers. So the next +time you do perl Build.PL you will see the previous choices you made as +the defaults. Doing Build realclean will remove this module and you will +see the original default answers. + +If you know the default answers are fully acceptable, you can do this: + + perl Build.PL use_defaults=1 + +and all the defaults will be used and no questions will be asked. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7e50a41 --- /dev/null +++ b/LICENSE @@ -0,0 +1,34 @@ +############################################################################### + Copyright and Licensing Information for Stem + +The copyright and license on the Stem software system is as follows: + + Copyright (C) 1999-2004 Stem Systems, Inc. + + Stem 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. + + Stem 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 Stem in the file "COPYING" in this directory. If not, write to + the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + Stem Systems, Inc. provides other licensing options for Stem as part of + our business. For a license to use the Stem under conditions other + than those described here, to purchase support for this software, or to + purchase a commercial warranty contract, please contact Stem Systems + at: + + Stem Systems, Inc. 781-643-7504 + 79 Everett St. info@stemsystems.com + Arlington, MA 02474 + USA +############################################################################### diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..141fa26 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,174 @@ +Makefile.PL +Build.PL +BuildStem.pm +META.yml +MANIFEST +CHANGELOG +COPYING +CREDITS +README +INSTALL +LICENSE +TODO +DEMO +DEMO_CHAT +DEMO_INETD +DEMO_TAIL +Cookbook/cookbook.txt +Cookbook/World1.pm +Cookbook/World2.pm +Cookbook/World3.pm +Cookbook/World4.pm +Cookbook/World5.pm +Cookbook/worlds.stem +Design/asyncio_notes +Design/Stem-Mon +Design/arch_notes +Design/config_notes +Design/cell_notes +Design/console_notes +Design/cron_notes +Design/debug_notes +Design/env_notes +Design/event_notes +Design/id_notes +Design/index +Design/log_notes +Design/logtail_notes +Design/message_notes +Design/portal_notes +Design/proc_notes +Design/registry_notes +Design/security_notes +Design/sock_msg_notes +Design/socket_notes +Design/switch_notes +Doc/FAQ.txt +Doc/HISTORY-2001 +FAQ/faq_maker.pl +FAQ/faq.text +bin/run_stem +bin/boot_stem +bin/stem_msg +bin/stem2pod +bin/cgi2stem.pl +bin/hello_demo +bin/chat_demo +bin/chat2_demo +bin/inetd_demo +bin/tail_demo +bin/quote_serve +bin/cli +conf/chat_client.stem +conf/archive.stem +conf/boot.stem +conf/chat.stem +conf/chat_label.stem +conf/chat_server.stem +conf/cron.stem +conf/cli.stem +conf/hello.stem +conf/hello_client.stem +conf/hello_server.stem +conf/hello_shell.stem +conf/hello_yaml.stem +conf/inetd.stem +conf/load_driver.stem +conf/load_echo.stem +conf/monitor.stem +conf/proc.stem +conf/slave.stem +conf/tail.stem +conf/test_udp.stem +conf/test_flow.stem +conf/test_packet_io.stem +conf/ticker.stem +conf/ttysock.stem +conf/type.stem +extras/sirc-2.211.tar.gz +lib/Stem.pm +lib/Stem/Event.pm +lib/Stem/Event/EventPM.pm +lib/Stem/Event/Perl.pm +lib/Stem/Event/Signal.pm +lib/Stem/Event/Queue.pm +lib/Stem/Event/Tk.pm +lib/Stem/Event/Wx.pm +lib/Stem/Msg.pm +lib/Stem/Route.pm +lib/Stem/Class.pm +lib/Stem/Conf.pm +lib/Stem/Boot.pm +lib/Stem/Portal.pm +lib/Stem/Hub.pm +lib/Stem/Vars.pm +lib/Stem/Cell.pm +lib/Stem/Cell/Sequence.pm +lib/Stem/Cell/Clone.pm +lib/Stem/Cell/Flow.pm +lib/Stem/Cell/Pipe.pm +lib/Stem/Cell/Work.pm +lib/Stem/Log.pm +lib/Stem/Log/Entry.pm +lib/Stem/Log/File.pm +lib/Stem/Log/Tail.pm +lib/Stem/Socket.pm +lib/Stem/SockMsg.pm +lib/Stem/UDPMsg.pm +lib/Stem/Proc.pm +lib/Stem/Switch.pm +lib/Stem/Debug.pm +lib/Stem/Trace.pm +lib/Stem/Inject.pm +lib/Stem/Cron.pm +lib/Stem/DBI.pm +lib/Stem/File.pm +lib/Stem/Console.pm +lib/Stem/Id.pm +lib/Stem/Gather.pm +lib/Stem/Util.pm +lib/Stem/AsyncIO.pm +lib/Stem/WorkQueue.pm +lib/Stem/ChatLabel.pm +lib/Stem/TtySock.pm +lib/Stem/Packet.pm +lib/Stem/Codec.pm +lib/Stem/Codec/Data/Dumper.pm +lib/Stem/Codec/Storable.pm +lib/Stem/Codec/YAML.pm +lib/Stem/Load/Driver.pm +lib/Stem/Load/Ticker.pm +lib/Stem/Test/ConfTypes.pm +lib/Stem/Test/Echo.pm +lib/Stem/Test/Flow.pm +lib/Stem/Test/PacketIO.pm +lib/Stem/Test/UDP.pm +lib/Stem/Demo/World.pm +lib/Stem/Demo/CLI.pm +t/event/event_test.pl +t/event/perl.t +t/event/event.t +t/event/poe.t +t/event/tk.t +t/event/wx.t +t/event/gtk.t +t/event/qt.t +t/socket/plain.t +t/socket/plain_fork.t +t/socket/ssl_fork.t +t/socket/udp.t +t/socket/SockFork.pm +t/cell/flow.t +t/io/packet.t +sessions/client.pl +sessions/mid_event.pl +sessions/mid_event_async.pl +sessions/backend.pl +certs/client-cert.pem +certs/client-key.enc +certs/client-key.pem +certs/my-ca.pem +certs/server-cert.pem +certs/server-key.enc +certs/server-key.pem +certs/test-ca.pem diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..e007605 --- /dev/null +++ b/META.yml @@ -0,0 +1,132 @@ +--- #YAML:1.0 +name: Stem +version: 0.11 +author: ~ +abstract: ~ +license: gpl +dynamic_config: 1 +provides: + Stem: + file: lib/Stem.pm + version: 0.11 + Stem::AsyncIO: + file: lib/Stem/AsyncIO.pm + Stem::Boot: + file: lib/Stem/Boot.pm + Stem::Cell: + file: lib/Stem/Cell/Sequence.pm + Stem::ChatLabel: + file: lib/Stem/ChatLabel.pm + Stem::Class: + file: lib/Stem/Class.pm + Stem::Codec: + file: lib/Stem/Codec.pm + Stem::Codec::Data::Dumper: + file: lib/Stem/Codec/Data/Dumper.pm + Stem::Codec::Storable: + file: lib/Stem/Codec/Storable.pm + Stem::Codec::YAML: + file: lib/Stem/Codec/YAML.pm + Stem::Conf: + file: lib/Stem/Conf.pm + Stem::Console: + file: lib/Stem/Console.pm + Stem::Cron: + file: lib/Stem/Cron.pm + Stem::DBI: + file: lib/Stem/DBI.pm + Stem::Debug: + file: lib/Stem/Debug.pm + Stem::Demo::CLI: + file: lib/Stem/Demo/CLI.pm + Stem::Demo::World: + file: lib/Stem/Demo/World.pm + Stem::Event: + file: lib/Stem/Event.pm + Stem::Event::EventPM: + file: lib/Stem/Event/EventPM.pm + Stem::Event::IO: + file: lib/Stem/Event.pm + Stem::Event::Perl: + file: lib/Stem/Event/Perl.pm + Stem::Event::Plain: + file: lib/Stem/Event/Perl.pm + Stem::Event::Queue: + file: lib/Stem/Event/Queue.pm + Stem::Event::Read: + file: lib/Stem/Event.pm + Stem::Event::Signal: + file: lib/Stem/Event/Signal.pm + Stem::Event::Timer: + file: lib/Stem/Event.pm + Stem::Event::Tk: + file: lib/Stem/Event/Tk.pm + Stem::Event::Write: + file: lib/Stem/Event.pm + Stem::Event::Wx: + file: lib/Stem/Event/Wx.pm + Stem::Event::Wx::App: + file: lib/Stem/Event/Wx.pm + Stem::Event::Wx::Timer: + file: lib/Stem/Event/Wx.pm + Stem::File: + file: lib/Stem/File.pm + Stem::Gather: + file: lib/Stem/Gather.pm + Stem::Hub: + file: lib/Stem/Hub.pm + Stem::Id: + file: lib/Stem/Id.pm + Stem::Inject: + file: lib/Stem/Inject.pm + Stem::Load::Driver: + file: lib/Stem/Load/Driver.pm + Stem::Load::Ticker: + file: lib/Stem/Load/Ticker.pm + Stem::Log: + file: lib/Stem/Log.pm + Stem::Log::Entry: + file: lib/Stem/Log/Entry.pm + Stem::Log::File: + file: lib/Stem/Log/File.pm + Stem::Log::Tail: + file: lib/Stem/Log/Tail.pm + Stem::Msg: + file: lib/Stem/Msg.pm + Stem::Packet: + file: lib/Stem/Packet.pm + Stem::Portal: + file: lib/Stem/Portal.pm + Stem::Proc: + file: lib/Stem/Proc.pm + Stem::Route: + file: lib/Stem/Route.pm + Stem::SockMsg: + file: lib/Stem/SockMsg.pm + Stem::Socket: + file: lib/Stem/Socket.pm + Stem::Switch: + file: lib/Stem/Switch.pm + Stem::Test::ConfTypes: + file: lib/Stem/Test/ConfTypes.pm + Stem::Test::Echo: + file: lib/Stem/Test/Echo.pm + Stem::Test::Flow: + file: lib/Stem/Test/Flow.pm + Stem::Test::PacketIO: + file: lib/Stem/Test/PacketIO.pm + Stem::Test::UDP: + file: lib/Stem/Test/UDP.pm + Stem::Trace: + file: lib/Stem/Trace.pm + Stem::TtySock: + file: lib/Stem/TtySock.pm + Stem::UDPMsg: + file: lib/Stem/UDPMsg.pm + Stem::Util: + file: lib/Stem/Util.pm + Stem::Vars: + file: lib/Stem/Vars.pm + Stem::WorkQueue: + file: lib/Stem/WorkQueue.pm +generated_by: Module::Build version 0.2611 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..696da33 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,31 @@ +# Note: this file was auto-generated by Module::Build::Compat version 0.03 + + unless (eval "use Module::Build::Compat 0.02; 1" ) { + print "This module requires Module::Build to install itself.\n"; + + require ExtUtils::MakeMaker; + my $yn = ExtUtils::MakeMaker::prompt + (' Install Module::Build now from CPAN?', 'y'); + + unless ($yn =~ /^y/i) { + die " *** Cannot install without Module::Build. Exiting ...\n"; + } + + require Cwd; + require File::Spec; + require CPAN; + + # Save this 'cause CPAN will chdir all over the place. + my $cwd = Cwd::cwd(); + my $makefile = File::Spec->rel2abs($0); + + CPAN::Shell->install('Module::Build::Compat') + or die " *** Cannot install without Module::Build. Exiting ...\n"; + + chdir $cwd or die "Cannot chdir() back to $cwd: $!"; + } + eval "use Module::Build::Compat 0.02; 1" or die $@; + use lib '_build/lib'; + Module::Build::Compat->run_build_pl(args => \@ARGV); + require BuildStem; + Module::Build::Compat->write_makefile(build_class => 'BuildStem'); diff --git a/README b/README new file mode 100644 index 0000000..19dec7c --- /dev/null +++ b/README @@ -0,0 +1,84 @@ +This is the README file for the Stem system. Stem is released under +the GNU General Public License (http://www.gnu.org/copyleft/gpl.html). +For more information regarding commercial licenses please visit the +Stem Systems website at http://www.stemsystems.com. + +The information below assumes the Stem tarball unpacks into a dir with +the name stem-0.NN (where NN is the version number). That directory path +will be referred to as $STEM. + +You can find more text (FAQ, products) on the Stem web site: +http://www.stemsystems.com. The technical notes are also on the web site +but may be more up to date in the tarball. + + +Stem Installation. + +The INSTALL file has complete instructions on how to install Stem on +your system. + +Stem Demonstrations + +There are 4 Stem demonstration scripts. Read the DEMO file first to +learn about their common features and then read the individual DEMO_* +files for each one. + +Manifest + +README (this file) is in: + + $STEM/README + +Stem installation instructions are in: + + $STEM/INSTALL + +Stem demonstration script instructions are in: + + $STEM/DEMO + $STEM/DEMO_CHAT + $STEM/DEMO_INETD + $STEM/DEMO_TAIL + +Event.pm is in the tarball (You can also it from CPAN): + + $STEM/modules/Event-0.77.tar.gz + +Stem modules are in: + + $STEM/lib/Stem.pm + $STEM/lib/Stem/ + +Stem Example Configuration files are in: + + $STEM/conf/ + +Stem Executable scripts are in: + + $STEM/bin/ + +Technical Design Notes are in: + + $STEM/Design/ + +Documentation is in: + + $STEM/Docs/ + + The latest FAQ is on the web site (http://www.stemsystems.com) + +Stem Cookbook is in: + + $STEM/Cookbook/ + +ssfe (split screen front end) is found in the sirc tarball: + + $STEM/extras/sirc-2.211.tar.gz + +Test scripts are in: + + $STEM/t/ + +Utility scripts are in: + + $STEM/utils/ diff --git a/TODO b/TODO new file mode 100644 index 0000000..3324809 --- /dev/null +++ b/TODO @@ -0,0 +1,140 @@ + Stem Development TODO List + +Feel free to tackle any of these tasks. Email uri@stemsystems.com if you +want design/code ideas or to discuss any of these projects. + +Last edited Fri Jan 16 17:07:14 EST 2004 + +Stem::Event::* + + Add support for more event loops (Tk, Qt, tcl, WxWindows, + POE). See Stem::Event::EventPM for an example of how to wrap an + event loop in Stem. + +Stem::Msg, Stem::Route + + enable tracing of delivered messages + + basic source routing needs design work. new client hubs will + flood upstream who they are. master servers can respond + downstream which will create a fully aware tree networks. + + add support for a forward. it takes a message, clones it and + changes the to address. + + maybe move Stem::Route to Stem::Msg::Route. it is only used by + Stem::Msg and that better describes its name. + +Stem::Portal + + finish and test Portal forking + + maybe convert to use Class clone/piped support. not sure if this + is reasonable or worth it. + +Stem::Log + + add support for single method ref to do all the filtering. it is + passed the log entry and is a stem::log::entry object. it can + call all the action methods. there is no need for the current + design of a list of key/values. it will be deprecated. + + timestamp is set with a template (strftime like but numbers only). + + add timer based filters. single shot cron entries to toggle + state are fine. but how do you set the state at startup? if it + is between start and end times, the filter should be + enabled. this needs design work. + + define and add more actions + + email + msg + +run_stem + + write up run_stem man page in pod + + write tech notes for run_stem + +Stem::Proc + + needs much more testing + + test pseudo tty + +Stem::File + + (some of this is done. a good simple project to pick up) + + design parent cell and how it spawns targeted file cells. + + code file stem based transfer stuff. + + add file compare options - size, timestamp, MD5 + + add single directory support. + filename filters + + add dir tree copy support + + add throttle support? don't want to slurp entire large file in + and clog the system. throttle with reply messages and/or timing + + add support for ftp and scp + +Stem::Util + + replace read_file/write_file with File::Slurp. might as well eat + my own dog food. this needs to be added to the required modules + list in Build.PL + +Stem::Expect + + use proc or socket and use async IO + + hook in Stem::Cell::Flow + + expect handles timeouts from async IO. then it drives the state + machine with a timeout method. regular input is sent to the + state machine as data. + + if no input matches but not timed out yet, we wait for more + input or the timeout. + + do we need code callbacks? i think the higher level object + (protocol::ftp?) would want callbacks itself in some cases. + +Testing + need tests for higher level cells. + + need load testing of various subsystems. + + need cross platform testing. + +Documentation + + full pods for all modules + + accurate docs for all class fields. this can be autogenerated + from the $field_info lists. we could parse only that code out + easily and eval the string. the either edit the internal pod or + some other text file. it would generate a nicely formatted + description of all the class fields. + +Tracing + + mark by name which message is currently being delivered + + use the hub/cell/target in the 'to' address. + + any newly dispatched messages get a from/origin of that to + + just copy the to? + + fix registry lookup by object to get cell/name and target + + save current event cell name and target + + create trace file to be written by events and message delivery + (or dispatch?) diff --git a/bin/boot_stem b/bin/boot_stem new file mode 100755 index 0000000..ba841b4 --- /dev/null +++ b/bin/boot_stem @@ -0,0 +1,49 @@ +#!/usr/local/bin/perl -w + +use strict ; + +use Getopt::Std ; + +use YAML ; + +my %opts ; + +getopts( 'v', \%opts ) ; + +my $cmds = Load do{ local $/ ; <> } ; + +foreach my $boot ( @{$cmds} ) { + + if ( my $skip = $boot->{'skip'} ) { + + next if $skip eq 'yes' ; + } + my $wrap = $boot->{'wrap'} || '/bin/sh -c' ; + my $cd = $boot->{'cd'} || '.' ; + my $cmd = $boot->{'cmd'} ; + + my $user = $boot->{'user'} || ''; + + my $env = $boot->{'env'} || {} ; + my $stem_env = $boot->{'stem_env'} || {} ; + + local( %ENV ) = ( %ENV, %{$env} ) ; + + my $cmd_env = join ' ', map( "$_='$stem_env->{$_}'", keys %$stem_env ) ; + + $cmd =~ s/run_stem/run_stem $cmd_env/ ; + + my $system; + $system = "su - $user;" if $user && getpwuid($<) ne $user; + $system .= qq|$wrap "cd $cd ; $cmd" &| ; + + print "$system\n" if $opts{'v'} ; + + system $system ; + + my $delay = $boot->{'delay'} || 3 ; + + print "waiting $delay seconds\n" if $opts{'v'}; + + sleep $delay ; +} diff --git a/bin/cgi2stem.pl b/bin/cgi2stem.pl new file mode 100755 index 0000000..4ab7577 --- /dev/null +++ b/bin/cgi2stem.pl @@ -0,0 +1,111 @@ +#!/usr/local/bin/perl -wT + +# This is a CGI script that interfaces to stem. it collects all the +# CGI data and sends it to a Stem::SockMsg cell as a single +# Stem::Packet. It reads a single Stem::Packet back from the socket +# and uses the data in there to generate a response page. + +$|++ ; + +use strict ; +use lib '/wrk/stem/src/stem/lib/' ; + +use CGI ; +use CGI::Carp qw(fatalsToBrowser) ; +use IO::Socket ; + +use Stem::Packet ; + +my $cgi = CGI->new() ; + +my %cgi_data ; + +# get all the cgi data we can + +$cgi_data{ 'params' } = get_cgi_data( 'param' ) ; +$cgi_data{ 'cookies' } = get_cgi_data( 'cookie' ) ; +#$cgi_data{ 'env' } = { %ENV } ; +#$cgi_data{ 'self_url' } = $cgi->self_url() ; +$cgi_data{ 'url' } = $cgi->url() ; +#$cgi_data{ 'cgi' } = $cgi ; + +# todo: handle default host:port here + +my $data = send_and_get_packet( \%cgi_data ) ; + +# use Data::Dumper ; + +# print $cgi->header() ; +# # print "
\n", Dumper( \%cgi_data ), "\n
\n" ; +# print "
\n", Dumper( $data ), "\n
\n" ; + +# exit ; + +if ( ref $data eq 'SCALAR' ) { + + print $$data ; + exit ; +} + +print $cgi->header(), < +cgi2stem error: $data + +HTML + + + +# this works for both cookies and params as their APIs are the same + +sub get_cgi_data { + + my( $type ) = @_ ; + + my %cgi_info ; + + foreach my $name ( $cgi->$type() ) { ; + + my @values = $cgi->$type( $name ) ; + + if ( @values > 1 ) { + $cgi_info{ $type } = \@values ; + next ; + } + + $cgi_info{ $name } = shift @values ; + } + + return \%cgi_info ; +} + +sub send_and_get_packet { + + my( $in_data, $host, $port ) = @_ ; + + $port ||= 9999 ; + $host ||= 'localhost' ; + + my $sock = IO::Socket::INET->new( "$host:$port" ) ; + + $sock or return "can't connect to $host:$port\n" ; + + my $packet = Stem::Packet->new( codec => 'Storable' ) ; + + my $write_buf = $packet->to_packet($in_data) ; + + syswrite( $sock, $$write_buf ) ; + + my $read_buf ; + + while( 1 ) { + + my $bytes_read = sysread( $sock, $read_buf, 8192 ) ; + + return "sysread error $!" unless defined $bytes_read ; + return "sysread closed" if $bytes_read == 0 ; + + my $result = $packet->to_data( $read_buf ) ; + + return $result if $result ; + } +} diff --git a/bin/chat2_demo b/bin/chat2_demo new file mode 100755 index 0000000..a2006a9 --- /dev/null +++ b/bin/chat2_demo @@ -0,0 +1,72 @@ +#!/usr/local/bin/perl -s + +$line_cnt = 10 ; +$offset = 175 ; +$base_off = 0 ; +$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ; + +my @children ; + +$SIG{ 'INT' } = \&cleanup ; + +if ( $s ) { + + $ssfe = 'ssfe' ; + $prompt = '-prompt Stem:' ; + $prompt2 = '-prompt Chat:' ; + $echo = 'console_echo=1' +} + +foreach $cmd ( split /\n/, < ) { + + next unless /^q/i ; + + cleanup() ; +} + +sub cleanup { + + print "clean up\n" ; + + kill 9, @children ; + wait ; + exit ; + +} + +sub fork_exec { + + my( @exec ) = @_ ; + + if ( $pid = fork() ) { + + push @children, $pid ; + return ; + } + + exec @exec ; +} diff --git a/bin/chat_demo b/bin/chat_demo new file mode 100755 index 0000000..8437913 --- /dev/null +++ b/bin/chat_demo @@ -0,0 +1,75 @@ +#!/usr/local/bin/perl -s + +$line_cnt = 10 ; +$offset = 175 ; +$base_off = 0 ; +$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ; + +print "CHAT DEMO\n" ; + +my @children ; + +$SIG{ 'INT' } = \&cleanup ; + +if ( $s ) { + + $ssfe = 'ssfe' ; + $prompt = '-prompt Stem:' ; + $prompt2 = '-prompt Chat:' ; + $echo = 'console_echo=1' +} + +foreach $cmd ( split /\n/, < ) { + + next unless /^q/i ; + + cleanup() ; +} + +sub cleanup { + + print "clean up\n" ; + + kill 9, @children ; + + wait ; + exit ; + +} + +sub fork_exec { + + my( @exec ) = @_ ; + + if ( $pid = fork() ) { + + push @children, $pid ; + return ; + } + + exec @exec ; +} diff --git a/bin/cli b/bin/cli new file mode 100644 index 0000000..833982a --- /dev/null +++ b/bin/cli @@ -0,0 +1,53 @@ +#!/usr/local/bin/perl -w + +use strict ; + +use IO::Socket ; +use Data::Dumper ; + +use Stem::Packet ; + +$| = 1 ; + +my $host = 'localhost' ; + +my $port = shift || 8888 ; + +my $sock = IO::Socket::INET->new( "$host:$port" ) ; +$sock or die "can't connect to $host:$port\n" ; + +#my $packet = Stem::Packet->new( codec => 'YAML' ) ; +my $packet = Stem::Packet->new() ; + +print "type 'help' for help\n\n" ; + +while( 1 ) { + + print "CLI > " ; + + chomp( my $line = <> ) ; + next unless $line =~ /\S/ ; + +#my $line = "foo bar bazz" ; + + my %data ; + @data{ qw( op key value ) } = split( ' ', $line, 3 ) ; + + my $write_buf = $packet->to_packet( \%data) ; +#print "WRITE [$$write_buf]\n" ; + + syswrite( $sock, "${$write_buf}" ) ; + +# this should be a proper non-blocking read loop but it is fine for this +# demo. + + my $bytes_read = sysread( $sock, my $read_buf, 8192 ) ; + last unless defined $bytes_read and $bytes_read > 0 ; + + my $result = $packet->to_data( \$read_buf ) ; + +# print "RESULT [$$result]\n" ; + print Dumper $result ; + +#exit ; +} diff --git a/bin/hello_demo b/bin/hello_demo new file mode 100644 index 0000000..5b525c1 --- /dev/null +++ b/bin/hello_demo @@ -0,0 +1,62 @@ +#!/usr/local/bin/perl -s + +use strict ; +use warnings ; +our $s ; + +if ( -d 'conf' && -e 'bin/run_stem' ) { + + $ENV{PERL5LIB} = 'lib' ; + $ENV{PATH} = "bin:$ENV{PATH}" ; +} + +print "HELLO DEMO\n" ; + +$SIG{ 'INT' } = \&cleanup ; + +my @children ; + +my $ssfe = $s ? 'ssfe -prompt Stem:' : '' ; +my $echo = $s ? 'console_echo=1' : '' ; + +my $cmd = < ) { + + next unless /^q/i ; + + cleanup() ; +} + +sub cleanup { + + print "clean up\n" ; + + kill 9, @children ; + + wait ; + exit ; + +} + +sub fork_exec { + + my( @exec ) = @_ ; + + if ( my $pid = fork() ) { + + push @children, $pid ; + return ; + } + + exec @exec ; +} diff --git a/bin/inetd_demo b/bin/inetd_demo new file mode 100755 index 0000000..9116feb --- /dev/null +++ b/bin/inetd_demo @@ -0,0 +1,75 @@ +#!/usr/local/bin/perl -s + +$line_cnt = 10 ; +$offset = 175 ; +$base_off = 0 ; +$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ; + +my @children ; + +print "INETD: $ENV{PATH}\n" ; + +$SIG{ 'INT' } = \&cleanup ; + +if ( $s ) { + + $ssfe = 'ssfe' ; + $prompt = '-prompt Stem:' ; + $echo = 'console_echo=1' +# $prompt2 = '-prompt Chat:' ; +} + +foreach $cmd ( split /\n/, < ) { + + next unless /^q/i ; + + cleanup() ; +} + +sub cleanup { + + print "clean up\n" ; + + kill 9, @children ; + + wait ; + exit ; + +} + +sub fork_exec { + + my( @exec ) = @_ ; + + if ( $pid = fork() ) { + + push @children, $pid ; + return ; + } + + exec @exec ; +} diff --git a/bin/quote_serve b/bin/quote_serve new file mode 100755 index 0000000..940389f Binary files /dev/null and b/bin/quote_serve differ diff --git a/bin/run_stem b/bin/run_stem new file mode 100755 index 0000000..1d20a3f --- /dev/null +++ b/bin/run_stem @@ -0,0 +1,283 @@ +#!/usr/local/bin/perl -w +# File: bin/run_stem + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +$Data::Dumper::Indent = 1 ; +$Data::Dumper::Purity = 1 ; +$Data::Dumper::Useqq = 1 ; + +$| = 1 ; + +#print "RUN STEM $0\n" ; + +my @conf_args ; + +# we set Stem's default environment before we load any Stem modules so +# they can use those values + +use Stem::InstallConfig ; + +BEGIN { + my $env_text ; + my $stem_lib_dir = $Stem::InstallConfig::Config{'conf_path'} ; + + my $is_win32 = $^O =~ /Win32/i ; + +# get the site env and home env files + + my @env_files = "$stem_lib_dir/env" ; + + unless ( $is_win32 ) { + + push @env_files, ( $ENV{HOME} || + $ENV{LOGDIR} || + (getpwuid($>))[7] ) . '/.stem_env' ; + } + + foreach my $env_file ( @env_files ) { + + next unless -r $env_file ; + +# shut up a dumb warning + use vars '*ARGVOUT' ; + $env_text .= + do { local( @ARGV, $/ ) = $env_file ; <> } ; + } + + +# set the starting %env from the files + + %Stem::Vars::Env = $env_text =~ /^([^=]+)=(.+)$/mg if $env_text ; + + +# set the %Stem::Vars::Env from %ENV any %ENV name starting with STEM_ +# is used. the STEM_ is deleted and the rest of the lower case name is +# used with the %ENV value + + /^STEM_(\w+)/ and $Stem::Vars::Env{ lc $1 } = $ENV{ $_ } for keys %ENV ; + +# set %Stem::Vars::Env from 'name=value' command line args +# all other args are assumed to be conf file names. +# we do this after we process %ENV so the command line args can override +# any shell environment values + + while( @ARGV ) { + + my $arg = shift ; + + if ( $arg =~ /([^=]+)=(.*)/ ) { + + $Stem::Vars::Env{ $1 } = $2 ; + next ; + } + + push @conf_args, $arg ; + } + +# set the default config search path. this will be changed by the install +# script. + + $Stem::Vars::Env{ 'conf_path' } ||= 'conf:.' ; + +# set the trace levels + +# $Stem::Vars::Env{ 'MainTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'MainTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'ProcTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'ProcTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'PortalTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'PortalTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'SockMsgTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'SockMsgTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'ConfTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'ConfTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'LogTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'LogTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'CellTraceStatus' } ||= 0 ; +# $Stem::Vars::Env{ 'CronTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'CronTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'EventTraceStatus' } ||= 0 ; +# $Stem::Vars::Env{ 'EventTraceError' } ||= 0 ; +# $Stem::Vars::Env{ 'GatherTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'GatherTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'HubTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'HubTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'TailTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'TailTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'MsgTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'MsgTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'MsgTraceMsg' } ||= 1 ; +# $Stem::Vars::Env{ 'SwitchTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'SwitchTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'AsynchIOTraceStatus'} ||= 1 ; +# $Stem::Vars::Env{ 'AsynchIOTraceError' } ||= 1 ; +# $Stem::Vars::Env{ 'TtyMsgTraceStatus' } ||= 1 ; +# $Stem::Vars::Env{ 'TtyMsgTraceError' } ||= 1 ; + +} + +# we load Stem after we process the command line args and %ENV so the +# modules can use those values + +use Stem ; + +use Stem::Trace 'log' => 'stem_status', + 'sub' => 'TraceStatus', + 'env' => 'MainTraceStatus' ; + +my $prog_name = $0 ; + +$prog_name =~ s|.+/|| ; + +unless ( @conf_args ) { + + $prog_name eq 'run_stem' && + die "run_stem must be passed a stem config file" ; + + @conf_args = $prog_name ; +} + +# always start with the site config file +# this defines site wide configuration settings that are internal +# to Stem + +my $err = Stem::Conf::load_confs( 'site' ) ; + +# ignore a missing site config + +die $err if defined $err && $err !~ /Can't find config/ ; + +$err = Stem::Conf::load_confs( @conf_args ) ; + +TraceStatus "Stem startup" ; + +TraceStatus $err if $err; + +die $err if $err ; + +############### +# this should use Stem::Event +############### +$SIG{ 'INT' } = sub { + TraceStatus "INT signal received" ; + Stem::Event::stop_loop() +} ; + +Stem::Event::start_loop() ; + +TraceStatus "Stem shutdown" ; + +exit; + +=head1 run_stem - Start up Stem and load configuration files + +=head2 Synopsis + + run_stem foo=bar stem_conf_file + +This script is the way most Stem applications are started. It does +several important things so you don't have to create your own top +level scripts. It is not required to execute run_stem to use Stem but +it makes it much easier to get it going in most cases. The following +are the steps that 'run_stem' does when bringing up Stem. + +=head2 * Load Stem Environment + +Many Stem modules and cells look at the Stem environment for default +configuration values or global flags. This allows you to control how +many of the cells and modules behave when loaded and instantiated. If +a Stem attribute in a specification has its 'env' name description +set, it will use that name (optionally prefixed with the cell's +registration name) as a key to lookup in the Stem Environement. If +found there, that value becomes is used and overrides the default and +any value set in a configuration file. This allows the user to +override default setting from the outside without modifying Stem +configuration files. See Stem::Class for more on this. The Stem +environment is set from these sources in the following order: + +=over 4 + +=item Global Site Environment File + +'run_stem' initially looks for a file named 'env' in the first +configuration directory (set at install time) and loads it if +found. These site and user files both have a simple key=value format +with one entry per line. + +=item User Environment File + +'run_stem' then will look in your home directory (not supported on +windows) for a file named .stem_env and loads it if found. + +=item Shell Environment + +Any shell environment variable with the form 'STEM_*' will +have the 'STEM_' part deleted and the rest of its name +converted to lower case. That will become the key in the Stem +environment with the value set to the shell variable's value. + +=item Command Line + +Any command line arguments of the form key=value will be +parsed out and used to set a Stem environment variable. + +=back + +=head2 * Load Stem Core Modules + + 'run_stem' then loads all the core Stem modules with a use + Stem line. + +=head2 * Load Configuration Files + +Any arguments left in @ARGV are assumed to be Stem configuration +files. Typically there is only one configuration file but you can have +pass in as many as you want. The config file arguments can have a +.stem suffix or no suffix. The configuration directory list is +searched in order for the file and it is loaded and all of its entries +are constructed. + +You can override the default configuration directory list (set at +install time) by setting the 'conf_path' Stem environment variable +from the shell environment or on the 'run_stem' command line. The +following are equivilent: + + export STEM_CONF_PATH=/etc/stem/conf:/home/foo/.stem + run_stem bar + + run_stem conf_path=/etc/stem/conf:/home/foo/.stem bar + +=head2 * Start Event Loop + +The final operation 'run_stem' does is start the main event +loop. If no events were created by the loaded configuration +files, this will fail and 'run_stem will exit immediately. If +all the created events eventually get canceled, the event loop +will exit and 'run_stem' will exit too. + +=cut diff --git a/bin/stem2pod b/bin/stem2pod new file mode 100755 index 0000000..97143d5 --- /dev/null +++ b/bin/stem2pod @@ -0,0 +1,410 @@ +#!/usr/local/bin/perl -w +# +# stem2pod +# +# takes filename (a stem module) arguments and it updates their +# pod from their attribute descriptions. it also will insert pod +# templates for methods, subs and standard pod sections. +# +# if a file is changed, it is written out over itself. unchanged +# files are not touched. + +use strict; + +use Carp qw( carp cluck ) ; +use Data::Dumper; + +#use Test::More tests => 1 ; + +#$SIG{__WARN__} = sub { cluck } ; + +my $changed ; +my $package ; + +my %is_attr_part = map { $_ => 1 } qw( + name + type + help + default + required + class + class_args +) ; + +foreach my $file_name ( @ARGV ) { + + process_source_file( $file_name ) ; +} + +exit ; + +sub process_source_file { + + my ( $file_name ) = @_ ; + + my $code_text = read_file( $file_name ) ; + + my $new_code_text = process_code_text( $file_name, $code_text ) ; + +#print $new_code_text ; + + if ( $new_code_text eq $code_text ) { + + print "$file_name SAME\n" ; + return ; + } + + print "$file_name CHANGED\n" ; + + write_file( "$file_name.new, $new_code_text ) ; + +# write_file( "$file_name.bak, $code_text ) ; +# write_file( $file_name, $new_code_text ) ; + +} + +sub process_code_text { + + my ( $file_name, $text ) = @_ ; + + $text =~ s{ + ( + ^package # start at package line + .+? # the middle stuff + ^sub # start of constructor + ) + } + { + update_attr_spec( $1, $file_name ) + }mgsex ; + + $text =~ s{ + (.{0,20}?) + ^sub + \s+ + (\w+) + \s* + } + { update_sub_pod( $1, $2 ) }mgsex ; + + unless( $text =~ /^=cut\s*^\s*1\s*;\s*/m ) { + + $text =~ s{^\s*1\s*;\s*$}{ update_trailing_pod() }mex ; + } + + return $text ; +} + + +sub update_attr_spec { + + my( $attr_text, $file_name ) = @_ ; + +#print "U1 <$attr_text>\n" ; + + ( $package ) = $attr_text =~ /^package\s+([\w:]+)/ ; + + $attr_text =~ s/\n*^\#{5,}\n.+?^\#{5,}\n*//ms ; +# and print "DELETED OLD POD\n" ; + +#print "U3 <$attr_text>\n" ; + + $attr_text =~ s{ (^my\s+\$attr_spec.+?^]\s*;\s*) } + { attr_spec_to_pod( $1, $file_name ) }gmsex ; + +#dump_attr( 'ATTR', $attr_text ) ; +#print "ATTR [", substr( $attr_text, -40 ), "]\n" ; +#print "U2 [$attr_text]\n" ; + + return $attr_text ; +} + +sub attr_spec_to_pod { + + my ( $attr_text, $file_name ) = @_ ; + + my $pod ; + +#print "ATTR [$attr_text]\n" ; +#print "ATTR END1 [", substr( $attr_text, -30), "]\n" ; + + $attr_text =~ s/\s*\z// ; + + my( $attr_list_text ) = + $attr_text =~ /^my\s+\$attr_spec.+?=(.+?^\])/ms ; + $attr_list_text or die + "can't parse out attr list from file $file_name class $package" ; + +#print "ATTR2 [$attr_list_text]\n" ; + my $attr_list = eval $attr_list_text ; + + $pod .= <{name} ; + + if ( $name ) { + + $pod .= < + +=over 4 + +POD + } + else { + + warn <{help} ; + + if ( defined( $help ) ) { + + $pod .= <{class} ) { + + my $class_args = '<' . + join( ', ', @{$attr_ref->{class_args} || []} ) + . '>' ; + + $pod .= <{type} ) and $pod .= <{type} +POD + + if ( exists( $attr_ref->{default} ) ) { + + my $default = $attr_ref->{default} ; + + if( ref($default) eq "ARRAY" ) { + + $default = + '(' . join( ', ', @{$default} ) . ')' ; + } + + $pod .= < value: + +$default +POD + } + + exists( $attr_ref->{required} ) and $pod .= <. +POD + + foreach my $attr ( sort keys %{ $attr_ref } ) { + next if $is_attr_part{ $attr } ; + $pod .= "Unknown attribute $attr\n" ; + } + + $pod .= < + +The B method creates an object of the class B<$package>. + +POD + + return < + +The B method is effectively a default method for message +delivery. If any message to this cell can't be delivered to another +method, then it will be delivered to the B method. If a +command message is delivered and a value is returned by B, a +response message is sent back to the originating cell with that value. +POD + + return < type messages are delivered to this method. Its return value is +ignored by the message delivery system. +POD + + return < command messages are delivered to this method. If any value is +returned, the message delivery system will create a response type +message and dispatch it back to the sending cell. +POD + + return <uri\@stemsystems.comE + +=cut + +1 ; +POD + +} + +sub read_file { + + my( $file_name ) = shift ; + + local( *FH ) ; + open( FH, $file_name ) || carp "can't open $file_name $!" ; + + return if wantarray ; + + my $buf ; + + sysread( FH, $buf, -s FH ) ; + return $buf ; +} + +sub write_file { + + my( $file_name ) = shift ; + + local( *FH ) ; + + open( FH, ">$file_name" ) || carp "can't create $file_name $!" ; + + print FH @_ ; +} + +sub dump_attr { + + my( $key, $text ) = @_ ; + + $text =~ /(;\s+#{3,})/s or return ; + + print "$key [$1]\n" ; +} + +__END__ diff --git a/bin/stem_msg b/bin/stem_msg new file mode 100755 index 0000000..be0f70c --- /dev/null +++ b/bin/stem_msg @@ -0,0 +1,191 @@ +#!/usr/local/bin/perl + +use Getopt::Long ; +use strict ; + +use Stem ; + +my %args ; +my $hub_name ; +my $portal ; + +parse_args() ; + +setup_hub() ; + +send_msg() ; + +Stem::Event::start_loop() ; + +# no return from here. +###################### + +sub setup_hub { + + $hub_name = "Stem_msg_$$" ; + + Stem::Route::register_class( __PACKAGE__ ) ; + + Stem::Hub->new( 'reg_name' => $hub_name ) ; + + my @portal_args ; + + push @portal_args, ( 'host' => $args{'host'} ) if $args{'host'} ; + push @portal_args, ( 'port' => $args{'port'} ) if $args{'port'} ; + +#print "portal args: @portal_args\n" ; + + $portal = Stem::Portal->new( @portal_args ) ; + + die "Can't create Portal: $portal" if $portal ; +} + +sub send_msg { + + my ( @msg_args, @target ) ; + + if ( $args{'cmd'} ) { + + @msg_args = ( 'type' => 'cmd', 'cmd' => $args{'cmd'} ) ; + } + else { + + @msg_args = ( 'type' => 'data' ) ; + } + + @target = ( 'to_target' => $args{'target'} ) if $args{'target'} ; + + push( @msg_args, ( 'ack_req' => 1 ) ) if $args{'ack'} ; + + my $data = exists( $args{'data'} ) ? $args{'data'} : '' ; + + my $msg = Stem::Msg->new( + 'to_hub' => 'DEFAULT', + 'to_cell' => $args{'cell'}, + @target, + 'from_cell' => __PACKAGE__, + 'from_hub' => $hub_name, + 'data' => \$data, + @msg_args, + ) ; + + $msg->dispatch() ; +} + +# this is the class method that gets back the response and ack messages. + +sub msg_in { + + my( $class, $msg ) = @_ ; + + if( $msg->type() eq 'msg_ack' ) { + +# print "ACK\n" ; + exit ; + } + + if ( my $data = $msg->data() ) { + + print ${$data} ; + } + +# $portal->shut_down() ; + + exit unless $args{'ack'} ; + + return ; +} + + +sub parse_args { + + Getopt::Long::Configure( 'no_ignore_case' ) ; + + GetOptions( \%args, + 'cell|C=s', + 'hub|H=s', + 'target|T=s', + 'cmd|c=s', + 'data|d=s', + 'ack|a', + 'host|h=s', + 'port|p=s', + 'help|?', + ) ; + +#print map "$_ => $args{$_}\n", sort keys %args ; + + usage( '' ) if $args{ 'help' } ; + + usage( 'Missing Cell address' ) unless $args{ 'cell' } ; +} + +sub usage { + + my $err_msg = shift ; + + my $usage = <<'=cut' ; +=pod + +=head1 NAME + +stem_msg - Inject a message into a Stem Hub + +=head1 SYNOPSIS + +stem_msg -cell [-hub ] [-target ] + [-cmd ] [-data ] [-ack] + [-host ] [-port ] + + -C The Stem Cell to send this message to. + -cell This is required. + + -H The hub which has the addressed Stem Cell. + -hub + + -T The target address of the Stem Cell + -target + + -c The cmd type to send in the message + -cmd If no cmd is set, it will be a data type + message. + + -d The data to be sent in the message. + -data Default is an empty string. + + -a Wait for an acknowledge message before + -ack exiting. + + -h The host which the Stem Hub is on. + -host Default: localhost + + -p The port which the Stem Portal is listening + -port to. + Default: 10,000 (probably will change) + +=head1 DESCRIPTION + +This program is meant to inject a single message into a Stem Hub. You +set the Cell address with the command line options and then which +command to execute in that Cell. If you don't set a command, then a +data message will be sent. You can send data in the message as well. + +If the Cell generates a response message, then its data will be +printed on stdout. + +If the -ack option is set, then the message will have the ack_req flag +will be set in the outgoing message. This will cause an 'ack' type +message to be sent back after the original message has been +delivered. This is meant for when you send a message to a Cell which +doesn't generate a response. It lets this program know that it can +exit. + +=cut + + $usage =~ s/^=\w+.*$//mg ; + + $usage =~ s/\n{2,}/\n\n/ ; + $usage =~ s/\A\n+/\n/ ; + + die "$err_msg\n$usage" ; +} diff --git a/bin/tail_demo b/bin/tail_demo new file mode 100755 index 0000000..ce04d7e --- /dev/null +++ b/bin/tail_demo @@ -0,0 +1,76 @@ +#!/usr/local/bin/perl -s + +$line_cnt = 10 ; +$offset = 175 ; +$base_off = 0 ; +$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ; + +my @children ; + +my $tail_dir = 'tail' ; + +$SIG{ 'INT' } = \&cleanup ; + +if ( $s ) { + + $ssfe = 'ssfe' ; + $prompt = '-prompt Stem:' ; + $echo = 'console_echo=1' +} + +-d $tail_dir or mkdir $tail_dir, 0722 or + die "can't create $tail_dir working directory" ; + +foreach my $log ( qw( foo bar bar_status ) ) { + unlink "$tail_dir/$log.log" ; +} + + +foreach $cmd ( split /\n/, < ) { + + next unless /^q/i ; + + cleanup() ; +} + +sub cleanup { + + print "clean up\n" ; + + kill 9, @children ; + wait ; + exit ; + +} + +sub fork_exec { + + my( @exec ) = @_ ; + + if ( $pid = fork() ) { + + push @children, $pid ; + return ; + } + + exec @exec ; +} diff --git a/certs/client-cert.pem b/certs/client-cert.pem new file mode 100644 index 0000000..e35a3ce --- /dev/null +++ b/certs/client-cert.pem @@ -0,0 +1,43 @@ +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Some-State, O=Dummy IO::Socket::SSL Certificate Authority, CN=Dummy IO::Socket::SSL Certificate Authority + Validity + Not Before: Jul 20 16:06:19 2002 GMT + Not After : Dec 5 16:06:19 2029 GMT + Subject: C=US, ST=Some-State, O=IO::Socket::SSL Dummy Certificate, CN=IO::Socket::SSL Dummy Certificate + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (512 bit) + Modulus (512 bit): + 00:cd:65:60:3d:8f:55:1f:7a:9e:87:a8:72:a0:53: + 9d:e9:31:9e:bc:f1:27:d3:ba:e9:ab:ca:28:a5:b5: + 48:a2:24:d8:ed:01:ec:ae:69:b1:91:7b:68:e6:d1: + 15:57:7e:b0:06:62:33:d5:88:3e:fc:dc:fb:db:8a: + c9:9b:bb:50:9f + Exponent: 65537 (0x10001) + Signature Algorithm: md5WithRSAEncryption + 0b:61:be:f8:12:9a:82:92:63:ed:57:f8:f9:dd:79:1b:46:a2: + 0c:7c:a0:80:01:88:38:0f:a1:c0:b3:2d:57:0e:ad:3c:a8:72: + e3:d9:50:7f:11:9a:af:2a:e3:d8:66:de:b2:18:3e:cf:c5:4a: + 71:00:b0:05:49:a3:83:9a:53:f9:83:92:2f:c7:f7:d3:df:8f: + 3d:92:5c:e5:3e:5a:f8:b6:55:71:5e:c8:85:a4:0c:0f:e7:1b: + 1f:b9:c9:db:c4:9e:d8:6a:fc:33:da:10:36:de:73:34:2f:ea: + 0d:29:e1:2b:90:89:a3:a9:74:8c:57:e3:ee:50:00:b4:0c:69: + 1a:f2 +-----BEGIN CERTIFICATE----- +MIICNDCCAZ0CAQEwDQYJKoZIhvcNAQEEBQAwgY4xCzAJBgNVBAYTAlVTMRMwEQYD +VQQIEwpTb21lLVN0YXRlMTQwMgYDVQQKEytEdW1teSBJTzo6U29ja2V0OjpTU0wg +Q2VydGlmaWNhdGUgQXV0aG9yaXR5MTQwMgYDVQQDEytEdW1teSBJTzo6U29ja2V0 +OjpTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTAyMDcyMDE2MDYxOVoXDTI5 +MTIwNTE2MDYxOVowejELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUx +KjAoBgNVBAoTIUlPOjpTb2NrZXQ6OlNTTCBEdW1teSBDZXJ0aWZpY2F0ZTEqMCgG +A1UEAxMhSU86OlNvY2tldDo6U1NMIER1bW15IENlcnRpZmljYXRlMFwwDQYJKoZI +hvcNAQEBBQADSwAwSAJBAM1lYD2PVR96noeocqBTnekxnrzxJ9O66avKKKW1SKIk +2O0B7K5psZF7aObRFVd+sAZiM9WIPvzc+9uKyZu7UJ8CAwEAATANBgkqhkiG9w0B +AQQFAAOBgQALYb74EpqCkmPtV/j53XkbRqIMfKCAAYg4D6HAsy1XDq08qHLj2VB/ +EZqvKuPYZt6yGD7PxUpxALAFSaODmlP5g5Ivx/fT3489klzlPlr4tlVxXsiFpAwP +5xsfucnbxJ7Yavwz2hA23nM0L+oNKeErkImjqXSMV+PuUAC0DGka8g== +-----END CERTIFICATE----- diff --git a/certs/client-key.enc b/certs/client-key.enc new file mode 100644 index 0000000..69eddc0 --- /dev/null +++ b/certs/client-key.enc @@ -0,0 +1,12 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,A24532ADA3AB45E3 + +sltzmMEb/cKphkOkMQsMJHyqc17pxXf3z3QrvktJRvZQtMU6dY0eisbW3gilU+80 +qgiMkkD9TEufYQ3oLRAF8hefS+iM+Mx3F5Ml5bhArXQBRN6Gd0QkepWnrrko8Y6N +6uGT9ndJHhLyOArDWgWkzfuT7NEVoYrLhf8x6E43v/6By5GmBZs5mbwygYr6vpzZ +6+KHgpVFYPNQvhJTKJTe2XO+kTQtOcYmrapTr6SG4i9i7x2Q21CGhum8492KA+3N ++EvmGAp0MMwZVTuCRyquLRfF6/NsPANb97yWsz3pe05I/p8CSzwU7ivNQZA51pyx +zlwzjoWElSzMTssIegcCctdjeTHhiFRfvW1YDinvXPiLfAjBOv37JDvLC6vclqev +yPXtne0iOn8hZc0QAnWESAAPz6PE0jIecqcZkXKQHfc= +-----END RSA PRIVATE KEY----- diff --git a/certs/client-key.pem b/certs/client-key.pem new file mode 100644 index 0000000..fc5baf2 --- /dev/null +++ b/certs/client-key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOQIBAAJBAM1lYD2PVR96noeocqBTnekxnrzxJ9O66avKKKW1SKIk2O0B7K5p +sZF7aObRFVd+sAZiM9WIPvzc+9uKyZu7UJ8CAwEAAQJAdCtxWoAWCh7lSR8J7go9 +Fya8fGr9NrDR9xr5EHhPI30lmzpj1Tst2VRymN8ojUu/3AgBAOshpqS5Ve38o2mU +yQIhAPd44uw37QTwmefTWV+REtaGHaRwbc5qemSV6WNpgei9AiEA1HlOzr1BxqCo +ZwlizSn5ox8YdJ6LAOA+1GOkvOyV2osCIGT26w4Y2xiy2PfeII5+78KaQSm/vO0E +QB8dknS+rQO5AiBwv6KjMGjsFyrl6mQkjOasuf6HO+51W4nbuLidjEoE+wIgN00d +/WcCSyj8HlfLgHPe8ZlEyMe6Hq7LfX5lShUq0qI= +-----END RSA PRIVATE KEY----- diff --git a/certs/my-ca.pem b/certs/my-ca.pem new file mode 100644 index 0000000..36bf8e4 --- /dev/null +++ b/certs/my-ca.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0BAQQFADCBjjELMAkGA1UEBhMCVVMx +EzARBgNVBAgTClNvbWUtU3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6 +OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpT +b2NrZXQ6OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNzIwMTYwNTU0 +WhcNMjkxMjA1MTYwNTU0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUt +U3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0aWZpY2F0 +ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0 +aWZpY2F0ZSBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALQm +bgkEUWImNkjWcO6qn5NZ7rCFbtrzqEYbqciy+1qlWuoBgU44n9ykD1c/BcmBPsDT +bIOfLzjcdJj38taXu7kcRclchJ+/c6o/SmDv7UqcL6QgVSZRvRrK7TDypMqe3sW8 +zCvTF8WtSsgFy5f9qlUdx4NowMzVV7OFl+6x4YlpAgMBAAGjge4wgeswHQYDVR0O +BBYEFDU4SrHVMHDjd2kBgFM/qyC3DPxFMIG7BgNVHSMEgbMwgbCAFDU4SrHVMHDj +d2kBgFM/qyC3DPxFoYGUpIGRMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29t +ZS1TdGF0ZTE0MDIGA1UEChMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENlcnRpZmlj +YXRlIEF1dGhvcml0eTE0MDIGA1UEAxMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENl +cnRpZmljYXRlIEF1dGhvcml0eYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BAUAA4GBAIbCsK/qUXiIsRvg1ptaLNM6VsuR8ifNrmo9A4zk1h4OCixys6Hmoow6 +3MndnLpD3rh3UCYh0M20+fiHcwSmHZvBo3dfSSvYnH0gFSBjKp/wgGcb3Cvl3dRX +aeWZGrKQKLI6DrHqAiSu9rv+2kfzgmRLt0K+gdb2GkQqCBwT8Gjr +-----END CERTIFICATE----- diff --git a/certs/server-cert.pem b/certs/server-cert.pem new file mode 100644 index 0000000..0fc5c24 --- /dev/null +++ b/certs/server-cert.pem @@ -0,0 +1,44 @@ +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 2 (0x2) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Some-State, O=Dummy IO::Socket::SSL Certificate Authority, CN=Dummy IO::Socket::SSL Certificate Authority + Validity + Not Before: Jul 20 16:06:37 2002 GMT + Not After : Dec 5 16:06:37 2029 GMT + Subject: C=US, ST=Some-State, O=IO::Socket::SSL Dummy Server Certificate, CN=IO::Socket::SSL Dummy Server Certificate + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (512 bit) + Modulus (512 bit): + 00:9f:27:5f:4a:8a:35:4a:7f:3f:d1:80:25:96:26: + 0a:da:af:9a:6d:bc:23:ba:71:91:5b:40:d1:2d:2b: + c8:60:2a:ef:e9:54:e5:a2:64:0a:57:90:35:bf:cd: + b6:36:f3:25:53:68:65:2c:d8:d0:f9:b7:f3:7f:2e: + f8:e2:3d:e0:dd + Exponent: 65537 (0x10001) + Signature Algorithm: md5WithRSAEncryption + 57:a7:2d:91:cc:e9:11:16:bb:c1:cd:b5:a5:e1:26:99:8f:ee: + 8c:b0:2d:b6:54:f4:8a:8e:fd:8f:45:9a:68:d8:0e:ef:d6:a5: + 38:6a:48:d0:08:da:a8:87:3c:70:05:18:69:a1:c8:ee:94:a7: + 87:40:f5:4f:64:b4:b0:c6:d3:d2:ed:f9:cc:d1:fe:da:4d:99: + 4d:22:02:f6:0e:9b:c0:cc:42:59:50:2f:5c:fc:5b:70:f9:0b: + ec:6e:5b:eb:d7:6f:a1:b8:67:57:b1:4f:99:bd:ad:03:9d:b5: + f3:44:5c:36:1c:fa:33:82:87:0b:99:aa:f5:39:5c:63:23:6b: + 48:2d +-----BEGIN CERTIFICATE----- +MIICQzCCAawCAQIwDQYJKoZIhvcNAQEEBQAwgY4xCzAJBgNVBAYTAlVTMRMwEQYD +VQQIEwpTb21lLVN0YXRlMTQwMgYDVQQKEytEdW1teSBJTzo6U29ja2V0OjpTU0wg +Q2VydGlmaWNhdGUgQXV0aG9yaXR5MTQwMgYDVQQDEytEdW1teSBJTzo6U29ja2V0 +OjpTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTAyMDcyMDE2MDYzN1oXDTI5 +MTIwNTE2MDYzN1owgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRl +MTEwLwYDVQQKEyhJTzo6U29ja2V0OjpTU0wgRHVtbXkgU2VydmVyIENlcnRpZmlj +YXRlMTEwLwYDVQQDEyhJTzo6U29ja2V0OjpTU0wgRHVtbXkgU2VydmVyIENlcnRp +ZmljYXRlMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ8nX0qKNUp/P9GAJZYmCtqv +mm28I7pxkVtA0S0ryGAq7+lU5aJkCleQNb/NtjbzJVNoZSzY0Pm3838u+OI94N0C +AwEAATANBgkqhkiG9w0BAQQFAAOBgQBXpy2RzOkRFrvBzbWl4SaZj+6MsC22VPSK +jv2PRZpo2A7v1qU4akjQCNqohzxwBRhpocjulKeHQPVPZLSwxtPS7fnM0f7aTZlN +IgL2DpvAzEJZUC9c/Ftw+Qvsblvr12+huGdXsU+Zva0DnbXzRFw2HPozgocLmar1 +OVxjI2tILQ== +-----END CERTIFICATE----- diff --git a/certs/server-key.enc b/certs/server-key.enc new file mode 100644 index 0000000..36a2d7f --- /dev/null +++ b/certs/server-key.enc @@ -0,0 +1,12 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,25B674CF19D8A5D7 + +fLLIyUYuJLP0YzaBTMrdHSN9ROApAIyDdZNUM3qOW19IfER87Rw90pTJLTIe2r9d +tDIBY0w3MCYHOKEC+g9R8nsgLJNEkXG/Zi6fbhufnPAk343X8mDm+hjtQVzZONVc +VE8v4EA6qYHm54y/UlXnK9fJ9OhFru+btueQ+8z2pYZJEZ9ktAEJBj+jeD9nOcw4 +lZ2vgpGwyj5pzNSS/4QlujRSB4gddPlJyig+STN2iom7BetPUVJg0XOW74ezZSr9 +WU/c8Ghbg/efzah56WUYzBdIhXjpQr5zDmXi6uj8zCLSSFjdR48KQVfbXULoeqeA +8Nru2tXv34C5UdDgOkKbZSHar0n3o6t73B3WS9i90/1A4VLHvCvNxoBet7JdkEx8 +yv0b3A6wBHtAI5LRryaHAVN7bkIpXCfXGeMGFoCUpdO2jCeg/j54AQ== +-----END RSA PRIVATE KEY----- diff --git a/certs/server-key.pem b/certs/server-key.pem new file mode 100644 index 0000000..b7a165f --- /dev/null +++ b/certs/server-key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAJ8nX0qKNUp/P9GAJZYmCtqvmm28I7pxkVtA0S0ryGAq7+lU5aJk +CleQNb/NtjbzJVNoZSzY0Pm3838u+OI94N0CAwEAAQJAf/DavcVVCco5t2TY0ldK +qno4Hrb70cmyHDWC8lkb/5HAGbCGxpsstXxVKczRO201vcFUKm6PX5moUnFCINpg +UQIhAM+ooHbD0eLL0K6limEnW7GId/+DFI/6KFXk2Nzm//XXAiEAxDQbWQvZS8DO +HJ5JV8flvMhH30KLeH+zpsvBjWJK4GsCIQCUF7woNsquJZBznNctJjZ8S8jYThES +BONTLluCXrNYDQIhAJFnsHDQqCxM6jMpV193pJnAsAsUbPpTYZeWX43hL26bAiEA +jNB3PPNvTNr5tICkO/lMZcN87eUn4ZAtrNzCVF5ilEo= +-----END RSA PRIVATE KEY----- diff --git a/certs/test-ca.pem b/certs/test-ca.pem new file mode 100644 index 0000000..36bf8e4 --- /dev/null +++ b/certs/test-ca.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0BAQQFADCBjjELMAkGA1UEBhMCVVMx +EzARBgNVBAgTClNvbWUtU3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6 +OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpT +b2NrZXQ6OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNzIwMTYwNTU0 +WhcNMjkxMjA1MTYwNTU0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUt +U3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0aWZpY2F0 +ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0 +aWZpY2F0ZSBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALQm +bgkEUWImNkjWcO6qn5NZ7rCFbtrzqEYbqciy+1qlWuoBgU44n9ykD1c/BcmBPsDT +bIOfLzjcdJj38taXu7kcRclchJ+/c6o/SmDv7UqcL6QgVSZRvRrK7TDypMqe3sW8 +zCvTF8WtSsgFy5f9qlUdx4NowMzVV7OFl+6x4YlpAgMBAAGjge4wgeswHQYDVR0O +BBYEFDU4SrHVMHDjd2kBgFM/qyC3DPxFMIG7BgNVHSMEgbMwgbCAFDU4SrHVMHDj +d2kBgFM/qyC3DPxFoYGUpIGRMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29t +ZS1TdGF0ZTE0MDIGA1UEChMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENlcnRpZmlj +YXRlIEF1dGhvcml0eTE0MDIGA1UEAxMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENl +cnRpZmljYXRlIEF1dGhvcml0eYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BAUAA4GBAIbCsK/qUXiIsRvg1ptaLNM6VsuR8ifNrmo9A4zk1h4OCixys6Hmoow6 +3MndnLpD3rh3UCYh0M20+fiHcwSmHZvBo3dfSSvYnH0gFSBjKp/wgGcb3Cvl3dRX +aeWZGrKQKLI6DrHqAiSu9rv+2kfzgmRLt0K+gdb2GkQqCBwT8Gjr +-----END CERTIFICATE----- diff --git a/conf/archive.stem b/conf/archive.stem new file mode 100644 index 0000000..dddb304 --- /dev/null +++ b/conf/archive.stem @@ -0,0 +1,66 @@ +# archive.stem +# +[ + class => 'Stem::Console', +], +[ + 'class' => 'Stem::Hub', + 'name' => 'archive', + 'args' => [], +], +[ + 'class' => 'Stem::Portal', + 'args' => [ + 'server' => 1, + ], +], +[ + 'class' => 'Stem::Log', + 'args' => [ + + 'name' => 'bar', + + 'file' => [ + 'path' => 'tail/bar.log', + ], + + 'filters' => [ + file => 1, + forward => [ 'bar_stdout' ], + ], + + ], +], +[ + 'class' => 'Stem::Log', + 'args' => [ + + 'name' => 'bar_stdout', + 'format' => '%f [%L][%l] %T', + 'strftime' => '%D %T', + 'filters' => [ + 'env_gt_level' => 'bar_stdout', + stdout => 1, + ], + ], +], +[ + 'class' => 'Stem::Log', + 'args' => [ + + 'name' => 'bar_status', + + 'file' => [ + 'path' => 'tail/bar_status.log', + ], + + 'format' => '[%f]%h:%H:%P - %T', + 'strftime' => '%T', + 'filters' => [ + file => 1, + 'env_gt_level' => 'bar_status', +# stdout => 1, + console => 1, + ], + ], +], diff --git a/conf/boot.stem b/conf/boot.stem new file mode 100644 index 0000000..8208b21 --- /dev/null +++ b/conf/boot.stem @@ -0,0 +1,16 @@ +[ class => 'Stem::Log', + args => [ + + name => 'stdout', + filters => [ + 'stdout' => 1, + ], + ], +], +[ + class => 'Stem::Boot', + name => 'test', + args => [ + boot_file => 'test/test.boot', + ] +], diff --git a/conf/chat.stem b/conf/chat.stem new file mode 100644 index 0000000..6e2a147 --- /dev/null +++ b/conf/chat.stem @@ -0,0 +1,71 @@ +# chat.stem +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::SockMsg', + name => 'A', + args => [ + port => 6666, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:a' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'B', + args => [ + port => 6667, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:b' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'C', + args => [ + port => 6668, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:c' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'D', + args => [ + port => 6669, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:d' + ], + ], +], +[ + class => 'Stem::Switch', + name => 'sw', + args => [ + + in_map => [ + + a => [ qw( a b c d ) ], + b => 'a', + c => [ qw( b d ) ], + d => 'c', + ], + + out_map => [ + + a => 'A', + b => 'B', + c => 'C', + d => 'D', + ], + ], +], diff --git a/conf/chat_client.stem b/conf/chat_client.stem new file mode 100644 index 0000000..aef3e40 --- /dev/null +++ b/conf/chat_client.stem @@ -0,0 +1,36 @@ +# chat_client.stem +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Hub', + name => 'client', + args => [], +], +[ + class => 'Stem::Portal', + args => [], +], +[ + class => 'Stem::SockMsg', + name => 'A', + args => [ + port => 6666, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:a' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'B', + args => [ + port => 6667, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:b' + ], + ], +], diff --git a/conf/chat_label.stem b/conf/chat_label.stem new file mode 100644 index 0000000..27147bc --- /dev/null +++ b/conf/chat_label.stem @@ -0,0 +1,84 @@ +# chat.stem +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Demo::Cmd', + name => 'cmd', +], +[ + class => 'Stem::SockMsg', + name => 'A', + args => [ + port => 6666, + server => 1, + cell_attr => [ + 'data_addr' => ':label:a' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'B', + args => [ + port => 6667, + server => 1, + cell_attr => [ + 'data_addr' => ':label:b' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'C', + args => [ + port => 6668, + server => 1, + cell_attr => [ + 'data_addr' => ':label:c' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'D', + args => [ + port => 6669, + server => 1, + cell_attr => [ + 'data_addr' => ':label:d' + ], + ], +], +[ + class => 'Stem::ChatLabel', + name => 'label', + args => [ + sw_addr => 'sw', + ], +], + + +[ + class => 'Stem::Switch', + name => 'sw', + args => [ + + in_map => [ + + a => [ qw( a b c d ) ], + b => 'a', + c => [ qw( b d ) ], + d => 'c', + ], + + out_map => [ + + a => 'A', + b => 'B', + c => 'C', + d => 'D', + ], + ], +], diff --git a/conf/chat_server.stem b/conf/chat_server.stem new file mode 100644 index 0000000..84a72fc --- /dev/null +++ b/conf/chat_server.stem @@ -0,0 +1,58 @@ +# chat_server.stem +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Hub', + name => 'server', + args => [], +], +[ + class => 'Stem::Portal', + args => ['server' => 1 ], +], +[ + class => 'Stem::SockMsg', + name => 'C', + args => [ + port => 6668, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:c' + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'D', + args => [ + port => 6669, + server => 1, + cell_attr => [ + 'data_addr' => ':sw:d' + ], + ], +], +[ + class => 'Stem::Switch', + name => 'sw', + args => [ + + in_map => [ + + a => [ qw( a b c d ) ], + b => 'a', + c => [ qw( b d ) ], + d => 'c', + ], + + out_map => [ + + a => 'client:A', + b => 'client:B', + c => 'C', + d => 'D', + ], + ], +], diff --git a/conf/cli.stem b/conf/cli.stem new file mode 100644 index 0000000..ab96791 --- /dev/null +++ b/conf/cli.stem @@ -0,0 +1,27 @@ +[ + class => 'Stem::Console', +], +[ + class => 'Stem::SockMsg', + name => 'cli_sock', + args => [ + port => 8888, + server => 1, + host => '', + cell_attr => [ + 'cloneable' => 1, + 'data_addr' => 'cli', + 'codec' => 'Data::Dumper', + ], + ], +], +[ + class => 'Stem::Demo::CLI', + name => 'cli', + args => [ + cell_attr => [ + cloneable => 1, + no_io => 1, + ], + ], +], diff --git a/conf/cron.stem b/conf/cron.stem new file mode 100644 index 0000000..832f390 --- /dev/null +++ b/conf/cron.stem @@ -0,0 +1,25 @@ +# cron.stem +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Demo::Cmd', + name => 'cmd', +], +[ + class => 'Stem::Cron', + name => 'cron', + args => [ + + 'hours' => [ 0 .. 8 ], + 'month_days' => [ '11-15' ], + 'months' => [ '12' ], + + 'msg' => [ + + 'to_cell' => 'console', + 'data' => "foo\n", + ] + ], +], diff --git a/conf/hello.stem b/conf/hello.stem new file mode 100644 index 0000000..6c01843 --- /dev/null +++ b/conf/hello.stem @@ -0,0 +1,9 @@ +# hello.stem +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Demo::World', + name => 'world', +], diff --git a/conf/hello_client.stem b/conf/hello_client.stem new file mode 100644 index 0000000..8e5b041 --- /dev/null +++ b/conf/hello_client.stem @@ -0,0 +1,24 @@ +# hello_client.stem +# +[ + class => 'Stem::Hub', + name => 'system_B', + args => [ + 'host' => 'localhost', + ], +], +[ + class => 'Stem::Portal', + args => [], +], +[ + class => 'Stem::Proc', + name => 'hello', + args => [ + path => 'bin/hello.sh', + proc_args => ['client'], + cell_attr => [ + 'data_addr' => 'system_A:console', + ], + ], +], diff --git a/conf/hello_server.stem b/conf/hello_server.stem new file mode 100644 index 0000000..d2dadc6 --- /dev/null +++ b/conf/hello_server.stem @@ -0,0 +1,43 @@ +# hello_server.stem +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Hub', + name => 'system_A', + args => [], +], +[ + class => 'Stem::Portal', + args => [ + 'server' => 1, + 'host' => 'localhost', + ], +], +[ + class => 'Stem::Switch', + name => 'sw', + args => [ + in_map => [ + + h => [ qw( h h3 ) ], + ], + out_map => [ + + h => 'hello', + h3 => 'system_B:hello', + ], + ], +], +[ + class => 'Stem::Proc', + name => 'hello', + args => [ + path => 'bin/hello.sh', + proc_args => ['server'], + cell_attr => [ + 'data_addr' => 'console', + ], + ], +], diff --git a/conf/hello_shell.stem b/conf/hello_shell.stem new file mode 100644 index 0000000..1d176b6 --- /dev/null +++ b/conf/hello_shell.stem @@ -0,0 +1,14 @@ +# hello_shell.stem +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Proc', + name => 'hello', + args => [ + path => 'bin/hello.sh', + cell_attr => [ + 'data_addr' => 'console', + ], + ], +], diff --git a/conf/hello_yaml.stem b/conf/hello_yaml.stem new file mode 100644 index 0000000..ad38628 --- /dev/null +++ b/conf/hello_yaml.stem @@ -0,0 +1,12 @@ +--- #YAML:1.0 +- + class: Stem::Console +- + class: Stem::Proc + name: hello + args: + path: bin/hello.sh + proc_args: + - Athena Health + cell_attr: + data_addr: console diff --git a/conf/inetd.stem b/conf/inetd.stem new file mode 100644 index 0000000..dc124e9 --- /dev/null +++ b/conf/inetd.stem @@ -0,0 +1,60 @@ +# inetd.stem +# +# Emulate an inetd daemon +# +# +# Load the consols module so we can enter commands to stem +[ + class => 'Stem::Console', +], + +# these two cells are both SockMsg's. they are servers listening for +# connections on different ports. they have different Cell addresses (A, +# B). Both use the Stem::Cell attributes to handle cloning upon +# connection and the logical pipe to the Stem::Proc cell. Note that B +# has the pipe_args option which sends extra arguments to the other side +# of the pipe. this enables line numbering in the output of the +# proc_serv script. + +[ + class => 'Stem::SockMsg', + name => 'A', + args => [ + port => 6666, + server => 1, + cell_attr => [ + 'cloneable' => 1, +# this name maps to the cell name of the Stem::Proc below + 'pipe_addr' => 'quote', + ], + ], +], +[ + class => 'Stem::SockMsg', + name => 'B', + args => [ + port => 6667, + server => 1, + cell_attr => [ + 'cloneable' => 1, +# this name maps to the cell name of the Stem::Proc below + 'pipe_addr' => 'quote', + 'pipe_args' => '-n', + ], + ], +], + +# this is the Stem::Proc cell actually forks the program. its name is +# refered to by the SockMsg cells. + +[ + class => 'Stem::Proc', + name => 'quote', + args => [ + path => 'quote_serve', + use_stderr => 1, + cell_attr => [ + 'cloneable' => 1, + ], + ], +], diff --git a/conf/load_driver.stem b/conf/load_driver.stem new file mode 100644 index 0000000..16c9404 --- /dev/null +++ b/conf/load_driver.stem @@ -0,0 +1,24 @@ +[ + class => 'Stem::Console', +], +[ + 'class' => 'Stem::Hub', + 'name' => 'Load', + 'args' => [], +], +[ + 'class' => 'Stem::Portal', + 'name' => 'load', + 'args' => [ + 'port' => 10001, + ], +], +[ + class => 'Stem::Load::Driver', + name => 'driver', + args => [ + 'load_addr' => 'Echo:echo', + 'max_loads_per_hub' => 1, + 'max_load_msgs' => 100, + ], +], diff --git a/conf/load_echo.stem b/conf/load_echo.stem new file mode 100644 index 0000000..26b2e8d --- /dev/null +++ b/conf/load_echo.stem @@ -0,0 +1,15 @@ +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Load::Driver', + name => 'driver', + args => [ + load_addr => 'echo', + ], +], +[ + class => 'Stem::Test::Echo', + name => 'echo', + args => [], +], diff --git a/conf/monitor.stem b/conf/monitor.stem new file mode 100644 index 0000000..aee3340 --- /dev/null +++ b/conf/monitor.stem @@ -0,0 +1,26 @@ +# monitor.stem +# +[ + class => 'Stem::Console', +], +[ + 'class' => 'Stem::Hub', + 'name' => 'monitor', + 'args' => [], +], +[ + class => 'Stem::Portal', + args => [ + ], +], +[ + 'class' => 'Stem::Log::Tail', + 'name' => 'foo', + 'args' => [ + 'path' => 'tail/foo.log', + 'interval' => 3, + 'delay' => 0, + 'data_log' => 'archive:bar', + 'status_log' => 'archive:bar_status', + ], +], diff --git a/conf/proc.stem b/conf/proc.stem new file mode 100644 index 0000000..61830c0 --- /dev/null +++ b/conf/proc.stem @@ -0,0 +1,30 @@ +[ + class => 'Stem::Console', +], +[ + 'class' => 'Stem::Hub', + 'name' => 'proc', + 'args' => [], +], + +# this portal listens for the driver hub +[ + 'class' => 'Stem::Portal', + 'name' => 'Echo', + 'args' => [ + 'host' => '', + 'port' => 10001, + 'server' => 1 + ], +], +[ + class => 'Stem::Proc', + name => 'echo', + args => [ + path => './bin/echo_worker.pl', + spawn_now => 1, + cell_attr => [ + 'worker_mode' => 1, + ], + ], +], diff --git a/conf/slave.stem b/conf/slave.stem new file mode 100644 index 0000000..a448d8d --- /dev/null +++ b/conf/slave.stem @@ -0,0 +1,17 @@ +# slave.stem +# this is used for slave Hubs which get their configs from a master Hub +# +[ + class => 'Stem::Console', +], +[ + class => 'Stem::Hub', + name => 'slave', + args => [], +], +[ + class => 'Stem::Portal', + args => [ + 'server' => 1 + ], +], diff --git a/conf/tail.stem b/conf/tail.stem new file mode 100644 index 0000000..67bd740 --- /dev/null +++ b/conf/tail.stem @@ -0,0 +1,41 @@ +# tail.stem +# +[ + 'class' => 'Stem::Console', +], +[ + 'class' => 'Stem::Hub', + 'name' => 'tail', + 'args' => [], +], +[ + 'class' => 'Stem::Log::Tail', + 'name' => 'foo', + 'args' => [ + 'path' => 'tail/foo', + 'interval' => 5, + 'data_log' => 'bar', + 'status_log' => 'bar_status', + ], +], +[ + 'class' => 'Stem::Log', + 'args' => [ + + 'name' => 'bar', + 'path' => 'tail/bar', + ], +], +[ + 'class' => 'Stem::Log', + 'args' => [ + + 'name' => 'bar_status', + 'path' => 'tail/bar_status', + + 'filters' => [ + stdout => 1, + file => 1, + ], + ], +], diff --git a/conf/test_flow.stem b/conf/test_flow.stem new file mode 100644 index 0000000..009d3f2 --- /dev/null +++ b/conf/test_flow.stem @@ -0,0 +1,6 @@ +# test_flow.stem +[ + class => 'Stem::Test::Flow', + name => 'flow', + args => [], +], diff --git a/conf/test_packet_io.stem b/conf/test_packet_io.stem new file mode 100644 index 0000000..88a8ca0 --- /dev/null +++ b/conf/test_packet_io.stem @@ -0,0 +1,12 @@ +[ + class => 'Stem::Test::Echo', + name => 'echo', + args => [], +], +[ + class => 'Stem::Test::PacketIO', + name => 'packet_io', + args => [ + write_addr => 'client_sock', + ], +], diff --git a/conf/test_udp.stem b/conf/test_udp.stem new file mode 100644 index 0000000..7097851 --- /dev/null +++ b/conf/test_udp.stem @@ -0,0 +1,28 @@ +# test_udp.stem +[ + class => 'Stem::UDPMsg', + name => 'send_only', + args => [ +# send_host => 'localhost', + ], +], +[ + class => 'Stem::UDPMsg', + name => 'recv_only', + args => [ + bind_port => 9999, + bind_host => 'localhost', + data_addr => 'udp_test', + timeout_addr => 'udp_test', + server => 1, + timeout => 1, + ], +], +[ + class => 'Stem::Test::UDP', + name => 'udp_test', + args => [ + send_addr => 'send_only', + send_port => '9999', + ], +], diff --git a/conf/ticker.stem b/conf/ticker.stem new file mode 100644 index 0000000..4e70874 --- /dev/null +++ b/conf/ticker.stem @@ -0,0 +1,29 @@ +[ + class => 'Stem::Console', +], +[ + 'class' => 'Stem::Hub', + 'name' => 'tick_driver', + 'args' => [], +], +[ + 'class' => 'Stem::Portal', + 'name' => 'tick_server', + 'args' => [ + 'server' => 1, + ], +], +[ + class => 'Stem::WorkQueue', + name => 'dbi_queue', + args => [], +], + + +[ + 'class' => 'Stem::Load::Ticker', + 'name' => 'tick', + 'args' => [ + dbi_addr => 'dbi_queue', + ], +], diff --git a/conf/ttysock.stem b/conf/ttysock.stem new file mode 100644 index 0000000..b4d501d --- /dev/null +++ b/conf/ttysock.stem @@ -0,0 +1,8 @@ +# ttysock.stem +# +# drive this from the command line with +# tty_host and tty_port +[ + class => 'Stem::TtySock', + args => [], +], diff --git a/conf/type.stem b/conf/type.stem new file mode 100644 index 0000000..6587288 --- /dev/null +++ b/conf/type.stem @@ -0,0 +1,104 @@ +--- #YAML:1.0 +# - +# class: Stem::Test::ConfTypes +# name: boolean +# args: +# bool_attr: yes +# - +# class: Stem::Test::ConfTypes +# name: scalar +# args: +# list_attr: scalar_val +# - +# class: Stem::Test::ConfTypes +# name: list +# args: +# list_attr: [ list, of, three ] +# - +# class: Stem::Test::ConfTypes +# name: hash_to_list +# args: +# list_attr: +# hash: of +# key: value +# - +# class: Stem::Test::ConfTypes +# name: hash +# args: +# hash_attr: +# foo: bar +# - +# class: Stem::Test::ConfTypes +# name: list_to_hash +# args: +# hash_attr: +# - foo +# - bar +# - +# class: Stem::Test::ConfTypes +# name: lol_to_lol +# args: +# lol_attr: +# - [ 1, 2 ] +# - [ 3, 4 ] +# - +# class: Stem::Test::ConfTypes +# name: loh_to_lol +# args: +# lol_attr: +# - +# foo: 1 +# - +# bar: 2 +# - +# class: Stem::Test::ConfTypes +# name: hol_to_lol +# args: +# lol_attr: +# foo: [ 1, 2 ] +# bar: 3 +- + class: Stem::Test::ConfTypes + name: hoh_to_lol + args: + lol_attr: + foo: [ 1, 2 ] + bar: 3 +- + class: Stem::Test::ConfTypes + name: hoh_to_hol + args: + hol_attr: + foo: [ 1, 2 ] + bar: 3 +- + class: Stem::Test::ConfTypes + name: lol_to_hol + args: + hol_attr: + - foo + - [ 1, 2 ] + - bar + - 3 +- + class: Stem::Test::ConfTypes + name: lolh_to_loh + args: + loh_attr: + - + foo: faaa + - [ 1, 2 ] + - + bar: jejej + - [ 3, 5, 4, 0 ] +- + class: Stem::Test::ConfTypes + name: lolh_to_hol + args: + hol_attr: + - + foo: faaa + - [ 1, 2 ] + - + bar: jejej + - [ 3, 5, 4, 0 ] diff --git a/extras/sirc-2.211.tar.gz b/extras/sirc-2.211.tar.gz new file mode 100644 index 0000000..0c65d6a Binary files /dev/null and b/extras/sirc-2.211.tar.gz differ diff --git a/lib/Stem.pm b/lib/Stem.pm new file mode 100644 index 0000000..a37d9d6 --- /dev/null +++ b/lib/Stem.pm @@ -0,0 +1,75 @@ +# File: Stem.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem ; + +=head1 Stem.pm - A Network Application Toolkit and Framework + +This module load these core Stem modules: + + Stem::Util ; # support utilities + Stem::Class ; # object constructor + Stem::Event ; # event loop + Stem::Msg ; # message creation and delivery + Stem::Route ; # message routing + Stem::Vars ; # stem environment + Stem::Conf ; # configuration file handling + Stem::Log ; # logging services + +You can create a script and just use Stem.pm (see the tests in t/ for +some examples) but most Stem applications will use the startup script +run_stem which loads this module and your configuration files. + +=cut + + + +use strict ; +use vars qw( $VERSION ) ; + +$VERSION = 0.12 ; + +use Stem::Util ; +use Stem::Class ; +use Stem::Event ; +use Stem::Msg ; +use Stem::Route qw( :cell ) ; +use Stem::Vars ; +use Stem::Conf ; +use Stem::Log ; +#use Stem::Hub ; + +register_class( __PACKAGE__, 'stem' ) ; + + +sub version_cmd { + + return "Stem Version: $VERSION" ; +} + +1 ; diff --git a/lib/Stem/AsyncIO.pm b/lib/Stem/AsyncIO.pm new file mode 100644 index 0000000..5b011e4 --- /dev/null +++ b/lib/Stem/AsyncIO.pm @@ -0,0 +1,527 @@ +# File: Stem/AsyncIO.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::AsyncIO ; + +use strict ; +use Data::Dumper ; + +use Stem::Vars ; + + +my $attr_spec = [ + + { + 'name' => 'object', + 'required' => 1, + 'help' => < 'read_method', + 'default' => 'async_read_data', + 'help' => < 'stderr_method', + 'default' => 'async_stderr_data', + 'help' => < 'closed_method', + 'default' => 'async_closed', + 'help' => < 'fh', + 'help' => < 'read_fh', + 'help' => < 'write_fh', + 'help' => < 'stderr_fh', + 'help' => < 'data_addr', + 'type' => 'address', + 'help' => < 'stderr_addr', + 'type' => 'address', + 'help' => < 'data_msg_type', + 'default' => 'data', + 'help' => < 'codec', + 'help' => < 'stderr_msg_type', + 'default' => 'stderr_data', + 'help' => < 'from_addr', + 'type' => 'address', + 'help' => < 'send_data_on_close', + 'type' => 'boolean', + 'help' => < 'id', + 'help' => < 'log_label', + 'default' => 'AIO', + 'help' => < 'log_level', + 'default' => 5, + 'help' => < 'read_log', + 'help' => < 'stderr_log', + 'help' => < 'write_log', + 'help' => <{'data_addr'} && ! $self->{'from_addr'} ) { + + return "Using 'data_addr in AsyncIO requires a 'from_addr'" ; + } + + if ( my $codec = $self->{'codec'} ) { + + require Stem::Packet ; + my $packet = Stem::Packet->new( 'codec' => $codec ) ; + return $packet unless ref $packet ; + + $self->{'packet'} = $packet ; + } + + $self->{'stderr_addr'} ||= $self->{'data_addr'} ; + + $self->{'buffer'} = '' if $self->{'send_data_on_close'} ; + + $self->{ 'read_fh' } ||= $self->{ 'fh' } ; + $self->{ 'write_fh' } ||= $self->{ 'fh' } ; + + if ( my $read_fh = $self->{'read_fh'} ) { + + my $read_event = Stem::Event::Read->new( + 'object' => $self, + 'fh' => $read_fh, + ) ; + + return $read_event unless ref $read_event ; + + $self->{'read_event'} = $read_event ; + } + + if ( my $stderr_fh = $self->{'stderr_fh'} ) { + + my $stderr_event = Stem::Event::Read->new( + 'object' => $self, + 'fh' => $stderr_fh, + 'method' => 'stderr_readable', + ) ; + + return $stderr_event unless ref $stderr_event ; + + $self->{'stderr_event'} = $stderr_event ; + } + + if ( my $write_fh = $self->{'write_fh'} ) { + + my $write_event = Stem::Event::Write->new( + 'object' => $self, + 'fh' => $write_fh, + ) ; + + return $write_event unless ref $write_event ; + + $self->{'write_event'} = $write_event ; + + $self->{'write_buf'} = '' ; + } + + return $self ; +} + +sub shut_down { + + my( $self ) = @_ ; + +#cluck "SHUT $self\n" ; + + + if ( $self->{'shut_down'} ) { + + return ; + } + + $self->{'shutting_down'} = 1 ; + + $self->read_shut_down() ; + + $self->write_shut_down() ; + + if ( my $event = delete $self->{'stderr_event'} ) { + + $event->cancel() ; + close( $self->{'stderr_fh'} ) ; + } + + $self->{'shut_down'} = 1 ; + +#print "DELETE OBJ", caller(), "\n" ; + + delete $self->{'object'} ; +} + +sub read_shut_down { + + my( $self ) = @_ ; + + if ( my $event = delete $self->{'read_event'} ) { + + $event->cancel() ; + } + + shutdown( $self->{'read_fh'}, 0 ) ; +} + +sub write_shut_down { + + my( $self ) = @_ ; + + if ( exists( $self->{'write_buf'} ) && + length( $self->{'write_buf'} ) ) { + +#print "write handle shut when empty\n" ; + $self->{'shut_down_when_empty'} = 1 ; + + return ; + } + + if ( my $event = delete $self->{'write_event'} ) { + + shutdown( $self->{'write_fh'}, 1 ) ; + $event->cancel() ; + } +} + +sub readable { + + my( $self ) = @_ ; + + my( $read_buf ) ; + + return if $self->{'shut_down'} ; + + my $bytes_read = sysread( $self->{'read_fh'}, $read_buf, 8192 ) ; + +#print "READ: $bytes_read [$read_buf]\n" ; + + unless( defined( $bytes_read ) && $bytes_read > 0 ) { + + $self->read_shut_down() ; + + if ( $self->{'send_data_on_close'} && + length( $self->{'buffer'} ) ) { + + $self->send_data() ; + +# since we sent the total read buffer, we don't do a closed callback. + + return ; + } + + $self->_callback( 'closed_method' ) ; + + return ; + } + +# decode the packet if needed + + if ( my $packet = $self->{packet} ) { + + my $buf_ref = \$read_buf ; + + while( my $data_ref = $packet->to_data( $buf_ref ) ) { + + $self->send_data( $data_ref ) ; + $buf_ref = undef ; + } + + return ; + } + + if ( $self->{'send_data_on_close'} ) { + + $self->{'buffer'} .= $read_buf ; + return ; + } + + $self->send_data( \$read_buf ) ; +} + +sub send_data { + + my( $self, $buffer ) = @_ ; + + my $buf_ref = $buffer || \$self->{'buffer'} ; + + $self->_send_data_msg( 'data_addr', 'data_msg_type', $buf_ref ) ; + $self->_callback( 'read_method', $buf_ref ) ; + + return ; +} + +sub stderr_readable { + + my( $self ) = @_ ; + + my( $read_buf ) ; + + my $bytes_read = sysread( $self->{'stderr_fh'}, $read_buf, 8192 ) ; + +# no callback on stderr close. let the read handle close deal with the +# shutdown + + return if $bytes_read == 0 ; + +#print "STDERR READ [$read_buf]\n" ; + + $self->_send_data_msg( 'stderr_addr', 'stderr_msg_type', \$read_buf ) ; + $self->_callback( 'stderr_method', \$read_buf ) ; +} + +sub _send_data_msg { + + my( $self, $addr_attr, $type_attr, $data_ref ) = @_ ; + + my $to_addr = $self->{$addr_attr} or return ; + + my $msg = Stem::Msg->new( + 'to' => $to_addr, + 'from' => $self->{'from_addr'}, + 'type' => $self->{$type_attr}, + 'data' => $data_ref, + ) ; + +#print $msg->dump( 'SEND DATA' ) ; + $msg->dispatch() ; +} + +sub _callback { + + my ( $self, $method_attr, @data ) = @_ ; + + my $obj = $self->{'object'} or return ; + + my $method = $self->{$method_attr} ; + + my $code = $obj->can( $method ) or return ; + + return $obj->$code( @data, $self->{'id'} ) ; +} + +sub write { + + my( $self ) = shift ; + + return unless @_ ; + + return unless exists( $self->{'write_buf'} ) ; + + my $buffer = shift ; + + return if $self->{'shut_down'} ; + +# encode the data in a packet if needed + + if ( my $packet = $self->{packet} ) { + + my $buf_ref = $packet->to_packet( $buffer ) ; + + $self->{'write_buf'} .= ${$buf_ref} ; + } + else { + + $self->{'write_buf'} .= ref $buffer eq 'SCALAR' ? + ${$buffer} : $buffer ; + } + + $self->{'write_event'}->start() ; +} + +sub final_write { + + my( $self ) = @_ ; + + $self->write( $_[1] ) ; + + $self->write_shut_down() ; +} + + +sub writeable { + + my( $self ) = @_ ; + + return if $self->{'shut_down'} ; + + my $buf_ref = \$self->{'write_buf'} ; + my $buf_len = length $$buf_ref ; + +#print "BUFLEN [$buf_len]\n" ; + + unless ( $buf_len ) { + +#print "AIO W STOPPING\n" ; + $self->{'write_event'}->stop() ; + return ; + } + + my $bytes_written = syswrite( $self->{'write_fh'}, $$buf_ref ) ; + + unless( defined( $bytes_written ) ) { + +# do a SHUTDOWN + return ; + } + +# remove the part of the buffer that was written + + substr( $$buf_ref, 0, $bytes_written, '' ) ; + + return if length( $$buf_ref ) ; + + $self->write_shut_down() if $self->{'shut_down_when_empty'} ; +} + + +# DESTROY { + +# my( $self ) = @_ ; + +# print "DESTROY $self\n" ; + +# } + +1 ; diff --git a/lib/Stem/Boot.pm b/lib/Stem/Boot.pm new file mode 100644 index 0000000..3b34d5b --- /dev/null +++ b/lib/Stem/Boot.pm @@ -0,0 +1,291 @@ +# File: Stem/Boot.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Boot ; + +use strict ; +use Carp ; +use Symbol ; + +my $attr_spec = [ + + { + 'name' => 'reg_name', + 'help' => < 'boot_file', + 'required' => 1, + 'help' => < 'name', + 'help' => < 'cmd', + 'help' => < 'log', + 'help' => < 'delay', + 'help' => < 'user', + 'help' => < 'wrap', + 'default' => '/bin/sh -c', + 'help' => < 'chdir', + 'help' => < 'boot_now', + 'type' => 'boolean', + 'default' => 1, + 'help' => < 'restart', + 'help' => <{'boot_file'} ) ; + return $boot_info unless ref $boot_info ; + + foreach my $boot ( @{$boot_info} ) { + + die "boot entry is not a hash\n" unless ref $boot eq 'HASH' ; + + if ( my $skip = $boot->{'skip'} ) { + + next if lc $skip eq 'yes' ; + } + + my $boot_obj = Stem::Class::parse_args( $attr_spec, + %{$self}, + %{$boot} + ) ; + + die "boot entry error: $boot_obj\n" unless ref $boot_obj ; + + my $cmd = $boot_obj->{'cmd'} ; + die "boot entry is missing 'cmd'\n" unless $cmd ; + + my $name = $boot_obj->{'name'} ; + die "boot entry is missing 'name'\n" unless $name ; + + $name2boot{ $name } = $boot_obj ; + + if ( $boot_obj->{'boot_now'} ) { + + $boot_obj->run_cmd() ; + } + } + + return ; +} + + +sub run_cmd { + + my( $self ) = @_ ; + +#print Store $self ; + + my $cmd ; + + if ( my $user = $self->{'user'} ) { + + if ( getpwuid($<) ne $user ) { + + $cmd .= "su - $user ; " ; + } + } + + if ( my $wrap = $self->{'wrap'} ) { + + $cmd .= qq{$wrap "} ; + $self->{'wrap_end'} ||= '"' ; + } + + if ( my $chdir = $self->{'chdir'} ) { + + $cmd .= "cd $chdir ; " ; + } + + if ( my $stem_env = $self->{'stem_env'} ) { + + my $cmd_env = join ' ', map( + "$_='$stem_env->{$_}'", keys %{$stem_env} ) ; + + $cmd =~ s/run_stem/run_stem $cmd_env/ ; + } + + $cmd .= $self->{'cmd'} ; + + $cmd .= $self->{'wrap_end'} if $self->{'wrap_end'} ; + + my $handle = gensym ; + +#print "$cmd\n" ; + + if ( my $pid = open( $handle, '-|' ) ) { + +#print "pid $pid\n" ; + $self->{'pid'} = $pid ; + $self->{'handle'} = $handle ; + } + elsif ( defined( $pid ) ) { + + local( %ENV ) = ( %ENV, %{ $self->{'env'} || {} } ) ; + + open( STDERR, '>&STDOUT' ) ; + + exec $cmd ; + die "Couldn't exec [$cmd]\n" ; + } + else { + + die "couldn't fork\n" ; + } + + my $aio = Stem::AsyncIO->new( + + 'object' => $self, + 'read_fh' => $handle, + 'read_method' => 'boot_read', + 'closed_method' => 'boot_closed', + ) ; + + $self->{'aio'} = $aio ; + + if ( my $log = $self->{'log'} ) { + + Stem::Log::Entry->new( + 'logs' => $log, + 'label' => 'boot', + 'text' => + "Booting $self->{'name'} PID = $self->{'pid'}: $cmd\n", + ) ; + } + + return ; +} + +sub boot_read { + + my( $self, $data ) = @_ ; + +#print "BOOT READ [$$data]\n" ; + + if ( my $log = $self->{'log'} ) { + + Stem::Log::Entry->new( + 'logs' => $log, + 'label' => 'boot', + 'text' => "Output for $self->{'name'}\n[${$data}]\n", + ) ; + } + + return ; +} + +sub boot_closed { + + my( $self ) = @_ ; + +#print "BOOT closed\n" ; + + $self->{'aio'}->shut_down() ; + delete $self->{'aio'} ; + + my $boot_pid = $self->{'pid'} ; + my $pid = waitpid( $boot_pid, 0 ) ; + +#print "WAIT [$pid]\n" ; + + if ( my $log = $self->{'log'} ) { + + Stem::Log::Entry->new( + 'logs' => $log, + 'label' => 'boot', + 'text' => "Boot $self->{'name'} exited PID = $pid", + ) ; + } + +# do restart if needed + + + + + return ; +} + +1 ; diff --git a/lib/Stem/Cell.pm b/lib/Stem/Cell.pm new file mode 100644 index 0000000..07e6f29 --- /dev/null +++ b/lib/Stem/Cell.pm @@ -0,0 +1,712 @@ +# File: Stem/Cell.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Cell ; + +use strict ; + +use Data::Dumper ; +use Carp qw( cluck ) ; + +use Stem::Route qw( :cell ) ; +use Stem::AsyncIO ; +use Stem::Id ; +use Stem::Gather ; +use Stem::Cell::Clone ; +use Stem::Cell::Pipe ; +use Stem::Cell::Flow ; +use Stem::Cell::Work ; + +use Stem::Trace 'log' => 'stem_status' , 'sub' => 'TraceStatus' ; + +my %class_to_attr_name ; + +my $attr_spec = [ + + { + 'name' => 'reg_name', + 'help' => < 'cloneable', + 'type' => 'boolean', + 'help' => < 'data_addr', + 'type' => 'address', + 'help' => < 'status_addr', + 'type' => 'address', + 'help' => < 'send_data_on_close', + 'type' => 'boolean', + 'help' => < 'no_io', + 'type' => 'boolean', + 'help' => < 'pipe_addr', + 'type' => 'address', + 'help' => < 'pipe_args', + 'help' => < 'aio_args', + 'type' => 'hash', + 'help' => < 'errors_to_output', + 'env' => 'errors_to_output', + 'help' => < 'id_size', + 'default' => 3, + 'help' => < 'trigger_method', + 'default' => 'triggered_cell', + 'help' => < 'shut_down_method', + 'default' => 'shut_down_cell', + 'help' => < 'activated_method', + 'default' => 'activate_cell', + 'help' => < 'sequence_done_method', + 'help' => < 'codec', + 'help' => < 'work_ready_addr', + 'type' => 'address', + 'help' => < 'stderr_log', + 'help' => <_dump( 'NEW' ) ; + + return( $self ) ; +} + +# this is only called in Stem::Conf for this class. +# it initializes the cell info object inside its owner object. + +sub cell_init { + + my( $self, $owner_obj, $cell_name, $cell_info_attr ) = @_ ; + +# the $owner_obj is the cell that owns this Stem::Cell object + + $self->{'owner_obj'} = $owner_obj ; + $self->{'cell_name'} = $cell_name ; +# $self->{'from_addr'} = $cell_name ; + + $self->{'from_addr'} = Stem::Msg::make_address_string( + $Stem::Vars::Hub_name, + $cell_name + ) ; + + $self->{'cell_info_attr'} = $cell_info_attr ; + +# save the attribute name that the owner class uses for the cell info. +# this is how a cell info object can be found given an owner cell object. +# also keep this name in the info itself + +#print "OWNER [$owner_obj]\n" ; + $class_to_attr_name{ ref $owner_obj } ||= $cell_info_attr ; + + if ( $self->{'cloneable'} ) { + + $self->{'id_obj'} = Stem::Id->new( + 'size' => $self->{'id_size'} ) ; + $self->{'is_parent'} = 1 ; + $self->{'target'} = '' ; + } +} + +# get the cell info whether we were called from the owner object or +# the cell info itself ; + +sub _get_cell_info { + + my ( $self ) = @_ ; + + my $class = ref $self ; + + return "can't get cell info from '$self'\n" unless $class ; + + return $self if $class eq __PACKAGE__ ; + +#print "CLASS [$class][$class_to_attr_name{ $class }]\n" ; + + return $self->{ $class_to_attr_name{ $class } } ; +} + +sub cell_trigger { + + my ( $self, @args ) = @_ ; + + my $self_info = $self->_get_cell_info() ; + + return $self_info unless ref $self_info ; + + return if $self_info->{'triggered'} ; + +# clone this cell and its info if needed +# $cell will either be $self or a clone of $self + + my $cell = $self_info->_clone() ; + + my $cell_info = $cell->_get_cell_info() ; + + $cell_info->{'triggered'} = 1 ; + +#print $cell_info->_dump( 'TRIGGER' ) ; + +# set any args (e.g. from trigger message) into this cell + + $cell_info->cell_set_args( @args ) ; + + $cell_info->_cell_pipe() ; + + if ( my $err = $cell_info->_gather_io_args() ) { + $cell_info->cell_shut_down( $err ) ; + return $err ; + } + +# do the callback into the (possibly cloned) cell + + if ( my $err = $cell_info->_callback( 'trigger_method' ) ) { + +#print "CALLBACK $err\n" ; + + $cell_info->cell_shut_down( $err ) ; + return $err ; + } + +# return $cell_info ; + return ; +} + +sub cell_trigger_cmd { + + my ( $self, $msg ) = @_ ; + + my @args ; + + if ( my $data = $msg->data() ) { + + $data = ${$data} if ref $data eq 'SCALAR' ; + + my $ref = ref $data ; + + if ( ! $ref && defined $data ) { + + unless ( @args = $data =~ /(\S+)=(\S+)/g ) { + + @args = ( 'args' => $data ) ; + } + } + elsif ( $ref eq 'HASH' ) { + + @args = %{$data} ; + } + elsif ( $ref eq 'ARRAY' ) { + + @args = @{$data} ; + } + } + + push( @args, triggering_msg => $msg ) ; + + my $err = $self->cell_trigger( @args ) ; + +print "TRIG ERR [$err]\n" if $err ; + + return $err if ref $err ; + return ; +} + + +sub cell_shut_down { + + my( $self, $error ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + +#cluck "CELL SHUT\n" ; + +#print $cell_info->_dump( 'SHUT' ) ; + + + return unless $error || $cell_info->{'active'} ; + + $cell_info->{'error'} = $error ; + +#print $cell_info->_dump( "SHUT $error" ) ; + + if ( my $aio = delete $cell_info->{'aio'} ) { + + $aio->shut_down() ; + } + + if ( my $gather = delete $cell_info->{'gather'} ) { + + $gather->shut_down() ; + } + + $cell_info->_close_pipe() ; + + $cell_info->_clone_delete() ; + + delete $cell_info->{'args'} ; +# delete $cell_info->{'data_addr'} ; + + $cell_info->{'active'} = 0 ; + $cell_info->{'triggered'} = 0 ; + + TraceStatus "cell shut down done" ; + + return ; +} + + +sub cell_set_args { + + my( $self, %args ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + @{$cell_info->{'args'}}{ keys %args } = values %args ; + + if ( my $gather = $cell_info->{'gather'} ) { + + my $err = $gather->gathered( keys %args ) ; + return $err if $err ; + } + + return ; +} + +sub cell_get_args { + + my( $self, @arg_keys ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + return( @{$cell_info->{'args'}}{@arg_keys } ) ; +} + +sub cell_info { + + my( $self ) = shift ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->{'info'} = shift if @_ ; + + return $cell_info->{'info'} ; +} + +sub _gather_io_args { + + my( $self ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + return if $cell_info->{'no_io'} ; + + my @gather_keys = 'aio_args' ; + + push( @gather_keys, 'data_addr' ) if + $cell_info->{'piped'} && + ! $cell_info->{'data_addr'} ; + + my $gather = Stem::Gather->new( + 'object' => $cell_info, + 'keys' => \@gather_keys, + 'gathered_method' => '_cell_activate_io', + ) ; + + return $gather unless ref $gather ; + + $cell_info->{'gather'} = $gather ; + + my $err = $gather->gathered( keys %{$cell_info->{'args'}} ) ; + + return $err if $err ; +} + +sub _cell_activate_io { + + my ( $self ) = @_ ; + + TraceStatus "cell activated" ; + + $self->{'active'} = 1 ; + +#print $self->_dump( "BEFORE AIO" ) ; + + my @aio_args ; + +# get any config args + + if ( my $aio_args = $self->{'aio_args'} ) { + + push( @aio_args, %{$aio_args} ) ; + } + +# args from a trigger message override any config args + + if ( my $msg_aio_args = $self->{'args'}{'aio_args'} ) { + + ref $msg_aio_args eq 'ARRAY' or return <{'args'}{'data_addr'} || $self->{'data_addr'} ; + + my $aio = Stem::AsyncIO->new( + + 'object' => $self->{'owner_obj'}, + 'data_addr' => $data_addr, + 'from_addr' => $self->{'from_addr'}, + 'send_data_on_close' => $self->{'send_data_on_close'}, + 'codec' => $self->{'codec'}, + @aio_args, + ) ; + +print "AIO ERR [$aio]\n" unless ref $aio ; + return $aio unless ref $aio ; + + $self->{'aio'} = $aio ; + +#print $self->_dump( "AFTER AIO" ) ; + + return ; +} + +sub cell_activate { + + my( $self ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->{'active'} = 1 ; +} + +*cell_status_cmd = \&status_cmd ; + +sub status_cmd { + + my( $self ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + my $info = $cell_info->{'info'} || $cell_info->{'args'}{'info'} || '' ; + + $info =~ s/^/\t\t/mg ; + + my $class = ref $cell_info->{'owner_obj'} ; + +# my $data_addr = Stem::Msg::address_string( + my $data_addr = $cell_info->{'data_addr'} || + $cell_info->{'args'}{'data_addr'} || + '[NONE]' ; + + my $active = ( $cell_info->{'active'} ) ? 'Active' : 'Inactive' ; + + my $codec = $cell_info->{codec} || 'NONE' ; + +print "CELL STATUS\n" ; + +#my $dump = $cell_info->_dump( 'STATUS' ) ; +my $dump = '' ; + + return <{'from_addr'} +Status: $active +Data Addr: $data_addr +Codec: $codec +Info:$info + +SELF: $self +CELL: $cell_info +AIO: $cell_info->{aio} +FH: $cell_info->{fh} + +$dump + +STATUS + +} + +sub data_in { + + my( $self, $msg ) = @_ ; + +#print "DATA SELF $self\n" ; + +#print $msg->dump( 'CELL IN' ) ; + + my $cell_info = $self->_get_cell_info() ; + + if ( $cell_info->{'is_parent'} ) { + +#print "PARENT\n" ; + TraceStatus "parent cell $cell_info->{'from_addr'} ignoring msg" ; + + return ; + } + + unless( $cell_info->{'active'} ) { +#print "INACTIVE\n" ; + + TraceStatus "cell not active. msg ignored FOO" ; + + return ; + } + +#print $cell_info->_dump( "DATA IN" ) ; + + $cell_info->{data_in_msg} = $msg ; + $cell_info->cell_write( $msg->data() ) ; +} + +sub cell_write { + + my( $self, $data ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->{'aio'}->write( $data ) ; +} + +sub _cell_write_sync { + + my( $self, $data ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + +#print "SYNC $$data\n" ; + +#print $cell_info->_dump( 'SYNC' ) ; + + if ( my $aio_args = $cell_info->{'args'}{'aio_args'} ) { + + my %aio_args = @{$aio_args} ; + + if ( my $fh = $aio_args{'fh'} ) { + +# $fh->blocking( 1 ) ; + + $fh->syswrite( (ref $data) ? $$data : $data ) ; + } + } +} + +# handle stderr data as plain data + +*stderr_data_in = \&data_in ; + + +# $cell_info is the Stem::Cell object of the parent cell. the name is +# not self as it is differentiated from $clone_info. + + + +sub _callback { + + my ( $self, $method_name, @data ) = @_ ; + + my $method = $self->{$method_name} ; + + my $owner_obj = $self->{'owner_obj'} ; + + if ( $owner_obj->can( $method ) ) { + + return $owner_obj->$method( @data ) ; + } + + TraceStatus "can't call $method in $owner_obj" ; + + return ; +} + +sub cell_from_addr { + + my ( $self ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + return( $cell_info->{'from_addr'} ) ; +} + +use Stem::Debug qw( dump_data ) ; + +sub _dump { + + my ( $self, $text ) = @_ ; + +return $text . dump_data( $self ) ; + + $text ||= 'CELL' ; + + my $dump = "$text =\n" ; + + my $cell_info = $self->_get_cell_info() ; + +# my $owner_obj = $cell_info->{owner_obj} ; +# my @names = lookup_cell_name( $owner_obj ) ; +# $dump .= "\nNames: @names\n" ; + + foreach my $key ( sort keys %{$cell_info} ) { + + my $val = $cell_info->{$key} ; + next unless defined $val ; + + if ( $key eq 'args' ) { + + $dump .= "\targs = {\n" ; + + foreach my $arg ( sort keys %{$val} ) { + + my $arg_val = $val->{$arg} || ''; + + $dump .= "\t\t$arg = '$arg_val'\n" ; + } + + $dump .= "\t}\n" ; + + next ; + } + + $dump .= "\t$key = '$val'\n" ; + } + + $dump .= "\n\n" ; + + return $dump ; +} + +sub dump_cmd { + + my ($self) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + return $cell_info->_dump() . Dumper $cell_info ; +} + +1 ; diff --git a/lib/Stem/Cell/Clone.pm b/lib/Stem/Cell/Clone.pm new file mode 100644 index 0000000..000cc60 --- /dev/null +++ b/lib/Stem/Cell/Clone.pm @@ -0,0 +1,161 @@ +# File: Stem/Cell/Clone.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Cell ; + +use strict ; + +sub cell_cloneable { + + my( $self ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + return $cell_info->{'cloneable'} ; +} + +##################### +##################### +# add check of max clone count +##################### +##################### + +my @clone_fields = qw( + + no_io + data_addr + errors_to_output + pipe_addr + pipe_args + codec + work_ready_addr + trigger_method + sequence_done_method + send_data_on_close + stderr_log +) ; + +sub _clone { + + my( $cell_info ) = @_ ; + + my $owner_obj = $cell_info->{'owner_obj'} ; + + return $owner_obj unless $cell_info->{'cloneable'} ; + +# copy the object + + my $clone = bless { %{$owner_obj} }, ref $owner_obj ; + +# get a new target id and the cell name + + my $target = $cell_info->{'id_obj'}->next() ; + + my $cell_name = $cell_info->{'cell_name'} ; + +# keep track of the clone in the parent and register it + + $cell_info->{'clones'}{$target} = $clone ; + + my $err = register_cell( $clone, $cell_name, $target ) ; + + die $err if $err ; + +# the parent loses its args to the clone. parent cells never do real work + +################## +## add parent private INFO/ARGS for use by status command +################## + + my $args = delete $cell_info->{'args'} ; + +# create the clone info and save it in the cloned object + + my $cell_info_attr = $cell_info->{'cell_info_attr'} ; + + my $from_addr = Stem::Msg::make_address_string( + $Stem::Vars::Hub_name, + $cell_name, + $target + ) ; + +#print "FROM ADDR $cell_info->{'from_addr'}\n" ; + my $clone_info = bless { + + 'owner_obj' => $clone, + 'parent_obj' => $owner_obj, + 'cell_name' => $cell_name, + 'target' => $target, + 'from_addr' => $from_addr, + 'args' => $args, + 'cell_info_attr' => $cell_info_attr, + map { $_ => $cell_info->{$_} } @clone_fields, + } ; + +# save the new clone info into the clone itself ; + + $clone->{$cell_info_attr} = $clone_info ; + + return $clone ; +} + +sub _clone_delete { + + my ( $self ) = @_ ; + + my $parent_obj = $self->{'parent_obj'} ; + + return unless $parent_obj ; + + my $owner_obj = $self->{'owner_obj'} ; + + my $cell_info_attr = $self->{'cell_info_attr'} ; + +#print $self->cell_status_cmd() ; + +# break all circular links +# delete the refs to the parent and parent objects in the cell info +# and the owner object ref to this cell info + + delete @{$self}{ qw( owner_obj parent_obj ) } ; + delete $owner_obj->{$cell_info_attr} ; + + delete $self->{'args'} ; + +# clean up the parent clones hash and the registry + + my $parent_info = $parent_obj->{$cell_info_attr} ; + my $target = $self->{'target'} ; + + delete $parent_info->{'clones'}{$target} ; + $parent_info->{'id_obj'}->delete( $target ) ; + + my $err = unregister_cell( $owner_obj ) ; +} + +1 ; diff --git a/lib/Stem/Cell/Flow.pm b/lib/Stem/Cell/Flow.pm new file mode 100644 index 0000000..79e62fb --- /dev/null +++ b/lib/Stem/Cell/Flow.pm @@ -0,0 +1,442 @@ +# File: Stem/Cell/Flow.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Cell ; + +use strict ; + +my $grammar = <<'GRAMMAR' ; + +flow : list /\s*\Z/ { $item[1] } + +top_list : list + +list : statement(s) + +statement : ifelse | + if | + while | + delay | + next | + stop | + method | + + +ifelse : /(?:if|unless)\b/i method_call block /else/i block { + { + op => 'IF', + not => lc $item[1] eq 'unless' ? + 1 : 0, + cond => $item[2], + then => $item[3], + else => $item[5] + } + } + +if : /(?:if|unless)\b/i method_call block { + { + op => 'IF', + not => lc $item[1] eq 'unless' ? + 1 : 0, + cond => $item[2], + then => $item[3], + } + } + +while : label(?) /(?:while|until)\b/i method_call block { + { + op => 'WHILE', + label => $item[1][0] || '', + not => lc $item[2] eq 'until' ? + 1 : 0, + cond => $item[3], + block => $item[4] + } + } + +next : /(?:next|last)\b/i name(?) ';' { + { + op => 'NEXT', + last => lc $item[1] eq 'last', + label => $item[2][0] || '' + } + } + +delay : /delay\b/i ( delay_value | delay_method ) ';' { + { + op => 'DELAY', + @{$item[2]} + } + } + + +stop : /stop/i ';' { + { + op => 'STOP', + } + } + +label : name ':' { $item[1] } + +delay_value : /\d+/ { + [value => $item[1]] + } + +delay_method : method_call { + [method => $item[1]] + } + +method : method_call ';' { $item[1] } + +method_call : args_method | plain_method + +plain_method : name { + { + op => 'METHOD', + method => $item[1], + } + } + +args_method : name '(' arg(s /,/) ')' { + { + op => 'METHOD', + method => $item[1], + args => $item[3], + } + } + +arg : /\w+/ + +name : /[^\W\d]\w*/ + +block : '{' list '}' { $item[2] } + +GRAMMAR + +my $flow_parser ; +my %flows ; + +my %flow_ops = ( + + WHILE => \&flow_while_op, + IF => \&flow_if_op, + NEXT => \&flow_next_op, + METHOD => \&flow_method_op, + DELAY => \&flow_delay_op, + STOP => \&flow_stop_op, +) ; + + +$::RD_HINT = 1 ; +$::RD_ERRORS = 1 ; + +use Data::Dumper ; + +sub cell_flow_init { + + my( $self, $name, $source ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + unless( $flow_parser ) { + + require Parse::RecDescent ; + + $flow_parser = Parse::RecDescent->new( $grammar ) or + die 'bad flow grammar' ; + } + + my $tree = $flows{$name}{'tree'} ; + + unless( $tree ) { + + $source =~ s/#.+$//mg ; + + $tree = $flow_parser->flow( $source ) ; + +#print Dumper $tree ; + + $flows{$name} = { + + 'tree' => $tree, + 'source' => $source, + } ; + } + + $cell_info->{'flow'} = { + + 'name' => $name, + 'tree' => $tree, + 'pc' => [ $tree, 0 ], + } ; + + return ; +} + +sub cell_flow_go_in { + + my( $self, $msg ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + +#print $msg->dump( 'GO') if $msg ; + +#print "GO\n" ; + my $flow = $cell_info->{'flow'} ; + +#print Dumper $flow ; + + while( 1 ) { + + my ( $pc_ref, $pc_index ) = @{$flow->{'pc'}} ; + +#print "IND $pc_index ", Dumper $pc_ref ; + + if ( $pc_index >= @{$pc_ref} ) { + +#print "LIST END\n" ; + + my $old_pc = pop( @{$flow->{'stack'}} ) ; + + $old_pc or die "FELL off FLOW STACK" ; + +#print "POP\n" ; + + $flow->{'pc'} = $old_pc ; + next ; + } + + my $op = $pc_ref->[$pc_index] ; + + my $op_name = $op->{'op'} ; + +#print "OP $op_name\n" ; + + my $code = $flow_ops{$op_name} ; + + $code or die "unknown flow op code [$code]" ; + + my $meth_val = $code->( $flow, $op, $self, $msg ) ; + + $msg = undef ; + + next unless $meth_val ; + + return if $meth_val && $meth_val eq 'FLOW_STOP' ; + +# check for a message + + if ( ref $meth_val eq 'Stem::Msg' ) { + + $meth_val->reply_type( 'cell_flow_go' ) ; + + $meth_val->dispatch() ; + + return ; + } + + return ; + } + + return ; +} + +sub flow_stop_op { + + my( $flow ) = @_ ; + + my $pc = $flow->{'pc'} ; + +# always go to the next op + + $pc->[1]++ ; + return 'FLOW_STOP' ; +} + +sub flow_method_op { + + my( $flow, $op, $obj, $msg ) = @_ ; + + my $pc = $flow->{'pc'} ; + +# always go to the next op + + $pc->[1]++ ; + +#print Dumper $pc ; + + return( flow_call_method( $op, $obj, $msg ) ) ; +} + +sub flow_while_op { + + my( $flow, $op, $obj ) = @_ ; + + my $pc = $flow->{'pc'} ; + + my $cond_val = flow_cond( $op, $obj ) ; + + unless( $cond_val ) { + +#print "WHILE END\n" ; + + $pc->[1]++ ; + return ; + } + +#print "WHILE LOOP\n" ; + + push( @{$flow->{'stack'}}, $pc ) ; + + $flow->{'pc'} = [ $op->{'block'}, 0 ] ; + + return ; +} + +sub flow_if_op { + + my( $flow, $op, $obj ) = @_ ; + + my $cond_val = flow_cond( $op, $obj ) ; + + my $block = $cond_val ? $op->{'then'} : $op->{'else'} ; + + my $pc = $flow->{'pc'} ; + +# always go to the next op + + $pc->[1]++ ; + + if ( $block ) { + + push( @{$flow->{'stack'}}, $pc ) ; + + $flow->{'pc'} = [ $block, 0 ] ; + } + + return ; +} + +sub flow_next_op { + + my( $flow, $op, $obj ) = @_ ; + + my $label = $op->{'label'} ; + + while( 1 ) { + + my $pc = pop( @{$flow->{'stack'}} ) ; + + $pc or die "can't find label '$label' in flow stack" ; + +#print "PC: ", Dumper $pc ; + + my $prev_op = $pc->[0][$pc->[1]] ; + +#print "PREV: ", Dumper $prev_op ; + + next unless $prev_op && $prev_op->{'op'} eq 'WHILE' ; + +#print "FOUND WHILE\n" ; + + next unless $prev_op->{'label'} eq $label ; + + $pc->[1]++ if $op->{'last'} ; + +#print "LAST PC: ", Dumper $pc ; + + $flow->{'pc'} = $pc ; + + return ; + } +} + + +sub flow_delay_op { + + my( $flow, $op, $obj ) = @_ ; + +#print Dumper $op ; + + my $pc = $flow->{'pc'} ; + $pc->[1]++ ; + + my $delay = $op->{'value'} ; + + unless ( defined $delay ) { + + $delay = flow_call_method( $op->{'method'}, $obj ) ; + } + + $flow->{'timer'} = Stem::Event::Timer->new( + 'object' => $obj, + 'method' => 'cell_flow_go_in', + 'delay' => $delay, + 'hard' => 1, + 'single' => 1, + ) ; + +# print "D $delay EVT $flow->{'timer'}\n" ; + + return 1 ; +} + +sub flow_cond { + + my( $op, $obj ) = @_ ; + + my $cond = $op->{'cond'} ; + + return 1 if $cond eq '1' ; + + my $cond_val = flow_call_method( $cond, $obj ) ? 1 : 0 ; + + return( $cond_val ^ $op->{'not'} ) ; +} + +sub flow_call_method { + + my( $call, $obj, $msg ) = @_ ; + + my $method = $call->{'method'} ; + + my @args = @{$call->{'args'} || []} ; + + unshift( @args, $msg ) if $msg ; + +# flow methods are always called in scalar context + +#print "METHOD $method ( @args )\n" ; + + my $val = $obj->$method( @args ) ; + + return $val ; +} + + +1 ; diff --git a/lib/Stem/Cell/Pipe.pm b/lib/Stem/Cell/Pipe.pm new file mode 100644 index 0000000..d5eca52 --- /dev/null +++ b/lib/Stem/Cell/Pipe.pm @@ -0,0 +1,153 @@ +# File: Stem/Cell/Pipe.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Cell ; + +use strict ; + +sub _cell_pipe { + + my( $self ) = @_ ; + + if ( $self->{'args'}{'pipe_open'} ) { + + $self->{'piped'} = 1 ; + +# return the connection handshake + + my $addr_msg = Stem::Msg->new( + 'cmd' => 'cell_pipe_addr', + 'to' => $self->{'args'}{'data_addr'}, + 'from' => $self->{'from_addr'}, + ) ; + + $addr_msg->dispatch() ; + + return ; + } + + my $pipe_addr = $self->{'args'}{'pipe_addr'} || $self->{'pipe_addr'} ; + + return unless $pipe_addr ; + + $self->{'piped'} = 1 ; + +# start the pipe connection handshake + + my $open_msg = Stem::Msg->new( + 'cmd' => 'cell_trigger', + 'to' => $pipe_addr, + 'from' => $self->{'from_addr'}, + 'data' => { + 'args' => $self->{'pipe_args'}, + 'pipe_open' => 1, + 'data_addr' => $self->{'from_addr'}, + }, + ) ; + + $open_msg->dispatch() ; +} + +# this command sub sets the data address at the end of a pipe handshake + +sub cell_pipe_addr_cmd { + + my( $self, $msg ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->{'data_addr'} = $msg->from() ; + + my $err = $cell_info->{'gather'}->gathered( 'data_addr' ) ; + return $err if $err ; + + return ; +} + +sub cell_pipe_close_cmd { + + my( $self, $msg ) = @_ ; + +#print $msg->dump( 'PIPE' ) ; + +# TraceStatus "pipe closed cmd" ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->{'close_cmd_seen'} = 1 ; + + my $data = $msg->data() ; + +# see if we dump the errors to the output handle + + if ( $data && $cell_info->{'errors_to_output'} ) { + + $data = <_cell_write_sync( \$data ) ; + } + + + + $self->cell_shut_down() ; + + return ; +} + +sub _close_pipe { + + my( $self ) = @_ ; + + return if $self->{'close_cmd_seen'} ; + +use Carp qw( cluck ) ; +#cluck() ; +#print $self->_dump( 'CLOSE PIPE' ) ; + + return unless $self->{'piped'} ; + +# TraceStatus "pipe closing" ; + + my $to_addr = $self->{'args'}{'data_addr'} || + $self->{'data_addr'} ; + + my $close_msg = Stem::Msg->new( + 'cmd' => 'cell_pipe_close', + 'to' => $to_addr, + 'from' => $self->{'from_addr'}, + 'data' => $self->{'error'}, + ) ; + +#print $close_msg->dump( '_close PIPE' ) ; + + $close_msg->dispatch() ; +} + +1 ; diff --git a/lib/Stem/Cell/Sequence.pm b/lib/Stem/Cell/Sequence.pm new file mode 100644 index 0000000..249f18f --- /dev/null +++ b/lib/Stem/Cell/Sequence.pm @@ -0,0 +1,219 @@ +# File: Stem/Cell/Sequence.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Cell ; + +use strict ; + +sub cell_set_sequence { + + my( $self, @sequence ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + +#print "@sequence\n" ; + + $cell_info->{'sequence'} = [ @sequence ] ; + $cell_info->{'sequence_left'} = [ @sequence ] ; + + return ; +} + + +sub cell_reset_sequence { + + my( $self ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->{'sequence_left'} = [ @{$cell_info->{'sequence'}} ] ; + + return ; +} + +sub cell_replace_next_sequence { + + my( $self, $method ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->{'sequence_left'}[0] = $method; + + return ; +} + +# +# This method lets you basically set up loops. For example, method X +# could insert itself as the next next method in the sequence. Then, +# when it is called again it can decide whether or not to insert +# itself again. +# +# A more complex example might see method X might say "now execute Y, +# Z, M, and X", which allows you to create loops. Then method Z might +# say "now execute Q and Z". +# +# Obviously, most loops will also need a break condition where method +# X decides _not_ to insert itself into the sequence. +# +sub cell_insert_next_sequence { + + my( $self, @sequence ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + unshift @{ $cell_info->{'sequence_left'} }, @sequence; + + return ; +} + +sub cell_skip_next_sequence { + + my( $self, $count ) = @_ ; + + $count ||= 1 ; + + my $cell_info = $self->_get_cell_info() ; + + shift @{ $cell_info->{'sequence_left'} } for 1..$count; + + return ; +} + +sub cell_skip_until_method { + + my( $self, $method ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + my $seq_left = $cell_info->{'sequence_left'} ; + + while( @{$seq_left} ) { + + return if $seq_left->[0] eq $method ; + shift @{$seq_left} ; + } + + die "skip sequence method $method is not found" ; +} + + +sub cell_next_sequence_in { + + my( $self, $msg ) = @_ ; + +#print $msg->dump( "NEXT IN" ) if $msg ; + + my $cell_info = $self->_get_cell_info() ; + + $cell_info->cell_next_sequence( $msg ) ; +} + +sub cell_next_sequence { + + my( $self, $in_msg ) = @_ ; + +#print caller(), "\n" ; + +#print $in_msg->dump('SEQ IN') if $in_msg ; + + my $cell_info = $self->_get_cell_info() ; + + my $owner_obj = $cell_info->{'owner_obj'} ; + + + while( my $next_sequence = shift @{$cell_info->{'sequence_left'}} ) { + +#print "LEFT @{$cell_info->{'sequence_left'}}\n" ; + + die "cannot call sequence method $next_sequence" + unless $owner_obj->can( $next_sequence ) ; + +#print "SEQ: $next_sequence\n" ; + + my $seq_val = $owner_obj->$next_sequence( $in_msg ) ; + +# don't pass in the message more than once. + + $in_msg = undef ; + + next unless $seq_val ; + + if ( ref $seq_val eq 'Stem::Msg' ) { + + +#print caller() ; +#print $seq_val->dump( 'SEQ: MSG' ) ; + $seq_val->reply_type( 'cell_next_sequence' ) ; + + $seq_val->dispatch() ; + + return ; + } + + if ( ref $seq_val eq 'HASH' ) { + + my $delay = $seq_val->{'delay'} ; + + if ( defined( $delay ) ) { + + $cell_info->cell_sequence_delay( $delay ) ; + return ; + } + } + } + + if ( my $seq_done_method = $cell_info->{'sequence_done_method'} ) { + + $owner_obj->$seq_done_method() ; + + return ; + } + +#warn "FELL off end of sequence" ; + + return ; +} + +sub cell_sequence_delay { + + my( $self, $delay ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + +#print "SEQ DELAY $delay\n" ; + + $cell_info->{'timer'} = Stem::Event::Timer->new( + 'object' => $cell_info, + 'method' => 'cell_next_sequence', + 'delay' => $delay, + 'hard' => 1, + 'single' => 1, + ) ; +} + +1 ; diff --git a/lib/Stem/Cell/Work.pm b/lib/Stem/Cell/Work.pm new file mode 100644 index 0000000..dfff2f5 --- /dev/null +++ b/lib/Stem/Cell/Work.pm @@ -0,0 +1,78 @@ +# File: Stem/Cell/Work.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001, 2002 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Cell ; + +use strict ; + + +sub cell_worker_ready { + + my ( $self ) = @_ ; + + my $cell_info = $self->_get_cell_info() ; + + my $ready_addr = $cell_info->{'work_ready_addr'} ; + + return unless $ready_addr ; + +#print "READY addr [$ready_addr]\n" ; + + my $worker_msg = Stem::Msg->new( + 'to' => $ready_addr, + 'type' => 'worker', + 'from' => $cell_info->{'from_addr'}, + ) ; + +#print $worker_msg->dump('worker ready') ; + + $worker_msg->dispatch() ; + + return ; +} + +sub cell_work_in { + + my ( $self, $msg ) = @_ ; + +#print $msg->dump( 'WORK MSG' ) ; + +################## +# handle error: work when work in progress. check 'work_msg' +################# + + my $cell_info = $self->_get_cell_info() ; + + my $obj = $msg->data() ; + + my $packet_text = $cell_info->{'packet'}->to_packet( $obj ) ; + + $cell_info->cell_write( $packet_text ) ; +} + +1 ; diff --git a/lib/Stem/ChatLabel.pm b/lib/Stem/ChatLabel.pm new file mode 100644 index 0000000..34fbe0f --- /dev/null +++ b/lib/Stem/ChatLabel.pm @@ -0,0 +1,42 @@ +package Stem::ChatLabel ; + +use strict ; + + +my $attr_spec = [ + + { + 'name' => 'sw_addr', + 'help' => <data() ; + +#print "$$data" ; + + substr( $$data, 0, 0, $msg->from_cell() . ': ' ) ; + + $msg->data( $data ) ; + $msg->to_cell( $self->{'sw_addr'} ) ; + + $msg->dispatch() ; +} + +1 ; diff --git a/lib/Stem/Class.pm b/lib/Stem/Class.pm new file mode 100644 index 0000000..8d4861c --- /dev/null +++ b/lib/Stem/Class.pm @@ -0,0 +1,420 @@ +# File: Stem/Class.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Class ; + +use strict ; + +#use Data::Dumper ; + +# dispatch table for attribute 'type' checking and conversion + +my %type_to_code = ( + + 'boolean' => \&_type_boolean, + 'hash' => \&_type_hash, + 'list' => \&_type_list, + 'HoL' => \&_type_hash_of_list, + 'LoL' => \&_type_list_of_list, + 'HoH' => \&_type_hash_of_hash, + 'LoH' => \&_type_list_of_hash, + 'addr' => \&_type_address, + 'address' => \&_type_address, + 'obj' => \&_type_object, + 'object' => \&_type_object, + 'cb_object' => \&_type_object, + 'handle' => \&_type_handle, +) ; + +sub parse_args { + + my( $attr_spec, %args_in ) = @_ ; + + my( $package ) = caller ; + +#print "PACK $package\n" ; + + my $obj = bless {}, $package ; + +#print Dumper( $attr_spec ) ; +#print "class args ", Dumper( \%args_in ) ; + + my( $cell_info_obj, $cell_info_name ) ; + + my $reg_name = $args_in{ 'reg_name' } || '' ; + + foreach my $field ( @{$attr_spec} ) { + + my $field_name = $field->{'name'} or next ; + + my $field_val = $args_in{ $field_name } ; + + if ( my $class = $field->{'class'} ) { + +# optinally force a sub-object build by passing a default empty list +# for its value +# Stem::Cell is always built + + if ( $field->{'always_create'} || + $class eq 'Stem::Cell' ) { + + $field_val ||= [] ; + } + + my @class_args ; + + if ( ref $field_val eq 'HASH' ) { + + @class_args = %{$field_val} ; + } + elsif ( ref $field_val eq 'ARRAY' ) { + + @class_args = @{$field_val} ; + } + else { + next ; + } + + my $class_args = $field->{'class_args'} ; + + if ( $class_args && ref $class_args eq 'HASH' ) { + + push( @class_args, %{$class_args} ) ; + } + elsif ( $class_args && ref $class_args eq 'ARRAY' ) { + + push( @class_args, @{$class_args} ) ; + } + +# Stem::Cell wants to know its owner's cell name + + push( @class_args, 'reg_name' => $reg_name ) + if $class eq 'Stem::Cell' ; + + $field_val = $class->new( @class_args ) ; + + return <{'callback'} and $field_val ) { + + + my $cb_err = $callback->( $obj, + $field_name, $field_val ) ; + + return $cb_err if $cb_err ; + + next ; + } + + if ( my $env_name = $field->{'env'} ) { + + my @prefixes = ( $reg_name ) ? + ( "${reg_name}:", "${reg_name}_", '' ) : + ( '' ) ; + + foreach my $prefix ( @prefixes ) { + +#print "ENV NAME [$prefix$env_name]\n" ; + + my $env_val = + $Stem::Vars::Env{"$prefix$env_name"} ; + + next unless defined $env_val ; + + $field_val = $env_val ; +#print "ENV field $field_name [$env_val]\n" ; + last ; + } + } + + unless( defined $field_val ) { + + if ( $field->{'required'} ) { + + return <{'default'} + if exists $field->{'default'} ; + } + +#print "field $field_name [$field_val]\n" ; + + next unless defined $field_val ; + + if ( my $type = $field->{'type'} ) { + + my $type_code = $type_to_code{$type} ; + return "Unknown attribute type '$type'" + unless $type_code ; + + my $err = $type_code->( + \$field_val, $type, $field_name ) ; +#print "ERR $err\n" ; + return $err if $err ; + } + + $obj->{$field_name} = $field_val ; + } + + if ( $cell_info_obj ) { + + return <cell_init( $obj, + $reg_name, + $cell_info_name + ) ; + } + +#print "class obj ", Dumper( $obj ) ; + + return $obj ; +} + +sub _type_boolean { + + my ( $val_ref, $type ) = @_ ; + + return if ${$val_ref} =~ s/^(?:|1|Y|Yes)$/1/i || + ${$val_ref} =~ s/^(?:|0|N|No)$/0/i ; + + return "Attribute value '${$val_ref}' is not boolean" +} + +sub _type_object { + + my ( $val_ref, $type ) = @_ ; + + return if ref ${$val_ref} ; + + return "Attribute value '${$val_ref}' is not an object" +} + +sub _type_address { + + my ( $val_ref, $type, $name ) = @_ ; + + my( $to_hub, $cell_name, $target ) = + Stem::Msg::split_address( ${$val_ref} ) ; + + return if $cell_name ; + + return "Attribute $name: value '${$val_ref}' is not a valid Stem address" +} + +sub _type_handle { + + my ( $val_ref, $type ) = @_ ; + + return if defined fileno( ${$val_ref} ) ; + + return "Attribute value '${$val_ref}' is not an open IO handle" +} + +sub _type_list { + + my ( $val_ref, $type ) = @_ ; + + my $err = _convert_to_list( $val_ref ) ; + + return unless $err ; + + return "Attribute value '${$val_ref}' is not a list\n$err" ; +} + +sub _type_hash { + + my ( $val_ref, $type ) = @_ ; + + my $err = _convert_to_hash( $val_ref ) ; + + return unless $err ; + + return "Attribute value '${$val_ref}' is not a hash\n$err" ; +} + +sub _type_list_of_list { + + my ( $val_ref, $type ) = @_ ; + +#print Dumper $val_ref ; + my $err = _convert_to_list( $val_ref ) ; + +#print Dumper $val_ref ; + + return $err if $err ; + + foreach my $sub_val ( @{$$val_ref}) { + + $err = _convert_to_list( \$sub_val ) ; + return < 'codec', + 'default' => 'Data::Dumper', + 'help' => < 'object', + 'type' => 'object', + 'help' => < 'encode_method', + 'default' => 'encoded_data', + 'help' => < 'decode_method', + 'default' => 'decoded_data', + 'help' => < + +=over 4 + + +=item Description: +If an object is passed in, the filter will use it for callbacks + + +=item Its B is: object + +=back + +=item * Attribute - B + +=over 4 + + +=item It B to: encoded_data + +=back + +=item * Attribute - B + +=over 4 + + +=item It B to: decoded_data + +=back + +=back + +=cut + +# End of autogenerated POD +########### + +my %loaded_codecs ; + +sub new { + + my( $class ) = shift ; + + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return $self unless ref $self ; + + my $err = $self->load_codec() ; + return $err if $err ; + + return $self ; +} + +sub load_codec { + + my( $self ) = @_ ; + + my $codec = $self->{codec} ; + + return if $loaded_codecs{ $codec } ; + + my $codec_class = "Stem::Codec::$codec" ; + + eval "require $codec_class" ; + + return "Can't load Stem codec '$codec_class' $@" if $@ ; + + $loaded_codecs{ $codec } = { + + encoder => $codec_class->make_encoder(), + decoder => $codec_class->make_decoder(), + } ; + + return ; +} + +sub encode { + + my $self = shift ; + + return unless @_ ; + + my $encoder = $loaded_codecs{ $self->{codec} }{encoder} ; + +# make sure scalars and scalar refs have a ref taken to them as codecs +# always take a ref. we do ref on scalar refs so we can tell at decode +# time that REF is a scalar ref but SCALAR is a plain scalar + +#print "IN $_[0] REF ", ref $_[0], "\n" ; + + my $data_ref = ( ! ref $_[0] || ref $_[0] eq 'SCALAR' ) ? + \$_[0] : $_[0] ; + +#print "DATA REF $data_ref\n" ; + + my $encoded_text = $encoder->( $data_ref ) ; + + if ( my $obj = $self->{'object'} ) { + + my $method = $self->{'encode_method'} ; + $obj->$method( $encoded_text ) ; + } + + return $encoded_text ; +} + +sub decode { + + my $self = shift ; + + my $decoder = $loaded_codecs{ $self->{codec} }{decoder} ; + + my $decoded_data = $decoder->( $_[0] ) ; + + $decoded_data = ${$decoded_data} if + ref $decoded_data eq 'SCALAR' || + ref $decoded_data eq 'REF' ; + + if ( my $obj = $self->{'object'} ) { + + my $method = $self->{'decode_method'} ; + $obj->$method( $decoded_data ) ; + } + + return( $decoded_data ) ; +} + +1 ; diff --git a/lib/Stem/Codec/Data/Dumper.pm b/lib/Stem/Codec/Data/Dumper.pm new file mode 100644 index 0000000..5af7fb2 --- /dev/null +++ b/lib/Stem/Codec/Data/Dumper.pm @@ -0,0 +1,49 @@ +# File: Stem/Codec/Data/Dumper.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Codec::Data::Dumper ; + +use strict ; +use Data::Dumper ; + +$Data::Dumper::Purity = 1 ; + +sub make_encoder { + +# strip out the '$VAR = ' stuff + + return sub { \substr( Dumper( $_[0] ), 8 ) } ; +# return sub { \Dumper( $_[0] ) } ; +} + +sub make_decoder { + + return sub { eval $_[0] } ; +} + +1 ; diff --git a/lib/Stem/Codec/Storable.pm b/lib/Stem/Codec/Storable.pm new file mode 100644 index 0000000..60a7dca --- /dev/null +++ b/lib/Stem/Codec/Storable.pm @@ -0,0 +1,44 @@ +# File: Stem/Codec/Storable.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Codec::Storable ; + +use strict ; +use Storable () ; + +sub make_encoder { + + return sub { \Storable::freeze( $_[0] ) } ; +} + +sub make_decoder { + + return sub { Storable::thaw( $_[0] ) } ; +} + +1 ; diff --git a/lib/Stem/Codec/YAML.pm b/lib/Stem/Codec/YAML.pm new file mode 100644 index 0000000..2fee821 --- /dev/null +++ b/lib/Stem/Codec/YAML.pm @@ -0,0 +1,44 @@ +# File: Stem/Codec/YAML.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Codec::YAML ; + +use strict ; +use YAML () ; + +sub make_encoder { + + return sub { \YAML::Dump( $_[0] ) } ; +} + +sub make_decoder { + + return sub { YAML::Load( $_[0] ) ; } ; +} + +1 ; diff --git a/lib/Stem/Conf.pm b/lib/Stem/Conf.pm new file mode 100644 index 0000000..8eed0c4 --- /dev/null +++ b/lib/Stem/Conf.pm @@ -0,0 +1,328 @@ +# File: Stem/Conf.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Conf ; + +use Data::Dumper ; +use strict ; + +use Stem::Vars ; + +use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; + +Stem::Route::register_class( __PACKAGE__, 'conf' ) ; + +my @conf_paths = split ':', $Env{ 'conf_path' } || '' ; +if ( my $add_conf_path = $Env{ 'add_conf_path' } ) { + + push @conf_paths, split( ':', $add_conf_path ) ; +} + +my $attr_spec = [ + + { + 'name' => 'path', + 'required' => 1, + 'help' => < 'to_hub', + 'help' => <{'to_hub'} ) { + + my $conf_data = load_conf_file( $self->{'path'} ) ; + + return $conf_data unless ref $conf_data ; + + my $msg = Stem::Msg->new( + 'to_hub' => $to_hub, + 'to_cell' => __PACKAGE__, + 'from_cell' => __PACKAGE__, + 'type' => 'cmd', + 'cmd' => 'remote', + 'data' => $conf_data, + ) ; + + $msg->dispatch() ; + + return ; + } + + my $err = load_conf_file( $self->{'path'}, 1 ) ; + +TraceError $err if $err ; + + return $err if $err ; + + return ; +} + + +sub load_cmd { + + my( $self, $msg ) = @_ ; + + my $data = $msg->data() ; + + my @conf_names ; + + push( @conf_names, @{$data} ) if ref $data eq 'ARRAY' ; + push( @conf_names, ${$data} ) if ref $data eq 'SCALAR' ; + + my $err = load_confs( @conf_names ) ; + +TraceError $err if $err ; + + return $err if $err ; + + return ; +} + +sub remote_cmd { + + my( $self, $msg ) = @_ ; + + my $err = configure( $msg->data() ) ; + +TraceError $err if $err ; + + return $err if $err ; + + return ; +} + +sub load_conf_file { + + my( $conf_path, $do_conf ) = @_ ; + + -r $conf_path or return "$conf_path can't be read: $!" ; + + my $conf_data = Stem::Util::load_file( $conf_path ) ; + + return "Stem::Conf load error:\n$conf_data" unless ref $conf_data ; + + return $conf_data unless $do_conf ; + + my $conf_err = configure( $conf_data ) ; + + return <$method( + 'reg_name' => $reg_name, + @args ) ) { + + return <can('config_done'); + next; + } + + } +# or else register the class if we have a name + + my $err = Stem::Route::register_class( $class, $reg_name ) ; + + return $err if $err ; + push @notify_done, $class if $class->can('config_done'); + } + + foreach my $class (@notify_done) { + $class->config_done(); + } + + return ; +} + +1 ; diff --git a/lib/Stem/Console.pm b/lib/Stem/Console.pm new file mode 100644 index 0000000..cf62156 --- /dev/null +++ b/lib/Stem/Console.pm @@ -0,0 +1,362 @@ +# File: Stem/Console.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Console ; + +use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; + +use strict ; + +use Data::Dumper ; +use Symbol ; +use Socket ; + +use Stem::AsyncIO ; +use Stem::Vars ; + +my $console_obj ; +my $line ; + +my( $read_fh, $write_fh, $parent_fh, $child_fh ) ; + +if ( $^O =~ /Win32/ ) { + + + $parent_fh = gensym ; + $child_fh = gensym ; + + socketpair( $parent_fh, $child_fh, AF_UNIX, SOCK_STREAM, PF_UNSPEC ) ; + start_reader() ; + start_writer() ; + +# close $child_fh ; + + $read_fh = $parent_fh ; + $write_fh = $parent_fh ; +} +else { + + $read_fh = \*STDIN ; + $write_fh = \*STDOUT ; +} + +return init() unless $Env{'console_disable'} || $Env{'tty_disable'} ; + + +sub start_reader { + +# back to parent + + return if fork() ; + + close $parent_fh ; + +#syswrite( \*STDERR, "reader started\n" ) ; +#warn "reader started2\n" ; + + while( 1 ) { + + my $buf ; + + my $cnt = sysread( \*STDIN, $buf, 1000 ) ; + +#syswrite( \*STDERR, $buf ) ; + + syswrite( $child_fh, $buf ) ; + } +} + +sub start_writer { + +# back to parent + + return if fork() ; + +# close $parent_fh ; + + while( 1 ) { + + my $buf ; + + my $cnt = sysread( $child_fh, $buf, 1000 ) ; + + syswrite( \*STDOUT, $buf ) ; + } +} + +sub init { + + Stem::Route::register_class( __PACKAGE__, 'cons', 'console', 'tty' ) ; + + $Env{'has_console'} = 1 ; + + my $self = bless {} ; + + my $aio = Stem::AsyncIO->new( + + 'object' => $self, + 'read_fh' => $read_fh, + 'write_fh' => $write_fh, + 'read_method' => 'stdin_read', + 'closed_method' => 'stdin_closed', + ) ; + + return $aio unless ref $aio ; + + $self->{'aio'} = $aio ; + + $self->{'prompt'} = $Env{'prompt'} || "\nStem > " ; + + $console_obj = $self ; + + $self->write( "\nEnter 'help' for help\n\n" ) ; + $self->prompt() ; + + return 1 ; +} + +sub stdin_read { + + my( $self, $line_ref ) = @_ ; + + $line = ${$line_ref} ; + + chomp( $line ) ; + + if ( $line =~ /^\s*$/ ) { + + $self->prompt() ; + return ; + } + + if ( $line =~ /^quit\s*$/i ) { + + TraceStatus "quitting" ; + + exit ; + } + + if ( $line =~ /^\s*help\s*$/i ) { + + $self->help() ; + $self->prompt() ; + return ; + } + + if ( my( $key, $val ) = $line =~ /^\s*(\w+)\s*=\s*(.+)$/ ) { + + $val =~ s/\s+$// ; + + $self->echo() ; + + $self->write( "Setting Environment '$key' to '$val'\n" ) ; + $Env{ $key } = $val ; + + $self->prompt() ; + + return ; + } + + unless ( $line =~ /^\s*(\S+)\s+(.*)$/ ) { + + $self->write( < command [args ...] + +ERR + $self->prompt() ; + + return ; + } + + my $addr = $1 ; + + my( $cmd_name, $cmd_data ) = split( ' ', $2, 2 ) ; + +# allow a leading : on the command to make it a regular message instead + + my $msg_type = ( $cmd_name =~ s/^:// ) ? 'type' : 'cmd' ; + + my $msg = Stem::Msg->new( + 'to' => $addr, + 'from' => 'console', + $msg_type => $cmd_name, + 'data' => \$cmd_data, + ) ; + + if( ref $msg ) { + + $self->echo() ; + + $msg->dispatch() ; + } + else { + $self->write( "Bad console command message: $msg\n" ) ; + } + + $self->prompt() ; + + return ; +} + +sub stdin_closed { + + my( $self ) = @_ ; + + *STDIN->clearerr() ; + + $self->write( "EOF (ignored)\n" ) ; + + $self->prompt() ; +} + +sub data_in { + + goto &response_in ; +} + +sub response_in { + + my( $self, $msg ) = @_ ; + + $self = $console_obj unless ref $self ; + + return unless $self ; + + my $data = $msg->data() ; + + $self->write( "\n\n" ) ; + + if ( $Env{'console_from'} ) { + + my $from = $msg->from() ; + + $self->write( "[From: $from]\n" ) ; + } + + if ( ref $data eq 'SCALAR' ) { + + $self->write( ${$data} ) ; + } + elsif( ref $data ) { + + $self->write( Dumper( $data ) ) ; + } + else { + + $self->write( $data ) ; + } + + $self->prompt() ; +} + +sub write { + + my( $self, $text ) = @_ ; + + $self = $console_obj unless ref $self ; + + $self->{'aio'}->write( $text ) ; +} + + +sub prompt { + + my( $self ) = @_ ; + + return unless $self->{'prompt'} ; + + $self->write( $self->{'prompt'} ) ; +} + +sub echo { + + my( $self ) = @_ ; + + return unless $Env{'console_echo'} ; + + $self->write( "->$line\n" ) ; +} + +sub help { + + my( $self ) = @_ ; + + $self->write( < 'stem_status', 'sub' => 'TraceStatus' ; +use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; + +Stem::Route::register_class( __PACKAGE__, 'cron' ) ; + +my %cron_entries ; +my $cron_timer ; +my $last_time ; + + +my @set_names = qw( minutes hours month_days months week_days ) ; + +{ + my $t = time ; + + my $interval = 60 ; + my $delay = 59 - $t % 60 ; + + if ( $Env{ 'cron_interval' } ) { + + $interval = $Env{ 'cron_interval' } ; + $delay = 0 ; + } + +# my $lt = localtime $t ; +# print "$t $lt ", $t % 60, "\n" ; + + $cron_timer = Stem::Event::Timer->new( + 'object' => __PACKAGE__, + 'method' => 'cron_triggered', + 'interval' => $interval, + 'delay' => $delay, + 'repeat' => 1, + 'hard' => 1, + ) ; +} + +die "Stem::Cron $cron_timer" unless ref $cron_timer ; + + +my $attr_spec = [ + { + 'name' => 'reg_name', + 'help' => < 'msg', + 'class' => 'Stem::Msg', + 'required' => 1, + 'help' => < 'minutes', + 'help' => < 'hours', + 'help' => < 'month_days', + 'help' => < 'months', + 'help' => < 'week_days', + 'help' => < [0, 59], + 'hours' => [0, 23], + 'month_days' => [1, 31], + 'months' => [1, 12], + 'week_days' => [0, 6], +) ; + + +sub new { + + my( $class ) = shift ; + + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return $self unless ref $self ; + + $self->{'msg'}->from_cell( $self->{'reg_name'} || 'cron' ) ; + +# make sets for each time part. if one isn't created because it is +# empty, it is a wild card with behaves as if all the slots are set. + + foreach my $set_name ( @set_names ) { + + $self->_make_cron_set( $set_name, @{$ranges{$set_name}} ) + } + +# keep track of all the active cron entries. + + $cron_entries{ $self } = $self ; + + TraceStatus Dumper($self) ; + +#################### +#################### +# why return cron entry? it should not be registered as you can't send +# it messages. do we need a way to cancel a cron entry? could we +# register in internally to cron and not need external registration? +#################### +#################### + + return $self ; +} + +sub _make_cron_set { + + my( $self, $set_name, $min, $max ) = @_ ; + + my $cron_list = $self->{$set_name} ; + + return unless ref $cron_list eq 'ARRAY' ; + + my( @cron_vals ) ; + + foreach my $cron_val ( @{$cron_list} ) { + + if ( $cron_val =~ /^(\d+)$/ && + $min <= $1 && $1 <= $max ) { + + push @cron_vals, $1 ; + next ; + } + + if ( $cron_val =~ /^(\d+)-(\d+)$/ && + $min <= $1 && $1 <= $2 && $2 <= $max ) { + + push @cron_vals, $1 .. $2 ; + next ; + } + +################## +################## +################## +# this is for normal cron entries with names like days of week and +# months. the name translation tables will be passed in or defaulted +# to american names. it needs work. +# +# also to be done is fancy entries like first thursday of month or +# weekend days, etc. it will be a filter to run when the numeric days +# of week or month days filter is run. +################## +################## +################## + +# if ( $convert_to_num && +# exists( $convert_to_num->{$cron_val} ) ) { + +# push @cron_vals, $convert_to_num->{$cron_val} ; +# next ; +# } + + TraceError "bad cron value '$cron_val'" ; + } + + if ( @cron_vals ) { + + my @cron_set ; + + @cron_set[@cron_vals] = (1) x @cron_vals ; + + $self->{"${set_name}_set"} = \@cron_set ; + } +} + + +sub cron_triggered { + + my $this_time = time() ; + + my %set_times ; + + TraceStatus scalar localtime( $this_time ) ; + +# get the current time part into a hash + + @set_times{ @set_names } = (localtime( $this_time ))[ 1, 2, 3, 4, 6 ] ; + +# one base the months + + $set_times{'months'}++ ; + + my( $set ) ; + +# loop over all the entries + + CRON: + foreach my $cron ( values %cron_entries ) { + +# loop over all the possible time sets + + foreach my $name ( @set_names ) { + +# my $s = $cron->{"${name}_set"} || [] ; +# print "C $name $set_times{ $name } @$s\n" ; + +# we don't trigger unless we have a set with data and the time slot +# for the current time is true + + next CRON if $set = $cron->{"${name}_set"} and + ! $set->[$set_times{ $name }] ; + } + +#print "C disp $cron\n" ; + +# we must have passed all the time filters, so send the message + + $cron->{'msg'}->dispatch() ; + } +} + +sub status_cmd { + +Dumper(\%cron_entries) ; + +} + +1 ; diff --git a/lib/Stem/DBI.pm b/lib/Stem/DBI.pm new file mode 100644 index 0000000..b4403ef --- /dev/null +++ b/lib/Stem/DBI.pm @@ -0,0 +1,400 @@ +# File: Stem/DBI.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::DBI ; + +use strict ; + +use DBI ; + +use base 'Stem::Cell' ; +use Stem::Route qw( :cell ) ; + + +my $attr_spec = [ + + { + 'name' => 'reg_name', + 'help' => < 'port', + 'help' => < 'host', + 'help' => < 'db_type', + 'required' => 1, + 'help' => < 'db_name', + 'required' => 1, + 'help' => < 'user_name', + 'env' => 'dbi_user_name', + 'help' => < 'password', + 'env' => 'dbi_password', + 'help' => < 'dsn_extras', + 'help' => < 'statements', + 'help' => < 'error_log', + 'help' => < 'default_return_type', + 'default' => 'list_of_hashes', + 'help' => < 'cell_attr', + 'class' => 'Stem::Cell', + 'help' => <{'statements'} eq 'ARRAY' ; + + if ( my $err = $self->db_connect() ) { + + return $err ; + } + + if ( my $err = $self->prepare() ) { + + return $err ; + } + + $self->cell_worker_ready() ; + + return $self ; +} + +sub db_connect { + + my ( $self ) = @_ ; + + my $db_type = $self->{'db_type'} ; + my $db_name = $self->{'db_name'} ; + my $host = $self->{'host'} ; + my $port = $self->{'port'} ; + my $user_name = $self->{'user_name'} ; + my $password = $self->{'password'} ; + my $extras = $self->{'dsn_extras'} ; + + my $dsn = "dbi:$db_type:$db_name" ; + $dsn .= ";host=$host" if defined $host ; + $dsn .= ";port=$port" if defined $port ; + $dsn .= ";$extras" if defined $extras ; + +#print "DSN [$dsn]\n" ; + my $dbh = DBI->connect( $dsn, $user_name, $password, + { 'PrintError' => 0, + 'FetchHashKeyName' => 'NAME_lc' } ) + or return DBI->errstr ; + + $self->{'dbh'} = $dbh ; + + return ; +} + + +sub prepare { + + my ( $self ) = @_ ; + + my %name2statement ; + + my $dbh = $self->{'dbh'} ; + + my $statements = $self->{'statements'} ; + + foreach my $statement ( @{$statements} ) { + + # Hey, this is ugly. I guess we need parameter type + # coercion ;) + $statement = { @{$statement} }; + my $name = $statement->{'name'} ; + + return "statement is missing a name" unless $name ; + + my $sql = $statement->{'sql'} ; + + return "statement '$name' is missing sql" unless defined $sql ; + + $statement->{'return_type'} ||= $self->{'default_return_type'}; + + unless ( $self->can( $statement->{'return_type'} ) ) { + + return + "No such return type for $name: $statement->{'return_type'}"; + } + + my $sth = $dbh->prepare( $sql ) + or return $dbh->errstr ; + + $statement->{'sth'} = $sth ; + + $name2statement{ $name } = $statement ; + } + + $self->{'name2statement'} = \%name2statement ; + + return ; +} + +sub execute_cmd { + + my( $self, $msg ) = @_ ; + +#print "EXEC\n" ; + +# why not tell the queue ready before we start this operation. since +# it blocks we will handle that new work until this is done. + + $self->cell_worker_ready() ; + + my $data = $msg->data() ; + + return $self->log_error( "No message data" ) + unless $data ; + return $self->log_error( "Message data is not a hash " ) + unless ref $data eq 'HASH' ; + + my $sth ; + my $statement ; + + if ( exists $data->{'sql'} ) { + + return "Must provide return type" unless exists $data->{'return_type'} ; + + $statement = $data->{'sql'} ; + + $sth = $self->{'dbh'}->prepare( $statement ) ; + + return $self->log_error( $self->{'dbh'}->errstr . "\n$statement" ) + if $self->{'dbh'}->errstr ; + } + else { + + $statement = $data->{'statement'} ; + + if ( my $in_cnt = $data->{'in_cnt'} ) { + + my $sql = $self->{'name2statement'}{$statement}{'sql'} ; + + my @qmarks = ('?') x $in_cnt ; + local( $" ) = ',' ; + $sql =~ s/IN\(\)/IN( @qmarks )/i ; + + $sth = $self->{'dbh'}->prepare( $sql ) ; + + return $self->log_error( + $self->{'dbh'}->errstr . "\n$statement" ) + if $self->{'dbh'}->errstr ; + } + else { + + $sth = $self->{'name2statement'}{$statement}{'sth'} ; + return $self->log_error( + "Unknown statement name: $statement" ) unless $sth ; + } + } + + + $self->{'statement'} = $statement ; + + my $bind = $data->{'bind'} || [] ; + return $self->log_error( "Statement arguments are not a list " ) + unless ref $bind eq 'ARRAY' ; + + my $dbh = $self->{'dbh'} ; + + my $return_type = $data->{'return_type'} || + $self->{'name2statement'}{$statement}{'return_type'} ; + + unless ( $self->can( $return_type ) ) { + + return $self->log_error( + "No such return type: $data->{'return_type'}" ) ; + } + + my $dbi_result = $self->$return_type( $sth, $bind ) ; + + if ( $dbi_result && ! ref $dbi_result ) { + + return( $self->log_error( "[$statement] $dbi_result" ) ) ; + } + + return $dbi_result ; +} + +sub list_of_hashes { + + return shift->_fetch( 'fetchall_arrayref', @_, {} ); +} + +sub list_of_arrays { + + return shift->_fetch( 'fetchall_arrayref', @_, [] ); +} + +sub one_hashref { + + return shift->_fetch( 'fetchrow_hashref', @_ ); +} + +sub column_as_array { + + my( $self, $sth, $bind ) = @_; + + my @column; + + $sth->finish if $sth->{'Active'} ; + + $sth->execute( @{$bind} ) or return $sth->errstr ; + + while ( my @row = $sth->fetchrow_array ) { + + push @column, $row[0]; + } + + return $sth->errstr() if $sth->errstr() ; + + return \@column; +} + +sub _fetch { + + my( $self, $method, $sth, $bind, @args ) = @_ ; + + $sth->finish if $sth->{'Active'} ; + + $sth->execute( @{$bind} ) or return $sth->errstr ; + + my $data = $sth->$method( @args ) ; + + return $sth->errstr if $sth->errstr ; + + return $data ; +} + +sub rows_affected { + + my( $self, $sth, $bind ) = @_; + + $sth->execute( @{$bind} ); + + return $sth->errstr if $sth->errstr ; + + return { 'rows' => $sth->rows }; +} + +sub insert_id { + + my( $self, $sth, $bind ) = @_; + + my $err = $sth->execute( @{$bind} ); + + return $sth->errstr if $sth->errstr ; + +#print "ID: [$self->{'dbh'}{'mysql_insertid'}]\n" ; + + return { 'insert_id' => $self->{'dbh'}{'mysql_insertid'} } ; +} + +sub log_error { + + my ( $self, $err ) = @_; + + my $log = $self->{'error_log'} ; + + return $err unless $log ; + + Stem::Log::Entry->new ( + 'logs' => $log, + 'level' => 5, + 'label' => 'Stem::DBI', + 'text' => "Statement: $self->{'statement'} - $err\n", + ) ; + + return \$err ; +} + +1 ; diff --git a/lib/Stem/Debug.pm b/lib/Stem/Debug.pm new file mode 100644 index 0000000..6178fcb --- /dev/null +++ b/lib/Stem/Debug.pm @@ -0,0 +1,95 @@ +#!/usr/local/bin/perl + +package Stem::Debug ; + +use strict ; +use Data::Dumper ; +use Scalar::Util qw( openhandle ) ; + +use base 'Exporter' ; +our @EXPORT_OK = qw ( dump_data dump_socket dump_owner ) ; + +sub dump_data { + + my( $data ) = @_ ; + + local $Data::Dumper::Sortkeys = \&dump_filter ; + + return Dumper $data ; +} + +sub dump_filter { + + my( $href ) = @_ ; + + my @keys ; + + my %fh_dumps ; + + while( my( $key, $val ) = each %{$href} ) { + + if( my $fh_val = dump_socket( $val ) ) { + + my $fh_key = "$key.FH" ; + $fh_dumps{$fh_key} = $fh_val ; + push @keys, $fh_key ; + next ; + } + + push @keys, $key ; + } + + @{$href}{ keys %{fh_dumps} } = values %{fh_dumps} ; + +#print "KEYS [@keys]\n" ; + + return [ sort @keys ] ; +} + +sub dump_socket { + + my ( $sock ) = @_ ; + + return 'UNDEF' unless defined $sock ; + return 'EMPTY' unless $sock ; + return 'NOT REF' unless ref $sock ; + + return 'NOT GLOB' unless $sock =~ /GLOB/ ; + +warn "SOCK [$sock]\n" ; + + my $fdnum = fileno( $sock ) ; + + return 'NO FD' unless defined $fdnum ; + + my $opened = openhandle( $sock ) ? 'OPEN' : 'CLOSED' ; + +# return "CLOSED $sock" if $opened eq 'CLOSED' ; + +# $fdnum = 'NONE' unless defined $fdnum ; + +# my $fdnum = "FOO" ; + +# return "FD [$fdnum]" unless $sock->isa('IO::Socket') ; + + return "FD [$fdnum] *$opened* $sock" ; +} + + + +sub dump_owner { + + my ( $owner ) = @_ ; + + my $owner_dump = "$owner" ; + + while( $owner->{object} ) { + + $owner = $owner->{object} ; + $owner_dump .= " -> $owner " ; + } + + return $owner_dump ; +} + +1 ; diff --git a/lib/Stem/Demo/CLI.pm b/lib/Stem/Demo/CLI.pm new file mode 100644 index 0000000..e5f339c --- /dev/null +++ b/lib/Stem/Demo/CLI.pm @@ -0,0 +1,171 @@ +package Stem::Demo::CLI ; + +print "LOAD\n" ; + +use strict; + +use base 'Stem::Cell' ; + +my $attr_spec = [ + { + name => 'reg_name', + help => < 'cell_attr', + class => 'Stem::Cell', + help => <cell_activate; + +#print $self->SUPER::_dump( "CLI TRIGGERED\n" ) ; + + return; +} + +my %op_to_code = ( + + set => \&_set, + get => \&_get, + dump => \&_dump, + clear => \&_clear, + help => \&_help, +) ; + +sub data_in { + + my( $self, $msg ) = @_; + +#print $msg->dump( 'IN' ) ; + + $self->{data_in_msg} = $msg ; + + my $data = $msg->data() ; + + my $op = $data->{op} ; + + if( my $code = $op_to_code{ $op } ) { + + $self->$code( $data ) ; + } + else { + + $self->send_reply( "unknown CLI op '$op'" ) ; + } +} + +sub send_reply { + + my ( $self, $data ) = @_; + + my $in_msg = delete $self->{data_in_msg} ; + + my $reply_msg = $in_msg->reply( type => 'data', data => $data ) ; + +#print $reply_msg->dump( 'REPLY' ) ; + + $reply_msg->dispatch() ; +} + +sub _set { + + my( $self, $data ) = @_; + + my $key = $data->{key} ; + if ( defined( $key ) ) { + + my $value = $data->{value} ; + + $self->{data}{$key} = $value ; + + $self->send_reply( "set '$key' to '$value'" ) ; + } + else { + $self->send_reply( "set is missing a key" ) ; + } +} + +sub _get { + + my( $self, $data ) = @_; + + my $key = $data->{key} ; + if ( defined( $key ) ) { + + my $value = $self->{data}{$key} ; + + $self->send_reply( "'$key' was set to '$value'" ) ; + } + else { + $self->send_reply( "get is missing a key" ) ; + } +} + +sub _clear { + + my( $self ) = @_; + + $self->{data} = {} ; + $self->send_reply( "cleared your data" ) ; +} + +sub _dump { + + my( $self ) = @_; + + my $text = join '', map "\t$_ => $self->{data}{$_}\n", + sort keys %{$self->{data}} ; + + $self->send_reply( "your data is:\n$text\n" ) ; +} + +sub _help { + + my( $self ) = @_; + + my $text = < +get +dump +clear +help + +set sets a value in the CLI session hash +get gets a value in the CLI session hash +dump returns a dump of the session hash +clear will empty the the session hash +help prints this text + +TEXT + + $self->send_reply( $text ) ; +} + + +1 ; diff --git a/lib/Stem/Demo/World.pm b/lib/Stem/Demo/World.pm new file mode 100644 index 0000000..bf5b3b3 --- /dev/null +++ b/lib/Stem/Demo/World.pm @@ -0,0 +1,3 @@ +package Stem::Demo::World ; +sub hello_cmd { return "Hello World!\n" } +1 ; diff --git a/lib/Stem/Event.pm b/lib/Stem/Event.pm new file mode 100644 index 0000000..bd80857 --- /dev/null +++ b/lib/Stem/Event.pm @@ -0,0 +1,923 @@ +# File: Stem/Event.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +# this is the base class for all of the other event classes. it +# provides common services and also stubs for the internal _methods so +# the other classes don't need to declare them if they don't use them. + +package Stem::Event ; + +use Stem::Class ; + +use strict ; + +# this will hold the hashes of events for each event type. + +my %all_events = ( + + plain => {}, + signal => {}, + timer => {}, + read => {}, + write => {}, +) ; + +# table of loop types to the Stem::Event::* class name + +my %loop_to_class = ( + + event => 'EventPM', + perl => 'Perl', + tk => 'Tk', + wx => 'Wx', +# gtk => 'Gtk', +# qt => 'Qt', +) ; + +# use the requested event loop and default to perl on windows and +# event.pm elsewhere. + +my $loop_class = _get_loop_class() ; + +init_loop() ; + + +sub init_loop { + + $loop_class->_init_loop() ; + +Stem::Event::Queue::_init_queue() if defined &Stem::Event::Queue::_init_queue ; + +} + +sub start_loop { + + $loop_class->_start_loop() ; +} + +sub stop_loop { + + $loop_class->_stop_loop() ; +} + +sub trigger { + + my( $self, $method ) = @_ ; + +# never trigger inactive events + + return unless $self->{active} ; + + + $method ||= $self->{'method'} ; +#print "METHOD [$method]\n" ; + + $self->{'object'}->$method( $self->{'id'} ) ; + + Stem::Msg::process_queue() if defined &Stem::Msg::process_queue; + + return ; +} + +################# +# all the stuff below is a rough cell call trace thing. it needs work +# it would be put inside the trigger method +# 'log_type' attribute is set or the event type is used. +#_init subs need to set event_log_type in the object +#use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +#use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; +# $log_type = $self->{'log_type'} || $self->{'event_type'} ; +# TraceStatus "[$log_type] [$object] [$method]\n" ; +# $Stem::Event::current_object = $object ; +# my ( $cell_name, $target ) = Stem::Route::lookup_cell_name( $object ) ; +# if ( $cell_name ) { +# # Debug +# # "EVENT $event to $cell_name:$target [$object] [$method]\n" ; +# } +# else { +# # Debug "EVENT $event to [$object] [$method]\n" ; +# } +################# + + +# get all the event objects for an event type +# this is a class sub. + +sub _get_events { + + my( $event_type ) = @_ ; + + my $events = $all_events{ $event_type } ; + + return unless $events ; + + return values %{$events} if wantarray ; + + return $events ; +} + +# initialize the subclass object for this event and store generic event +# info. + +sub _build_core_event { + +#print "BAZ\n" ; + + my( $self, $event_type ) = @_ ; + + +#print "EVT [$self] [$event_type]\n" ; + +# call and and check the return of the core event constructor + + if ( my $core_event = $self->_build() ) { + +# return the error if it was an error string + + return $core_event unless ref $core_event ; + +# save the core event + + $self->{core_event} = $core_event ; + } + +# mark the event type and track it + + $self->{event_type} = $event_type ; + $all_events{ $event_type }{ $self } = $self ; + + return ; +} + +# these are the public versions of the support methods. +# subclasses can provide a _method to override the stub ones in this class. + +sub cancel { + + my( $self ) = @_ ; + + $self->{'active'} = 0 ; + delete $self->{'object'} ; + +# delete the core object + + if ( my $core_event = delete $self->{core_event} ) { + + # call the core cancel + + $self->_cancel( $core_event ) ; + } + +# delete this event from the tracking hash + + delete $all_events{ $self->{event_type} }{ $self } ; + + return ; +} + +sub start { + my( $self ) = @_ ; + + $self->{'active'} = 1 ; + $self->_start( $self->{core_event} ) ; + + return ; +} + +sub stop { + my( $self ) = @_ ; + + $self->{'active'} = 0 ; + $self->_stop( $self->{core_event} ) ; + + return ; +} + +# stubs for the internal methods that subclasses should override if needed. + +sub _init_loop {} +sub _build {} +sub _start {} +sub _stop {} +sub _reset {} +sub _cancel {} + +use Stem::Debug qw( dump_socket dump_owner dump_data ) ; + +sub dump_events { + + print dump_data( \%all_events ) ; +} + +sub dump { + + my( $self ) = @_ ; + + my $event_text = <{'active'} +TEXT + + my $obj_dump = dump_owner $self->{'object'} ; + $event_text .= <{'method'} +TEXT + + if ( my $fh = $self->{'fh'} ) { + + my $fh_text = dump_socket( $self->{'fh'} ) ; + $event_text .= <{event_type} eq 'timer' ) { + + my $delay = $self->{delay} || 'NONE' ; + my $interval = $self->{interval} || 'NONE' ; + $event_text .= <{'io_timer_event'} ) { + + $event_text = "IO TIMER: >>>>>\n" . $io_timer_event->dump() . + "END\n"; + } + + return <>> +$event_text<<< + +DUMP + +} + +############# +# change this to a cleaner loop style which can handle more event loops and +# try them in sequence +############# + +sub _get_loop_class { + + my $loop_type = $Stem::Vars::Env{ 'event_loop' } || + ($^O =~ /win32/i ? 'perl' : 'event' ); + + $loop_type = 'perl' unless $loop_to_class{ $loop_type } ; + my $loop_class = "Stem::Event::$loop_to_class{ $loop_type }" ; + + unless ( eval "require $loop_class" ) { + die "can't load $loop_class: $@" if $@ && $@ !~ /locate/ ; + + $loop_type = 'perl' ; + eval { require Stem::Event::Perl } ; + die "can't load event loop Stem::Event::Perl $@" if $@ ; + } + + # save the event loop that we loaded. + + #print "using event loop [$loop_type]\n" ; + $Stem::Vars::Env{ 'event_loop' } = $loop_type ; + + return $loop_class ; +} + + +############################################################################ + +package Stem::Event::Plain ; + +BEGIN { + @Stem::Event::Plain::ISA = qw( Stem::Event ) ; +} + +=head2 Stem::Event::Plain::new + +This class creates an event that will trigger a callback after all +other pending events have been triggered. + +=head2 Example + + $plain_event = Stem::Event::Plain->new( 'object' => $self ) ; + +=cut + +my $attr_spec_plain = [ + + { + 'name' => 'object', + 'required' => 1, + 'type' => 'object', + 'help' => < 'method', + 'default' => 'triggered', + 'help' => < 'id', + 'help' => <_core_event_build( 'plain' ) ; + return $err if $err ; + + return $self ; +} + +############################################################################ + +package Stem::Event::Signal ; + +BEGIN { our @ISA = qw( Stem::Event ) } ; + +=head2 Stem::Event::Signal::new + +This class creates an event that will trigger a callback whenever +its its signal has been received. + +=head2 Example + + $signal_event = Stem::Event::Signal->new( 'object' => $self, + 'signal' => 'INT' ) ; + + sub sig_int_handler { die "SIGINT\n" } + +=cut + +my $attr_spec_signal = [ + + { + 'name' => 'object', + 'required' => 1, + 'type' => 'object', + 'help' => < 'method', + 'help' => < 'signal', + 'required' => 1, + 'help' => < 'active', + 'default' => 1, + 'type' => 'boolean', + 'help' => < 'id', + 'help' => <{'signal'} ; + + return "Unknown signal: $signal" unless exists $SIG{ $signal } ; + + $self->{'method'} ||= "sig_\L${signal}_handler" ; + $self->{'signal'} = $signal ; + + my $err = $self->_build_core_event( 'signal' ) ; + return $err if $err ; + +#print "SELF SIG $self\nPID $$\n" ; + + return $self ; +} + + +############################################################################ + +package Stem::Event::Timer ; + +BEGIN { our @ISA = qw( Stem::Event ) } ; + +=head2 Stem::Event::Timer::new + +This class creates an event that will trigger a callback after a time +period has elapsed. The initial timer delay is set from the 'delay', +'at' or 'interval' attributes in that order. If the 'interval' +attribute is not set, the timer will cancel itself after its first +triggering (it is a one-shot). The 'hard' attribute means that the +next interval delay starts before the callback to the object is +made. If a soft timer is selected (hard is 0), the delay starts after +the callback returns. So the hard timer ignores the time taken by the +callback and so it is a more accurate timer. The accuracy a soft timer +is affected by how much time the callback takes. + +=head2 Example + + $timer_event = Stem::Event::Timer->new( 'object' => $self, + 'delay' => 5, + 'interval' => 10 ) ; + + sub timed_out { print "timer alert\n" } ; + + +=cut + +BEGIN { + +my $attr_spec_timer = [ + + { + 'name' => 'object', + 'required' => 1, + 'type' => 'object', + 'help' => < 'method', + 'default' => 'timed_out', + 'help' => < 'delay', + 'help' => < 'interval', + 'help' => < 'at', + 'help' => < 'hard', + 'type' => 'boolean', + 'default' => 0, + 'help' => < 'active', + 'default' => 1, + 'type' => 'boolean', + 'help' => < 'id', + 'help' => <{ 'delay' } ) ? + $self->{ 'delay' } : + exists( $self->{ 'at' } ) ? + $self->{ 'at' } - time() : + $self->{'interval'} ; + +#print "INT $self->{'interval'} DELAY $delay\n" ; + +# squawk if no delay value + + return "No initial delay was specified for timer" + unless defined $delay ; + + $self->{'delay'} = $delay ; + $self->{'time_left'} = $delay ; + + my $err = $self->_build_core_event( 'timer' ) ; + return $err if $err ; + +########## +# check on this logic +######### + + $self->_stop unless $self->{'active'} ; + + return $self ; +} + +} + +sub reset { + + my( $self, $reset_delay ) = @_ ; + + return unless $self->{'active'} ; + +# if we don't get passed a delay, use the interval or the delay attribute + + $reset_delay ||= ($self->{'interval'}) ? + $self->{'interval'} : $self->{'delay'} ; + +# track the new delay and reset the real timer (if we are using one) + + $self->{'time_left'} = $reset_delay ; + + $self->_reset( $self->{core_event}, $reset_delay ) ; + + return ; +} + +sub timer_triggered { + + my( $self ) = @_ ; + +#print time(), " TIMER TRIG\n" ; +#use Carp qw( cluck ) ; +#cluck ; + +# check if this is a one-shot timer + + $self->cancel() unless $self->{'interval'} ; + +# reset the timer count before the trigger code for hard timers +#(trigger on fixed intervals) + + $self->reset( $self->{'interval'} ) if $self->{'hard'}; + + $self->trigger() ; + +# reset the timer count before the trigger code for soft timers +#(trigger on at least fixed intervals) + + $self->reset( $self->{'interval'} ) unless $self->{'hard'}; +} + +############################################################################ + +#################################################################### +# common methods for the Read/Write event classes to handle the optional +# I/O timeouts. +# these override Stem::Event's methods and then call those via SUPER:: + +package Stem::Event::IO ; + +BEGIN { our @ISA = qw( Stem::Event ) } ; + +sub init_io_timeout { + + my( $self ) = @_ ; + + my $timeout = $self->{'timeout'} ; + return unless $timeout ; + + $self->{'io_timer_event'} = Stem::Event::Timer->new( + 'object' => $self, + 'interval' => $timeout, + ) ; + + return ; +} + +sub cancel { + + my( $self ) = @_ ; + +#print "IO CANCEL $self\n" ; + + if ( my $io_timer_event = delete $self->{'io_timer_event'} ) { + $io_timer_event->cancel() ; + } + + $self->SUPER::cancel() ; + + delete $self->{'fh'} ; + + return ; +} + +sub start { + + my( $self ) = @_ ; + + if ( my $io_timer_event = $self->{'io_timer_event'} ) { + $io_timer_event->start() ; + } + + $self->SUPER::start() ; + + return ; +} + +sub stop { + + my( $self ) = @_ ; + + $self->{'active'} = 0 ; + + if ( my $io_timer_event = $self->{'io_timer_event'} ) { + $io_timer_event->stop() ; + } + + $self->SUPER::stop() ; + + return ; +} + +sub timed_out { + + my( $self ) = @_ ; + +# $self->{log_type} = "$self->{'event_type'}_timeout" ; + $self->trigger( $self->{'timeout_method'} ) ; +} + +####################################################### + +package Stem::Event::Read ; + +BEGIN { our @ISA = qw( Stem::Event::IO ) } + +=head2 Stem::Event::Read::new + +This class creates an event that will trigger a callback whenever +its file descriptor has data to be read. It takes an optional timeout +value which will trigger a callback to the object if no data has been +read during that period. + +Read events are active when created - a call to the stop method is +needed to deactivate them. + +=cut + +BEGIN { + +my $attr_spec_read = [ + + { + 'name' => 'object', + 'required' => 1, + 'type' => 'object', + 'help' => < 'fh', + 'required' => 1, + 'type' => 'handle', + 'help' => < 'timeout', + 'help' => < 'method', + 'default' => 'readable', + 'help' => < 'timeout_method', + 'default' => 'read_timeout', + 'help' => < 'active', + 'default' => 1, + 'type' => 'boolean', + 'help' => < 'id', + 'help' => <{fh} ; +# Stem::Event::Read: $self->{fh} is not an open handle +# ERR + + my $err = $self->_build_core_event( 'read' ) ; + return $err if $err ; + + $self->init_io_timeout() ; + + return $self ; +} + +} +############################################################################ + +package Stem::Event::Write ; + +BEGIN { our @ISA = qw( Stem::Event::IO ) } ; + +=head2 Stem::Event::Write::new + +This class creates an event that will trigger a callback whenever +its file descriptor can be written to. It takes an optional timeout +value which will trigger a callback to the object if no data has been +written during that period. + +Write events are stopped when created - a call to the start method is +needed to activate them. + +=cut + +my $attr_spec_write = [ + + { + 'name' => 'object', + 'required' => 1, + 'type' => 'object', + 'help' => < 'fh', + 'required' => 1, + 'type' => 'handle', + 'help' => < 'timeout', + 'help' => < 'method', + 'default' => 'writeable', + 'help' => < 'timeout_method', + 'default' => 'write_timeout', + 'help' => < 'active', + 'default' => 0, + 'type' => 'boolean', + 'help' => < 'id', + 'help' => <_build_core_event( 'write' ) ; + return $err if $err ; + +#print $self->dump_events() ; + + $self->init_io_timeout() ; + + $self->stop() unless $self->{'active'} ; + +#print $self->dump() ; + + return $self ; +} + +1 ; diff --git a/lib/Stem/Event/EventPM.pm b/lib/Stem/Event/EventPM.pm new file mode 100644 index 0000000..425126d --- /dev/null +++ b/lib/Stem/Event/EventPM.pm @@ -0,0 +1,242 @@ +# File: Stem/Event/EventPM.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +#print "required" ; + +=head1 Stem::Event::EventPM + +This module wraps the CPAN module Event.pm for use by the rest of +Stem. It provides the common API for the standard Stem::Event classes: + +=over 4 + +=item Stem::Event +=item Stem::Event::Plain +=item Stem::Event::Timer +=item Stem::Event::Signal +=item Stem::Event::Read +=item Stem::Event::Write + +=back + +=cut + +package Stem::Event::EventPM ; + +use strict ; +use Event ; + +@Stem::Event::EventPM::ISA = qw( Stem::Event ) ; + +# basic wrappers for top level Event.pm calls. + +sub _start_loop { + $Event::DIED = \&_died ; + Event::loop() ; +} + +sub _died { + my( $event, $err ) = @_ ; + use Carp; + Carp::cluck( "Stem::Event died: $err", "die called in [$event]\n", + map( "<$_>", caller() ), "\n" ) ; + + exit; +} ; + + +sub _stop_loop { + Event::unloop_all( 1 ) ; +} + +############################################################################ + +package Stem::Event::Plain ; + +sub _build { + + my( $self ) = @_ ; + +# create the plain event watcher + + $self->{'idle_event'} = Event->idle( + 'cb' => [ $self, 'idle_triggered' ], + 'repeat' => 0 + ) ; + + return $self ; +} + +sub idle_triggered { + + my( $self ) = @_ ; + + $self->trigger( 'plain' ) ; + my $idle_event = delete $self->{'idle_event'} ; + $idle_event->cancel() ; +} + +############################################################################ + +package Stem::Event::Signal ; + +sub _build { + + my( $self ) = @_ ; + + my $signal = $self->{'signal'} ; + +# create the signal event watcher + + return Event->signal( + 'cb' => sub { $self->trigger() }, + 'signal' => $signal, + ) ; +} + +sub _cancel { + my( $self, $signal_event ) = @_ ; + $signal_event->cancel() ; + return ; +} + +############################################################################ + +package Stem::Event::Timer ; + +sub _build { + + my( $self ) = @_ ; + + return Event->timer( + 'cb' => [ $self, 'timer_triggered' ], + 'hard' => $self->{'hard'}, + 'after' => $self->{'delay'}, + 'interval' => $self->{'interval'}, + ) ; +} + +sub _reset { + my( $self, $timer_event, $delay ) = @_ ; + $timer_event->again() ; + return ; +} + +sub _cancel { + my( $self, $timer_event ) = @_ ; + $timer_event->cancel() ; + return ; +} + +sub _start { + my( $self, $timer_event ) = @_ ; + $timer_event->start() ; + return ; +} + +sub _stop { + my( $self, $timer_event ) = @_ ; + $timer_event->stop() ; + return ; +} + +############################################################################ + +package Stem::Event::Read ; + +sub _build { + + my( $self ) = @_ ; + +# create the read event watcher + + return Event->io( + 'cb' => sub { $self->trigger() }, + 'fd' => $self->{'fh'}, + 'poll' => 'r', + ) ; +} + +sub _cancel { + my( $self, $read_event ) = @_ ; + $read_event->cancel() ; + return ; +} + +sub _start { + my( $self, $read_event ) = @_ ; + $read_event->start() ; + return ; +} + +sub _stop { + my( $self, $read_event ) = @_ ; + $read_event->stop() ; + return ; +} + +############################################################################ + +package Stem::Event::Write ; + +sub _build { + + my( $self ) = @_ ; + +# create the write event watcher + +# create the read event watcher + + return Event->io( + 'cb' => sub { $self->trigger() }, + 'fd' => $self->{'fh'}, + 'poll' => 'w', + ) ; + + return $self ; +} + +sub _cancel { + my( $self, $write_event ) = @_ ; + $write_event->cancel() ; + return ; +} + +sub _start { + my( $self, $write_event ) = @_ ; + $write_event->start() ; + return ; +} + +sub _stop { + my( $self, $write_event ) = @_ ; + $write_event->stop() ; + return ; +} + +1 ; diff --git a/lib/Stem/Event/Perl.pm b/lib/Stem/Event/Perl.pm new file mode 100644 index 0000000..f614da7 --- /dev/null +++ b/lib/Stem/Event/Perl.pm @@ -0,0 +1,217 @@ +# File: Stem/Event/Perl.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +=head1 Stem::Event::Perl + +This module is a pure Perl event loop. It requires Perl 5.8 (or +better) which has safe signal handling. It provides the common event +API for the standard classes: + +=cut + +package Stem::Event::Perl ; + +use strict ; +use Stem::Event::Signal ; + +@Stem::Event::Perl::ISA = qw( Stem::Event ) ; + +BEGIN { + + unless ( eval { require Time::HiRes } ) { + + Time::HiRes->import( qw( time ) ) ; + } +} + +# get the hashes for each of the event types + +my ( $signal_events, $timer_events, $read_events, $write_events ) = + map scalar( Stem::Event::_get_events( $_ )), qw( signal timer + read write ) ; + +sub _start_loop { + +#print "PERL START\n" ; + + while( keys %{$timer_events} || + keys %{$signal_events} || + keys %{$read_events} || + keys %{$write_events} ) { + + my $timeout = find_min_delay() ; + +#print "TIMEOUT [$timeout]\n" ; + + my $time = time() ; + + _one_time_loop( $timeout ) ; + + my $delta_time = time() - $time ; + trigger_timer_events( $delta_time ) ; + } +} + +sub _one_time_loop { + + my( $timeout ) = @_ ; + +# force a no wait select call if no timeout was passed in + + $timeout ||= 0 ; + +#print "ONE TIME $timeout\n" ; +# use Carp qw( cluck ) ; +# cluck ; + +# print "\n\n********EVENT LOOP\n\n" ; +# print "READ EVENTS\n", map $_->dump(), values %{$read_events} ; +# print "WRITE EVENTS\n", map $_->dump(), values %{$write_events} ; + + my $read_vec = make_select_vec( $read_events ) ; + my $write_vec = make_select_vec( $write_events ) ; + +#print "R BEFORE ", unpack( 'b*', $read_vec), "\n" ; +#print "W BEFORE ", unpack( 'b*', $write_vec), "\n" ; + + + my $cnt = select( $read_vec, $write_vec, undef, $timeout ) ; + +#print "SEL CNT [$cnt]\n" ; +#print "R AFTER ", unpack( 'b*', $read_vec), "\n" ; +#print "W AFTER ", unpack( 'b*', $write_vec), "\n" ; + + trigger_select_vec( 'read', $read_events, $read_vec ) ; + trigger_select_vec( 'write', $write_events, $write_vec, ) ; + +#print "\n\n********END EVENT LOOP\n\n" ; + +} + +sub _stop_loop { + + $_->cancel() for values %{$signal_events}, + values %{$timer_events}, + values %{$read_events}, + values %{$write_events} ; +} + +sub find_min_delay { + + my $min_delay = 0 ; + + while( my( undef, $event ) = each %{$timer_events} ) { + + if ( $event->{'time_left'} < $min_delay || $min_delay == 0 ) { + + $min_delay = $event->{'time_left'} ; + +#print "MIN [$min_delay]\n" ; + } + } + + return unless $min_delay ; + + return $min_delay ; +} + +sub trigger_timer_events { + + my( $delta ) = @_ ; + +#print "TIMER DELTA $delta\n" ; + + while( my( undef, $event ) = each %{$timer_events} ) { + +#print $event->dump() ; + + next unless $event->{'active'} ; + + next unless ( $event->{'time_left'} -= $delta ) <= 0 ; + + $event->timer_triggered() ; + } +} + +sub make_select_vec { + + my( $io_events ) = @_ ; + + my $select_vec = '' ; + + while( my( undef, $event ) = each %{$io_events} ) { + +#print "make F: [", fileno $event->{'fh'}, "] ACT [$event->{'active'}]\n" ; + + unless ( defined fileno $event->{'fh'} ) { + +#print "BAD FH $event->{'fh'}\n" ; +print "\n\n***EVENT BAD FH\n", $event->dump() ; + + $event->cancel() ; + } + + next unless $event->{'active'} ; + vec( $select_vec, fileno $event->{'fh'}, 1 ) = 1 ; + } + + return $select_vec ; +} + +sub trigger_select_vec { + + my( $event_type, $io_events, $select_vec ) = @_ ; + + while( my( undef, $event ) = each %{$io_events} ) { + + next unless $event->{'active'} ; + if ( vec( $select_vec, fileno $event->{'fh'}, 1 ) ) { + + $event->trigger() ; + } + } + + return ; +} + +############################################################################ + +package Stem::Event::Plain ; + +###### +# right now we trigger plain events when they are created. this should +# change to a queue and trigger after i/o and timer events +###### + +sub _build { + my( $self ) = @_ ; + $self->trigger() ; + return ; +} + +1 ; diff --git a/lib/Stem/Event/Queue.pm b/lib/Stem/Event/Queue.pm new file mode 100644 index 0000000..e6549d9 --- /dev/null +++ b/lib/Stem/Event/Queue.pm @@ -0,0 +1,110 @@ +# File: Stem/Event/Queue.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +# this class provides a way to deliver certain events and messages +# synchronously with the main event loop. this is done by queueing the +# actual event/message and writing a byte down a special pipe used +# only inside this process. the other side of the pipe has a read +# event that when triggered will then deliver the queued +# events/messages. + +# when using Stem::Event::Signal you need to use this module as +# well. perl signals will be delivered (safely) between perl +# operations but they could then be delivered inside an executing +# event handler and that means possible corruption. so this module +# allows those signal events to be delivered by the event loop itself. + + +package Stem::Event::Queue ; + +use strict ; +use warnings ; + +use Socket; +use IO::Handle ; + +use base 'Exporter' ; +our @EXPORT = qw( &mark_not_empty ) ; + +my( $queue_read, $queue_write, $queue_read_event ) ; + +my $self ; + +sub _init_queue { + + socketpair( $queue_read, $queue_write, + AF_UNIX, SOCK_STREAM, PF_UNSPEC ) || die <blocking( 0 ) ; + $queue_read_event = Stem::Event::Read->new( + 'object' => $self, + 'fh' => $queue_read, + ) ; + + ref $queue_read_event or die <{'signal'} ; + + $self->{'method'} ||= "sig_\L${signal}_handler" ; + +# create the signal event handler and cache it. +# we cache them so we can reuse these closures and never leak + + $SIG{ $signal } = $cached_handlers{$signal} ||= + sub { + mark_not_empty() ; +#print "HIT $signal\n"; + push @signal_queue, $signal + } ; + +# track the event object for this signal + + $signal2event{$signal} = $self ; + +#print "$signal = $SIG{ $signal }\n" ; + return ; +} + +sub _cancel { + + my( $self ) = @_ ; + + $SIG{ $self->{'signal'} } = 'DEFAULT' ; + + return ; +} + +sub process_signal_queue { + + my $sig_count = @signal_queue ; + +#print "PROCESS SIGNAL Q $sig_count\n" ; + +# return if we have no pending signals + + return $sig_count unless $sig_count ; + + while( my $signal = shift @signal_queue ) { + + my $event = $signal2event{ $signal } ; + + next unless $event ; + next unless $event->{'active'} ; + + $event->trigger() ; + } + + return $sig_count ; +} + +1 ; diff --git a/lib/Stem/Event/Tk.pm b/lib/Stem/Event/Tk.pm new file mode 100644 index 0000000..5b174d7 --- /dev/null +++ b/lib/Stem/Event/Tk.pm @@ -0,0 +1,210 @@ +# File: Stem/Event/Tk.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +=head1 Stem::Event::Tk + +This module wraps the CPAN module Event.pm for use by the rest of +Stem. It provides the common API for the standard Stem::Event classes: + +=over 4 + +=item Stem::Event +=item Stem::Event::Plain +=item Stem::Event::Timer +=item Stem::Event::Signal +=item Stem::Event::Read +=item Stem::Event::Write + +=back + +=cut + +package Stem::Event::Tk ; + +use strict ; +use Tk ; + +use Stem::Event::Signal ; + +my $tk_main_window ; + +# basic wrappers for top level Tk.pm calls. + +sub _init_loop { + + $tk_main_window ||= MainWindow->new() ; + $tk_main_window->withdraw() ; +} + +sub _start_loop { + _init_loop() ; + MainLoop() ; +} + +sub _stop_loop { + +#print "STOP INFO ", $tk_main_window->afterInfo(), "\n" ; + + $tk_main_window->destroy() ; + $tk_main_window = undef ; +} + +############################################################################ + +package Stem::Event::Plain ; + +sub _build { + + my( $self ) = @_ ; + +# create the plain event watcher + + $self->{'idle_event'} = Event->idle( + 'cb' => [ $self, 'idle_triggered' ], + 'repeat' => 0 + ) ; + + return $self ; +} + +sub idle_triggered { + + my( $self ) = @_ ; + + $self->trigger() ; + my $idle_event = delete $self->{'idle_event'} ; + $idle_event->cancel() ; +} + +############################################################################ + +package Stem::Event::Timer ; + +sub _build { + + my( $self ) = @_ ; + +Stem::Event::Tk::_init_loop() ; + +# tk times in milliseconds and stem times in floating seconds so +# we convert to integer ms. + + my $delay_ms = int( $self->{'delay'} * 1000 ) ; + +# $self->{interval_ms} = int( ( $self->{'interval'} || 0 ) * 1000 ) ; + + my $timer_method = $self->{'interval'} ? 'repeat' : 'after' ; + + return $tk_main_window->$timer_method( + $delay_ms, + [$self => 'timer_triggered'] + ) ; +} + +sub _reset { + + my( $self, $timer_event, $delay ) = @_ ; + my $delay_ms = int( $delay * 1000 ) ; + $timer_event->time( $delay_ms ) ; +} + +sub _cancel { + my( $self, $timer_event ) = @_ ; + $timer_event->cancel() ; + return ; +} + +############################################################################ + +package Stem::Event::Read ; + +sub _build { + + my( $self ) = @_ ; + goto &_start if $self->{active} ; + return ; +} + +sub _start { + + my( $self ) = @_ ; + + return $tk_main_window->fileevent( + $self->{'fh'}, + 'readable', + [$self => 'trigger'] + ) ; +} + +sub _cancel { goto &_stop } + +sub _stop { + my( $self ) = @_ ; + + $tk_main_window->fileevent( + $self->{'fh'}, + 'readable', + '' + ) ; +} + +############################################################################ + +package Stem::Event::Write ; + +sub _build { + my( $self ) = @_ ; + goto &_start if $self->{active} ; + return ; +} + +sub _start { + + my( $self ) = @_ ; + + return $tk_main_window->fileevent( + $self->{'fh'}, + 'writable', + [$self => 'trigger'] + ) ; +} + +sub _cancel { goto &_stop } + +sub _stop { + + my( $self ) = @_ ; + + $tk_main_window->fileevent( + $self->{'fh'}, + 'writable', + '' + ) ; +} + +1 ; diff --git a/lib/Stem/Event/Wx.pm b/lib/Stem/Event/Wx.pm new file mode 100644 index 0000000..c71bcfe --- /dev/null +++ b/lib/Stem/Event/Wx.pm @@ -0,0 +1,148 @@ +# File: Stem/Event/Wx.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +=head1 Stem::Event::Wx + +This module is a pure Perl event loop. It requires Perl 5.8 (or +better) which has safe signal handling. It provides the common event +API for the standard classes: + +=cut + +package Stem::Event::Wx ; + +use strict ; + +use base qw( Stem::Event ) ; +use Stem::Event::Perl ; +use Wx ; + +my $app = Stem::Event::Wx::App->new() ; +my $wx_timer = Stem::Event::Wx::Timer->new() ; + +# this will call the io_poll_timer method in $wx_timer's class + +my $io_poll_timer = Stem::Event::Timer->new( + object => $wx_timer, + interval => 1, # .1 second poll + method => 'io_poll_timer', +) ; + +sub _start_loop { + +# _build just sets the min delay for the wx timer. this will make sure +# any timer events get going when we start the loop. + + Stem::Event::Timer::_build() ; + Wx::wxTheApp->MainLoop() ; +} + +sub _stop_loop { + + Wx::wxTheApp->ExitMainLoop() ; +} + + +package Stem::Event::Timer ; + +sub _build { + + my $min_delay = Stem::Event::Perl::find_min_delay() ; + $wx_timer->set_wx_timer_delay( $min_delay ) ; + return ; +} + +############################################################################ + +# this class subclasses Wx::Timer and its Notify method will be called +# after the current delay. + +package Stem::Event::Wx::Timer ; + +use base qw( Wx::Timer ) ; + +BEGIN { + + unless ( eval { require Time::HiRes } ) { + + Time::HiRes->import( qw( time ) ) ; + } +} + +my $last_time ; + +sub set_wx_timer_delay { + + my( $self, $delay ) = @_ ; + +#print "WX DELAY [$delay]\n" ; + if ( $delay ) { + + $self->Start( int( $delay * 1000 ), 0 ); + $last_time = time() ; + return ; + } + + $self->Stop(); +} + +# Wx calls this method when its timers get triggered. this is the only +# wx timer callback in this wrapper. all the others are handled with +# perl in Event.pm and Event/Perl.pm + +sub Notify { + +#print "NOTIFY\n" ; + my $delta_time = time() - $last_time ; + my $min_delay = Stem::Event::Perl::find_min_delay() ; + $wx_timer->set_wx_timer_delay( $min_delay ) ; + Stem::Event::Perl::trigger_timer_events( $delta_time ) ; +} + +sub io_poll_timer { + +#print "IO POLL\n" ; + + Stem::Event::Perl::_one_time_loop() ; +} + + +############################################################################ + +# this class is needed to subclass Wx::App and to make our own +# WxApp. it needs to provide OnInit which is called at startup and has +# to return true. + +package Stem::Event::Wx::App ; + +use base 'Wx::App' ; +sub OnInit { return( 1 ) } + +1 ; + +__END__ diff --git a/lib/Stem/File.pm b/lib/Stem/File.pm new file mode 100644 index 0000000..f057a40 --- /dev/null +++ b/lib/Stem/File.pm @@ -0,0 +1,113 @@ +# File: Stem/File.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::File ; + +use strict ; + +my $attr_spec = [ + + { + 'name' => 'path', + 'required' => 1, + 'help' => < 'mode', + 'default' => 'read', + 'help' => <type() ; + +#print $msg->dump( 'switch' ) ; + + if ( $type eq 'cmd' ) { + + $self->cmd_in( $msg ) ; + return ; + } +} + + +sub read { + + my( $self, $read_size_wanted ) = @_ ; + + +} + +sub read_line { + + my( $self, $read_size_wanted ) = @_ ; + + $self->{'handle'}->readline() ; +} + +sub write { + + my( $self, $write_data ) = @_ ; + + $self->{'handle'}->write( $write_data ) ; +} + +sub close { + + my( $self ) = @_ ; + + $self->{'handle'}->close() ; + + delete( $self->{'handle'} ) ; +} + +1 ; diff --git a/lib/Stem/Gather.pm b/lib/Stem/Gather.pm new file mode 100644 index 0000000..b67eea1 --- /dev/null +++ b/lib/Stem/Gather.pm @@ -0,0 +1,383 @@ +# File: Stem/Gather.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Gather ; + +#use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +#use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; + +=head1 Description + +This is a object module used by Stem Cells and objects to detect when +a set of asynchronous events have finished. It is constructed by an +owner object which then stores it in itselt. Gather objects are +initialized with a set of keys to be gathered. When the owner object +is notified of an event, it calls the C method of the gather +object with a list of keys. When all of the keys are gathered, a +callback is made to the owner object. An optional timeout is available +which will also generate a callback if the keys are not gathered in +time. + +=head1 Synopsis + + use Stem::Gather ; + + # $self is the owner object that has already been created + + my $gather = Stem::Gather->new( + 'object' => $self, + 'keys' => [qw( msg1 msg2 )] + ) ; + + $self->{'gather'} = $gather ; + + sub msg1_in { + + my( $self ) = @_ ; + $self->{'gather'}->gathered( 'msg1' ) ; + } + + sub msg2_in { + + my( $self ) = @_ ; + $self->{'gather'}->gathered( 'msg2' ) ; + } + + sub gather_done { + + my( $self ) = @_ ; + + print "we have gathered\n" ; + } + +=cut + +use strict ; + +my %class_to_attr_name ; + +my $attr_spec = [ + + { + 'name' => 'object', + 'required' => 1, + 'type' => 'object', + 'help' => < 'keys', + 'required' => 1, + 'type' => 'list', + 'help' => < 'gathered_method', + 'default' => 'gather_done', + 'help' => < 'no_start', + 'type' => 'boolean', + 'help' => < must be made. This only meaningful if this gather has a +timeout set. +HELP + }, + { + 'name' => 'timeout', + 'help' => < 'timeout_method', + 'default' => 'gather_timeout', + 'help' => < + +=over 4 + + +=item Description: +This is the owner object which has the methods that get called when Stem::Gather +has either finished gathering all of the keys or it has timed out. + + +=item Its B is: object + +=item It is B. + +=back + +=item * Attribute - B + +=over 4 + + +=item Description: +This is the list of keys to gather. + + +=item Its B is: list + +=item It is B. + +=back + +=item * Attribute - B + +=over 4 + + +=item Description: +This method is called in the owner object when all of the keys are gathered. + + +=item It B to: gather_done + +=back + +=item * Attribute - B + +=over 4 + + +=item Description: +If set, then do not start the gather object upon creation. A call to +the C must be made. This only meaningful if this gather has a +timeout set. + + +=item Its B is: boolean + +=back + +=item * Attribute - B + +=over 4 + + +=item Description: +This is an optional timeout period (in seconds) waiting for the gather +to be completed + + +=back + +=item * Attribute - B + +=over 4 + + +=item Description: +This method is called in the owner object if the gather timed out +before all keys were gathered. + + +=item It B to: gather_timeout + +=back + +=back + +=cut + +# End of autogenerated POD +########### + + + + +=head2 Method new + +This is the constructor method for Stem::Gather. It uses the standard +Stem key/value API with the + +=cut + +sub new { + + my( $class ) = shift ; + + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return $self unless ref $self ; + +# return 'Stem::Gather "keys" is not an array reference' +# unless ref $self->{'keys'} eq 'ARRAY' ; + + $self->restart() unless $self->{'no_start'} ; + + return( $self ) ; +} + +=head2 Method restart + +This method is called to start up the gather object when it has +already gathered all the keys, it has timed out or it was never +started (the no_start attribute was enabled). It takes no arguments. + +=cut + + +sub restart { + + my( $self ) = @_ ; + + $self->{'gathered'} = 0 ; + + $self->{'keys_left'} = { map { $_, 1 } @{$self->{'keys'}} } ; + +# TraceStatus "GAT keys '@{$self->{'keys'}}'" ; + + $self->_cancel_timeout() ; + + if ( my $timeout = $self->{'timeout'} ) { + + $self->{'timer_event'} = Stem::Event::Timer->new( + 'object' => $self, + 'delay' => $timeout, + 'hard' => 1, + 'repeat' => 0 ) ; + } +} + +=head2 Method add_keys + +This method is passed a list of keys which will be added to the list +to be watched for by the Stem::Gather object. The new keys are not +looked for until a call to the C method is made. + +=cut + +sub add_keys { + + my( $self, @keys ) = @_ ; + + push @{$self->{'keys'}}, @keys ; +} + +=head2 Method gathered + +This method is called with a list of keys that are gathered. The keys +that haven't been gathered before are marked as gathered. If there are +no more keys to be gathered, the method in the C +attribute is called in the owner object. You have to call the +C method on this gather object to use it again.You can pass +this methods keys that have been gathered or are not even in the list +to be gathered and they are ignored. + +=cut + +sub gathered { + + my( $self, @keys ) = @_ ; + +# TraceStatus "gathered: @keys" ; + + return if $self->{'gathered'} ; + + delete @{$self->{'keys_left'}}{@keys} ; + + return if keys %{$self->{'keys_left'}} ; + + $self->_cancel_timeout() ; + $self->{'gathered'} = 1 ; + + my $method = $self->{'gathered_method'} ; + +# TraceStatus "gathered done: calling $method" ; + + return $self->{'object'}->$method() ; +} + +sub timed_out { + + my( $self ) = @_ ; + + $self->_cancel_timeout() ; + + my $method = $self->{'timeout_method'} ; + $self->{'object'}->$method() ; + + return ; +} + +sub _cancel_timeout { + + my( $self ) = @_ ; + + if ( my $timer = $self->{'timer_event'} ) { + $timer->cancel() ; + + delete $self->{'timer_event'} ; + } +} + +=head2 Method + +This method B be called if the owner object is being shut down or +destroyed. It will cancel any pending timeout and break the link back +to the owner object. The owner object can then be destroyed without +leaking memory. + +=cut + +sub shut_down { + + my( $self ) = @_ ; + + $self->_cancel_timeout() ; + + delete $self->{'object'} ; +} + +1 ; diff --git a/lib/Stem/Hub.pm b/lib/Stem/Hub.pm new file mode 100644 index 0000000..936959c --- /dev/null +++ b/lib/Stem/Hub.pm @@ -0,0 +1,126 @@ +# File: Stem/Hub.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Hub ; + +use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; + +use strict ; +use Carp ; +use Sys::Hostname ; + +use Stem::Vars ; + +$Stem::Vars::Hub_name = '' ; +$Stem::Vars::Program_name = $0 ; +$Stem::Vars::Host_name = hostname() ; + +Stem::Route::register_class( __PACKAGE__, 'hub' ) ; + +my $attr_spec = [ + + { + 'name' => 'reg_name', + 'help' => < + +=over 4 + + +=item Description: +The registration name is used to name this Hub. + + +=back + +=back + +=cut + +# End of autogenerated POD +########### + + + + +sub new { + + my( $class ) = shift ; + + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return $self unless ref $self ; + + $Stem::Vars::Hub_name = $Env{ 'hub_name' } || + $self->{ 'reg_name' } || + $Stem::Vars::Program_name ; + + TraceStatus "hub name is '$Stem::Vars::Hub_name'" ; + +########################### +########################### +# add code to open hub log +# +########################### +########################### + + return ; +} + +sub status_cmd { + + my $hub = $Stem::Vars::Hub_name || 'NONE' ; + + return < 'size', + 'default' => 6, + 'help' => < + +=over 4 + + +=item Description: +This sets the number of characters in the Id. It defaults to 6. + + +=item It B to: 6 + +=back + +=back + +=cut + +# End of autogenerated POD +########### + + + +=head2 new + +The new method constructs a Stem::Id object. It initializes the Id +string to a string of 'a's. The string size determines how long this +object can go before it has to reuse previously deleted Id strings. + +=cut + +sub new { + + my( $class ) = shift ; + + my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; + return $self unless ref $self ; + + my $size = $self->{'size'} ; + + $self->{'start'} = 'a' x $size ; + $self->{'next'} = 'a' x $size ; + $self->{'end'} = 'a' x ( $size + 1 ) ; + $self->{'in_use'} = {} ; + + return $self ; +} + +=head2 next + +The next method returns the next available Id in the object and marks +that as in use. It fails if all possible Id's are in use. + +=cut + +sub next { + + my( $self ) = @_ ; + + my $next = $self->{'next'} ; + my $curr_next = $next ; + my $end = $self->{'end'} ; + my $in_use = $self->{'in_use'} ; + + while( exists( $in_use->{$next} ) ) { + + $next++ ; + +# fail if we looped around. + +#print "curr $curr_next $next\n" ; + + return if $next eq $curr_next ; + + $next = $self->{'start'} if $next eq $end ; + } + + $in_use->{$next} = 1 ; + $self->{'next'} = $next ; + + return $next ; +} + +=head2 delete + +The delete method allows this Id to be reused by a call to the next +method. + +=cut + +sub delete { + + my( $self, $id ) = @_ ; + + delete $self->{'in_use'}{ $id } ; +} + +=head2 dump + +The dump method returns a the list of Ids that are in use. used. It +either returns the list of keys or an anonymous array with them +depending on the calling context. + +=cut + +sub dump { + + my( $self ) = @_ ; + + return( wantarray ? keys %{ $self->{'in_use'} } : + [ keys %{ $self->{'in_use'} } ] ) ; +} + +1 ; diff --git a/lib/Stem/Inject.pm b/lib/Stem/Inject.pm new file mode 100644 index 0000000..9546cde --- /dev/null +++ b/lib/Stem/Inject.pm @@ -0,0 +1,324 @@ +# -*- mode: cperl; cperl-indent-level:8; tab-width:8; indent-tabs-mode:t; -*- + +# File: Stem/Inject.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +####################################################### + +package Stem::Inject ; + +use strict ; + +use IO::Socket ; + +use Stem::Msg ; +use Stem::Packet ; + +my $attr_spec = [ + + { + 'name' => 'host', + 'required' => 1, + 'help' => < 'port', + 'required' => 1, + 'help' => < 'to', + 'required' => 1, + 'help' => < 'type', + 'required' => 1, + 'help' => < 'cmd', + 'help' => < 'codec', + 'help' => < 'data', + 'help' => < 'timeout', + 'default' => 60, + 'help' => < 'wait_for_reply', + 'default' => 1, + 'help' => <{'from'} = "Stem::Inject:inject$$"; + + $self->{'packet'} = + Stem::Packet->new( codec => $self->{'codec'} ) ; + + local $SIG{'ALRM'} = sub { die 'Read or write to socket timed out' }; + + my $result; + + eval { + + my $address = "$self->{'host'}:$self->{'port'}"; + $self->{'sock'} = IO::Socket::INET->new($address) ; + $self->{'sock'} or die "can't connect to $address\n" ; + + alarm $self->{'timeout'} if $self->{'timeout'} ; + + $self->_register() ; + + $result = $self->_inject_msg() ; + } ; + + alarm 0 ; + + return $@ if $@ ; + + return unless $self->{'wait_for_reply'}; + + return $result ; +} + +sub _register { + + my( $self, $data ) = @_ ; + + my $reg_msg = + Stem::Msg->new( from => $self->{'from'}, + type => 'register', + ) ; + + die $reg_msg unless ref $reg_msg ; + + my $reg = $self->{'packet'}->to_packet($reg_msg) ; + + my $written = syswrite( $self->{'sock'}, $$reg ) ; + defined $written or die "can't write to socket\n" ; + + my $read_buf ; + while (1) { + + my $bytes_read = sysread( $self->{'sock'}, $read_buf, 8192 ) ; + + defined $bytes_read or die "can't read from socket" ; + last if $bytes_read == 0 ; + + my $data = $self->{'packet'}->to_data( $read_buf ) ; + + last; + } +} + +sub _inject_msg { + + my( $self ) = @_; + + my %msg_p = + ( 'to' => $self->{'to'}, + 'from' => $self->{'from'}, + 'type' => $self->{'type'}, + ) ; + + $msg_p{'cmd'} = $self->{'cmd'} if $self->{'type'} eq 'cmd'; + $msg_p{'data'} = $self->{'data'}, + + my $data_msg = Stem::Msg->new(%msg_p) ; + + die $data_msg unless ref $data_msg ; + + my $data = $self->{'packet'}->to_packet($data_msg) ; + + my $written = syswrite( $self->{'sock'}, $$data ) ; + defined $written or die "can't write to socket\n" ; + + return unless $self->{'wait_for_reply'}; + + my $read_buf ; + while (1) { + + my $bytes_read = sysread( $self->{'sock'}, $read_buf, 8192 ) ; + + defined $bytes_read or die "can't read from socket" ; + last if $bytes_read == 0 ; + + my $reply = $self->{'packet'}->to_data( $read_buf ) ; + + return $reply->data ; + } +} + +1 ; + +__END__ + +=pod + +=head1 NAME + +Stem::Inject - Inject a message into a portal via a socket connection + +=head1 SYNOPSIS + + my $return = + Stem::Inject->inject( to => 'some_cell', + type => 'do_something', + port => 10200, + host => 'localhost', + data => { foo => 1 }, + ); + + # do something with data returned + +=head1 USAGE + +This class contains just one method, C, which can be used to +inject a single message into a Stem hub, via a known server portal. + +This is very useful if you have a synchronous system which needs to +communicate with a Stem system via messages. + +=head1 METHODS + +=over 4 + +=item * inject + +This method is the sole interface provided by this class. It accepts +the following parameters: + +=over 8 + +=item * host (required) + +This parameter specifies the host with which to connect. + +=item * port (required) + +The port with which to connect on the specified host. + +=item * to (required) + +The address of the cell to which the message should be delivered. + +=item * type (required) + +The type of the message to be delivered. + +=item * cmd + +The cmd being given. This is only needed if the message's type is +"cmd". + +=item * data + +The data that the message will carry, if any. + +=item * codec + +The codec to be used when communicating with the port. This defaults +to "Data::Dumper", but be careful to set this to whatever value the +receiving port is using. + +=item * timeout (defaults to 60) + +The amount of time, in seconds, before giving up on message delivery +or reply. This is the I amount of time allowed for message +delivery and receiving a reply. + +=item * wait_for_reply (defaults to true) + +If this is true then the C method will expect a reply to the +message it delivers. If it doesn't receive one this will be +considered an error. + +=back + +If there is an error in trying to inject a message, either with the +parameters given, or with the socket connection, then this method will +return an error string. + +If no reply was expected, this method simply returns false. +Otherwise, it returns the reply message's data, which will always be a +reference. + +=back + +=head1 AUTHOR + +Dave Rolsky + +=cut diff --git a/lib/Stem/Load/Driver.pm b/lib/Stem/Load/Driver.pm new file mode 100644 index 0000000..3cea905 --- /dev/null +++ b/lib/Stem/Load/Driver.pm @@ -0,0 +1,124 @@ + +package Stem::Load::Driver ; + +use strict ; + +use Time::HiRes qw( gettimeofday tv_interval ) ; + +my $attr_spec = [ + + + { + 'name' => 'reg_name', + 'help' => < 'load_addr', + 'help' => < 'load_data', + 'help' => < 'data_sizes', + 'help' => < 'max_msg_cnt', + 'default' => 1000, + 'help' => < 'max_duration', + 'default' => 10, + 'help' => <{'echo_cnt'} = 0 ; + + $self->{'start_time'} = gettimeofday() ; + + $self->{'go_from_addr'} = $msg->from() ; + + $self->send_load_msg() ; + + return "Load Started\n" ; +} + +sub response_in { + + my( $self, $msg ) = @_ ; + + my $time_delta = gettimeofday() - $self->{'start_time'} ; + + if ( ++$self->{'echo_cnt'} >= $self->{'max_msg_cnt'} || + $time_delta > $self->{'max_duration'} ) { + + my $msgs_per_second = $self->{'echo_cnt'} / $time_delta ; + + my $done_msg = Stem::Msg->new( + 'to' => $self->{'go_from_addr'}, + 'from' => $self->{'reg_name'}, + 'type' => 'response', + 'data' => <{'echo_cnt'} messages in $time_delta seconds +$msgs_per_second messages per second +DATA + ) ; + + $done_msg->dispatch() ; + + return ; + } + + $self->send_load_msg() ; + + return ; +} + + +sub send_load_msg { + + my( $self ) = @_ ; + + $self->{'echo_msg'} ||= Stem::Msg->new( + 'to' => $self->{'load_addr'}, + 'from' => $self->{'reg_name'}, + 'type' => 'echo', + 'data' => \'echo me', + ) ; + + $self->{'echo_msg'}->dispatch() ; + + return ; +} + +1 ; diff --git a/lib/Stem/Load/Ticker.pm b/lib/Stem/Load/Ticker.pm new file mode 100644 index 0000000..dd69dd8 --- /dev/null +++ b/lib/Stem/Load/Ticker.pm @@ -0,0 +1,163 @@ + + +package Stem::Load::Ticker ; + +use strict ; + +use Time::HiRes qw( gettimeofday tv_interval ) ; + +my $attr_spec = [ + + + { + 'name' => 'reg_name', + 'help' => < 'dbi_addr', + 'help' => < 'max_cnt', + 'default' => 20, + 'help' => < 'parallel_cnt', + 'default' => 1, + 'help' => <data() ) { + + %go_args = ${$data} =~ /(\S+)=(\S+)/g if $$data ; + } + + $self->{'start_time'} = gettimeofday() ; + $self->{'go_from_addr'} = $msg->from() ; + $self->{'go_max_cnt'} = $go_args{'max_cnt'} || $self->{'max_cnt'} ; + + $self->{'inserted_cnt'} = 0 ; + $self->{'send_cnt'} = $self->{'go_max_cnt'} ; + $self->{'parallel_cnt'} = $go_args{'para_cnt'} if $go_args{'para_cnt'} ; + + $self->send_ticker_msgs( $self->{'parallel_cnt'} ) ; + + return "Ticker Started\n" ; +} + +sub send_ticker_msgs { + + my( $self, $parallel_cnt ) = @_ ; + +#print "PARA $parallel_cnt\n" ; + + while ( $parallel_cnt-- ) { + + $self->insert_ticker_row() ; + } + + return ; +} + +sub insert_ticker_row { + + my( $self ) = @_ ; + + return if $self->{'send_cnt'} <= 0 ; + $self->{'send_cnt'}-- ; + + my $ticker = join '', map ['A' .. 'Z']->[rand 26], 1 .. 3 ; + + my $price = 100 + int rand 9900 ; + + my $delta = -1000 + int rand 2000 ; + + my $dbi_msg = Stem::Msg->new( + + 'to' => $self->{'dbi_addr'}, + 'from' => $self->{'reg_name'}, + 'type' => 'cmd', + 'cmd' => 'execute', + 'reply_type' => 'insert_done', + 'data' => { + statement => 'insert_tick', + bind => [ $ticker, $price, $delta ], + }, + ); + +#print $dbi_msg->dump( 'SEND' ) ; + $dbi_msg->dispatch() ; + + return ; +} + +sub insert_done_in { + + my( $self, $msg ) = @_ ; + +#print $msg->dump( 'DONE' ) ; + + if ( $self->{'send_cnt'} ) { + + $self->send_ticker_msgs( 1 ) ; + } + + if ( ++$self->{'inserted_cnt'} >= $self->{'go_max_cnt'} ) { + + my $data = $msg->data() ; + + die "insert_done_in: $$data" unless ref $data eq 'HASH' ; + + my $time_delta = sprintf( "%8.4f", + gettimeofday() - $self->{'start_time'} ) ; + + my $rows_per_second = $self->{'inserted_cnt'} / $time_delta ; + + my $done_msg = Stem::Msg->new( + 'to' => $self->{'go_from_addr'}, + 'from' => $self->{'reg_name'}, + 'type' => 'response', + 'data' => <{'inserted_cnt'} rows in $time_delta seconds +$rows_per_second rows per second +with $self->{'parallel_cnt'} inserts in parallel +last row ID $data->{'insert_id'} +DATA + ) ; + + $done_msg->dispatch() ; + + return ; + } + + + return ; +} + +1 ; diff --git a/lib/Stem/Log.pm b/lib/Stem/Log.pm new file mode 100644 index 0000000..fc9dd43 --- /dev/null +++ b/lib/Stem/Log.pm @@ -0,0 +1,475 @@ +# File: Stem/Log.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +use strict ; + +use Stem::Log::Entry ; +use Stem::Log::File ; + +my %logs ; + +package Stem::Log ; + +use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; + + +use Data::Dumper ; + +use Stem::Vars ; + +Stem::Route::register_class( __PACKAGE__, 'log' ) ; + +my $attr_spec = [ + + { + 'name' => 'name', + 'required' => 1, + 'help' => < 'file', + 'class' => 'Stem::Log::File', + 'help' => < 'format', + 'default' => '%T', + 'help' => < 'strftime', + 'default' => '%C', + 'help' => < 'use_gmt', + 'default' => 1, + 'type' => 'boolean', + 'help' => < 'filters', + 'help' => <{'name'} } = $self ; + + return ; +} + +# table to convert filter keys to code refs to execute +# these are all passed the $entry hash ref, the filter arg and the log object + +my %filter_to_code = ( + + 'match_text' => sub { $_[0]->{'text'} =~ /$_[1]/ }, + 'match_label' => sub { $_[0]->{'label'} =~ /$_[1]/ }, + + 'eq_level' => sub { $_[0]->{'level'} == $_[1] }, + 'lt_level' => sub { $_[0]->{'level'} < $_[1] }, + 'le_level' => sub { $_[0]->{'level'} <= $_[1] }, + 'gt_level' => sub { $_[0]->{'level'} > $_[1] }, + 'ge_level' => sub { $_[0]->{'level'} >= $_[1] }, + + 'env_eq_level' => sub { $_[0]->{'level'} == ( $Env{ $_[1] } || 0 ) }, + 'env_lt_level' => sub { $_[0]->{'level'} > ( $Env{ $_[1] } || 0 ) }, + 'env_le_level' => sub { $_[0]->{'level'} >= ( $Env{ $_[1] } || 0 ) }, + 'env_gt_level' => sub { $_[0]->{'level'} < ( $Env{ $_[1] } || 0 ) }, + 'env_ge_level' => sub { $_[0]->{'level'} <= ( $Env{ $_[1] } || 0 ) }, + + 'file' => \&_action_file, + 'stdout' => \&_action_stdout, + 'stderr' => \&_action_stderr, + 'dev_tty' => \&_action_dev_tty, + 'console' => \&_action_console, +# 'msg' => \&_action_msg, + 'write' => \&_action_write, + 'wall' => \&_action_wall, + 'email' => \&_action_email, + 'page' => \&_action_page, + 'forward' => \&_action_forward, + + 'custom' => \&_custom_filter, +) ; + +my %flag_to_code = ( + + 'set' => sub { $_[0]->{'flag'} = 1 }, + 'clear' => sub { $_[0]->{'flag'} = 0 }, + 'invert' => sub { $_[0]->{'flag'} = ! $_[0]->{'flag'} }, + 'inverted_test' => sub { $_[0]->{'invert_test'} = 1 }, + 'normal_test' => sub { $_[0]->{'invert_test'} = 0 }, + 'or' => sub { $_[0]->{'or'} = 1 }, + 'and' => sub { $_[0]->{'or'} = 0 }, +) ; + +sub submit { + + my( $self, $entry ) = @_ ; + + $entry->{'format'} = $self->{'format'} ; + $entry->{'strftime'} = $self->{'strftime'} ; + $entry->{'use_gmt'} = $self->{'use_gmt'} ; + + my $filter_list = $self->{'filters'} ; + + unless ( $filter_list ) { + +# no filter so the default is to log to the file + + _action_file( $entry, 0, $self ) ; + + return ; + } + +# start with all actions enabled + + $entry->{'flag'} = 1 ; + +# scan the filter list by pairs + + for( my $i = 0 ; $i < @{$filter_list} ; $i += 2 ) { + + my ( $filter_key, $filter_arg ) = + @{$filter_list}[$i, $i + 1] ; + +# handle the flag operations first. + + if ( $filter_key eq 'flag' ) { + + if ( my $code = $flag_to_code{ $filter_arg } ) { + + $code->( $entry ) ; + } + + next ; + } + +# skip this filter rule/action if the flag is false + + next unless $entry->{'flag'} && ! $entry->{'invert_test'} ; + +# check for and remove a 'not_' prefix + + my $not = $filter_key =~ s/^not_(\w+)$/$1/ ; + +#print "FILT $filter_key $filter_arg\n" ; + + my $code = $filter_to_code{ $filter_key } ; + + next unless $code ; + +# execute the rule/action code + + my $flag_val = $code->( $entry, $filter_arg, $self ) ; + +# don't mung the flag unless we get a boolean return + + next unless defined( $flag_val ) ; + +# invert the returned flag value if needed + + $flag_val = ! $flag_val if $not ; + +# do the right boolean op + + if ( $entry->{'or'} ) { + + $entry->{'flag'} ||= $flag_val ; + } + else { + + $entry->{'flag'} &&= $flag_val ; + } + } +} + + +sub _format_entry { + + my( $entry ) = @_ ; + + my $formatted = $entry->{'format'} ; + + $formatted =~ s/%(.)/_format_field( $entry, $1 )/seg ; + + return $formatted ; +} + +my %letter_to_key = ( + + 'T' => 'text', + 't' => 'time', + 'L' => 'label', + 'l' => 'level', + 'H' => 'hub_name', + 'h' => 'host_name', + 'P' => 'program_name', +) ; + +sub _format_field { + + my( $entry, $letter ) = @_ ; + + if ( my $key = $letter_to_key{ $letter } ) { + + return $entry->{$key} ; + } + + if ( $letter eq 'f' ) { + + require POSIX ; + + $entry->{'formatted_time'} ||= do { + + my @times = ( $entry->{'use_gmt'} ) ? + gmtime( $entry->{'time'} ) : + localtime( $entry->{'time'} ) ; + + POSIX::strftime( $entry->{'strftime'}, @times ) ; + } ; + + return $entry->{'formatted_time'} ; + } + + return $letter ; +} + +sub _action_file { + + my( $entry, $arg, $log_obj ) = @_ ; + + my $file = $log_obj->{'file'} ; + + $file or return ; + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + $file->write( $entry->{'formatted'} ) ; + + return ; +} + +sub _action_stdout { + + my( $entry ) = shift ; + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + print STDOUT $entry->{'formatted'} ; + + return ; +} + +sub _action_stderr { + + my( $entry ) = shift ; + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + print STDERR $entry->{'formatted'} ; + + return ; +} + +sub _action_write { + + my( $entry, $arg ) = @_ ; + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + my @users = ref $arg ? @{$arg} : $arg ; + + foreach my $user ( @users ) { + + system <{'formatted'}' | write $user >/dev/null 2>&1 & +SYS + } + + return ; +} + +sub _action_wall { + + my( $entry ) = shift ; + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + + system <{'formatted'}' | wall & +SYS + + return ; +} + +# handle to write log entries to /dev/tty + +my $tty_fh ; + +sub _action_dev_tty { + + my( $entry ) = shift ; + + $tty_fh ||= IO::File->new( ">/dev/tty" ) ; + + unless( $tty_fh ) { + + warn "can't open log file /dev/tty $!" ; + return ; + } + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + print $tty_fh $entry->{'formatted'} ; + + return ; +} + +sub _action_console { + + my( $entry ) = shift ; + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + return unless Stem::Console->can( 'write' ) ; + + Stem::Console->write( $entry->{'formatted'} ) ; + + return ; +} + +sub _action_forward { + + my( $entry, $arg ) = @_ ; + + my @logs = ref $arg ? @{$arg} : $arg ; + + my $entry_obj = $entry->{'entry_obj'} ; + + $entry_obj->submit( @logs ) ; + + return ; +} + +sub _action_email { + + my( $entry, $arg ) = @_ ; + + $entry->{'formatted'} ||= _format_entry( $entry ) ; + + my ( $email_addr, $subject ) = ( ref $arg ) ? + @{$arg} : ( $arg, 'Stem::Log' ) ; + +#print "EMAIL $email_addr: $subject\n" ; + + require Mail::Send ; + + my $mail = Mail::Send->new( + 'To' => $email_addr, + 'Subject' => $subject + ) ; + + my $fh = $mail->open(); + + $fh->print( $entry->{'formatted'} ) ; + + $fh->close; + + return ; +} + +sub _custom_filter { + + my( $entry, $arg ) = @_ ; + +##### +# do this +##### + + return ; +} + +sub find_log { + + my ( $log_name ) = @_ ; + + return( $logs{ $log_name } ) ; +} + +sub status_cmd { + + my $status_text .= sprintf( "%-20s%-40s%10s\n", + "Logical Log", + "Physical File", + "Size" ) ; + $status_text .= sprintf "-" x 70 . "\n"; + + foreach my $log_name ( sort keys %logs ) { + + my $ref = $logs{$log_name} ; + + $status_text .= sprintf "%-20s%-40s%10s\n", + $log_name, + $ref->{'file'}{'path'}, + $ref->{'file'}{'size'} ; + } + + $status_text .= "\n\n" ; + + return $status_text ; +} + +1 ; diff --git a/lib/Stem/Log/Entry.pm b/lib/Stem/Log/Entry.pm new file mode 100644 index 0000000..c5edb43 --- /dev/null +++ b/lib/Stem/Log/Entry.pm @@ -0,0 +1,157 @@ +# File: Stem/Log/Entry.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +use strict ; + +use Stem::Log ; + +package Stem::Log::Entry ; + +Stem::Route::register_class( __PACKAGE__, 'entry' ) ; + + +my $attr_spec = [ + + { + 'name' => 'text', + 'default' => '', + 'help' => < 'label', + 'default' => 'info', + 'help' => < 'level', + 'default' => '1', + 'help' => < 'logs', + 'type' => 'list', + 'help' => <{'time'} = time() ; + $self->{'hub_name'} = $Stem::Vars::Hub_name ; + $self->{'host_name'} = $Stem::Vars::Host_name ; + $self->{'program_name'} = $Stem::Vars::Program_name ; + + if ( my $logs_attr = $self->{'logs'} ) { + + $self->submit( @{$logs_attr} ) ; + } + + return $self ; +} + +sub submit { + + my( $self, @logs ) = @_ ; + + foreach my $log_name ( @logs ) { + +#print "LOG [$log_name]\n" ; + if ( $log_name =~ /^(\w+):(\w+)$/ ) { + + my $to_hub = $1 ; + my $to_log = $2 ; + + my $log_msg = Stem::Msg->new( + 'to' => "$to_hub:" . __PACKAGE__, + 'from' => __PACKAGE__, + 'type' => 'log', + 'log' => $to_log, + 'data' => $self, + ) ; + +#print $log_msg->dump( 'LOG out' ) ; + + + $log_msg->dispatch() ; + + next ; + } + + my $log_obj = Stem::Log::find_log( $log_name ) ; + + next unless $log_obj ; + + + my $entry_copy ||= { %{$self} } ; + + $entry_copy->{'log_name'} = $log_name ; + $entry_copy->{'orig_log_name'} ||= $log_name ; + $entry_copy->{'entry_obj'} = $self ; + + $log_obj->submit( $entry_copy ) ; + } +} + +# this method is how a remote log message is delivered locally + +sub log_in { + + my( $class, $msg ) = @_ ; + + my $entry = $msg->data() ; + + print "$entry\n" unless ref $entry ; + +#print $msg->dump( 'LOG in' ) ; + + $entry->submit( $msg->log() ) ; + + return ; +} + +1 ; diff --git a/lib/Stem/Log/File.pm b/lib/Stem/Log/File.pm new file mode 100644 index 0000000..3a3fcc6 --- /dev/null +++ b/lib/Stem/Log/File.pm @@ -0,0 +1,210 @@ +# File: Stem/Log/File.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +use strict ; + +use IO::File ; +use File::Basename ; + + +package Stem::Log::File ; + +######################### +######################### +# add stuff for file rotation, number suffix, etc. +######################### +######################### + +my $attr_spec_log = [ + + { + 'name' => 'path', + 'required' => 1, + 'help' => < 'strftime', + 'default' => '%Y%m%d%H%M%S', + 'help' => < 'use_gmt', + 'default' => 1, + 'type' => 'boolean', + 'help' => < 'rotate', + 'type' => 'hash', + 'help' => <{'rotate'} ) { + + if ( ref $rotate_options eq 'ARRAY' ) { + + $self->{'rotate'} = { @{$rotate_options} } ; + } + } + + $self->{'base_path'} = $self->{'path'} ; + ( $self->{'log_dir'}, $self->{'file_name'} ) = + File::Basename::fileparse( $self->{'path'} ) ; + + my $err = $self->_open_file() ; + return $err if $err ; + + return( $self ) ; +} + + +sub write { + + my( $self, $text ) = @_ ; + + $self->{'fh'}->print( $text ) ; + + $self->{'size'} += length( $text ) ; + + my $rotate_options = $self->{'rotate'} ; + + if ( $rotate_options && + $self->{'size'} >= $rotate_options->{'max_size'} ) { + + $self->_rotate() ; + } +} + +sub status_cmd { + + +} + +sub rotate_cmd { + + my ( $self ) = @_ ; + + $self->_rotate() ; +} + +sub _rotate { + + my ( $self ) = @_ ; + + my $fh = $self->{'fh'} ; + + close( $fh ) ; + + $self->_open_file() ; +} + + +sub _open_file { + + my ( $self ) = @_ ; + + my $open_path = $self->{'base_path'} ; + + if ( $self->{'rotate'} ) { + + my $suffix = $self->_get_last_suffix() || + $self->_generate_suffix() ; + + + $open_path .= ".$suffix" ; + } + + $self->{'open_path'} = $open_path ; + + my $fh = IO::File->new( ">>$open_path" ) or + return "Can't append to log file '$open_path' $!" ; + + $self->{'size'} = -s $fh ; + + $fh->autoflush( 1 ) ; + + $self->{'fh'} = $fh ; + + return ; +} + +sub _get_last_suffix { + + my ( $self ) = @_ ; + + my $log_dir = $self->{'log_dir'} ; + my $file_name = $self->{'file_name'} ; + + local( *DH ) ; + + opendir( DH, $log_dir ) || return '' ; + + my @files = sort grep { /^$file_name/ } readdir(DH) ; + +# return the latest file suffix + + if ( @files ) { + + return $1 if $files[-1] =~ /\.(\d+$)/ ; + } + + return '' ; +} + + +sub _generate_suffix { + + my ( $self ) = @_ ; + + require POSIX ; + + my @time = ( $self->{'use_gmt'} ) ? gmtime : localtime ; + + return POSIX::strftime( $self->{'strftime'}, @time ) ; +} + +1 ; diff --git a/lib/Stem/Log/Tail.pm b/lib/Stem/Log/Tail.pm new file mode 100644 index 0000000..6698f8f --- /dev/null +++ b/lib/Stem/Log/Tail.pm @@ -0,0 +1,242 @@ +# File: Stem/Log/Tail.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Log::Tail ; + +use strict ; +use IO::Seekable ; +use Data::Dumper ; + +use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; + +my $attr_spec = [ + + { + 'name' => 'path', + 'required' => 1, + 'help' => < 'data_log', + 'required' => 1, + 'help' => < 'status_log', + 'help' => < 'label', + 'default' => 'tail', + 'help' => < 'level', + 'default' => '5', + 'help' => < 'interval', + 'help' => < 'delay', + 'default' => 10, + 'help' => <{'interval'}\n" ; + + if ( my $interval = $self->{'interval'} ) { + + $self->{'timer'} = Stem::Event::Timer->new( + 'object' => $self, + 'method' => 'tail_cmd', + 'interval' => $interval, + 'delay' => $self->{'delay'}, + 'repeat' => 1, + 'hard' => 1, + ) ; + +print "TIMER $self->{'timer'}\n" ; + + } + + $self->{'prev_size'} = 0 ; + $self->{'prev_mtime'} = 0 ; + $self->{'prev_inode'} = -1 ; + + return( $self ) ; +} + +sub tail_cmd { + + my( $self ) = @_ ; + +print "TAILING\n" ; + + local( *LOG ) ; + + my $path = $self->{'path'} ; + + unless( open( LOG, $path ) ) { + + return if $self->{'open_failed'} ; + $self->{'open_failed'} = 1 ; + + if ( my $status_log = $self->{'status_log'} ) { + + Stem::Log::Entry->new( + 'logs' => $status_log, + 'label' => 'LogTail status', + 'text' => + "LogTail: missing log $path $!\n", + ) ; + } + return ; + } + + $self->{'open_failed'} = 0 ; + + my( $inode, $size, $mtime ) = (stat LOG)[1, 7, 9] ; + + TraceStatus "size $size mtime $mtime $inode" ; + + my $prev_inode = $self->{'prev_inode'} ; + my $prev_size = $self->{'prev_size'} ; + + if ( $prev_inode == -1 ) { + + $self->{'prev_inode'} = $inode ; + + if ( my $status_log = $self->{'status_log'} ) { + + Stem::Log::Entry->new( + 'logs' => $status_log, + 'level' => 6, + 'label' => 'LogTail status', + 'text' => + "LogTail: first open of $path\n", + ) ; + } + } + elsif ( $inode != $prev_inode ) { + + $self->{'prev_inode'} = $inode ; + + if ( my $status_log = $self->{'status_log'} ) { + + Stem::Log::Entry->new( + 'logs' => $status_log, + 'level' => 6, + 'label' => 'LogTail status', + 'text' => + "LogTail: $path has moved\n", + ) ; + } + +# tail the entire file as it is new + + $prev_size = 0 ; + + } + elsif ( $size < $prev_size ) { + + if ( my $status_log = $self->{'status_log'} ) { + + Stem::Log::Entry->new( + 'logs' => $status_log, + 'level' => 6, + 'label' => 'LogTail status', + 'text' => + "LogTail: $path has shrunk\n", + ) ; + } + +# tail the entire file as it has shrunk + + $prev_size = 0 ; + } + elsif ( $size == $prev_size ) { + + TraceStatus "no changes" ; + return ; + } + + $self->{'prev_size'} = $size ; + + my $delta_size = $size - $prev_size ; + + return unless $delta_size ; + + my $read_buf ; + + sysseek( *LOG, $prev_size, SEEK_SET ) ; + sysread( *LOG, $read_buf, $delta_size ) ; + + Stem::Log::Entry->new( + 'logs' => $self->{'data_log'}, + 'level' => $self->{'level'}, + 'label' => $self->{'label'}, + 'text' => $read_buf, + ) ; + + return ; +} + +1 ; diff --git a/lib/Stem/Msg.pm b/lib/Stem/Msg.pm new file mode 100644 index 0000000..ddf468d --- /dev/null +++ b/lib/Stem/Msg.pm @@ -0,0 +1,636 @@ +# File: Stem/Msg.pm + +# This file is part of Stem. +# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc. + +# Stem 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. + +# Stem 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 Stem; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# For a license to use the Stem under conditions other than those +# described here, to purchase support for this software, or to purchase a +# commercial warranty contract, please contact Stem Systems at: + +# Stem Systems, Inc. 781-643-7504 +# 79 Everett St. info@stemsystems.com +# Arlington, MA 02474 +# USA + +package Stem::Msg ; + +use strict ; +use Carp ; + +use Stem::Route qw( lookup_cell ) ; +use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ; +use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ; +use Stem::Trace 'log' => 'stem_msg' , 'sub' => 'TraceMsg' ; + +my $msg_id = 0; + +my $attr_spec = [ + + { + 'name' => 'type', + 'help' => < 'cmd', + 'help' => < 'reply_type', + 'default' => 'response', + 'help' => < 'data', + 'help' => < 'log', + 'help' => < 'status', + 'help' => < 'ack_req', + 'type' => 'boolean', + 'help' => < 'in_portal', + 'help' => < 'msg_id', + 'help' => < 'reply_id', + 'help' => <{'name'}, 1 } @{$attr_spec} ; + +# add the address types and parts to our attribute spec with callbacks +# for parsing + +# lists of the address types and parts + +my @addr_types = qw( to from reply_to ) ; +my @addr_parts = qw( hub cell target ) ; + +# these are used to grab the types and parts from the method names in AUTOLOAD + +my $type_regex = '(' . join( '|', @addr_types ) . ')' ; +my $part_regex = '(' . join( '|', @addr_parts ) . ')' ; + +# build all the accessor methods as closures + +{ + no strict 'refs' ; + + foreach my $attr ( map $_->{'name'}, @{$attr_spec} ) { + + *{$attr} = sub { + + $_[0]->{$attr} = $_[1] if @_ > 1 ; + return $_[0]->{$attr} + } ; + } + + foreach my $type ( @addr_types ) { + + *{$type} = sub { + my $self = shift ; + $self->{ $type } = shift if @_ ; + return $self->{ $type } ; + } ; + +########## +# WORKAROUND +# this array seems to be needed. i found a bug when i used +# a scalar and bumped it. the closures all had the value of 3. +########## + + my @part_nums = ( 0, 1, 2 ) ; + + foreach my $part ( @addr_parts ) { + + my $part_num = shift @part_nums ; + + *{"${type}_$part"} = sub { + my $self = shift ; + +# split the address for this type of address (to,from,reply_to) + + my @parts = split_address( $self->{$type} ) ; + + + if ( @_ ) { + + $parts[ $part_num ] = shift ; + + $self->{$type} = + make_address_string( @parts ) ; + } +#print "PART $type $part_num [$parts[ $part_num ]]\n" if $type eq 'from' ; + + return $parts[ $part_num ] ; + } ; + } + } +} + +# used for faster parsing. + +my @attrs = qw( to from reply_to type cmd reply_type log data ) ; + +sub new { + + my( $class ) = shift ; + +# my $self = Stem::Class::parse_args( $attr_spec, @_ ) ; +# return $self unless ref $self ; + +#print "A [$_]\n" for @_ ; + + my %args = @_ ; + +#use YAML ; +#print Dump \%args ; + + my $self = bless { map { exists $args{$_} ? + ( $_ => $args{$_} ) : () } @attrs } ; + +#print $self->dump( 'NEW' ) ; + + $self->{'type'} = 'cmd' if exists $self->{'cmd'} ; + + $self->{'msg_id'} ||= $class->_new_msg_id; + +# TraceMsg "MSG: [$_] => [$args{$_}]\n" for sort keys %args ; + +# TraceMsg $self->dump( 'new MSG' ) ; + + return( $self ) ; +} + +sub _new_msg_id { + + my( $class ) = shift ; + + $msg_id = 0 if $msg_id == 2 ** 31; + + return ++$msg_id; +} + +sub clone { + + my( $self ) = shift ; + + my $msg = Stem::Msg->new( + ( map { exists $self->{$_} ? + ( $_, $self->{$_} ) : () } + @addr_types, keys %is_plain_attr ), + @_ + ) ; + +# TraceMsg $self->dump( 'self' ) ; +# TraceMsg $msg->dump( 'clone' ) ; + + return $msg ; +} + +sub split_address { + +# return an empty address if no input + + return( '', '', '' ) unless @_ && $_[0] ; + +# parse out the address parts so + +# the cell part can be a token or a class name with :: between tokens. +# delimiter can be /, @, -, or : with : being the convention +# this is how triplets +# hub:cell:target + +#print "SPLIT IN [$_[0]]\n" ; + + $_[0] =~ m{ + ^ # beginning of string + (?: # group /hub:/ + (\w*) # grab /hub/ + ([:/@-]) # grab any common delimiter + )? # hub: is optional + ( # grab /cell/ + (?:\w+|::)+ # group cell (token or class name) + ) # /cell/ is required + (?: # group /:target/ + \2 # match first delimiter + (\w*) # grab /target/ + )? # :target is optional + $}x # end of string + +# an bad address can be checked with @_ == 1 as a proper address is +# always 3. + + or return "bad string address" ; + +# we return the list of hub, cell, target and give back nice null strings if +# needed. + +#print "SPLIT ", join( '--', $1 || '', $3, $4 || '' ), "\n" ; + + return( $1 || '', $3, $4 || '' ) ; +} + +# sub address_string { + +# my( $addr ) = @_ ; + +# #use YAML ; +# #print "ADDR [$addr]", Dump( $addr ) ; +# return $addr unless ref $addr ; + +# return 'BAD ADDRESS' unless ref $addr eq 'HASH' ; + +# return $addr->{'cell'} if keys %{$addr} == 1 && $addr->{'cell'} ; + +# return join ':', map { $_ || '' } @{$addr}{qw( hub cell target ) } ; +# } + +sub make_address_string { + + my( $hub, $cell_name, $target ) = @_ ; + + $hub = '' unless defined $hub ; + $target = '' unless defined $target ; + + return $cell_name unless length $hub || length $target ; + + return join ':', $hub, $cell_name, $target ; +} + +sub reply { + + my( $self ) = shift ; + +# TraceMsg "Reply [$self]" ; + +# TraceMsg $self->dump( 'reply self' ) ; + +#print $self->dump( 'reply self' ) ; + + my $to = $self->{'reply_to'} || $self->{'from'} ; + my $from = $self->{'to'} ; + + my $reply_msg = Stem::Msg->new( + 'to' => $to, + 'from' => $from, + 'type' => $self->{'reply_type'} || 'response', + 'reply_id' => $self->{'msg_id'}, + @_ + ) ; + +# TraceMsg $reply_msg->dump( 'new reply' ) ; +#$reply_msg->dump( 'new reply' ) ; + + return( $reply_msg ) ; +} + +##################### +##################### +# add forward method which clones the old msg and just updates the to address. +# +# work needs to be done on from/origin parts and who sets them +##################### +##################### + + +sub error { + + my( $self, $err_text ) = @_ ; + +# TraceError "ERR [$self] [$err_text]" ; + + my $err_msg = $self->reply( 'type' => 'error', + 'data' => \$err_text ) ; + +# TraceError $err_msg->dump( 'error' ) ; + + return( $err_msg ) ; +} + + +######################################## +######################################## +# from/origin address will be set if none by looking up the cell that +# is currently be called with a message. or use +# Stem::Event::current_object which is set before every event +# delivery. +######################################## +######################################## + + +my @msg_queue ; + +sub dispatch { + + my( $self ) = @_ ; + +warn( caller(), $self->dump() ) and die + 'Msg: No To Address' unless $self->{'to'} ; +warn( caller(), $self->dump() ) and die + 'Msg: No From Address' unless $self->{'from'} ; + + +# $self->deliver() ; +# return ; + +# unless ( @msg_queue ) { + unless ( ref ( $self ) ) { + $self = Stem::Msg->new( @_ ) ; + } +# Stem::Event::Plain->new( 'object' => __PACKAGE__, +# 'method' => 'deliver_msg_queue' ) ; +# } + return "missing to attr in msg" unless $self ->{"to"} ; + return "missing from attr in msg" unless $self ->{"from"} ; + return "missing type attr in msg" unless $self ->{"type"} ; + push @msg_queue, $self ; +} + +sub process_queue { + + while( @msg_queue ) { + + my $msg = shift @msg_queue ; + +#print $msg->dump( 'PROCESS' ) ; + my $err = $msg->_deliver() ; + + if ( $err ) { + + my $err_text = "Undelivered:\n$err" ; +#print $err_text, $msg->dump( 'ERR' ) ; + TraceError $msg->dump( "$err_text" ) ; + + } + } +} + +sub _deliver { + + my( $self ) = @_ ; + +#print $self->dump( "DELIVER" ) ; + + my( $to_hub, $cell_name, $target ) = split_address( $self->{'to'} ) ; + + unless( $cell_name ) { + + return <{'to'}' +ERR + } + +#print "H [$to_hub] C [$cell_name] T [$target]\n" ; + + if ( $to_hub && $Stem::Vars::Hub_name ) { + + if ( $to_hub eq $Stem::Vars::Hub_name ) { + + if ( my $cell = lookup_cell( $cell_name, $target ) ) { + + return $self->_deliver_to_cell( $cell ) ; + } + + return <send_to_portal( $to_hub ) ; + } + +# no hub, see if we can deliver to a local cell + + if ( my $cell = lookup_cell( $cell_name, $target ) ) { + + return $self->_deliver_to_cell( $cell ) ; + } + +# see if this came in from a portal + + if ( $self->{'in_portal'} ) { + + return "message from another Hub can't be delivered" ; + } + +# not a local cell or named hub, send it to DEFAULT portal + + my $err = $self->send_to_portal() ; + return $err if $err ; + + return ; +} + +sub send_to_portal { + + my( $self, $to_hub ) = @_ ; + + eval { + + Stem::Portal::send_msg( $self, $to_hub ) ; + } ; + + return "No Stem::Portal Cell was configured" if $@ ; + + return ; +} + + +sub _find_local_cell { + + my ( $self ) = @_ ; + + my $cell_name = $self->{'to'}{'cell'} ; + my $target = $self->{'to'}{'target'} ; + + return lookup_cell( $cell_name, $target ) ; +} + +sub _deliver_to_cell { + + my ( $self, $cell ) = @_ ; + +# set the method + + my $method = ( $self->{'type'} eq 'cmd' ) ? + "$self->{'cmd'}_cmd" : + "$self->{'type'}_in" ; + +#print "METH: $method\n" ; + +# check if we can deliver there or to msg_in + + unless ( $cell->can( $method ) ) { + + return $self->dump( <can( 'msg_in' ) ) ; +missing message delivery methods '$method' and 'msg_in' +DUMP + + $method = 'msg_in' ; + } + + TraceMsg "MSG to $cell $method" ; + + my @response = $cell->$method( $self ) ; + +#print "RESP [@response]\n" ; + +# if we get a response then return it in a message + + if ( @response && $self->{'type'} eq 'cmd' ) { + +# make the response data a reference + + my $response = shift @response ; + my $data = ( ref $response ) ? $response : \$response ; + +#print $self->dump( 'CMD msg' ) ; + my $reply_msg = $self->reply( + 'data' => $data, + ) ; + +#print $reply_msg->dump( 'AUTO REPONSE' ) ; + + $reply_msg->dispatch() ; + } + + if ( $self->{'ack_req'} ) { + + my $reply_msg = $self->reply( 'type' => 'msg_ack' ) ; + + $reply_msg->dispatch() ; + } + + return ; +} + +# dump a message for debugging + +sub dump { + + my( $self, $label, $deep ) = @_ ; + + require Data::Dumper ; + + my $dump = '' ; + $label ||= 'UNKNOWN' ; + + my( $file_name, $line_num ) = (caller)[1,2] ; + + $dump .= <