diff --git a/.gitignore b/.gitignore
index 4f632c572e..8555dcf4f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -170,3 +170,13 @@
# Mac OS X
.DS_Store
+
+sw/ground_segment/lpc21iap/lpc21iap.dSYM/Contents/Info.plist
+
+sw/ground_segment/lpc21iap/lpc21iap.dSYM/Contents/Resources/DWARF/lpc21iap
+
+tests/results/*
+
+sw/logalizer/plotprofile.dSYM/Contents/Resources/DWARF/plotprofile
+
+sw/logalizer/plotprofile.dSYM/Contents/Info.plist
diff --git a/Makefile b/Makefile
index 9285339d7b..e61763d022 100644
--- a/Makefile
+++ b/Makefile
@@ -243,6 +243,7 @@ clean:
$(Q)make -C sw/ext clean
$(Q)find . -name '*~' -exec rm -f {} \;
$(Q)rm -f paparazzi sw/simulator/launchsitl
+ $(Q)rm -rf tests/results/*
cleanspaces:
find ./sw/airborne -name '*.[ch]' -exec sed -i {} -e 's/[ \t]*$$//' \;
@@ -263,9 +264,6 @@ dist_clean_irreversible: clean
ab_clean:
find sw/airborne -name '*~' -exec rm -f {} \;
-test_all_example_airframes: replace_current_conf_xml
- for ap in `grep name conf/conf.xml.example | sed -e 's/.*name=\"//' | sed -e 's/".*//'`; do for airframe in `grep $$ap conf/conf.xml.example | sed -e 's/.*airframe=\"//' | sed -e 's/".*//'`; do for target in `grep target conf/$$airframe | grep name | sed -e 's/.*name=\"//' | sed -e 's/\".*//'`; do echo "Making $$ap $$target"; make -C ./ AIRCRAFT=$$ap clean_ac $$target.compile || exit 1; done; done; done
-
replace_current_conf_xml:
test conf/conf.xml || mv conf/conf.xml conf/conf.xml.backup.`date +%Y%m%d-%H%M%s`
cp conf/conf.xml.example conf/conf.xml
@@ -280,4 +278,9 @@ sw/simulator/launchsitl:
cat src/$(@F) | sed s#OCAMLRUN#$(OCAMLRUN)# | sed s#OCAML#$(OCAML)# > $@
chmod a+x $@
+test: all replace_current_conf_xml
+ cd tests; $(MAKE) $(@)
+
+test_all_example_airframes: replace_current_conf_xml
+ cd tests; $(MAKE) $(@) TARGET_BOARD=examples
diff --git a/conf/Makefile.stm32 b/conf/Makefile.stm32
index 92aa55d708..0a6d4e3263 100644
--- a/conf/Makefile.stm32
+++ b/conf/Makefile.stm32
@@ -78,6 +78,9 @@ OOCD = $(shell if test -e $(TOOLCHAIN_DIR)/bin/openocd ; then echo $(TOOLCHAIN_D
endif
endif
+ifneq ($(BOARD_SERIAL),)
+OOCD_OPTIONS = -c "ft2232_serial $(BOARD_SERIAL)"
+endif
LOADER=/home/poine/work/stm32/stm32loader-a3c51c26ad6c/stm32loader.py
@@ -257,7 +260,7 @@ upload: $(OBJDIR)/$(TARGET).elf
@echo "Using OOCD = $(OOCD)"
@echo " OOCD\t$<"
$(Q)$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \
- -f board/$(OOCD_BOARD).cfg \
+ -f board/$(OOCD_BOARD).cfg $(OOCD_OPTIONS) \
-c init \
-c "reset halt" \
-c "reset init" \
diff --git a/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_fw.xml b/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_fw.xml
index ab07cfb2b8..0769b226a4 100644
--- a/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_fw.xml
+++ b/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_fw.xml
@@ -1,2 +1,192 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_rc.xml b/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_rc.xml
index ab07cfb2b8..fe79b77174 100644
--- a/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_rc.xml
+++ b/conf/airframes/TestHardware/LisaL_v1.1_aspirin_v1.5_overo_rc.xml
@@ -1,2 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_fw.xml b/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_fw.xml
index ab07cfb2b8..3486e98270 100644
--- a/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_fw.xml
+++ b/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_fw.xml
@@ -1,2 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_rc.xml b/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_rc.xml
index ab07cfb2b8..3d0592f223 100644
--- a/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_rc.xml
+++ b/conf/airframes/TestHardware/LisaL_v1.1_b2_v1.2_rc.xml
@@ -1,2 +1,303 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/conf.xml.example b/conf/conf.xml.example
index 84058ae592..6554a44879 100644
--- a/conf/conf.xml.example
+++ b/conf/conf.xml.example
@@ -1,22 +1,92 @@
-
+
-
+
+
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/tests/LisaL/01_upload.t b/tests/LisaL/01_upload.t
new file mode 100644
index 0000000000..c32d5b62b5
--- /dev/null
+++ b/tests/LisaL/01_upload.t
@@ -0,0 +1,95 @@
+#!/usr/bin/perl -w
+
+use Test::More tests => 7;
+use lib "$ENV{'PAPARAZZI_SRC'}/tests/lib";
+use Program;
+use Proc::Background;
+use Ivy;
+
+$|++;
+
+####################
+# Make the airframe
+my $make_compile_options = "AIRCRAFT=LisaLv11_Booz2v12_RC clean_ac ap.compile";
+my $compile_output = run_program(
+ "Attempting to build and upload the firmware.",
+ $ENV{'PAPARAZZI_SRC'},
+ "make $make_compile_options",
+ 0,1);
+unlike($compile_output, '/Aircraft \'LisaLv11_Booz2v12_RC\' not found in/', "The compile output does not contain the message \"Aircraft \'LisaLv11_Booz2v12_RC\' not found in\"");
+unlike($compile_output, '/Error/i', "The compile output does not contain the word \"Error\"");
+
+####################
+# Upload the airframe
+my $make_upload_options = "AIRCRAFT=LisaLv11_Booz2v12_RC BOARD_SERIAL=LISA-L-000156 ap.upload";
+my $upload_output = run_program(
+ "Attempting to build and upload the firmware.",
+ $ENV{'PAPARAZZI_SRC'},
+ "make $make_upload_options",
+ 0,1);
+unlike($upload_output, '/Error/i', "The upload output does not contain the word \"Error\"");
+
+# Start the server process
+my $server_command = "$ENV{'PAPARAZZI_HOME'}/sw/ground_segment/tmtc/server";
+my $server_options = "";
+my $server = Proc::Background->new($server_command, $server_options);
+sleep 2; # The service should die in this time if there's an error
+ok($server->alive(), "The server started successfully");
+
+# Start the link process
+my $link_command = "$ENV{'PAPARAZZI_HOME'}/sw/ground_segment/tmtc/link";
+my @link_options = qw(-d /dev/tty.usbserial-000013FD -s 57600 -transport xbee -xbee_addr 123);
+#my @link_options = qw(-d /dev/tty.usbserial-000013FD -s 57600);
+sleep 2; # The service should die in this time if there's an error
+my $link = Proc::Background->new($link_command, @link_options);
+ok($link->alive(), "The link started successfully");
+
+# Open the Ivy bus and read from it...
+# TODO: learn how to read and write to the Ivy bus
+
+# Shutdown the server and link processes
+ok($server->die(), "The server shutdown successfully.");
+ok($link->die(), "The link shutdown successfully.");
+
+################################################################################
+# functions used by this test script.
+sub run_program
+{
+ my $message = shift;
+ my $dir = shift;
+ my $command = shift;
+ my $verbose = shift;
+ my $dont_fail_on_error = shift;
+
+ warn "$message\n" if $verbose;
+ if (defined $dir)
+ {
+ $command = "cd $dir;" . $command;
+ }
+ my $prog = new Program("bash");
+ my $fh = $prog->open("-c \"$command\"");
+ warn "Running command: \"". $prog->last_command() ."\"\n" if $verbose;
+ $fh->autoflush(1);
+ my @output;
+ while (<$fh>)
+ {
+ warn $_ if $verbose;
+ chomp $_;
+ push @output, $_;
+ }
+ $fh->close;
+ my $exit_status = $?/256;
+ unless ($exit_status == 0)
+ {
+ if ($dont_fail_on_error)
+ {
+ warn "Error: The command \"". $prog->last_command() ."\" failed to complete successfully. Exit status: $exit_status\n" if $verbose;
+ }
+ else
+ {
+ die "Error: The command \"". $prog->last_command() ."\" failed to complete successfully. Exit status: $exit_status\n";
+ }
+ }
+ return wantarray ? @output : join "\n", @output;
+}
+
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000000..f6eb2bd4b5
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,19 @@
+Q = @
+PERL = /usr/bin/perl
+TEST_VERBOSE = 0
+ifeq ($(TARGET_BOARD),)
+ TARGET_BOARD = *
+endif
+TEST_FILES := $(shell ls $(TARGET_BOARD)/*.t)
+
+ifneq ($(JUNIT),)
+ PERLENV=PERL_TEST_HARNESS_DUMP_TAP=$(PAPARAZZI_SRC)/tests/results
+ RUNTESTS=use TAP::Harness; TAP::Harness->new({ formatter_class => 'TAP::Formatter::JUnit', verbosity => $(TEST_VERBOSE), merge => 1, } )->runtests(qw($(TEST_FILES)))
+else
+ PERLENV=
+ RUNTESTS=use TAP::Harness;TAP::Harness->new( { verbosity => $(TEST_VERBOSE) } )->runtests(qw($(TEST_FILES)))
+endif
+
+test:
+ $(Q)$(PERLENV) $(PERL) "-e" "$(RUNTESTS)"
+
diff --git a/tests/examples/01_compile_all_example_targets.t b/tests/examples/01_compile_all_example_targets.t
new file mode 100644
index 0000000000..d8cb760458
--- /dev/null
+++ b/tests/examples/01_compile_all_example_targets.t
@@ -0,0 +1,109 @@
+#!/usr/bin/perl -w
+
+use Test::More;
+use lib "$ENV{'PAPARAZZI_SRC'}/tests/lib";
+use XML::Simple;
+use Program;
+use Data::Dumper;
+use Config;
+
+$|++;
+my $examples = XMLin("$ENV{'PAPARAZZI_SRC'}/conf/conf.xml.example");
+
+use Data::Dumper;
+
+ok(1, "Parsed the example file");
+foreach my $example (sort keys%{$examples->{'aircraft'}})
+{
+ #next unless $example =~ m#easystar#i;
+ my $airframe = $examples->{'aircraft'}->{$example}->{'airframe'};
+ my $airframe_config = XMLin("$ENV{'PAPARAZZI_SRC'}/conf/$airframe");
+ foreach my $process (sort keys %{$airframe_config->{'firmware'}})
+ {
+ if ($process =~ m#setup|fixedwing|rotorcraft|lisa_test_progs#)
+ {
+ #warn "EX: [$example] ". Dumper($airframe_config->{'firmware'}->{$process}->{'target'});
+ foreach my $target (sort keys %{$airframe_config->{'firmware'}->{$process}->{'target'}})
+ {
+ next unless scalar $airframe_config->{'firmware'}->{$process}->{'target'}->{$target}->{'board'};
+
+ # Exclude some builds on Mac as they are currently broken.
+ next if ( ($Config{'osname'} =~ m#darwin#i) and ($example =~ m#LISA_ASCTEC_PIOTR|LisaLv11_Booz2v12_RC|BOOZ2_A1#i) and ($target =~ m#sim#i) );
+
+ #warn "EXAMPLE: [$example] TARGET: [$target]\n";
+ my $make_upload_options = "AIRCRAFT=$example clean_ac $target.compile";
+ my $upload_output = run_program(
+ "Attempting to build the firmware $target for the airframe $example.",
+ $ENV{'PAPARAZZI_SRC'},
+ "make $make_upload_options",
+ $ENV->{'TEST_VERBOSE'},1);
+ unlike($upload_output, '/Error/i', "The upload output does not contain the word \"Error\"");
+ }
+ }
+ elsif ($process =~ m#target#)
+ {
+ #warn "EXT: [$example] ". Dumper($airframe_config->{'firmware'}->{$process});
+ foreach my $target (sort keys %{$airframe_config->{'firmware'}->{$process}})
+ {
+ next unless scalar $airframe_config->{'firmware'}->{$process}->{$target}->{'board'};
+
+ # Exclude some builds on Mac as they are currently broken.
+ next if ( ($Config{'osname'} =~ m#darwin#i) and ($example =~ m#LISA_ASCTEC_PIOTR|LisaLv11_Booz2v12_RC|BOOZ2_A1#i) and ($target =~ m#sim#i) );
+
+ #warn "EXAMPLET: [$example] TARGET: [$target]\n";
+ my $make_upload_options = "AIRCRAFT=$example clean_ac $target.compile";
+ my $upload_output = run_program(
+ "Attempting to build the firmware $target for the airframe $example.",
+ $ENV{'PAPARAZZI_SRC'},
+ "make $make_upload_options",
+ $ENV->{'TEST_VERBOSE'},1);
+ unlike($upload_output, '/Error/i', "The upload output does not contain the word \"Error\"");
+ }
+ }
+ }
+}
+
+done_testing();
+
+################################################################################
+# functions used by this test script.
+sub run_program
+{
+ my $message = shift;
+ my $dir = shift;
+ my $command = shift;
+ my $verbose = shift;
+ my $dont_fail_on_error = shift;
+
+ warn "$message\n" if $verbose;
+ if (defined $dir)
+ {
+ $command = "cd $dir;" . $command;
+ }
+ my $prog = new Program("bash");
+ my $fh = $prog->open("-c \"$command\"");
+ warn "Running command: \"". $prog->last_command() ."\"\n" if $verbose;
+ $fh->autoflush(1);
+ my @output;
+ while (<$fh>)
+ {
+ warn $_ if $verbose;
+ chomp $_;
+ push @output, $_;
+ }
+ $fh->close;
+ my $exit_status = $?/256;
+ unless ($exit_status == 0)
+ {
+ if ($dont_fail_on_error)
+ {
+ warn "Error: The command \"". $prog->last_command() ."\" failed to complete successfully. Exit status: $exit_status\n" if $verbose;
+ }
+ else
+ {
+ die "Error: The command \"". $prog->last_command() ."\" failed to complete successfully. Exit status: $exit_status\n";
+ }
+ }
+ return wantarray ? @output : join "\n", @output;
+}
+
diff --git a/tests/lib/Ivy.pm b/tests/lib/Ivy.pm
new file mode 100644
index 0000000000..a580b6e891
--- /dev/null
+++ b/tests/lib/Ivy.pm
@@ -0,0 +1,3091 @@
+#
+# Ivy, Perl interface
+#
+# Copyright 1997-2009
+# Centre d'Études de la Navigation Aérienne
+#
+# Authors: Alexandre Bustico
+# Stéphane Chatty
+# Hervé Damiano
+# Christophe Mertz
+#
+# All functions
+#
+# $Id: Ivy.pm 3491 2011-06-20 09:35:58Z bustico $
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU LGPL Libray 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 Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library 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,
+# or refer to http://www.gnu.org/copyleft/lgpl.html
+#
+##################################################################
+
+package Ivy ;
+
+use Sys::Hostname;
+use IO::Socket;
+use strict;
+use Time::HiRes qw(gettimeofday);
+use Carp;
+use IO::Socket::Multicast;
+use File::Temp;
+
+use vars qw($VERSION);
+use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
+use Socket qw(TCP_NODELAY);
+
+# to compute the VERSION from the CVS tag (or if no tag, as the cvs file revision)
+my $TAG= q$Name: $;
+my $REVISION = q$Revision: 3491 $ ;
+$VERSION = '1.49' ; # for Makefile.PL
+($VERSION) = $TAG =~ /^\D*([\d_]+)/ ;
+if (defined $VERSION and $VERSION ne "_") {
+ $VERSION =~ s/_/\./g;
+}
+else {
+ $VERSION = $REVISION;
+}
+
+#############################################################################
+#### PROTOTYPES #####
+#############################################################################
+sub init; # methode de classe, permet de renseigner
+ # tous les parametres globaux. Ces parametres
+ # seront utilises par new si ils ne sont pas
+ # donnes lors de l'appel de new.
+
+
+sub new ($%); # verifie la validite de tous les parametres,
+ # cree et retourne un objet Ivy. Les parametres
+ # appName, networks, messWhenReady, peuvent
+ # etre donnes, meme si ils ont deja ete
+ # donnes dans init, dans ce cas, ce sont ceux
+ # de new qui prevalent
+
+sub start; # debut de l'integration au bus :
+ # - cree la socket d'application, recupere le no
+ # de port
+ # - cree la socket supervision
+ # - envoie le "no de port"
+ # - bind le file descriptor de la socket de
+ # supervision a la fonction getBonjour pour
+ # traiter les bonjours
+ # - bind le fd de connection sur la fonction
+ # getConnections
+ # pour etablir les connections "application"
+
+sub DESTROY ($); # - envoie un BYE et clôt les connections
+
+sub bindRegexp ($$$;$$) ; # permet d'associer une regexp avec un callBack
+ # ou d'annuler une precedente association
+sub bindRegexpOneShot ($$$); # permet d'associer une regexp avec un callBack avec
+ # desabonnement automatique après reception du premier
+ # message qui matche
+
+sub changeRegexp ($$$); # permet de changer une regexp d'un abonnement
+ # precedemment fait avec bindRegexp
+
+sub bindDirect; # permet d'associer un identifiant de msg direct
+ # avec une fonction de callBack, ou de l'annuler
+
+sub sendMsgs; # envoie une liste de messages
+sub sendAppNameMsgs; # envoie une liste de messages precedes
+ # du nom de l'application
+sub sendDirectMsgs; # envoie une liste de messages directs a une appli
+sub sendDieTo; # envoie un <> a une appli
+sub ping ($$\&); # teste qu'une appli soit encore vivante
+sub dumpTable ($$\&); # demande à une appli de dumper sa table de regexps dans
+ # un fichier à des fins de debug
+sub mainLoop (;$); # la mainloop locale (sans tk)
+sub stop (); # methode de classe : on delete les bus, mais
+ # on reste dans la mainloop
+sub exit (); # methode de classe : on delete tous les
+ # bus (donc on ferme proprement toutes les
+ # connexions).
+ # Si on est en mainloop locale on sort de la
+ # mainloop, le code qui suit l'appel mainLoop
+ # sera execute.
+ # par contre si on est en mainloop Tk,
+ # il faut en plus detruire la mainwindow
+ # pour sortir de la mainloop;
+sub after ($$;$); # temps en millisecondes, callback
+sub repeat ($$;$); # temps en millisecondes, callback
+sub afterCancel ($;$); # l'id d'un cancel ou d'un repeat
+sub afterResetTimer ($;$); # pour ré-armer un timer non-encore déclenché à sa valeur initiale
+sub fileEvent ($$;$); # associe un fd a un callback pour la mainloop locale
+sub getUuid ($); # rend un identifiant unique d'application sur le bus, utile
+ # pour faire des genres de rpc avec un abonnement desabonnement dynamique
+
+################ PRIVEE ####################################################
+sub _getBonjour ($); # lit le (ou les) bonjour(s) sur le canal de supervision
+ # et se connecte, verifie qu'il ne se reponds pas lui
+ # meme, ni qu'il ne repond pas a un service deja connecte
+
+sub _getConnections ($); # est appele lors d'une demande de connection :
+ # accepte la connection et mets a jour @sendRegList
+ # rajoute le fd du canal aux fd a scruter dans la
+ # boucle d'evenements
+
+sub _getMessages ($$); # est appele lorqu'un message arrive
+
+sub _sendWantedRegexp ($$); # envoie les regexp a l'appli distante
+
+sub _sendLastRegexpToAllreadyConnected ($$) ; # envoie la derniere regexp
+ # pushee dans @recCbList
+ # a toutes les applis deja
+ # connectees
+sub _removeFileDescriptor ($$$); # on vire un fd et les structures associees
+sub _sendErrorTo ($$$); #(fd, error) envoie un message d'erreur a un fd
+sub _sendDieTo ($$); #(fd) envoie un message de demande de suicide a un fd
+sub _sendMsgTo ($$\$); # (fd, message)
+sub _pong ($$$); # (fd)
+sub _dumpTable ($$$); # (fd)
+sub _tkFileEvent ($$); # associe un fd a un callback pour la mainloop tk
+sub _scanAfter () ; # parse si il faut appeler un callback associe a un after
+sub _myCanRead (); # interface au select
+sub _scanConnStatus ($$$@); # verifie les connections effectuees et
+ # appelle la fonction $statusFunc
+sub _inetAdrByName ($$); # transforme une adresse inet native en chaine
+ # $host:$port
+sub _getHostByAddr ($);
+sub _toBePruned ($$$);
+sub _parseIvyBusParam ($); # prends une adresse de bus de la forme
+ # 143.196.53,DGAC-CENATLS:2010 et
+ # renvoie une liste de deux elements :
+ # un numero de port et une ref sur une
+ # liste d'adresses addr_inet
+
+sub _substituteEscapedChar ($$); #permet de transformer une regexp etendue
+ # 'perl' en regexp de base
+
+sub _callCongestionCb ($$$); # appelle la callback de notification de congestion,
+ # si elle a été définie par l'utilisateur
+
+sub _getNameByFileDes ($$); # retourne le nom de l'appi en fonction du filedes
+ # de la socket
+sub _univSend ($$$); # effectue les send de manière bloquante ou non bloquante
+ # et accumule les messages si la socket est bloquée
+
+sub _regexpGen ($$$); # routines for generating regexps wich matches
+sub _strictPosRegexpGen ($$$$); # numerical interval using the special syntax
+sub _genAtRank ($$$); # (?I-20#-10) or (?I-20#-10i)
+sub _genPreRank ($$$);
+sub _genRank ($$$);
+sub _genPostRank ($);
+sub _nextMax ($$);
+sub _max ($$);
+sub _min ($$);
+
+#############################################################################
+#### CONSTANTES #####
+#############################################################################
+use constant MSG_FMT => "%d %d\002%s\n";
+
+# par defaut, on diffuse le bonjour en local
+use constant BROADCAST_ADDRS => "127.255.255.255" ;
+use constant BROADCAST_PORT => "2010";
+
+use constant BYE => 0;
+use constant REGEXP => 1;
+use constant MSG => 2;
+use constant ERROR => 3;
+use constant DELREGEXP => 4;
+use constant ENDREGEXP => 5;
+use constant APP_NAME => 6;
+use constant DIRECT_MSG => 7;
+use constant DIE => 8;
+use constant PING => 9;
+use constant PONG => 10;
+use constant DUMP_TABLES => 11;
+use constant DUMP_TABLES_FILE => 12;
+use constant IVY_PROTOCOLE_VERSION => 3;
+
+use constant AFTER => 0;
+use constant REPEAT => 1;
+use constant TK_MAINLOOP => 0;
+use constant LOCAL_MAINLOOP => 1;
+use constant CALL_BY_VALUE => 0;
+use constant CALL_BY_REF => 1;
+use constant BIND_ONCE => 2;
+use constant MAX_TIMOUT => 1000;
+
+
+# TCP_NODELAY is for a specific purpose; to disable the Nagle buffering
+# algorithm. It should only be set for applications that send frequent
+# small bursts of information without getting an immediate response,
+# where timely delivery of data is required (the canonical example is
+# mouse movements).
+# Since Ivy is most of the time used to send events, we will priviligiate
+# lag over throughtput, so _TCP_NO_DELAY_ACTIVATED is set to 1
+use constant TCP_NO_DELAY_ACTIVATED => 1;
+
+
+# pour pouvoir employer les regexps perl. Attention lors de l'utilisation
+# ne pas mettre un \n dans une chaine entre "" car l'\n sera interprete.
+use constant REG_PERLISSISME => ('w' => '[a-zA-Z0-9_]',
+ 'W' => '[^a-zA-Z0-9_]',
+ 's' => "[\t ]",
+ 'S' => "[^\t ]",
+ 'd' => '[0-9]',
+ 'D' => '[^0-9]',
+ 'n' => '', # Il ne faut pas mettre d'\n :
+ # c'est un delimiteur pour le bus
+ 'e' => '[]') ;
+
+#############################################################################
+#### VARIABLES de CLASSE #####
+#############################################################################
+
+# l'objet Ivy utilise par defaut quand le programmeur
+# utilise le mode de compatibilite de la version 3, et ne
+# manipule pas d'objets
+my $globalIvy ;
+
+# optimisation : si l'on connait les sujets des messages
+# qu'on envoie, cette variable contient une liste de
+# sujets qui doivent matcher les regexps d'abonnement
+# pour que celle ci se soient pas eliminees
+my @topicRegexps;
+
+# les adresses de reseau sur lesquelles ont broadcaste
+# suivies du No de port :
+# exemples : "143.196.1.255,143.196.2.255:2010"
+# "DGAC-CENATLS-PII:DGAC-CENATLS:2010"
+# ":2010" <= dans ce cas c'est la valeur
+# de reseau de broadcast par defaut qui est prise : 127.255.255.255
+# c.a.d local a la machine
+my $ivyBus ;
+
+# le nom de l'appli pour le bus
+my $appName ;
+
+# message a envoyer a un canal lorsqu'on
+# a recu le message endregexp.
+my $messWhenReady ;
+
+# fonction de cb appelee lorsque l'appli a recu l'ordre
+# de quitter, on peut dans ce callback fermer
+# proprement les ressources avant de sortir.
+# ps : ne pas fasire d'exit dans le callback,
+# c'est le bus qui s'en charge
+my $onDieFunc;
+
+
+# permet de donner des valeurs successives aux constantes permettant
+# d'acceder aux differents champs de l'objet
+my $constantIndexer =0;
+
+# pointeur sur la fonction permettant d'associer
+# des callbacks a un file desc, (ainsi que de les enlever)
+my $fileEventFunc;
+
+# dans le cas ou l'on soit dans une mainLoop
+# locale, cette var pointe une un objet
+# de type IO::Select, qui est l'ensemble des file descriptor
+# des sockets que l'on scrute en lecture (attente des messages)
+my $localLoopSelRead;
+
+# dans le cas ou l'on soit dans une mainLoop
+# locale, cette var pointe une un objet
+# de type IO::Select qui est l'ensemble des file descriptor
+# des sockets que l'on scrute en ecriture : les fd congestionnés
+# des "slow agent" que l'on surveille pour ecrire dedans dès que
+# possible (mode non bloquant)
+my $localLoopSelWrite;
+
+# table d'ass. handle -> callback
+my %localBindByHandle;
+
+# table d'ass. fhd -> nom appli
+my %nameByHandle;
+
+# tableau d'ass [AFTER ou REPEAT,
+# timeTotal, deadLine, [callback, arg, arg, ...]]
+my %afterList=();
+
+my $afterId = 0;
+
+# timeout le plus petit pour le select
+my $selectTimout = MAX_TIMOUT;
+my $loopMode;
+
+# liste des bus actifs
+my %allBuses = ();
+
+# cache des nom retournés par gethostbyaddr pour _getHostByAddr
+my %hostNameByAddr = ();
+
+my $pingId = 1; # identifiant d'un ping (renvoyé par le pong)
+
+#my $trace;
+
+#############################################################################
+#### CLEFS DES VARIABLES D'INSTANCE #####
+#### #####
+#### l'objet Ivy sera 'blessed' sur une reference sur un array et non #####
+#### sur une table de hash comme pratique courament de facon a #####
+#### 1/ optimiser au niveau vitesse #####
+#### 2/ avoir des clefs sous forme de symboles (use constant...) #####
+#### et nom des clefs sous forme de chaines de caracteres #####
+#### de facon a eviter des erreurs #####
+#### #####
+#### #####
+#############################################################################
+use constant servPort => $constantIndexer++;
+use constant neededApp => $constantIndexer++;
+use constant statusFunc => $constantIndexer++;
+use constant slowAgentFunc => $constantIndexer++;
+use constant blockOnSlowAgent => $constantIndexer++;
+use constant supSock => $constantIndexer++;
+use constant connSock => $constantIndexer++;
+use constant sockList => $constantIndexer++;
+use constant threadList => $constantIndexer++;
+use constant appliList => $constantIndexer++;
+use constant sendRegList => $constantIndexer++;
+use constant sendRegListSrc => $constantIndexer++;
+use constant topicRegexps => $constantIndexer++;
+use constant recCbList => $constantIndexer++;
+use constant directCbList => $constantIndexer++;
+use constant cnnxion => $constantIndexer++;
+use constant connectedUuid => $constantIndexer++;
+use constant bufRecByCnnx => $constantIndexer++;
+use constant bufEmiByCnnx => $constantIndexer++;
+use constant broadcastPort => $constantIndexer++;
+use constant broadcastBuses => $constantIndexer++;
+use constant useMulticast => $constantIndexer++;
+use constant appName => $constantIndexer++;
+use constant messWhenReady => $constantIndexer++;
+use constant uuid => $constantIndexer++;
+use constant pongQueue => $constantIndexer++;
+use constant readyToSend => $constantIndexer++;
+
+#############################################################################
+#### METHODES PUBLIQUES #####
+#############################################################################
+sub init
+{
+ if (defined $fileEventFunc) {
+ print STDERR "Ivy warning: init has already been called\n";
+ return;
+ }
+
+
+ srand(); # initialisation du generateur aléatoire qui sert à produire un UUID
+ my $class = shift if (@_ and $_[0] eq __PACKAGE__);
+ my (%options) = @_;
+
+ # valeurs par defaut pour le parametre : variable d'environnement
+ # ou valeur cablee, a defaut
+ my $default_ivyBus = defined $ENV{"IVYBUS"} ?
+ $ENV{"IVYBUS"} :
+ BROADCAST_ADDRS.':'.BROADCAST_PORT;
+
+ my %optionsAndDefaults = ( #PARAMETRES OBLIGATOIRES
+ -loopMode => undef,
+ # TK ou LOCAL
+
+ -appName => undef,
+ # nom de l'appli
+
+ # PARAMETRES FACULTATIFS (avec valeurs par defaut)
+
+ # les adresses de reseau sur lesquelles ont broadcaste
+ # suivies du No de port :
+ # exemples : "143.196.1.255,143.196.2.255:2010"
+ # "DGAC-CENATLS-PII:DGAC-CENATLS:2010"
+ # ":2010" <= dans ce cas c'est la valeur
+ # de reseau de broadcast par defaut qui est prise :
+ # 127.255.255.255 c.a.d local a la machine
+ -ivyBus => $default_ivyBus,
+
+ -messWhenReady => "_APP NAME READY",
+ # message de synchro a envoyer quand pret
+
+ -onDieFunc => [sub {}],
+ # fonction de cb appelee lorsque l'appli a recu l'ordre
+ # de quitter, on peut dans ce callback fermer
+ # proprement les ressources avant de sortir.
+ # ps : ne pas faire d'exit dans le callback,
+ # c'est le bus qui s'en charge
+
+
+ -filterRegexp => [],# nouvelle api cohérente avec ivy-c, c++, java
+ -pruneRegexp => [], # obsolete
+ # optimisation : si l'on connait les sujets des messages
+ # qu'on envoie, on fournit la liste des sujets
+ # et les regexps qui ne matchent pas
+ # ces sujets sont eliminees.
+ ) ;
+
+ # on examine toutes les options possibles
+ foreach my $opt (keys %optionsAndDefaults) {
+ # si un parametre a ete fourni, ignorer les valeurs par defaut
+ next if defined $options{$opt} ;
+ # sinon, prendre la valeur par defaut si elle existe
+ if (defined $optionsAndDefaults{$opt}) {
+ $options{$opt} = $optionsAndDefaults{$opt} ;
+ # sinon, on jette l'eponge
+ } else {
+ croak "Error in Ivy::init: option $opt is mandatory\n";
+ }
+ }
+
+ # on examine toutes les options fournies, pour detecter les inutiles
+ foreach my $opt (keys %options) {
+ unless (exists ($optionsAndDefaults{$opt})) {
+ carp "Warning in Ivy::init: option $opt is unknown";
+ }
+ }
+
+ $ivyBus = $options{-ivyBus};
+ $appName = $options{-appName} ;
+ $onDieFunc = $options{-onDieFunc} ;
+
+
+ # trace pour le debug
+ my $verFile = 0;
+# while (!open ($trace, , ">>", "/tmp/Ivy_$appName:$verFile.log")) {$verFile++};
+# syswrite ($trace, "DEBUT\n");
+
+
+ if (scalar (@{$options{-pruneRegexp}})) {
+ carp "-pruneRegexp is *OBSOLETE*. -filterRegexp should be used instead\n";
+ $options{-filterRegexp} = $options{-pruneRegexp} unless defined $options{-filterRegexp};
+ }
+
+ @topicRegexps = @{$options{-filterRegexp}};
+ $messWhenReady = $options{-messWhenReady} eq "_APP NAME READY" ?
+ "$appName READY" :
+ $options{-messWhenReady};
+
+
+ if ($options{-loopMode} =~ /local/i) {
+ # mode boucle d'evenement locale
+ use IO::Select;
+ $fileEventFunc = \&fileEvent ;
+ $localLoopSelRead = IO::Select->new ();
+ $localLoopSelWrite = IO::Select->new ();
+ $loopMode = LOCAL_MAINLOOP;
+ } elsif ($options{-loopMode} =~ /tk/i) {
+ # mode boucle d'evenement de TK
+ $fileEventFunc = \&_tkFileEvent ;
+ $loopMode = TK_MAINLOOP;
+ } else {
+ croak "Error in Ivy::init, argument loopMode must be either TK or LOCAL\n";
+ }
+
+ $SIG{'PIPE'} = 'IGNORE' ;
+} # end init
+
+############# METHODE DE CLASSE NEW
+sub new ($%)
+{
+ my ($class, %options) = @_;
+ my $self = [];
+ $#{$self} = $constantIndexer; # on predimensionne le tableau
+ bless ($self, $class);
+
+ # on verifie que la methode de classe init ait ete appelee
+ unless ((defined $appName) && ($appName ne '')) {
+ croak "Error in Ivy::new, you should have called Ivy->init () first.";
+ }
+
+ # No de port tcp du serveur
+ $self->[servPort] = '';
+
+ # liste des applis necessaires a l'appli locale
+ $self->[neededApp] = [];
+
+ # callback prenant en param 3 refs sur des listes :
+ # [applis presentes, appli absentes, hash_applis_present]
+ # cette fonction est appelee :
+ # - tout les pollingTime tant que toutes les applis
+ # ne sont pas presentes
+ # - des qu'une appli se connecte
+ # - lorsqu'une appli se deconnecte
+ $self->[statusFunc] = '';
+ $self->[slowAgentFunc] = '';
+
+ # callback prenant en param 1 refs sur une liste :
+ # [ref sur fonction, parametres]
+
+ # socket de supervision en lecture/ecriture
+ $self->[supSock] = '';
+
+ # socket de connexion tcp
+ $self->[connSock] = '';
+
+ # tab ass : nom du fd => fd
+ $self->[sockList] = {};
+
+ # tab ass : nom de l'appli => fd
+ $self->[appliList] = {};
+
+ # tableau ass de liste du type
+ # sockId => [fonction, fonction, ...]
+ # pour savoir quoi envoyer a qui
+ # les fonctions anonymes sont compilees
+ # dynamiquement a la reception des messages REGEXP
+ # et filtrent les mess a envoyer et les envoient
+ # au besoin
+ $self->[sendRegList] = {};
+
+ # tableau ass de liste du type
+ # sockId => ["regexp"...]
+ # pour connaitre la valeur des regexp meme apres compilation
+ $self->[sendRegListSrc] = {};
+
+ # liste des topics qu'on envoie si on
+ # filtre les regexps
+ $self->[topicRegexps] = [];
+
+ # liste de ref sur des couples
+ # (regexp,callBack) les callbacks
+ # sont appeles lors de
+ # la reception de messages en fonction
+ # du numero de regexp.
+ $self->[recCbList] = [];
+
+ # liste de callBack pour les messages directs
+ $self->[directCbList] = [];
+
+ # tableau ass : clef = nom:numero_de port
+ # permet de verifier qu'on ne se connecte pas
+ # sur nous meme et qu'on ne se reconnecte
+ # pas sur un service en cas de bonjours repetes
+ # valeur : nom de l'application
+ $self->[cnnxion] = {};
+ $self->[connectedUuid] = {};
+
+
+ # tableau associatif, clef => file desc,
+ # valeur :buffer au cas ou la lecture ne se termine
+ # pas par \n, de maniere a resegmenter les messages
+ $self->[bufRecByCnnx] = {};
+
+ # tableau associatif, clef => file desc,
+ # valeur :buffer au cas ou l'écriture bloque sur un fd
+ # pour eviter de bloquer la mainloop sur un send on bufferise
+ # les données dans le process
+ $self->[bufEmiByCnnx] = {};
+
+ # identifiant unique
+ $self->[uuid] = sprintf ("%d%d", time(), rand()*(2**31));
+
+ # queue de gestion des pings et des dumpTable:
+ # clef : socket fd, valeur :liste [timestamp, machine:port, callBack]
+ $self->[pongQueue] = {};
+
+
+
+
+ my %optionsAndDefaults = (
+ -appName => $appName,
+ # nom de l'appli
+
+ # PARAMETRES FACULTATIFS (avec valeurs par defaut)
+ -messWhenReady => $messWhenReady,
+ # message de synchro a envoyer quand pret
+
+
+ # PARAMETRES FACULTATIFS (avec valeurs par defaut)
+
+ # les adresses de reseau sur lesquelles ont broadcaste
+ # suivies du No de port :
+ # exemples : "143.196.1.255,143.196.2.255:2010"
+ # "DGAC-CENATLS-PII:DGAC-CENATLS:2010"
+ # ":2010" <= dans ce cas c'est la valeur
+ # de reseau de broadcast par defaut qui est prise :
+ # 127.255.255.255 c.a.d local a la machine
+ -ivyBus => $ivyBus,
+
+ -neededApp => [],
+ # liste des appplis necessaires
+
+ -statusFunc => sub {},
+ # fonction de callBack qui sera appelee tant que
+ # toutes les applis necessaires ne sont pas presentes,
+ # et des que toutes les applis necessaires sont
+ # presentes, et si une appli necessaire se deconnecte
+ # les parametres passes à la callback sont :
+ # °[liste des applis presentes],
+ # °[liste des applis absentes],
+ # °[table de hash, clefs = applis presentes, valeurs = nombre d'applis .
+ # normalement ce nombre devrait etre 1, sinon
+ # ca veut dire que plus d'une appli de meme nom
+ # tourne sur le meme bus : danger !!
+ # ° nom de l'appli qui genere l'evenement
+ # ° evenement : 'subscribing'|'filtered'|'unsubscribing'|'died'|'new'
+ # ° adresse
+ # ° regexp si c'est un abonnement, un desabonnement ou un filtered
+
+ -blockOnSlowAgent => 1,
+ # comportement lorque un ou plusieurs des agents connectés
+ # ne consomment pas suffisement rapidement les messages. Ou bien on laisse
+ # le send bloquer, ou bien on accumule les messages localement
+ # en attendant que l'agent congestionné soit capable de les traiter.
+ # cette méthode a l'avantage de ne pas bloquer la mainloop locale,
+ # par contre la consomation mémoire peut devenir problématique.
+
+ -slowAgentFunc => sub {},
+ # fonction de callBack qui sera appelee si l'envoi de messages
+ # à un agent n'est plus possible parce que l'agent en question ne
+ # consomme pas ses messages assez vite, ou si un agent qui etait
+ # dans cette etat retrouve sa capacité à lire les messages.
+ # les paramètres passés à la callback sont
+ # nom de l'appli,
+ # adresse
+ # etat (congestion = 1, decongestion = 0)
+
+ -filterRegexp => [@topicRegexps],
+ -pruneRegexp => [], # obsolete
+ # optimisation : si l'on connait les sujets des messages
+ # qu'on envoie, on fournit la liste des sujets
+ # et les regexps qui ne matchent pas
+ # ces sujets sont eliminees.
+ ) ;
+
+
+ # on examine toutes les options possibles
+ foreach my $opt (keys %optionsAndDefaults) {
+ # si un parametre a ete fourni, ignorer les valeurs par defaut
+ next if defined $options{$opt} ;
+ # sinon, prendre la valeur par defaut si elle existe
+ if (defined $optionsAndDefaults{$opt}) {
+ $options{$opt} = $optionsAndDefaults{$opt} ;
+ # sinon, on jette l'eponge
+ } else {
+ croak "Error in Ivy::new: option $opt is mandatory\n";
+ }
+ }
+
+ if (scalar (@{$options{-pruneRegexp}})) {
+ carp "-pruneRegexp is *OBSOLETE*. -filterRegexp should be used instead\n";
+ $options{-filterRegexp} = $options{-pruneRegexp} unless defined $options{-filterRegexp};
+ }
+
+ # on examine toutes les options fournies, pour detecter les inutiles
+ foreach my $opt (keys %options) {
+ unless (exists ($optionsAndDefaults{$opt})) {
+ carp "Warning in Ivy::new, option $opt is unknown";
+ }
+ }
+
+ $self->[appName] = $options{-appName} ;
+ $self->[messWhenReady] = $options{-messWhenReady} ;
+ @{$self->[neededApp]} = @{$options{-neededApp}} ;
+ $self->[statusFunc] = $options{-statusFunc} ;
+ $self->[slowAgentFunc] = $options{-slowAgentFunc} ;
+ $self->[blockOnSlowAgent] = $options{-blockOnSlowAgent} ;
+
+ $self->[topicRegexps] = $options{-filterRegexp} ;
+ $allBuses{$self} = $self;
+
+ ($self->[useMulticast], $self->[broadcastPort], $self->[broadcastBuses]) =
+ _parseIvyBusParam ($options{-ivyBus});
+ $self->[readyToSend] = {};
+
+
+ return ($self);
+} # end new
+
+############### METHODE IVY DESTROY
+sub DESTROY ($)
+{
+ my $self = shift;
+ return unless exists $allBuses{$self};
+ # print ("DBG> DESTROY appele sur l'objet $self\n");
+
+ # pour toutes les connections
+ foreach my $fd (values %{$self->[sockList]}) {
+ # send ($fd, sprintf (MSG_FMT, BYE, 0, ""), 0)
+ # or $self->_removeFileDescriptor ($fd);
+ # the 2 previous lines seems to works with other ivy-perl applis
+ # but DO NOT work with ivy-c api.
+ # the 2 next lines works. This has to been validated! CM 21/12/2000
+ if (defined $fd) {
+ _univSend ($self, $fd, sprintf (MSG_FMT, BYE, 0, ""));
+ $self->_removeFileDescriptor ($fd, 'DESTROY');
+ }
+ }
+
+ # on clot la socket de signalisation (UDP)
+ # print "DBG> fermeture de supSock ", $self->[supSock] ,"\n";
+ # the following test has been expanded to avoid some nasty bug
+ # which appeared when upgrading from perl-tk 800.023 to 800.024
+ $self->[supSock]->close() if ($self->[supSock] and $self->[supSock]->connected());
+ delete $allBuses{$self};
+
+ # on clot la socket de connection
+ # print "DBG> fermeture de connSock ", $self->[connSock], "\n";
+ # the following test has been expanded to avoid some nasty bug
+ # which appeared when upgrading from perl-tk 800.023 to 800.024
+ $self->[connSock]->close() if ($self->[connSock] and $self->[connSock]->connected());
+ undef (@$self);
+# close ($trace);
+} # end DESTROY
+
+############### METHODE DE CLASSE STOP
+sub stop ()
+{
+ foreach my $bus (values %allBuses) {
+ $bus->DESTROY();
+ } # pour toutes les connections
+} # end stop
+
+
+############## METHODE DE CLASSE EXIT
+sub exit ()
+{
+ Ivy::stop ();
+ if (defined $localLoopSelRead) {
+ undef $localLoopSelRead ;
+ undef $localLoopSelWrite ;
+ } else {
+ Tk::exit ();
+ }
+} # end exit
+
+############### PROCEDURE BUS START
+sub start
+{
+ my $self;
+
+ # compatibility for version 3 interface, ie. no objects manipulated by programmer
+ if (not @_ or ref ($_[0]) ne __PACKAGE__) {
+ init (@_);
+ $self = $globalIvy = new Ivy;
+ } else {
+ $globalIvy = $self = shift;
+ }
+
+ if ($self->[connSock]) {
+ print "*** the Ivy bus is already started\n";
+ return;
+ }
+ # cree la socket de connexion, recupere le no de port
+ my $connSock = $self->[connSock] = IO::Socket::INET->new(Listen => 128,
+ Proto => 'tcp',
+ ReuseAddr => 1) ;
+ # on memorise tout ca, ce qui evitera par la suite de se
+ # repondre a soi-meme. On le fait sous nos deux noms :
+ # le nom de machine et 'localhost'
+ my ($n, $al, $t, $l, @hostAddrs) = gethostbyname (hostname());
+ foreach my $a (@hostAddrs) {
+# syswrite ($trace, ("DBG> I am " . unpack ('CCCC', $a) . $connSock->sockport . "\n"));
+ $self->[cnnxion]->{"$a:". $connSock->sockport} = "\004";
+ }
+
+ my $localhostAddr = (gethostbyname ('localhost'))[4] ;
+ $self->[cnnxion]->{"$localhostAddr:". $connSock->sockport} = "\004";
+
+ # le message de bonjour à envoyer: "no de version no de port"
+ my $bonjourMsg = sprintf ("%d %d %s %s\n", IVY_PROTOCOLE_VERSION, $connSock->sockport(),
+ $self->[uuid], $self->[appName]);
+
+ if (!$self->[useMulticast]) {
+ # cree la socket de broadcast
+ $self->[supSock] = IO::Socket::INET->new
+ (LocalPort => $self->[broadcastPort],
+ Proto => 'udp',
+ Type => SOCK_DGRAM,
+ ReuseAddr => 1);
+
+ $self->[supSock]->sockopt (SO_BROADCAST, 1);
+ foreach my $netBroadcastAddr (@{$self->[broadcastBuses]}) {
+# print "BroadcastBus: --", $netBroadcastAddr, "--\n";
+ send ($self->[supSock], $bonjourMsg, 0, $netBroadcastAddr) or
+ carp "Warning in Ivy::start, broadcast of Hello message failed: $!";
+ }
+ }
+ else {
+ # creating the multicast socket
+ $self->[supSock] = IO::Socket::Multicast->new
+ (LocalPort => $self->[broadcastPort],
+ ReuseAddr => 1);
+
+ # Multicast datagrams with initial TTL 0 are restricted to the same host.
+ # Multicast datagrams with initial TTL 1 are restricted to the same subnet.
+ # Multicast datagrams with initial TTL 32 are restricted to the same site.
+ # Multicast datagrams with initial TTL 64 are restricted to the same region.
+ # Multicast datagrams with initial TTL 128 are restricted to the same continent.
+ # Multicast datagrams with initial TTL 255 are unrestricted in scope.
+ $self->[supSock]->mcast_ttl(64);
+ # $self->[supSock]->mcast_loopback(1); must be 1, which is the default
+
+ foreach my $netMulticastAddr (@{$self->[broadcastBuses]}) {
+ my ($port,$multicastGroupI) = sockaddr_in ($netMulticastAddr);
+ my $multicastGroup = inet_ntoa($multicastGroupI);
+ # print "DBG> MulticastBus: --", $multicastGroup,":$port", "--\n";
+ $self->[supSock]->mcast_add($multicastGroup);
+ $self->[supSock]->mcast_send($bonjourMsg, $multicastGroup.":".$port) or
+ carp "Warning in Ivy::start, multicast of Hello message failed: $!";
+ }
+ }
+ # callback pour traiter la reception des bonjours
+ &$fileEventFunc ($self->[supSock], [\&_getBonjour, $self]) ;
+
+ # callback pour traiter les demandes de cxion
+ &$fileEventFunc ($self->[connSock], [\&_getConnections, $self]) ;
+
+ return $self;
+} # end start
+
+
+############### PROCEDURE BIND REGEXP
+sub bindRegexp ($$$;$$)
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($regexp, $cb, $callByRef, $bindOnce) = @_;
+ my $id;
+
+ $callByRef = 0 unless defined $callByRef ;
+ $bindOnce = 0 unless defined $bindOnce ;
+
+ my $extraParam;
+ if ($bindOnce) {
+ $extraParam = BIND_ONCE;
+ } else {
+ $extraParam = $callByRef ? CALL_BY_REF : CALL_BY_VALUE;
+ }
+
+# print ("DBG> bindRegexp:: self=$self, regexp=$regexp, extraParam=$extraParam\n");
+# my $original_regexp = $regexp;
+# # on substitue les meta caracteres des regexps perl : \d, \w, \s, \e
+# # par les classes de caracteres corespondantes de maniere a ce
+# # qu'une appli distante non perl comprenne ces regexp.
+# $regexp =~ s|
+# (
+# (? regexp = $regexp\n");
+ if ($^W) {
+ eval {my $test = "a" =~ /$regexp/ } ; # testing the regexp for avoiding
+ if ($@) {
+ carp "Warning in Ivy::bindRegexp, ill-formed regexp: '$regexp'" ;
+ return;
+ };
+ }
+
+
+ if ($cb) {
+ # on rajoute le couple $regexp, $cb dans la liste des messages
+ # qu'on prend
+
+ # on teste la validité de l'argument
+ if (ref ($cb) ne 'ARRAY') {
+ carp ("Warning binRegexp on $regexp :\nargument 3 (callback) is not correct and will be ignored\n");
+ return ();
+ }
+ if ((ref ($cb->[0]) ne 'CODE') && (ref ($cb->[1]) ne 'CODE')) {
+ carp ("Warning binRegexp on $regexp :\nargument 3 (callback) is not correct and will be ignored\n");
+ return ();
+ }
+
+ # on commence par tester si on a un id libere dans le tableau
+ for ($id=0; $id <= ($#{$self->[recCbList]}+1); $id++) {
+ last unless (defined $self->[recCbList][$id]) && @{$self->[recCbList][$id]->[1]};
+ }
+ $self->[recCbList][$id] = [$regexp, $cb, $extraParam];
+
+ # on envoie les messages regexps aux processus deja connectes
+ _sendLastRegexpToAllreadyConnected ($self, $id) ;
+ }
+ else {
+ # on vire le callback, et on se desabonne de cette regexp
+ for (my $id=0; $id <= $#{$self->[recCbList]}; $id++) {
+
+ next unless (defined $self->[recCbList][$id]) &&
+ @{$self->[recCbList][$id]->[1]};
+
+ if ($self->[recCbList][$id]->[0] eq $regexp) {
+
+ $self->[recCbList][$id]->[1] = [];
+ # on envoie le mesage delregexp
+ foreach my $fd (values %{$self->[sockList]}) {
+ _univSend ($self, $fd, sprintf (MSG_FMT, DELREGEXP, $id, ""));
+ }
+ }
+ }
+ }
+ return ($id);
+} # end bindRegexp
+
+############### PROCEDURE BIND REGEXP ONCE
+sub bindRegexpOneShot ($$$)
+{
+ Ivy::bindRegexp ($_[0], $_[1], $_[2], 0, 1);
+}
+
+############### PROCEDURE CHANGE REGEXP
+sub changeRegexp ($$$)
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($regexpId, $regexp) = @_;
+
+ $regexp =~ s|
+ \(\?I # l'extension (?I
+ ([\d-]+) # la borne inférieure
+ \# # l'operateur d'intervalle
+ ([\d-]+) # la borne supérieure
+ ([if]?) # le caractère de codage f pour flottant, i pour integer, flottant par defaut
+ \) # la parenthèse fermante
+ |
+ _regexpGen ($1, $2, $3);
+ |xge;
+
+ # print ("DBG> regexp = $regexp\n");
+ if ($^W) {
+ eval {my $test = "a" =~ /$regexp/ } ; # testing the regexp for avoiding
+ if ($@) {
+ carp "Warning in Ivy::changeRegexp, ill-formed regexp: '$regexp'" ;
+ return;
+ };
+ }
+
+ unless (exists $self->[recCbList][$regexpId]) {
+ warn ("Warning in Ivy::changeRegexp, invalid regexpId\n");
+ return (-1);
+ } else {
+ $self->[recCbList][$regexpId]->[0] = $regexp;
+ _sendLastRegexpToAllreadyConnected ($self, $regexpId) ;
+ return ($regexpId);
+ }
+} # end changeRegexp
+
+############### METHODE BIND REGEXP
+sub bindDirect
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($id, $cb) = @_;
+
+ if ($cb) {
+ # on rajoute la $cb dans la liste des messages
+ # qu'on prend
+ $self->[directCbList][$id] = $cb;
+ } else {
+ # on vire le callback
+ undef $self->[directCbList][$id];
+ }
+} # end bindDirect
+
+
+############### PROCEDURE SEND MSGS
+sub sendMsgs
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my $appSock;
+ my $total = 0;
+
+ # pour tous les messages
+ foreach my $msg (@_) {
+ study ($msg);
+ carp "Warning in Ivy::sendMsgs, a message contains a '\\n'. " .
+ "You should correct it:\n'$msg'" if ($^W && ($msg =~ /\n/)) ;
+
+ # pour routes les connections
+ foreach $appSock (values %{$self->[sockList]}) {
+ # pour toutes les fonctions de filtrage de regexp
+ $total += _sendMsgTo ($self, $appSock, $msg);
+ }
+ }
+ # print "DBG> sended $total times\n";
+ return $total;
+} # end sendMsgs
+
+
+
+
+
+
+
+############### PROCEDURE SEND MSGS
+sub sendAppNameMsgs
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ return $self->sendMsgs (map ("$self->[appName] $_", @_));
+}
+
+
+# sub sendAppNameMsgs
+# {
+# my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+# my @msgs = @_;
+# my $total = 0;
+
+# # pour tous les messages
+# foreach (@msgs) {
+# carp "Warning in Ivy::sendAppNameMsgs, a message contains a '\\n'. Skipping it:\n'$_'" if ($_ =~ /\n/);
+
+# my $msg = "$self->[appName] $_";
+# study ($msg);
+
+# # pour toutes les connections
+# foreach my $fd (keys %{$self->[sockList]}) {
+
+# # pour toutes les fonctions de filtrage de regexp
+# foreach my $regexpFunc (@{$self->[sendRegList]{$fd}}) {
+# $total += &{$regexpFunc} (\$msg) if defined $regexpFunc;
+# }
+# }
+# }
+# # print "DBG> sended $total times\n";
+# return $total;
+# } # end sendAppNameMsgs
+
+
+
+############### PROCEDURE SEND DIRECT MSGS
+sub sendDirectMsgs
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($to, $id, @msgs) = @_;
+
+ if (defined $to and defined ($self->[appliList]{$to})) {
+ my @fds = @{$self->[appliList]{$to}};
+ # pour tous les messages
+ foreach my $msg (@msgs) {
+ carp "Warning in Ivy::sendDirectMsgs, a message contains a '\\n'. Skipping it:\n'$msg'" if ($msg =~ /\n/);
+
+ foreach my $fd (@fds) {
+ _univSend ($self, $fd, sprintf (MSG_FMT, DIRECT_MSG, $id, "$msg"));;
+ }
+ }
+ return 1;
+ }
+ else {
+ my $to_appli = (defined $to) ? $to : '';
+ carp "Warning in Ivy::sendDirectMsgs, application $to_appli unknown";
+ return 0;
+ }
+} # end sendDirectMsgs
+
+
+############### METHOD SEND DIE TO
+sub sendDieTo
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($to) = @_;
+
+ if (defined $to and defined $self->[appliList]{$to}) {
+ my @fds = @{$self->[appliList]{$to}};
+
+ carp "Attention : in Ivy::sendDieTo big BUG \@fds is empty"
+ if (scalar (@fds) == 0);
+
+ # pour tous les messages
+ foreach my $fd (@fds) {
+ $self->_sendDieTo($fd);
+ }
+ return 1;
+ }
+ else {
+ my $to_appli = (defined $to) ? $to : '';
+ carp "Warning in Ivy::sendDieTo, application '$to_appli' is unknown" if $^W;
+ return 0;
+ }
+} # end sendDieTo
+
+
+############### METHOD PING
+sub ping ($$\&)
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($to, $pongCbRef) = @_;
+ my @fds;
+
+ return unless defined $to;
+
+ if (defined ($self->[appliList]{$to})) {
+ @fds = @{$self->[appliList]{$to}};
+ } else {
+ my %handleByName = reverse %nameByHandle;
+# printf "DBG>>> all names : %s\n", join (', ', keys %handleByName);
+ if (exists $handleByName{$to}) {
+ @fds = ($handleByName{$to});
+ } else {
+ carp "Warning in Ivy::ping, application '$to' is unknown" if $^W;
+ return 0;
+ }
+ }
+
+ # pour tous les messages
+ foreach my $fd (@fds) {
+# print ("DBG>> ping : send to fd $fd\n");
+ $self->[pongQueue]->{$fd} = [$pingId, Time::HiRes::time(), $pongCbRef];
+ _univSend ($self, $self->[sockList]->{$fd}, sprintf (MSG_FMT, PING, $pingId, ""));
+ }
+ return ($pingId++);
+} # end ping
+
+############### METHOD DUMP_TABLE
+sub dumpTable ($$\&)
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($to, $dumpTableCbRef) = @_;
+ my @fds;
+
+ return unless defined $to;
+
+ if (defined ($self->[appliList]{$to})) {
+ @fds = @{$self->[appliList]{$to}};
+ } else {
+ my %handleByName = reverse %nameByHandle;
+# printf "DBG>>> all names : %s\n", join (', ', keys %handleByName);
+ if (exists $handleByName{$to}) {
+ @fds = ($handleByName{$to});
+ } else {
+ carp "Warning in Ivy::ping, application '$to' is unknown" if $^W;
+ return 0;
+ }
+ }
+
+ # pour tous les messages
+ foreach my $fd (@fds) {
+# print ("DBG>> dumpTable : send to fd $fd\n");
+ $self->[pongQueue]->{$fd} = [$pingId, $dumpTableCbRef];
+ _univSend ($self, $self->[sockList]->{$fd}, sprintf (MSG_FMT, DUMP_TABLES, $pingId, ""));
+ }
+ return ($pingId++);
+} # end ping
+############### METHODE MAINLOOP
+sub mainLoop (;$)
+{
+ my $self = ref($_[0]) eq __PACKAGE__ ? shift : $globalIvy;
+ my ($fd, @selRes, @allDesc);
+
+
+ if ($loopMode == TK_MAINLOOP) {
+ eval {Tk::MainLoop ()};
+ return;
+ }
+
+ croak "Error in Ivy::mainLoop, Ivy not properly initialized\n"
+ unless defined $localLoopSelRead;
+
+ while (defined $localLoopSelRead) {
+# READ
+ @selRes = IO::Select::select ($localLoopSelRead, $localLoopSelWrite, undef ,
+ $selectTimout) ;
+ _scanAfter () ;
+
+ foreach $fd (@{$selRes[0]}) {
+ if (ref $localBindByHandle{$fd} eq 'CODE') {
+ &{$localBindByHandle{$fd}} ;
+ }
+ else {
+ my ($cb, @arg) = @{$localBindByHandle{$fd}} ;
+ &$cb (@arg)
+ }
+ }
+
+#WRITE
+ my $bufEmiRef;
+ my $sent;
+
+ foreach $fd (@{$selRes[1]}) {
+ $bufEmiRef = \($self->[bufEmiByCnnx]->{$fd});
+ $sent = send ($fd, $$bufEmiRef, 0);
+ unless (defined $sent) {
+ # y a rien à faire
+ } elsif ($sent == length ($$bufEmiRef)) {
+ $$bufEmiRef = "";
+ _callCongestionCb ($self, $fd, 0);
+ } elsif ($sent >= 0) {
+ substr ($$bufEmiRef, 0, $sent, '');
+ } else {
+ $self->_removeFileDescriptor ($fd, 'mainLoop[WRITE]') unless ($!{EAGAIN} || $!{EWOULDBLOCK}|| $!{EINTR} || $!{EMSGSIZE} || $!{ENOBUFS})
+ }
+ }
+ }
+} # end mainLoop
+
+
+############### METHODE AFTER
+sub after ($$;$)
+{
+ # test du premier argument au cas où la fonction est
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq __PACKAGE__) || ($_[0] eq __PACKAGE__)) ;
+
+ my ($timeAfter, $cbListRef) = @_;
+ $timeAfter /= 1000;
+ $selectTimout = $timeAfter if $timeAfter < $selectTimout;
+
+ # si la valeur de timout est negative : c'est un after sinon
+ # c'est un repeat
+ $afterList{++$afterId} = [AFTER, $timeAfter,
+ gettimeofday()+$timeAfter, $cbListRef];
+
+ return ($afterId);
+} # end after
+
+############### METHODE REPEAT
+sub repeat ($$;$)
+{
+ # test du premier argument au cas où la fonction est
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq __PACKAGE__) || ($_[0] eq __PACKAGE__)) ;
+
+ # on passe le temps en secondes pour le select
+ my ($timeAfter, $cbListRef) = @_;
+ $timeAfter /= 1000;
+ $selectTimout = $timeAfter if $timeAfter < $selectTimout;
+
+ $afterList{++$afterId}= [REPEAT, $timeAfter, gettimeofday()+$timeAfter,
+ $cbListRef];
+ return ($afterId);
+} # end repeat
+
+############### METHODE AFTER CANCEL
+sub afterCancel ($;$)
+{
+ # test du premier argument au cas où la fonction est
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq __PACKAGE__) || ($_[0] eq __PACKAGE__)) ;
+
+ my $id = shift;
+
+ if (defined ($id) && defined $afterList{$id}) {
+ if ($afterList{$id}->[1] <= $selectTimout) {
+ delete $afterList{$id} ;
+ # le timout de l'after/repeat etait le plus petit des timout
+ # on cherche donc le plus petit parmi ceux qui restent;
+ $selectTimout = MAX_TIMOUT;
+ foreach my $af (values %afterList) {
+ $selectTimout = $af->[1] if $af->[1] < $selectTimout ;
+ }
+ }
+ else {
+ delete $afterList{$id} ;
+ }
+ }
+} # end afterCancel
+
+############### METHODE AFTER RESET TIMER
+# permet de gérer des timout plus facilement en permettant de
+# réarmer un after non encore déclenché à sa valeur initiale
+# cela évite le aftercancel suivi d'un nouvel after
+sub afterResetTimer ($;$)
+{
+ # test du premier argument au cas où la fonction est
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq __PACKAGE__) || ($_[0] eq __PACKAGE__)) ;
+
+ my $id = shift;
+ if (defined ($id) && defined $afterList{$id}) {
+ $afterList{$id}->[2] = $afterList{$id}->[1] + gettimeofday();
+ }
+} # end afterResetTimer
+
+
+############### METHODE FILE EVENT
+sub fileEvent ($$;$)
+{
+ # test du premier argument au cas où la fonction est
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq __PACKAGE__) || ($_[0] eq __PACKAGE__)) ;
+
+ my ($fd, $cb) = @_;
+
+
+ unless (defined $localLoopSelRead) {
+ croak ("Error in Ivy::fileEvent, Ivy should have been initialised in LOCAL loop mode\n");
+ }
+
+ if ($cb) {
+ # adding the handler
+ $localBindByHandle{$fd} = $cb;
+ $localLoopSelRead->add ($fd);
+ } else {
+ # deleting the handler
+ delete $localBindByHandle{$fd};
+# print ("DBG> Ivy::fileEvent : removing fd from the select\n");
+ $localLoopSelRead->remove ($fd);
+ $localLoopSelWrite->remove ($fd);
+ }
+} # end fileEvent
+
+
+
+
+sub getUuid ($)
+{
+ my $self = shift;
+ return $self->[uuid];
+}
+#############################################################################
+#### METHODES PRIVEES #####
+#############################################################################
+
+
+############### METHODE GET BONJOUR
+sub _getBonjour ($)
+{
+ my $self = shift;
+# my $DTS = sprintf ("%2d:%2d:%2d", (localtime())[2,1,0]);
+
+ my $bonjourMsg = '';
+
+ # l'hote distant
+ my $inetAddr = $self->[supSock]->recv ($bonjourMsg, 1024, 0);
+
+ unless (length $inetAddr) {
+ carp "Warning in Ivy::_getBonjour, recv error, Hello message discarded";
+ return;
+ }
+
+ my $addr = (unpack_sockaddr_in ($inetAddr))[1];
+
+ my $peerName = _getHostByAddr ($addr);
+
+ # on force $peerPort a etre vu comme une valeur numerique
+ my ($version, $peerPort, $uuid, $udpAppName) =
+# $bonjourMsg =~ /^(\d+)\s+(\d+)\s+(?:(\w+)\s+(.*))?/;
+ $bonjourMsg =~ /^(\d+)\s+(\d+)(?:\s+(\S+)\s+(.*))?\n/;
+
+ $udpAppName = 1 unless defined $udpAppName;
+# syswrite ($trace, "DBG<$DTS>[$appName]> bonjourMsg = '$bonjourMsg'\n");
+# syswrite ($trace, "DBG<$DTS>[$appName]> reception de $peerName : bonjour $peerPort uuid = $uuid\n");
+
+ unless (defined ($version) && defined ($peerPort)) {
+ carp "Warning[$appName] in Ivy::_getBonjour, ill-formed Hello message \"$bonjourMsg\"" ;
+ return;
+ }
+
+ if ($version != IVY_PROTOCOLE_VERSION) {
+ carp "Warning[$appName] in Ivy::_getBonjour, connection request from ".
+ "$peerName with protocol version $version,\ncurrent version is " .
+ IVY_PROTOCOLE_VERSION ;
+ return;
+ }
+
+
+ # on verifie qu'on ne se repond pas et qu'on ne
+ # se reconnecte pas a un process deja connecte
+ if (exists ($self->[cnnxion]{"$addr:$peerPort"})) {
+# syswrite ($trace, "DBG<$DTS>[$appName]> from $self->[appName] DISCARD bonjour de $peerName:$peerPort [$udpAppName]: DEJA CONNECTE\n") ;
+ return ;
+ } elsif ((defined $uuid) && ($uuid eq $self->[uuid])) {
+# syswrite ($trace, "DBG<$DTS>[$appName]> from $self->[appName] DISCARD bonjour de $peerName:$peerPort [$udpAppName]: $uuid c'est MOI\n") ;
+ return;
+ } elsif ((defined $uuid) && (exists ($self->[connectedUuid]->{$uuid}))) {
+# syswrite ($trace, "DBG<$DTS>[$appName]> from $self->[appName] DISCARD bonjour de $peerName:$peerPort:$uuid [$udpAppName] DEJA CONNECTE\n") ;
+ return;
+ } else {
+# syswrite ($trace, "DBG<$DTS>[$appName]> reception de $peerName : bonjour $udpAppName:$peerPort") ;
+# syswrite ($trace, " uuid=$uuid") if (defined $uuid);
+# syswrite ($trace, "\n");
+# syswrite ($trace, "DBG<$DTS>>[$appName] from $self->[appName] ACCEPT bonjour de $peerName:$peerPort:$uuid [$udpAppName]\n") ;
+ $self->[connectedUuid]->{$uuid} = 1 if (defined $uuid);
+ }
+
+ # on verifie que l'adresse fasse partie de l'ensemble de reseau
+ # definis par ivybus
+ my $addrInIvyBus = 0;
+ my @ivyBusAddrList = map ( (unpack_sockaddr_in ($_))[1],
+ @{$self->[broadcastBuses]});
+ # Bon dans cette version on reponds aux bonjour emis par
+ # la machine locale, on ne peut donc pas avoir
+ # une appli qui ne causerait qu'a des machines sur une
+ # autre reseau, si ca embete qqun, qu'il me le dise
+ push (@ivyBusAddrList, pack ("CCCC", 127,255,255,255));
+ push (@ivyBusAddrList, (gethostbyname (hostname()))[4]);
+
+ use bytes;
+ foreach my $ivyBusAddr (@ivyBusAddrList) {
+ $addrInIvyBus = 1 unless (grep ($_ != 0, unpack ("C4",
+ ($addr & $ivyBusAddr) ^ $addr)));
+ }
+ no bytes;
+
+ if ($addrInIvyBus == 0) {
+ carp "Warning[$appName]: Hello message from $peerName ignored,\n".
+ "this guy is outside our emission zone\n" if $^W;
+ return;
+ }
+
+ # ouverture du canal de communication
+ my $appSock = IO::Socket::INET->new (PeerAddr => inet_ntoa ($addr),#$peerName,
+ PeerPort => $peerPort,
+ Proto => 'tcp');
+
+ if ($appSock) {
+ my $flags = fcntl($appSock, F_GETFL, 0);
+ unless (fcntl($appSock, F_SETFL, $flags | O_NONBLOCK)) {
+ carp "Warning[$appName] Can't set flags for the socket: $!\n";
+ return;
+ }
+ $appSock->sockopt(Socket::TCP_NODELAY, TCP_NO_DELAY_ACTIVATED);
+
+
+ binmode ($appSock);
+ # on cree une entree pour $appSock dans la liste des regexp
+ $nameByHandle{$appSock}=_getHostByAddr($addr) .":$peerPort";
+ $self->[cnnxion]{"$addr:$peerPort"} = $udpAppName;
+ $self->[sendRegList]{$appSock} = [];
+ $self->[sendRegListSrc]{$appSock} = [];
+ $self->[bufRecByCnnx]{$appSock} = '';
+ $self->[bufEmiByCnnx]{$appSock} = '';
+ $self->[sockList]{$appSock} = $appSock;
+
+
+# syswrite ($trace, sprintf ("_getBonjour : ajout dans le fdset de %s[%s]:%d\n",
+# (gethostbyaddr ($appSock->peeraddr(),AF_INET))[0],
+# join (':', unpack ('C4', $appSock->peeraddr())),
+# $appSock->peerport()));
+
+ &$fileEventFunc ($appSock, [\&_getMessages, $self, $appSock]) ;
+
+ # on balance les regexps qui nous interessent a l'appli distante
+ $self->_sendWantedRegexp ($appSock);
+ } else {
+ carp "Warning[$appName] in Ivy::_getBonjour, connection to " .
+ "$peerName:$peerPort is impossible" ;
+ }
+} # end _getBonjour
+
+
+############### PROCEDURE GET CONNECTIONS
+sub _getConnections ($)
+{
+ my $self = shift;
+
+ my $appSock = $self->[connSock]->accept();
+
+ unless (defined $appSock) {
+ carp "Warning in Ivy::_getConnections, \$appSock not defined";
+ return;
+ } else {
+ my $flags = fcntl($appSock, F_GETFL, 0);
+ unless (fcntl($appSock, F_SETFL, $flags | O_NONBLOCK)) {
+ carp "Can't set flags for the socket: $!\n";
+ return;
+ }
+
+ $appSock->sockopt(Socket::TCP_NODELAY, TCP_NO_DELAY_ACTIVATED);
+ binmode ($appSock);
+ }
+
+
+# syswrite ($trace, sprintf ("_getConnections : ajout dans le fdset de %s[%s]:%d\n",
+# (gethostbyaddr ($appSock->peeraddr(),AF_INET))[0],
+# join (':', unpack ('C4', $appSock->peeraddr())),
+# $appSock->peerport()));
+
+
+ # callback pour traiter la reception des messages
+ &$fileEventFunc ($appSock, [\&_getMessages, $self, $appSock]) ;
+
+ # on cree une entree pour $appSock dans la liste des regexp
+ $self->[sendRegList]{$appSock} = [];
+ $self->[sendRegListSrc]{$appSock} = [];
+ $self->[bufRecByCnnx]{$appSock} = '';
+ $self->[bufEmiByCnnx]{$appSock} = '';
+ $self->[sockList]{$appSock} = $appSock;
+ # on balance les regexps qui nous interessent a l'appli distante
+ $self->_sendWantedRegexp ($appSock);
+} # end _getConnections
+
+
+############### METHODE GET MESSAGES
+sub _getMessages ($$)
+{
+ my ($self, $appSock) = @_;
+
+ unless (defined $appSock) {
+ carp "Warning in Ivy::_getMessages : *UN*inititialized appSock, don't do anything\n" if $^W;
+ return;
+ }
+
+ my $bufferRef = \$self->[bufRecByCnnx]{$appSock};
+ my ($addr, $peerPort, $senderName);
+ my $nlIndex;
+ my $mess;
+
+
+# syswrite ($trace, sprintf ("_getMessages from %s[%s]:%d\n",
+# (gethostbyaddr ($appSock->peeraddr(),AF_INET))[0],
+# join (':', unpack ('C4', $appSock->peeraddr())),
+# $appSock->peerport()));
+
+ # on recupere le message
+ unless (sysread ($appSock, $$bufferRef, 65536, length ($$bufferRef))) {
+ # message null : broken pipe, ça s'est deconnecte a l'autre bout
+ # on vire ce fd de la boucle d'evenements
+ # print ("DBG> _getMessages, recv err, calling removeFileDesc.\n");
+ # Bon la il faudra un jour clarifier ce bordel, lister toutes
+ # les facons dont un couple d'applis connectées peuvent sortir et
+ # eviter les dead lock qui doivent subsister.
+# syswrite ($trace, sprintf ("_getMessage : bad FD[%d] detected errno=%d\n", $appSock->peerport(), $!));
+ $self->_removeFileDescriptor ($appSock, '_getMessages') unless ($!{EAGAIN});
+ return;
+ }
+
+ $addr = $appSock->peeraddr();
+ $peerPort = $appSock->peerport() ;
+ $senderName = $self->[cnnxion]{"$addr:$peerPort"} ;
+ $senderName = "NONAME" unless $senderName;
+ $senderName =~ s/^\004//g;
+
+ while (($nlIndex= index ($$bufferRef, "\n")) > 0) {
+ $mess = substr ($$bufferRef, 0, $nlIndex, '');
+ substr ($$bufferRef, 0, 1, '');
+ # on recupere les 3 champs : le type, le numero de regexp, les valeurs
+ my ($type, $id, $valeurs) = $mess =~ /^(\d+)
+ \s+
+ (\d+)
+ \002
+ (.*)/x ;
+
+ # si ca a chie on rale
+ (carp "Warning in Ivy::_getMessages, ill-formated message \'$mess\'" and return) unless defined $type ;
+
+# syswrite ($trace, "_getMessage type = $type\n");
+
+ # sinon on fait en fonction du type de message
+ if ($type == MSG) { # M S G
+ # on recupere le couple call back, regexp correspondant
+ # a l'identifiant et on appelle la fonction avec les parametres
+ # traites par la regexp
+
+# if ((ref ($self->[recCbList][$id]) eq 'ARRAY') &&
+# (my @cb = @{$self->[recCbList][$id]->[1]})) {
+
+
+ if (my @cb = @{$self->[recCbList][$id]->[1]}) {
+ my $cb = shift @cb;
+
+ # cleaning $sendername with previous \004 used for connection status
+ # bindRegexp avancé : on envoie une liste nom adresse port au lieu du nom
+ $senderName = [$senderName, _getHostByAddr ($addr), $peerPort]
+ if ($self->[recCbList][$id]->[2] == CALL_BY_REF);
+
+ if (ref($cb) ne 'CODE') {
+ my $method = shift @cb;
+ # on split sur ETX
+ $cb->$method($senderName, @cb, split ("\003", $valeurs)) ;
+ } else {
+ &$cb ($senderName, @cb, split ("\003", $valeurs)) ;
+ }
+
+ if ($self->[recCbList][$id]->[2] == BIND_ONCE) {
+ # on vire la regexp des regexps verifiées
+ # print ("DBG> receive BIND ONCE message\n");
+ $self->[recCbList][$id]->[1] = [];
+ # on envoie le mesage delregexp
+ foreach my $fd (values %{$self->[sockList]}) {
+ _univSend ($self, $fd, sprintf (MSG_FMT, DELREGEXP, $id, ""));
+ }
+ }
+ } else {
+ #_sendErrorTo ($appSock, "REEGXP ID $id inconnue");
+ carp ("Warning in Ivy::_getMessages, received an unknown message or double one shot message ".
+ "with id $id from $senderName :\n\"$mess\"") if $^W;
+ }
+ }
+
+ elsif ($type == BYE) {
+# syswrite ($trace, "reception d'un bye\n");
+ $self->_removeFileDescriptor ($appSock, '_getMessages[BYE]'); # B Y E
+ }
+
+ elsif ($type == REGEXP) { # R E G E X P
+ # on ajoute une fonction traitant la regexp et envoyant le
+ # message sur le bon fd dans la liste des fonctions de filtrage
+ # ca permet de compiler les regexp avec 'once' donc une
+ # fois pour toute, et ainsi optimiser la vitesse de
+ # filtrage des messages a envoyer
+# print "DBG> REGEXP from $senderName '$id' '$valeurs'\n";
+ my $host = _getHostByAddr ($addr);
+ if ($self->_toBePruned ($senderName, $valeurs)) {
+ &_scanConnStatus ($self, $senderName, 'filtered', "$host:$peerPort" , $valeurs);
+ next;
+ } else {
+ &_scanConnStatus ($self, $senderName, 'subscribing', "$host:$peerPort" , $valeurs);
+ }
+ # on affecte la nouvelle regexp a un id
+ $self->[sendRegListSrc]{$appSock}->[$id] = $valeurs;
+# printf ("DBG> add id $id regexps=[$valeurs]\n");
+ $self->[sendRegList]{$appSock}->[$id] =
+ eval ('sub {@{$_[1]} = ${$_[0]} =~ /($valeurs)/io;}');
+
+
+# $self->[sendRegList]{$appSock}->[$id] = eval <<'_EOL_';
+# sub {
+# use strict;
+# if (my @args = ${$_[0]} =~ /($valeurs)/io) {
+# shift @args;
+# $args[$#args] .= "\003" if @args;
+# $self->[bufEmiByCnnx]->{$appSock} .=
+# sprintf (MSG_FMT, MSG, $id, join ("\003",@args));
+# my $sent = send ($appSock, $self->[bufEmiByCnnx]->{$appSock}, 0);
+# unless (defined $sent) {
+# # y a rien à faire
+# } elsif ($sent == length ($self->[bufEmiByCnnx]->{$appSock})) {
+# $self->[bufEmiByCnnx]->{$appSock} = "";
+# } elsif ($sent >= 0) {
+# substr ($self->[bufEmiByCnnx]->{$appSock}, 0, $sent, '');
+# } else {
+# $self->_removeFileDescriptor ($appSock) ;
+# }
+# }
+# return 1;
+# }
+# _EOL_
+ }
+
+ elsif ($type == ERROR) { # E R R O R
+ carp ("Warning in Ivy::_getMessages, error message received from ".
+ "$senderName : \"$valeurs\"");
+ }
+
+ elsif ($type == DELREGEXP) { # D E L R E G E X P
+ # on vire la regexp des regexps verifiées
+# printf ("DBG> delete id $id\n");
+ $self->[sendRegList]{$appSock}->[$id] = undef ;
+ my $regexp = $self->[sendRegListSrc]{$appSock}->[$id];
+ $self->[sendRegListSrc]{$appSock}->[$id] = undef;
+ my $host = _getHostByAddr ($addr);
+ &_scanConnStatus ($self, $senderName, 'unsubscribing', "$host:$peerPort" , $regexp);
+ }
+
+ elsif ($type == ENDREGEXP) { # E N D R E G E X P
+ # on envoie le message ready uniquement a celui qui nous
+ # a envoye le message endregexp, et uniquement si on a
+ # à la fois envoyé le end regexp, et reçu le endregexp de l'autre
+ $self->[readyToSend]->{"$addr:$peerPort"} = 0 unless
+ exists $self->[readyToSend]->{"$addr:$peerPort"};
+ if (++($self->[readyToSend]->{"$addr:$peerPort"}) == 2) {
+ $self->_sendMsgTo ($appSock, \$self->[messWhenReady]);
+ }
+
+ # on passe de l'etat Connecte a l'etat Ready
+ $self->[cnnxion]{"$addr:$peerPort"} =~ s/^\004//g;
+ $senderName = $self->[cnnxion]{"$addr:$peerPort"};
+ unless (exists $self->[appliList]{$senderName}) {
+ $self->[appliList]{$senderName} = [$appSock];
+ }
+ else {
+ push @{$self->[appliList]{$senderName}}, $appSock;
+ }
+
+ my $host = _getHostByAddr ($addr);
+ $self->_scanConnStatus ($senderName, "new", "$host:$peerPort", undef);
+ }
+
+ elsif ($type == APP_NAME) {
+ # etat Connecte1558
+ if (($self->[appName] eq $valeurs) && $^W) {
+ carp "\033[1mWarning in Ivy::_getMessages, there is already an instance of ".
+ "$self->[appName] \033[m" ;
+ }
+
+ $senderName = $valeurs;
+ $self->[cnnxion]{"$addr:$peerPort"} = "\004$valeurs";
+ $nameByHandle{$appSock}=_getHostByAddr($addr) .":$peerPort";
+ }
+
+ elsif ($type == DIRECT_MSG) {
+
+ if (defined $self->[directCbList][$id]) {
+ my @cb = @{$self->[directCbList][$id]};
+ my $cb = shift @cb;
+ if (ref($cb) ne 'CODE') {
+ my $method = shift @cb;
+ $cb->$method(@cb, $valeurs);
+ }
+ else {
+ &$cb (@cb, $valeurs);
+ }
+ }
+ else {
+ $self->_sendErrorTo ($appSock, "DIRECT ID $id inconnue");
+ carp "Warning in Ivy::_getMessages, received a DIRECT message with ".
+ "unknown id $id from $senderName :\n\"$mess\"";
+ }
+ }
+
+ elsif ($type == DIE) {
+ # il faut quitter
+ # on commence par appeler la callback de fin
+ my @cb = @{$onDieFunc};
+ my $cb = shift @cb;
+ if (ref($cb) ne 'CODE') {
+ my $method = shift @cb;
+ $cb->$method(@cb);
+ }
+ else {
+ &$cb (@cb);
+ }
+ # on avertit les autres qu'on se barre
+# my $adr = $self->_inetAdrByName ($senderName) ;
+# carp "Notice in Ivy::_getMessages, received a suicide request from " . "$senderName ($adr) ... exiting" if $^W;
+ # adios
+ Ivy::exit ();
+
+ }
+
+ elsif ($type == PING) {
+ # si on recois un ping, on envoie un pong
+ $self->_pong ($appSock, $id);
+ }
+
+ elsif ($type == PONG) {
+ if (exists $self->[pongQueue]->{$appSock}) {
+ my ($pingid, $time, $funcRef) = @{$self->[pongQueue]->{$appSock}};
+# printf ("DBG>>> stocked Id = $pingid;; message id = $id\n");
+ &$funcRef ((Time::HiRes::time()-$time)*1000, $nameByHandle{$appSock})
+ if ($pingid == $id);
+ delete $self->[pongQueue]->{$appSock};
+ }
+ }
+
+ elsif ($type == DUMP_TABLES) {
+ # si on recois un ping, on envoie un pong
+ $self->_dumpTable ($appSock, $id);
+ }
+
+ elsif ($type == DUMP_TABLES_FILE) {
+ if (exists $self->[pongQueue]->{$appSock}) {
+ my ($pingid, $funcRef) = @{$self->[pongQueue]->{$appSock}};
+# printf ("DBG>>> stocked Id = $pingid;; message id = $id valeur=$valeurs\n");
+ &$funcRef ($valeurs, $nameByHandle{$appSock})
+ if ($pingid == $id);
+ delete $self->[pongQueue]->{$appSock};
+ }
+ }
+
+
+ else {
+ _$self->sendErrorTo ($appSock, "TYPE DE MESS $type inconnu");
+ warn ("Warning in Ivy::_getMessages, received a message of unknown ".
+ " type $type from $senderName :\n\"$mess\"");
+ }
+ }
+ return 0;
+} # end _getMessages
+
+############### METHODE SEND WANTED REGEXP
+sub _sendWantedRegexp ($$)
+{
+ my ($self, $appSock) = @_;
+ my $connSock = $self->[connSock] ;
+ my $msg;
+ # on envoie le message "Nom appli"
+ _univSend ($self, $appSock, sprintf (MSG_FMT, APP_NAME, $connSock->sockport,
+ $self->[appName]));
+ # on envoie les regexps
+ for (my $id = 0; $id <= $#{$self->[recCbList]}; $id++) {
+ next unless defined $self->[recCbList][$id]->[1]->[0];
+ $msg = sprintf (MSG_FMT, REGEXP, $id, $self->[recCbList][$id]->[0]);
+ _univSend ($self, $appSock, \$msg);
+ # print sprintf ("DBG> %s %d %s\n",
+ # 'REGEXP', $id, $self->[recCbList][$id]->[0]);
+ }
+ # on envoie le message de fin d'envoi de regexps
+ _univSend ($self, $appSock, sprintf (MSG_FMT, ENDREGEXP, 0, ""));
+
+ # on envoie le message ready uniquement a celui qui nous
+ # a envoye le message endregexp, et uniquement si on a
+ # à la fois envoyé le end regexp, et reçu le endregexp de l'autre
+ my $addr = $appSock->peeraddr();
+ my $peerPort = $appSock->peerport() ;
+ $self->[readyToSend]->{"$addr:$peerPort"} = 0 unless
+ exists $self->[readyToSend]->{"$addr:$peerPort"};
+ if (++($self->[readyToSend]->{"$addr:$peerPort"}) == 2) {
+ $self->_sendMsgTo ($appSock, \$self->[messWhenReady]);
+ }
+} # end _sendWantedRegexp
+
+############### METHODE SEND LAST REGEXP TO ALLREADY CONNECTED
+sub _sendLastRegexpToAllreadyConnected ($$)
+{
+ my ($self, $id) = @_;
+ my $msg = sprintf (MSG_FMT, REGEXP, $id, $self->[recCbList][$id]->[0]);
+ foreach my $fd (values %{$self->[sockList]}) {
+ _univSend ($self, $fd, \$msg);
+ }
+} # end _sendLastRegexpToAllreadyConnected
+
+############### METHODE INET ADR BY NAME
+sub _inetAdrByName ($$) {
+
+ my ($self, $appName) = @_;
+
+ my $addrInet = (grep ($self->[cnnxion]{$_} eq $appName,
+ keys %{$self->[cnnxion]}))[0];
+
+ return ("unknow") unless defined $addrInet;
+
+ my ($port) = $addrInet =~ /:(.*)/;
+ my $addr = substr ($addrInet,0,4);
+ my $host = _getHostByAddr ($addr);
+ return "$host:$port";
+} # end _inetAdrByName
+
+
+############### PROCEDURE REMOVE FILE DESCRIPTOR
+sub _removeFileDescriptor ($$$)
+{
+ my ($self, $fd, $callBy) = @_;
+
+ unless (defined $fd) {
+# syswrite ($trace, "_removeFileDescriptor : *UN*inititialized fd, don't do anything\n");
+ return;
+ }
+
+
+
+ # on s'est deja occupe de lui
+ return unless exists $self->[sockList]->{$fd};
+ my $diedAppName = _getNameByFileDes ($self, $fd);
+
+ # on efface les structures de donnees associees au fd
+ # on vire ce fd des fd a scruter dans la bcle d'evenements
+ # uniquement si on est dans le thread principal
+ # sinon le select merde salement sur ce coup
+ my $peerPort = $fd->peerport() ;
+ $peerPort = 0 unless defined $peerPort;
+
+# syswrite ($trace, sprintf ("_removeFileDescriptor : suppression dans le fdset de %s[%s]:%d\n",
+# (gethostbyaddr ($fd->peeraddr(),AF_INET))[0],
+# join (':', unpack ('C4', $fd->peeraddr())),
+# $fd->peerport()));
+ &$fileEventFunc ($fd, '') ;
+ delete $self->[sendRegList]->{$fd};
+ delete $self->[sendRegListSrc]->{$fd};
+ delete $self->[sockList]->{$fd};
+ delete $self->[bufRecByCnnx]->{$fd};
+ delete $self->[bufEmiByCnnx]->{$fd};
+
+ $fd->close();
+
+ # remove all occurence of fd from $self->[appliList]
+ foreach my $name (keys %{$self->[appliList]}) {
+ for (my $i=0; $i < scalar (@{$self->[appliList]{$name}}); $i++) {
+ my $fdp = $self->[appliList]{$name}->[$i];
+ if ($fd eq $fdp) {
+ delete ($self->[appliList]{$name}->[$i]);
+ }
+ }
+ }
+
+ unless (defined $diedAppName) {
+ warn "Ivy::__removeFileDescriptor (called by $callBy) : disconnection of NONAME\n" if $^W;
+ return;
+ }
+
+ my $addrInet = (grep ($self->[cnnxion]{$_} eq $diedAppName,
+ keys %{$self->[cnnxion]}))[0];
+
+ unless (defined $addrInet) {
+ carp "Warning in Ivy::_removeFileDescriptor (called by $callBy) : disconnection of $diedAppName with ".
+ "addrInet not defined\n";
+ return;
+ }
+
+# syswrite ($trace,
+# sprintf ("DBG> _removeFileDescriptor : deconnection de %s ($diedAppName)\n", $self->_inetAdrByName ($diedAppName)));
+
+ delete $self->[cnnxion]{$addrInet};
+ delete $nameByHandle{$fd};
+
+ # on vire l'entree correspondant a ce canal dans la liste des
+ # regexps par canal
+
+ my $addr = substr ($addrInet,0,4);
+ my $host = _getHostByAddr ($addr);
+ $self->_scanConnStatus ($diedAppName, "died", "$host:$peerPort", undef) ;
+} # end _removeFileDescriptor
+
+
+############### METHODE SEND ERROR TO
+sub _sendErrorTo ($$$)
+{
+ my ($self, $fd, $error) = @_;
+
+ _univSend ($self, $fd, join (' ', ERROR, "0\002$error\n"));
+} # end _sendErrorTo
+
+
+############### METHODE PONG
+sub _pong ($$$)
+{
+ my ($self, $fd, $pongId) = @_;
+
+# printf ("DBG>>> PONG Id = $pongId\n");
+ _univSend ($self, $fd, sprintf (MSG_FMT, PONG, $pongId, ""));
+} # end _pong
+
+############### METHODE _DUMP_TABLE
+sub _dumpTable ($$$)
+{
+ my ($self, $fd, $pongId) = @_;
+ my ($fh, $tmpFileName) = File::Temp::tempfile("IvyTable_XXXXX", SUFFIX => '.txt', DIR => $ENV{'TMPDIR'});
+# print ("DBG> tmpFileName=$tmpFileName\n");
+ my $ofs = $,;
+ $, = ', ';
+ print $fh <[appName]
+selectTimout = $selectTimout
+loopMode = $loopMode
+messWhenReady = $self->[messWhenReady]
+blockOnSlowAgent = $self->[blockOnSlowAgent]
+topicRegexps = @{$self->[topicRegexps]}
+useMulticast = $self->[useMulticast]
+broadcastPort = $self->[broadcastPort]
+readyToSend = %{$self->[readyToSend]}
+neededApp = @{$self->[neededApp]}
+
+TABLE DE REGEXPS :
+
+EOF
+
+ foreach my $appSock (keys %{$self->[sendRegListSrc]}) {
+ my $appName = $self->_getNameByFileDes ($appSock);
+# my $ap2 = $self->[cnnxion]{$nameByHandle{$appSock}};
+# $ap2 =~ s/^\004//g;
+ print $fh "POUR l'application ${appName}[$nameByHandle{$appSock}] : \n";
+ for (my $idx=0; $idx < scalar (@{$self->[sendRegListSrc]{$appSock}}); $idx++) {
+ printf $fh ("id[$idx] => '%s'\n",
+ defined ($self->[sendRegListSrc]{$appSock}->[$idx])
+ ? $self->[sendRegListSrc]{$appSock}->[$idx]
+ : "DELETED or UNDEFINED");
+ }
+ # print $fh join ("\n", @{$self->[sendRegListSrc]{$appSock}});
+ print $fh "\n-----------------------------------\n";
+ }
+
+ close ($fh);
+ $, = $ofs;
+
+
+# printf ("DBG>>> DUMP_TABLE Id = $pongId tmpFile= $tmpFileName\n");
+ _univSend ($self, $fd, sprintf (MSG_FMT, DUMP_TABLES_FILE, $pongId, $tmpFileName));
+} # end _pong
+
+
+############### METHODE SEND ERROR TO
+sub _sendDieTo ($$)
+{
+ my ($self, $fd) = @_;
+
+ _univSend ($self, $fd, join (' ', DIE, "0\002\n"));
+} # end _sendDieTo
+
+
+############### METHODE SEND MSG TO
+sub _sendMsgTo ($$\$)
+{
+ my ($self, $fd, $msg) = @_;
+ my $id = -1;
+ my $total = 0;
+ my @args = (); # tableau passé en reference aux fonctions
+ # anonymes compilées par eval pour receuillir les arguments filtrés
+ # par les regexp à envoyer sur le tuyau
+ my $sent;
+ my $regexpFunc;
+ my $bufEmiRef;
+
+ # pour toutes les fonctions de filtrage de regexp
+ foreach $regexpFunc (@{$self->[sendRegList]{$fd}}) {
+ $id++;
+ # $total += &{$regexpFunc} (\$msg) if defined $regexpFunc;
+ next unless defined $regexpFunc;
+ next unless &{$regexpFunc} ($msg, \@args);
+ next unless @args;
+ $total ++;
+ shift @args;
+ $args[$#args] .= "\003" if @args;
+ $bufEmiRef = \($self->[bufEmiByCnnx]->{$fd});
+ my $enCongestion = $$bufEmiRef ? 1 : 0;
+ $$bufEmiRef .= sprintf (MSG_FMT, MSG, $id, join ("\003",@args));
+ next if $enCongestion;
+ $sent = send ($fd, $$bufEmiRef, 0);
+ unless (defined $sent) {
+ # y a rien à faire
+ } elsif ($sent == length ($$bufEmiRef)) {
+ $$bufEmiRef = "";
+ } elsif ($sent >= 0) {
+ substr ($$bufEmiRef, 0, $sent, '');
+ _callCongestionCb ($self, $fd, 1);
+ if ($self->[blockOnSlowAgent]) {
+ my $win = '';
+ vec($win, fileno ($fd), 1) = 1;
+ select (undef, $win, undef, undef);
+ }
+ } else {
+ $self->_removeFileDescriptor ($fd, '_sendMsgTo') ;
+ }
+ }
+
+ return ($total);
+} # end _sendMsgTo
+
+
+
+############### METHODE UNIV SEND
+sub _univSend ($$$)
+{
+ my ($self, $fd, $msg) = @_;
+
+ unless (defined $fd) {
+ carp "WARN _univSend fd is undefined, message will not be sent\n" if $^W;
+ afficher la call stack
+ }
+ my $bufEmiRef = \($self->[bufEmiByCnnx]->{$fd});
+ my $enCongestion = $$bufEmiRef ? 1 : 0;
+ if (ref $msg) {
+ $$bufEmiRef .= $$msg;
+ } else {
+ $$bufEmiRef .= $msg;
+ }
+ return if $enCongestion;
+ my $sent = send ($fd, $$bufEmiRef, 0);
+ unless (defined $sent) {
+ # y a rien à faire
+ } elsif ($sent == length ($$bufEmiRef)) {
+ $$bufEmiRef = "";
+ } elsif ($sent >= 0) {
+ substr ($$bufEmiRef, 0, $sent, '');
+ _callCongestionCb ($self, $fd, 1);
+ if ($self->[blockOnSlowAgent]) {
+ my $win = '';
+ vec($win, fileno ($fd), 1) = 1;
+ select (undef, $win, undef, undef);
+ }
+ } else {
+ if ($!{EWOULDBLOCK}) {
+ # Aucun octet n'a été envoyé, mais le send ne rend pas 0
+ # car 0 peut être une longueur passée au send, donc dans ce cas
+ # send renvoie -1 et met errno a EWOULDBLOCK
+ _callCongestionCb ($self, $fd, 1);
+ if ($self->[blockOnSlowAgent]) {
+ my $win = '';
+ vec($win, fileno ($fd), 1) = 1;
+ select (undef, $win, undef, undef);
+ }
+ }
+ $self->_removeFileDescriptor ($fd, '_univSend') unless ($!{EAGAIN} || $!{EWOULDBLOCK}|| $!{EINTR} || $!{EMSGSIZE} || $!{ENOBUFS})
+ }
+}
+
+
+############### PROCEDURE TK FILE EVENT
+sub _tkFileEvent ($$)
+{
+ my ($fd, $cb) = @_;
+
+ Tk::fileevent ('', $fd, 'readable', $cb) ;
+} # end _tkFileEvent
+
+
+############### PROCEDURE SCAN AFTER
+sub _scanAfter ()
+{
+ my $stamp = gettimeofday ();
+ $selectTimout = MAX_TIMOUT;
+ foreach my $afk (keys %afterList) {
+ my $af = $afterList{$afk};
+ # si ce timer est a declencher
+ if ($af->[2] <= $stamp) {
+ # on traite : le temps de declencher le cb est arrive
+ if (ref $af->[3] eq 'CODE') {
+ &{$af->[3]};
+ }
+ else {
+ my ($cb, @args) = @{$af->[3]};
+ &$cb (@args);
+ }
+ # si c'est un repeat on le reconduit
+ if ($af->[0]) {
+ $af->[2] = $stamp + $af->[1] ;
+ $selectTimout = $af->[1] if $af->[1] < $selectTimout;
+ }
+ else {
+ # si c'est un after on le vire
+ afterCancel ($afk);
+ }
+ }
+ else {
+ my $timeTotrigg = $af->[2] - $stamp;
+ $selectTimout = $timeTotrigg if $timeTotrigg < $selectTimout;
+ }
+ }
+} # end _scanAfter
+
+
+############### METHODE SCAN CONN STATUS
+sub _scanConnStatus ($$$@)
+{
+ my ($self, $appname, $status, @addr) = @_;
+
+ my (%readyApp, @nonReadyApp);
+
+ foreach (values %{$self->[cnnxion]}) {
+ next if $_ eq "1";
+ $readyApp{$_}++ unless /^\004/; # connecte mais pas ready
+ }
+
+ foreach (@{$self->[neededApp]}) {
+ push (@nonReadyApp, $_) unless exists $readyApp{$_};
+ }
+
+ # par compatibilite avec l'ancienne version, on envoie comme
+ # deux premiers arguments une ref sur la liste des applis presentes,
+ # une ref de la liste des applis absentes, mais on rajoute comme troisieme
+ # argument une ref sur une table de hash : comme clef les
+ # applis presentes, comme valeur le nombre d'applis ayant ce nom,
+ # de facon a detecter plus facilement quand il y a trop d'applis
+ # de meme nom sur le meme bus.
+ # les nouveaux arguments sont:
+ # le 4eme arg est l'appli nouvelle, deconnecté, qui s'abonne ou se desabonne
+ # le 5eme arg est le statut (actuellement: 'subscribing'|'filtered'|'unsubscribing'|'died'|'new')
+ # le 6eme arg est l'addresse de la machine sur laquelle tourne l'agent
+ # le 7eme arg est la regexp si c'est un abonnement ou desabonnement
+ &{$self->[statusFunc]} ([keys %readyApp], \@nonReadyApp, \%readyApp, $appname, $status, @addr);
+} # end _scanConnStatus
+
+
+############### METHODE TO BE PRUNED
+sub _toBePruned ($$$)
+{
+ my ($self, $from, $regexp) = @_;
+
+ # for message purposes, removing the \004 which indicates the connection status
+
+ my ($cleaned_from) = $from =~ /\004?(.*)/ ;
+ # print "DBG> $from s'abonne à nouvelle regexp '$regexp'\n";
+
+ # testing the received regexp for avoiding illformed regexp
+ eval {my $test = "a" =~ /$regexp/ } ;
+ if ($@) {
+ warn "Warning in Ivy::_toBePruned, receiving ill-formed regexp: '$regexp' from '$cleaned_from'" ;
+ return 1};
+
+
+ # si il n'y a pas de liste de sujets, on ne
+ # filtre pas
+ return 0 unless @{$self->[topicRegexps]};
+
+ unless ($regexp =~ /^\^/) {
+ #print "DBG> regexp non ANCREE de $from : $regexp\n";
+ return (0);
+ }
+
+ if ($regexp =~ /^\^(\w+)/) {
+ my $topic = $1;
+ if (grep (/$topic/, @{$self->[topicRegexps]})) {
+ # on a trouve ce topic : on ne filtre pas la regexp
+ #print "DBG> on garde de $from : $regexp\n";
+ return (0);
+ }
+ #print "DBG> on ELIMINE de $from : $regexp\n";
+ return (1);
+ }
+ else {
+ #print "DBG> on garde de $from : $regexp\n";
+ return (0);
+ }
+} # end _toBePruned
+
+
+############### PROCEDURE PARSE IVY BUS PARAM
+sub _parseIvyBusParam ($)
+{
+ my $ivyBus = shift;
+
+ my ($ivyNetworks, $ivyPort) = $ivyBus =~ /^(.*):(.*)/;
+
+ my $useMulticast = 0;
+
+ croak ("Error in Ivy::_parseIvyBusParam, illegal bus address format: $ivyBus\n")
+ unless $ivyPort =~ /^\d+$/;
+
+ my @ivyAddrInet = ();
+
+ $ivyNetworks =~ s/ //g;
+ my @broadcastAddrs = split (',', $ivyNetworks);
+
+ foreach my $netAddr (@broadcastAddrs) {
+ $netAddr = BROADCAST_ADDRS if
+ (($netAddr eq '') || ($netAddr =~ /^127/) || ($netAddr =~ /^loopback/));
+
+ # deux cas de figure : on a un nom de sous reseau, ou
+ # une adresse ip de la forme \d+.\d+....
+ my $netAddrInet;
+
+ if ($netAddr !~ /^\d+(.\d+)+/) {
+ # on a un nom de reseau, il faut trouver son adresse ip
+ # on contourne un bug : Si les adresses sont incompletes
+ # dans la map network : 143.196.53 au lieu de 143.196.53.255
+ # getbyname renvoie une adresse de type 0.143.196.53.
+ # on doit donc faire un decalage des octets vers la gauche,
+ # chaque 0 qui sort a gauche est remplace par un 255 a droite.
+ if ($useMulticast) {
+ carp "Warning in Ivy::_parseIvyBusParam, cannot mix broadcast and multicast.\n\tSkipping broadcast address $netAddr";
+ next;
+ }
+ my $networkAddr = getnetbyname ($netAddr);
+ unless (defined $networkAddr) {
+ warn ("Warning in Ivy::_parseIvyBusParam, network $netAddr is unknown\n");
+ next;
+ }
+
+ my @dummyNetAddr = unpack ("CCCC", pack ('N', $networkAddr));
+ while (!$dummyNetAddr[0]) {
+ # tant que le premier octet est 0, on decale vers la gauche et
+ # ont fait rentrer un 255 sur la droite
+ shift @dummyNetAddr;
+ push (@dummyNetAddr, 255);
+ }
+ $netAddrInet = pack ("CCCC", @dummyNetAddr);
+ } else {
+ # on a deja une adresse ip, on rajoute les .255
+ # a la fin s'ils ont ete omis.
+ ($netAddr .= ".255.255.255") =~ s/^((\d+\.){3}\d+).*/$1/;
+ my ($dClass) = $netAddr =~ /^(\d*)/ ;
+ if ($dClass >= 224 and $dClass <= 239) { # this adress is for multicast
+ if (!$useMulticast) { # removing all broadcast addresses
+ carp "Warning in Ivy::_parseIvyBusParam, cannot mix broadcast and multicast.\n\tSkipping previous broadcast address" if (scalar @ivyAddrInet);
+ @ivyAddrInet = ();
+ $useMulticast = 1;
+ }
+ }
+ else { # this adress is for broadcast
+ if ($useMulticast) { # we are already in multicast, forget this new address
+ carp "Warning in Ivy::_parseIvyBusParam, cannot mix broadcast and multicast.\n\tSkipping broadcast address $netAddr";
+ next;
+ }
+ }
+ $netAddrInet = inet_aton ($netAddr);
+ push (@ivyAddrInet, pack_sockaddr_in ($ivyPort, $netAddrInet));
+ }
+}
+ unless (scalar @ivyAddrInet) {
+ push (@ivyAddrInet, pack_sockaddr_in ($ivyPort, inet_aton(BROADCAST_ADDRS)));
+ }
+
+ foreach my $ivyAddr (@ivyAddrInet) {
+ my ($port, $iaddr) = unpack_sockaddr_in($ivyAddr);
+ my $iaddrH = unpack ("H8",$iaddr);
+ my $iaddrInt = inet_ntoa($iaddr);
+ if ($useMulticast) {print "Multicasting"}
+ else {print "Broadcasting"}
+ print " on network $iaddrInt ($iaddrH) on port $ivyPort\n";
+ }
+
+ return ($useMulticast, $ivyPort, \@ivyAddrInet);
+} # end _parseIvyBusParam
+
+############# Procedure _SUBSTITUTE ESCAPED CHAR
+sub _substituteEscapedChar ($$)
+{
+ my ($scope, $reg) = @_;
+
+ my %escapeRegexp = REG_PERLISSISME;
+ # Si on fait la substitution dans une classe de caractere
+ # on elimine les crochets.
+ grep ($escapeRegexp{$_} =~ s/[\[\]]//g, keys %escapeRegexp)
+ if ($scope eq 'inside') ;
+
+ $reg =~ s/\\([wWsSdDne])/$escapeRegexp{$1}/ge;
+ return $reg;
+} # end _substituteEscapedChar
+
+############# Procedure __CALL CONGESTION CALLBACK
+sub _callCongestionCb ($$$)
+{
+ my ($self, $fd, $congestion) = @_;
+ my $addrInet;
+ my $appName = _getNameByFileDes ($self, $fd);
+
+ if ($loopMode == LOCAL_MAINLOOP) {
+ if ($congestion) {
+ $localLoopSelWrite->add ($fd);
+ } else {
+ $localLoopSelWrite->remove ($fd);
+ }
+ } else {
+ if ($congestion) {
+ Tk::fileevent ('', $fd, 'writable',
+ sub {
+ my $bufEmiRef = \($self->[bufEmiByCnnx]->{$fd});
+ my $sent = send ($fd, $$bufEmiRef, 0);
+ unless (defined $sent) {
+ # y a rien à faire
+ } elsif ($sent == length ($$bufEmiRef)) {
+ $$bufEmiRef = "";
+ _callCongestionCb ($self, $fd, 0);
+ } elsif ($sent >= 0) {
+ substr ($$bufEmiRef, 0, $sent, '');
+ } else {
+ $self->_removeFileDescriptor ($fd, '_callCongestionCb') unless ($!{EAGAIN} || $!{EWOULDBLOCK}||
+ $!{EINTR} || $!{EMSGSIZE} || $!{ENOBUFS});
+ }
+ });
+ } else {
+ Tk::fileevent ('', $fd, 'writable', '');
+ }
+ }
+
+ if (defined $appName) {
+ $addrInet = $self->_inetAdrByName ($appName);
+ } else {
+ $appName = 'NONAME'; $addrInet = 'undef';
+ }
+
+ &{$self->[slowAgentFunc]} ($appName, $addrInet, $congestion);
+}
+
+############# Procedure __GET NAME BY FILEDES
+
+sub _getNameByFileDes ($$)
+{
+ my ($self, $fd) = @_;
+ my $appName = 'NONAME';
+ EXT_LOOP:
+ foreach my $name (keys %{$self->[appliList]}) {
+ foreach my $fdp (@{$self->[appliList]{$name}}) {
+ if ($fd eq $fdp) {
+ $appName = $name;
+ last EXT_LOOP;
+ }
+ }
+ }
+ return $appName;
+}
+
+
+sub _getHostByAddr ($)
+{
+ my $addr = shift;
+
+ unless (defined $addr) {
+ warn "_getHostByAddr : no argument\n";
+ return "EMPTY_ADDR";
+ } elsif ((length ($addr)) != 4) {
+ warn "_getHostByAddr : bad argument (len != 4)\n";
+ return "BAD_ADDR";
+ }
+
+ $hostNameByAddr{$addr} = (gethostbyaddr ($addr, AF_INET))[0] || inet_ntoa($addr)
+ unless exists $hostNameByAddr{$addr};
+
+ return $hostNameByAddr{$addr};
+}
+
+
+
+sub _regexpGen ($$$)
+{
+ my ($min, $max, $withDecimal) = @_;
+
+# print ("DBG> min=$min max=$max withDecimal=$withDecimal\n");
+
+ ($min, $max) = ($max, $min) if $min > $max;
+
+ $min = int ($min);
+ $max = int ($max);
+
+ my ($decimalPart,$boundDecimalPart, $reg) = ('') x 3;
+
+ if ((!defined $withDecimal) || ($withDecimal ne 'i')) {
+ $decimalPart = '(?:\.\d+)?';
+ $boundDecimalPart = '(?:\.0+)?';
+ }
+
+ if ($min == $max) {
+ $reg= $min;
+ } elsif ($min < 0) {
+ if ($max < 0) {
+ $reg = '\-(?:' . _strictPosRegexpGen (-$max, -$min, $decimalPart, $boundDecimalPart). ')';
+ } elsif ($max == 0) {
+ $reg = "(?:0${boundDecimalPart})|(?:-0${decimalPart})|-(?:" .
+ _strictPosRegexpGen (1, -$min, $decimalPart,$boundDecimalPart ) . ')';
+ } else {
+ $reg ='(?:' . regexpGen ($min, 0,$withDecimal) . '|' . regexpGen (0, $max, $withDecimal). ')' ;
+ }
+ } elsif ($min == 0) {
+ $reg = "(?:0${decimalPart})|" . _strictPosRegexpGen (1, $max, $decimalPart,$boundDecimalPart) ;
+ } else {
+ $reg = _strictPosRegexpGen ($min, $max, $decimalPart,$boundDecimalPart);
+ }
+ return ("(?:$reg)(?![\\d.])");
+}
+
+
+
+sub _strictPosRegexpGen ($$$$)
+{
+ my ($min, $max, $decimalPart,$boundDecimalPart) = @_;
+ carp "min[$min] sould be <= max[$max]\n " unless ($min <= $max);
+ carp "min[$min] and max[$max] should be strictly positive\n " unless (($min >0) && ($max > 0));
+
+# my $fixBound ;
+# $max -- if ($fixBound = ($decimalPart ne '') && ((int ($max /10) *10) != $max));
+
+ if ($min == $max) {
+ return ($min);
+ }
+
+ $max -- ;
+
+ my @regexps ;
+ my $nbRank = length ($max);
+ my ($rank, $lmax) ;
+
+ do {
+ ($lmax, $rank) = _nextMax ($min, $max);
+ push (@regexps, _genAtRank ($min, $lmax, $rank));
+ $min = $lmax+1;
+ } while ($lmax != $max) ;
+
+ my $regexp = join ('|', map ("(?:$_$decimalPart)", @regexps));
+ $max ++;
+ $regexp .= "|(?:${max}$boundDecimalPart)";
+
+ return ($regexp);
+}
+
+
+sub _genAtRank ($$$)
+{
+ my ($min, $max, $rank) = @_;
+ my $reg = _genPreRank ($min, $max, $rank);
+ $reg .= _genRank ($min, $max, $rank);
+ $reg .= _genPostRank ($rank);
+ return ($reg);
+}
+
+
+sub _nextMax ($$)
+{
+ my ($min, $max) = @_;
+ my $nextMax;
+
+ # on a les unités au debut
+ my (@min) = reverse split ('', $min);
+ my (@max) = reverse split ('', $max);
+ my $nbDigit = scalar (@max);
+ my ($rankRev, $rankForw, $rank) = (0, $nbDigit-1, 0) ;
+
+ # on rajoute des 0 en face si min n'a pas le même nombre de digits que max
+ push (@min, ('0') x ($#max - $#min)) if ($#min != $#max);
+
+ # on calcule le rang concerné par le prochain intervale
+
+ # en partant des unitées (digit de poids faible), premier champ de min != 0
+ while (($min[$rankRev] == 0) && $rankRev < $nbDigit) {$rankRev++} ;
+ # printf ("DBG> min = $min[0]|$min[1]|$min[2] rankRev=$rankRev, nbDigit=$nbDigit\n");
+
+ # en partant du digit de poids fort, premier champ de max != du même champ
+ while (($min[$rankForw] == $max[$rankForw]) && $rankForw > 0) {$rankForw--};
+# printf ("DBG> min = $min[0]|$min[1]|$min[2] rankForw=$rankForw, nbDigit=$nbDigit\n");
+
+ if ($rankForw <= $rankRev) {
+ $rank = $rankForw;
+ $min[$rankForw]= $max[$rankForw] - ($rankForw ? 1 : 0);
+ @min[0 .. $rankForw-1]= (9) x ($rankForw);
+ } else {
+ $rank = $rankRev;
+ @min[0 .. $rankRev]= (9) x ($rankRev+1);
+ }
+
+# print ("DBG> NEWmin = $min[0]|$min[1]|$min[2]\n");
+ $nextMax = join ('',reverse @min);
+ $nextMax = $max if $nextMax > $max;
+
+ return ($nextMax, $rank+1);
+}
+
+
+
+sub _genPreRank ($$$)
+# les invariants du min
+{
+ my ($min, $max, $rank) = @_;
+
+ $min = $min + 0; # force scalar to be evaluated as numérical
+ $max = $max + 0; # instead string (eliminate leading zeroes)
+ my $a = substr ($min, 0, (length ($min) - $rank));
+ my $b = substr ($max, 0, (length ($max) - $rank));
+ carp "genPreRank error $min, $max are not invariant @ rank $rank\n" if $a ne $b;
+ return $a;
+}
+
+
+sub _genRank ($$$)
+{
+ my ($min, $max, $rank) = @_;
+ my $syl ;
+
+ my $a = substr ($min, (length ($min) - $rank), 1);
+ my $b = substr ($max, (length ($max) - $rank), 1);
+
+ $min = _min ($a, $b);
+ $max = _max ($a, $b);
+
+ if (($min == 0) && ($max == 9)) {
+ $syl = '\d';
+ } elsif ($min == $max) {
+ $syl = $min;
+ } elsif ($max == $min+1) {
+ $syl = "[${min}${max}]"
+ } else {
+ $syl = "[$min-$max]";
+ }
+
+ return ($syl);
+}
+
+
+sub _genPostRank ($)
+{
+ my $rank = shift;
+
+ return "" if ($rank <= 1);
+ return ($rank == 2) ? '\d' : sprintf ('\d{%d}', $rank -1);
+}
+
+sub _max ($$)
+{
+ my ($a,$b) = @_;
+ return ($a > $b) ? $a : $b;
+}
+
+sub _min ($$)
+{
+ my ($a,$b) = @_;
+ return ($a > $b) ? $b : $a;
+}
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Ivy - Perl extension for implementing a software bus
+
+=head1 SYNOPSIS
+
+use Ivy;
+
+=head1 DESCRIPTION
+
+The Ivy perl module implements a software bus that provides easy
+communication between applications. Messages are broadcast as ASCII strings
+over a network defined by a list of domains and a port.
+Messages are received if they match a regular expressions and if your application
+is on the same network as remote ones.
+Before receive or send message you must call 'init', and 'new' class methods,
+followed by 'start' method.
+When you quit your application don't forget to call 'exit' class methods.
+
+=head1 CLASS METHODS
+
+=over 2
+
+=item B
+
+ Ivy->init(...);
+ Ivy::init(...);
+
+Allows one to define global parameters which may be used as default ones
+at object creation time.
+
+Parameters are :
+
+=over 4
+
+=item B<-loopMode =E 'TK'|'LOCAL'>
+
+Mode of events loop among TK or LOCAL. According to this mode, you must
+use Ivy->mainLoop or Tk::MainLoop(3)
+
+=item B<-appName =E 'your app ivy name'>
+
+Name of your application used to identify on ivy bus.
+
+=item B<-ivyBus =E 'domain 1,...,domain n:port number'>
+
+A list of domains (may be empty), followed by a port number where to broadcast messages.
+If the domain list is empty (i.e. parameter is ':port number'), broadcast will be done
+on localhost (i.e. '127:port number'). Default is the value of the environment variable
+IVYBUS and if it is not defined the default is 127:2010.
+
+Since V4.12, it is possible to use multicast (ie. with a domain between 224.0.0.0 and 239.255.255.255). You must be aware than when multicast is used, udp broadcast (defined in the B<-ivyBus> paramter) are skipped. You should also probably avoid using the 244.x.x.x domain often used for networking management.
+
+=item B<-messWhenReady =E 'your message when ready'>
+
+Synchronisation message sent when application is ready to receive and send
+messages.
+
+=item B<-onDieFunc =E [\&yourdiefunc, @parameters]>
+
+=item B<-onDieFunc =E [$an_object, \&a_method, @parameters]>
+
+A callback or method to call when your application receive a suicide request.
+Do not call exit() in the callback, Ivy will do it for you.
+
+The prototype of your callback must be as follows:
+
+ sub MyCallback {
+ my @parameters = @_;
+ ...
+ }
+
+The prototype of your method must be as follows:
+
+ sub MyMethod {
+ my ($self, @parameters) = @_;
+ ...
+ }
+
+=item B<-filterRegexp =E ['subject 1', ..., 'subject n']>
+
+Optimize communication using this option. Regexps
+which don't match these subjects are removed.
+
+=item B
+
+ Ivy->init(-loopMode => 'TK',
+ -appName => 'MyWonderfulApp',
+ -onDieFunc => [\&restorecontext] ,
+ -filterRegexp => ['MyWonderfulApp', 'ClockStart', 'ClockStop']);
+
+=back
+
+=item B
+
+ Ivy::new(...);
+ Ivy->new(...);
+
+Check parameters, and create an Ivy bus object. You must call
+Ivy->init before creating a bus.
+
+Parameters are :
+
+=over 4
+
+=item B<-appName =E 'your application name'>
+
+Name of your application used to identify it with other applications
+connected on the same bus.
+
+=item B<-ivyBus =E 'domain 1,...,domain n:port number'>
+
+A list of domains, followed by port number where to broadcast messages.
+Default is 127:2010
+
+=item B<-messWhenReady =E 'your message when ready'>
+
+Synchronisation message sent when your application is ready to receive and send
+messages.
+
+=item B<-onDieFunc =E [\&yourdiefunc, @parameters]>
+
+=item B<-onDieFunc =E [$an_object, \&a_method, @parameters]>
+
+A callback or method called when your application receives a suicide request.
+DO NOT CALL exit() in the callback, Ivy will do it for you.
+The prototype of your callback must be as follows:
+
+ sub MyCallback {
+ my @parameters = @_;
+ ...
+ }
+
+The prototype of your method must be as follows:
+
+ sub MyMethod {
+ my ($self, @parameters) = @_;
+ ...
+ }
+
+=item B<-filterRegexp =E ['subject 1', ..., 'subject n']>
+
+Optimize communication using this option. Regexps which don't match these subjects are removed.
+
+=item B<-neededApp =E ['app 1', ..., 'app n']>
+
+A list of applications that your application needs present on the bus
+before running.
+
+=item B<-statusFunc =E sub {}>
+
+A callback which is called every time an agent C connects on the bus,
+disconnects from the bus, subscribes to a regexp, or unsubscribes to a
+regexp. When the agent A is stopping, this function is also called
+inside the agent A for every other agents C on the bus, as they are
+disconnecting. The first 3 parameters are a reference to an array of
+connected agents Ci, a reference to an array of not connected agents
+(according to the "-neededApp" argument of the new method / function),
+a reference to a hash table of connected agents Ci (giving the number
+of each agent). These 3 parameters are maintained for upwards
+compatibility but should no more be used, since the following four
+parameters are much easier to use: the name of an appearing /
+disapearing or subscribing / filtered / unsubscribing agent C, its status either
+"new" or "died" or "subscribing" or "unsubscribing", and the hostname
+where this agent C is running / dying OR the subscribed / unsubscribed
+regexp. If the hostname of this agent C is not known, it will be
+replaced by its IP address.
+
+
+
+Your callback could be:
+
+ sub MyCallback {
+ my ($ref_array_present, $ref_array_absent, $ref_hash_present,
+ $appname, $status, $host, $regexp) = @_;
+
+ # $status is either new or died
+
+ my %present=%$ref_hash_present;
+ foreach my $remoteapp (keys %present) {
+ if ($present{$remoteapp} > 1) {
+ print "n apps $remoteapp are presents on bus\n";
+ }
+ }
+ if ($status eq "new") {
+ print "$appname connected from $host\n";
+ }
+ elsif ($status eq "died") {
+ print "$appname disconnected from $host\n";
+ }
+ elsif ($status eq "subscribing") {
+ print "$appname subscribes to $regexp\n";
+ }
+ elsif ($status eq "filtered") {
+ print "$appname subscribes to FILTERED $regexp check -filterRegexp option\n";
+ }
+ elsif ($status eq "unsubscribing") {
+ print "$appname unsubscribed to $regexp\n";
+ }
+ }
+
+
+
+
+=item B<-blockOnSlowAgent =E 0 or 1>
+
+Behavior when the bus is being congested due to an ivy agent which
+doesn't read messages sufficiently quickly. In blocking mode the local
+app will block on a send, so it won't be interactive until the send
+return, and it will at his turn don't read his pending message,
+leading to a global sluggishness of the entire ivy bus. In non
+blocking mode the messages are stocked until they could be sent, so
+the problem is the uncontrolled memory consumption.
+
+
+=item B<-slowAgentFunc=E \&congestionFunc >
+
+ A callback which is called every time a congestion event occurs. A
+ congestion event is emitted each time an agent is being congested,
+ or, after being congested is able to read his messages again. The
+ parameters are the name of the app, his address (hostname+port
+ number), and the state, 1 for congested, 0 for able to read.
+
+ Your callback could be:
+
+sub congestionFunc ($$$)
+{
+ my ($name, $addr, $state) = @_;
+ printf ("$name [$addr] %s\n", $state ? "CONGESTION" : "OK");
+}
+
+
+
+=item B
+
+ Ivy->new(-ivyBus => '156,157:2204',
+ -onDieFunc => [\&restorecontext],
+ -neededApp => ["DataServer", "HMI"],
+ -slowAgentFunc=> \&congestionFunc,
+ -blockOnSlowAgent => 1,
+ -statusFunc => \&MyCallback);
+
+=back
+
+
+
+
+
+
+=item B
+
+ Ivy->mainLoop;
+ Ivy::mainLoop;
+ $ivyobj->mainLoop;
+
+ main events loop, call local mainloop or Tk::MainLoop according so specified mode
+
+=item B
+
+ $ivyobj->stop;
+ Ivy::stop;
+
+To stop the Ivy main loop.
+
+=back
+
+=head1 OBJECT METHODS
+
+=over 2
+
+=item B
+
+ $ivyobj->start;
+ Ivy::start;
+
+You must call this after you are ready to communicate through an Ivy bus
+and before you really communicate. The method returns the $ivyobj.
+
+=item B
+
+ $ivyobj->sendMsgs(@messages);
+ Ivy::sendMsgs(@messages);
+
+Send a list of messages. A message should not contain a '\n' or it will not be delivered.
+
+ Example :
+ $ivyobj->sendMsgs("Hello", "Don't Bother", "Y2K is behind us");
+
+=item B
+
+ $ivyobj->sendAppNameMsgs(@messages);
+ Ivy::sendAppNameMsgs(@messages);
+
+Send a list of messages preceded by your application's name. A message should not contain a '\n' or it will not be delivered.
+
+ Example :
+ $ivyobj->sendMsgs("Hello World");
+ # it will send "$appName Hello World" over the Ivy bus
+
+=item B
+
+ $ivyobject->bindRegexp($regexp, [\&callback, @cb_parameters]);
+ Ivy::bindRegexp($regexp, [\&callback, @cb_parameters]);
+
+ $ivyobject->bindRegexp($regexp, [$an_obj, \&method, @cb_parameters]);
+ Ivy::bindRegexp($regexp, [$an_obj, \&method, @cb_parameters]);
+
+This allows you to bind a regular expression to a
+callback or method. The callback or method will be called for every
+message that matches the regexp (case insensitive).
+See perlre(1) to find how to write regexps.
+Use the bracketing construct ( ... ) so that your callback is
+called with the captured bits of text as parameters.
+To unbind callback(s) associated to a regexp use bindRegexp with only
+one argument, the regexp. Note that doing the same binding more than
+once will induce multiple call of the same callback (this is usually a bug).
+
+there is a special syntax for specifying numeric interval, in this case
+the interval is locally transformed in a pcre regexp.
+syntax is (?Imin#max[fi]). min and max are the bounds,
+by default the regexp match decimal number, but if max bound is
+followed by 'i', the regexp match only integers ex : (?I-10#20), (?I20#25i)
+Note that due to the regexp generator limitation (which will perhaps be raised eventually)
+the bounds are always integer.
+
+Return value : regexpId
+
+ Example :
+ $ivyobject->bindRegexp("\w+ (\d+)", [\&callback, @cb_parameters]);
+ $ivyobject->bindRegexp("\w+ ((?I-10#20i))", [\&callback, @cb_parameters]);
+
+ # Your callback will be called with one more parameter which will be
+ # the name of appli which send the message
+
+ # Your callback and method must be like:
+ sub callback {
+ my ($sendername, @cb_parameters,
+ @matched_regexps_in_brackets) = @_;
+ ...
+ }
+
+ sub method {
+ my ($self, $sendername, @cb_parameters,
+ @matched_regexps_in_brackets) = @_;
+ ...
+ }
+
+ # to unbind:
+ $ivyobject->bindRegexp("\w+ (\d+)");
+
+=item B
+
+ $ivyobject->bindRegexpOneShot($regexp, [\&callback, @cb_parameters]);
+ Ivy::bindRegexpOneShot($regexp, [\&callback, @cb_parameters]);
+
+ bindRegexpOneShot behavior is similar at bindRegexp one, except that
+ the callback is called once, it is similar as a bindRegexp with an unbind in the callback
+ but is simpler to write.
+
+=item B
+
+ $regexpId = $ivyobject->bindRegexp("initialRegexp", [\&callback, @cb_parameters]);
+ $ivyobject->changeRegexp($regexpId, "newRegexp");
+ or
+ Ivy::changeRegexp($regexpId, "newRegexp");
+
+This allow you to change the regexp of a previously made bindRegexp, the callback
+will remain the same. This is equivalent to unbinding current regexp and binding the new regexp,
+but in this last case the 2 op are not done in atomic manner, and you can miss a message
+or receive it twice.
+
+
+=item B
+
+ $ivyobj->sendDirectMsgs($to, $id, @msgs);
+ Ivy::sendDirectMsgs($to, $id, @msgs);
+
+Send a message a message to appli $to. This appli must have done a bindDirect before to accept this message. regexp matching is not used with direct Messages. A message should not contain a '\n' or it will not be delivered.
+
+=item B
+
+ $ivyobj->bindDirect($regexp, $id, [\&callback, @cb_parameters]);
+ Ivy::bindDirect($id, [\&callback, @cb_parameters]);
+
+The callback will be called with both the @msgs and the @cb_parameters.
+
+ Example :
+ $ivyobject->bindDirectMessage("id1", [\&callback, @cb_parameters]);
+
+ # Your callback and method must be like:
+ sub cb {
+ my (@cb_parameters, @msgs) = @_;
+ ...
+ }
+
+ sub method {
+ my ($self, @cb_parameters, @msgs) = @_;
+ ...
+ }
+
+=item B
+
+ $ivyobj->sendDieTo($to);
+ Ivy::sendDieTo($to);
+
+Send a suicide to the application named $to.
+
+=item B
+
+ $ivyobj->ping($to, $timeout);
+ Ivy::ping($to, \&callBack);
+
+Send a ping message, callBack will be called on response.
+
+=item B
+
+ $after_id = $ivyobj->after($timeAfter, \@callbacks_list);
+ $after_id = Ivy::after($timeAfter, \@callbacks_list);
+
+Call a list of callbacks after $timeAfter milliseconds. To be used only in conjonction with the 'LOCAL' Mainloop (see the Init method). When using the TK mainloop, you must use the Tk::after method.
+
+=item B
+
+ $repeat_id = $ivyobj->repeat($timeAfter, \@callbacks_list);
+ $repeat_id = Ivy:repeat($timeAfter, \@callbacks_list);
+
+Have a list of callbacks repeatedly called every $timeAfter milliseconds. To be used only in conjonction with the 'LOCAL' Mainloop (see the Init method). When using the TK mainloop, you must use the Tk::repeat method.
+
+=item B
+
+ $ivyobj->afterCancel($after_or_repeat_id);
+ Ivy::afterCancel($after_or_repeat_id);
+
+Cancel an after callback call. To be used only in conjonction with the 'LOCAL' Mainloop (see the Init method). When using the TK mainloop, you must use the Tk::afterCancel method.
+
+=item B
+
+ $ivyobj->afterResetTimer($after_id);
+ Ivy::afterResetTimer($after_id);
+
+Reset a timer if this timer has not yet been triggered. To be used only in conjonction with the 'LOCAL' Mainloop (see the Init method).
+
+=item B
+
+ $ivyobj->fileEvent($fd, $cb);
+ Ivy::fileEvent($fd, $cb);
+
+Add a fileEvent handler (or remove any handler associated to $fd if $cb paramter is omitted).
+The callback $cb will get the filehandle $fd as parameter. To be used only in conjonction with the 'LOCAL' Mainloop (see the Init method). When using the TK mainloop, you must use the Tk::fileevent method.
+
+=item B
+
+ $ivyobj->DESTROY;
+
+Destroy the $ivyobj object. No other method should be applied to the reference of this deleted object. This method should not be used directly.
+
+=back
+
+=head1 BUGS
+
+The stop method does not work!
+
+In the statusFunc, an agent is identified by its name which is not garantted as unique
+
+A message to be sent should not contain '\n' char, because the '\n' is the message separator. Ivy.pm will detect and skip such messages.
+
+No other known bugs at this time. If you find one, please report them to the authors.
+
+=head1 SEE ALSO
+
+perl(1), perlre(1), ivyprobe.pl(1)
+
+=head1 AUTHORS
+ Alexandre Bustico
+ Stéphane Chatty
+ Hervé Damiano
+ Christophe Mertz
+
+=head1 COPYRIGHT
+
+CENA (C) 1997-2006
+
+=head1 HISTORY
+
+=cut
diff --git a/tests/lib/Proc/Background.pm b/tests/lib/Proc/Background.pm
new file mode 100644
index 0000000000..aeac81550b
--- /dev/null
+++ b/tests/lib/Proc/Background.pm
@@ -0,0 +1,477 @@
+# Proc::Background: Generic interface to background process management.
+#
+# Copyright (C) 1998-2005 Blair Zajac. All rights reserved.
+
+package Proc::Background;
+
+require 5.004_04;
+
+use strict;
+use Exporter;
+use Carp;
+use Cwd;
+
+use vars qw(@ISA $VERSION @EXPORT_OK);
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(timeout_system);
+$VERSION = sprintf '%d.%02d', '$Revision: 1.10 $' =~ /(\d+)\.(\d+)/;
+
+# Determine if the operating system is Windows.
+my $is_windows = $^O eq 'MSWin32';
+
+# Set up a regular expression that tests if the path is absolute and
+# if it has a directory separator in it. Also create a list of file
+# extensions of append to the programs name to look for the real
+# executable.
+my $is_absolute_re;
+my $has_dir_element_re;
+my @extensions = ('');
+if ($is_windows) {
+ $is_absolute_re = '^(?:(?:[a-zA-Z]:[\\\\/])|(?:[\\\\/]{2}\w+[\\\\/]))';
+ $has_dir_element_re = "[\\\\/]";
+ push(@extensions, '.exe');
+} else {
+ $is_absolute_re = "^/";
+ $has_dir_element_re = "/";
+}
+
+# Make this class a subclass of Proc::Win32 or Proc::Unix. Any
+# unresolved method calls will go to either of these classes.
+if ($is_windows) {
+ require Proc::Background::Win32;
+ unshift(@ISA, 'Proc::Background::Win32');
+} else {
+ require Proc::Background::Unix;
+ unshift(@ISA, 'Proc::Background::Unix');
+}
+
+# Take either a relative or absolute path to a command and make it an
+# absolute path.
+sub _resolve_path {
+ my $command = shift;
+
+ return unless length $command;
+
+ # Make the path to the progam absolute if it isn't already. If the
+ # path is not absolute and if the path contains a directory element
+ # separator, then only prepend the current working to it. If the
+ # path is not absolute, then look through the PATH environment to
+ # find the executable. In all cases, look for the programs with any
+ # extensions added to the original path name.
+ my $path;
+ if ($command =~ /$is_absolute_re/o) {
+ foreach my $ext (@extensions) {
+ my $p = "$command$ext";
+ if (-f $p and -x _) {
+ $path = $p;
+ last;
+ }
+ }
+ unless (defined $path) {
+ warn "$0: no executable program located at $command\n";
+ }
+ } else {
+ my $cwd = cwd;
+ if ($command =~ /$has_dir_element_re/o) {
+ my $p1 = "$cwd/$command";
+ foreach my $ext (@extensions) {
+ my $p2 = "$p1$ext";
+ if (-f $p2 and -x _) {
+ $path = $p2;
+ last;
+ }
+ }
+ } else {
+ foreach my $dir (split($is_windows ? ';' : ':', $ENV{PATH})) {
+ next unless length $dir;
+ $dir = "$cwd/$dir" unless $dir =~ /$is_absolute_re/o;
+ my $p1 = "$dir/$command";
+ foreach my $ext (@extensions) {
+ my $p2 = "$p1$ext";
+ if (-f $p2 and -x _) {
+ $path = $p2;
+ last;
+ }
+ }
+ last if defined $path;
+ }
+ }
+ unless (defined $path) {
+ warn "$0: cannot find absolute location of $command\n";
+ }
+ }
+
+ $path;
+}
+
+# We want the created object to live in Proc::Background instead of
+# the OS specific class so that generic method calls can be used.
+sub new {
+ my $class = shift;
+
+ my $options;
+ if (@_ and defined $_[0] and UNIVERSAL::isa($_[0], 'HASH')) {
+ $options = shift;
+ }
+
+ unless (@_ > 0) {
+ confess "Proc::Background::new called with insufficient number of arguments";
+ }
+
+ return unless defined $_[0];
+
+ my $self = $class->SUPER::_new(@_) or return;
+
+ # Save the start time of the class.
+ $self->{_start_time} = time;
+
+ # Handle the specific options.
+ if ($options) {
+ $self->{_die_upon_destroy} = $options->{die_upon_destroy};
+ }
+
+ bless $self, $class;
+}
+
+sub DESTROY {
+ my $self = shift;
+ if ($self->{_die_upon_destroy}) {
+ $self->die;
+ }
+}
+
+# Reap the child. If the first argument is 0 the wait should return
+# immediately, 1 if it should wait forever. If this number is
+# non-zero, then wait. If the wait was sucessful, then delete
+# $self->{_os_obj} and set $self->{_exit_value} to the OS specific
+# class return of _reap. Return 1 if we sucessfully waited, 0
+# otherwise.
+sub _reap {
+ my $self = shift;
+ my $timeout = shift || 0;
+
+ return 0 unless exists($self->{_os_obj});
+
+ # Try to wait on the process. Use the OS dependent wait call using
+ # the Proc::Background::*::waitpid call, which returns one of three
+ # values.
+ # (0, exit_value) : sucessfully waited on.
+ # (1, undef) : process already reaped and exist value lost.
+ # (2, undef) : process still running.
+ my ($result, $exit_value) = $self->_waitpid($timeout);
+ if ($result == 0 or $result == 1) {
+ $self->{_exit_value} = defined($exit_value) ? $exit_value : 0;
+ delete $self->{_os_obj};
+ # Save the end time of the class.
+ $self->{_end_time} = time;
+ return 1;
+ }
+ return 0;
+}
+
+sub alive {
+ my $self = shift;
+
+ # If $self->{_os_obj} is not set, then the process is definitely
+ # not running.
+ return 0 unless exists($self->{_os_obj});
+
+ # If $self->{_exit_value} is set, then the process has already finished.
+ return 0 if exists($self->{_exit_value});
+
+ # Try to reap the child. If it doesn't reap, then it's alive.
+ !$self->_reap(0);
+}
+
+sub wait {
+ my $self = shift;
+
+ # If neither _os_obj or _exit_value are set, then something is wrong.
+ if (!exists($self->{_exit_value}) and !exists($self->{_os_obj})) {
+ return;
+ }
+
+ # If $self->{_exit_value} exists, then we already waited.
+ return $self->{_exit_value} if exists($self->{_exit_value});
+
+ # Otherwise, wait forever for the process to finish.
+ $self->_reap(1);
+ return $self->{_exit_value};
+}
+
+sub die {
+ my $self = shift;
+
+ # See if the process has already died.
+ return 1 unless $self->alive;
+
+ # Kill the process using the OS specific method.
+ $self->_die;
+
+ # See if the process is still alive.
+ !$self->alive;
+}
+
+sub start_time {
+ $_[0]->{_start_time};
+}
+
+sub end_time {
+ $_[0]->{_end_time};
+}
+
+sub pid {
+ $_[0]->{_pid};
+}
+
+sub timeout_system {
+ unless (@_ > 1) {
+ confess "$0: timeout_system passed too few arguments.\n";
+ }
+
+ my $timeout = shift;
+ unless ($timeout =~ /^\d+(?:\.\d*)?$/ or $timeout =~ /^\.\d+$/) {
+ confess "$0: timeout_system passed a non-positive number first argument.\n";
+ }
+
+ my $proc = Proc::Background->new(@_) or return;
+ my $end_time = $proc->start_time + $timeout;
+ while ($proc->alive and time < $end_time) {
+ sleep(1);
+ }
+
+ my $alive = $proc->alive;
+ if ($alive) {
+ $proc->die;
+ }
+
+ if (wantarray) {
+ return ($proc->wait, $alive);
+ } else {
+ return $proc->wait;
+ }
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Proc::Background - Generic interface to Unix and Win32 background process management
+
+=head1 SYNOPSIS
+
+ use Proc::Background;
+ timeout_system($seconds, $command, $arg1);
+ timeout_system($seconds, "$command $arg1");
+
+ my $proc1 = Proc::Background->new($command, $arg1, $arg2);
+ my $proc2 = Proc::Background->new("$command $arg1 1>&2");
+ $proc1->alive;
+ $proc1->die;
+ $proc1->wait;
+ my $time1 = $proc1->start_time;
+ my $time2 = $proc1->end_time;
+
+ # Add an option to kill the process with die when the variable is
+ # DETROYed.
+ my $opts = {'die_upon_destroy' => 1};
+ my $proc3 = Proc::Background->new($opts, $command, $arg1, $arg2);
+ $proc3 = undef;
+
+=head1 DESCRIPTION
+
+This is a generic interface for placing processes in the background on
+both Unix and Win32 platforms. This module lets you start, kill, wait
+on, retrieve exit values, and see if background processes still exist.
+
+=head1 METHODS
+
+=over 4
+
+=item B [options] I, [I, [I, ...]]
+
+=item B [options] 'I [I [I ...]]'
+
+This creates a new background process. As exec() or system() may be
+passed an array with a single single string element containing a
+command to be passed to the shell or an array with more than one
+element to be run without calling the shell, B has the same
+behavior.
+
+In certain cases B will attempt to find I on the system
+and fail if it cannot be found.
+
+For Win32 operating systems:
+
+ The Win32::Process module is always used to spawn background
+ processes on the Win32 platform. This module always takes a
+ single string argument containing the executable's name and
+ any option arguments. In addition, it requires that the
+ absolute path to the executable is also passed to it. If
+ only a single argument is passed to new, then it is split on
+ whitespace into an array and the first element of the split
+ array is used at the executable's name. If multiple
+ arguments are passed to new, then the first element is used
+ as the executable's name.
+
+ If the executable's name is an absolute path, then new
+ checks to see if the executable exists in the given location
+ or fails otherwise. If the executable's name is not
+ absolute, then the executable is searched for using the PATH
+ environmental variable. The input executable name is always
+ replaced with the absolute path determined by this process.
+
+ In addition, when searching for the executable, the
+ executable is searched for using the unchanged executable
+ name and if that is not found, then it is checked by
+ appending `.exe' to the name in case the name was passed
+ without the `.exe' suffix.
+
+ Finally, the argument array is placed back into a single
+ string and passed to Win32::Process::Create.
+
+For non-Win32 operating systems, such as Unix:
+
+ If more than one argument is passed to new, then new
+ assumes that the command will not be passed through the
+ shell and the first argument is the executable's relative
+ or absolute path. If the first argument is an absolute
+ path, then it is checked to see if it exists and can be
+ run, otherwise new fails. If the path is not absolute,
+ then the PATH environmental variable is checked to see if
+ the executable can be found. If the executable cannot be
+ found, then new fails. These steps are taking to prevent
+ exec() from failing after an fork() without the caller of
+ new knowing that something failed.
+
+The first argument to B I may be a reference to a hash
+which contains key/value pairs to modify Proc::Background's behavior.
+Currently the only key understood by B is I.
+When this value is set to true, then when the Proc::Background object
+is being DESTROY'ed for any reason (i.e. the variable goes out of
+scope) the process is killed via the die() method.
+
+If anything fails, then new returns an empty list in a list context,
+an undefined value in a scalar context, or nothing in a void context.
+
+=item B
+
+Returns the process ID of the created process. This value is saved
+even if the process has already finished.
+
+=item B
+
+Return 1 if the process is still active, 0 otherwise.
+
+=item B
+
+Reliably try to kill the process. Returns 1 if the process no longer
+exists once B has completed, 0 otherwise. This will also return
+1 if the process has already died. On Unix, the following signals are
+sent to the process in one second intervals until the process dies:
+HUP, QUIT, INT, KILL.
+
+=item B
+
+Wait for the process to exit. Return the exit status of the command
+as returned by wait() on the system. To get the actual exit value,
+divide by 256 or right bit shift by 8, regardless of the operating
+system being used. If the process never existed, then return an empty
+list in a list context, an undefined value in a scalar context, or
+nothing in a void context. This function may be called multiple times
+even after the process has exited and it will return the same exit
+status.
+
+=item B
+
+Return the value that the Perl function time() returned when the
+process was started.
+
+=item B
+
+Return the value that the Perl function time() returned when the exit
+status was obtained from the process.
+
+=back
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item B I, I, [I, [I...]]
+
+=item B 'I I [I [I...]]'
+
+Run a command for I seconds and if the process did not exit,
+then kill it. While the timeout is implemented using sleep(), this
+function makes sure that the full I is reached before killing
+the process. B does not wait for the complete
+I number of seconds before checking if the process has
+exited. Rather, it sleeps repeatidly for 1 second and checks to see
+if the process still exists.
+
+In a scalar context, B returns the exit status from
+the process. In an array context, B returns a two
+element array, where the first element is the exist status from the
+process and the second is set to 1 if the process was killed by
+B or 0 if the process exited by itself.
+
+The exit status is the value returned from the wait() call. If the
+process was killed, then the return value will include the killing of
+it. To get the actual exit value, divide by 256.
+
+If something failed in the creation of the process, the subroutine
+returns an empty list in a list context, an undefined value in a
+scalar context, or nothing in a void context.
+
+=back
+
+=head1 IMPLEMENTATION
+
+I comes with two modules, I
+and I. Currently, on Unix platforms
+I uses the I class and on
+Win32 platforms it uses I, which makes use of
+I.
+
+The I assigns to @ISA either
+I or I, which does
+the OS dependent work. The OS independent work is done in
+I.
+
+Proc::Background uses two variables to keep track of the process.
+$self->{_os_obj} contains the operating system object to reference the
+process. On a Unix systems this is the process id (pid). On Win32,
+it is an object returned from the I class. When
+$self->{_os_obj} exists, then the process is running. When the
+process dies, this is recorded by deleting $self->{_os_obj} and saving
+the exit value $self->{_exit_value}.
+
+Anytime I is called, a waitpid() is called on the process and
+the return status, if any, is gathered and saved for a call to
+I. This module does not install a signal handler for SIGCHLD.
+If for some reason, the user has installed a signal handler for
+SIGCHLD, then, then when this module calls waitpid(), the failure will
+be noticed and taken as the exited child, but it won't be able to
+gather the exit status. In this case, the exit status will be set to
+0.
+
+=head1 SEE ALSO
+
+See also L and L.
+
+=head1 AUTHOR
+
+Blair Zajac
+
+=head1 COPYRIGHT
+
+Copyright (C) 1998-2005 Blair Zajac. All rights reserved. This
+package is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
diff --git a/tests/lib/Proc/Background/Unix.pm b/tests/lib/Proc/Background/Unix.pm
new file mode 100644
index 0000000000..07f2ea7328
--- /dev/null
+++ b/tests/lib/Proc/Background/Unix.pm
@@ -0,0 +1,138 @@
+# Proc::Background::Unix: Unix interface to background process management.
+#
+# Copyright (C) 1998-2005 Blair Zajac. All rights reserved.
+
+package Proc::Background::Unix;
+
+require 5.004_04;
+
+use strict;
+use Exporter;
+use Carp;
+use POSIX qw(:errno_h :sys_wait_h);
+
+use vars qw(@ISA $VERSION);
+@ISA = qw(Exporter);
+$VERSION = sprintf '%d.%02d', '$Revision: 1.10 $' =~ /(\d+)\.(\d+)/;
+
+# Start the background process. If it is started sucessfully, then record
+# the process id in $self->{_os_obj}.
+sub _new {
+ my $class = shift;
+
+ unless (@_ > 0) {
+ confess "Proc::Background::Unix::_new called with insufficient number of arguments";
+ }
+
+ return unless defined $_[0];
+
+ # If there is only one element in the @_ array, then it may be a
+ # command to be passed to the shell and should not be checked, in
+ # case the command sets environmental variables in the beginning,
+ # i.e. 'VAR=arg ls -l'. If there is more than one element in the
+ # array, then check that the first element is a valid executable
+ # that can be found through the PATH and find the absolute path to
+ # the executable. If the executable is found, then replace the
+ # first element it with the absolute path.
+ my @args = @_;
+ if (@_ > 1) {
+ $args[0] = Proc::Background::_resolve_path($args[0]) or return;
+ }
+
+ my $self = bless {}, $class;
+
+ # Fork a child process.
+ my $pid;
+ {
+ if ($pid = fork()) {
+ # parent
+ $self->{_os_obj} = $pid;
+ $self->{_pid} = $pid;
+ last;
+ } elsif (defined $pid) {
+ # child
+ exec @_ or croak "$0: exec failed: $!\n";
+ } elsif ($! == EAGAIN) {
+ sleep 5;
+ redo;
+ } else {
+ return;
+ }
+ }
+
+ $self;
+}
+
+# Wait for the child.
+sub _waitpid {
+ my $self = shift;
+ my $timeout = shift;
+
+ {
+ # Try to wait on the process.
+ my $result = waitpid($self->{_os_obj}, $timeout ? 0 : WNOHANG);
+ # Process finished. Grab the exit value.
+ if ($result == $self->{_os_obj}) {
+ return (0, $?);
+ }
+ # Process already reaped. We don't know the exist status.
+ elsif ($result == -1 and $! == ECHILD) {
+ return (1, 0);
+ }
+ # Process still running.
+ elsif ($result == 0) {
+ return (2, 0);
+ }
+ # If we reach here, then waitpid caught a signal, so let's retry it.
+ redo;
+ }
+ return 0;
+}
+
+sub _die {
+ my $self = shift;
+
+ # Try to kill the process with different signals. Calling alive() will
+ # collect the exit status of the program.
+ SIGNAL: {
+ foreach my $signal (qw(HUP QUIT INT KILL)) {
+ my $count = 5;
+ while ($count and $self->alive) {
+ --$count;
+ kill($signal, $self->{_os_obj});
+ last SIGNAL unless $self->alive;
+ sleep 1;
+ }
+ }
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Proc::Background::Unix - Unix interface to process mangement
+
+=head1 SYNOPSIS
+
+Do not use this module directly.
+
+=head1 DESCRIPTION
+
+This is a process management class designed specifically for Unix
+operating systems. It is not meant used except through the
+I class. See L for more information.
+
+=head1 AUTHOR
+
+Blair Zajac
+
+=head1 COPYRIGHT
+
+Copyright (C) 1998-2005 Blair Zajac. All rights reserved. This
+package is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
diff --git a/tests/lib/Proc/Background/Win32.pm b/tests/lib/Proc/Background/Win32.pm
new file mode 100644
index 0000000000..62b7949c1c
--- /dev/null
+++ b/tests/lib/Proc/Background/Win32.pm
@@ -0,0 +1,157 @@
+# Proc::Background::Win32 Windows interface to background process management.
+#
+# Copyright (C) 1998-2005 Blair Zajac. All rights reserved.
+
+package Proc::Background::Win32;
+
+require 5.004_04;
+
+use strict;
+use Exporter;
+use Carp;
+
+use vars qw(@ISA $VERSION);
+@ISA = qw(Exporter);
+$VERSION = sprintf '%d.%02d', '$Revision: 1.10 $' =~ /(\d+)\.(\d+)/;
+
+BEGIN {
+ eval "use Win32";
+ $@ and die "Proc::Background::Win32 needs Win32 from libwin32-?.??.zip to run.\n";
+ eval "use Win32::Process";
+ $@ and die "Proc::Background::Win32 needs Win32::Process from libwin32-?.??.zip to run.\n";
+}
+
+sub _new {
+ my $class = shift;
+
+ unless (@_ > 0) {
+ confess "Proc::Background::Win32::_new called with insufficient number of arguments";
+ }
+
+ return unless defined $_[0];
+
+ # If there is only one element in the @_ array, then just split the
+ # argument by whitespace. If there is more than one element in @_,
+ # then assume that each argument should be properly protected from
+ # the shell so that whitespace and special characters are passed
+ # properly to the program, just as it would be in a Unix
+ # environment. This will ensure that a single argument with
+ # whitespace will not be split into multiple arguments by the time
+ # the program is run. Make sure that any arguments that are already
+ # protected stay protected. Then convert unquoted "'s into \"'s.
+ # Finally, check for whitespace and protect it.
+ my @args;
+ if (@_ == 1) {
+ @args = split(' ', $_[0]);
+ } else {
+ @args = @_;
+ for (my $i=1; $i<@args; ++$i) {
+ my $arg = $args[$i];
+ $arg =~ s#\\\\#\200#g;
+ $arg =~ s#\\"#\201#g;
+ $arg =~ s#"#\\"#g;
+ $arg =~ s#\200#\\\\#g;
+ $arg =~ s#\201#\\"#g;
+ if (length($arg) == 0 or $arg =~ /\s/) {
+ $arg = "\"$arg\"";
+ }
+ $args[$i] = $arg;
+ }
+ }
+
+ # Find the absolute path to the program. If it cannot be found,
+ # then return. To work around a problem where
+ # Win32::Process::Create cannot start a process when the full
+ # pathname has a space in it, convert the full pathname to the
+ # Windows short 8.3 format which contains no spaces.
+ $args[0] = Proc::Background::_resolve_path($args[0]) or return;
+ $args[0] = Win32::GetShortPathName($args[0]);
+
+ my $self = bless {}, $class;
+
+ # Perl 5.004_04 cannot run Win32::Process::Create on a nonexistant
+ # hash key.
+ my $os_obj = 0;
+
+ # Create the process.
+ if (Win32::Process::Create($os_obj,
+ $args[0],
+ "@args",
+ 0,
+ NORMAL_PRIORITY_CLASS,
+ '.')) {
+ $self->{_pid} = $os_obj->GetProcessID;
+ $self->{_os_obj} = $os_obj;
+ return $self;
+ } else {
+ return;
+ }
+}
+
+# Reap the child.
+sub _waitpid {
+ my ($self, $timeout) = @_;
+
+ # Try to wait on the process.
+ my $result = $self->{_os_obj}->Wait($timeout ? INFINITE : 0);
+ # Process finished. Grab the exit value.
+ if ($result == 1) {
+ my $_exit_status;
+ $self->{_os_obj}->GetExitCode($_exit_status);
+ return (0, $_exit_status<<8);
+ }
+ # Process still running.
+ elsif ($result == 0) {
+ return (2, 0);
+ }
+ # If we reach here, then something odd happened.
+ return (0, 1<<8);
+}
+
+sub _die {
+ my $self = shift;
+
+ # Try the kill the process several times. Calling alive() will
+ # collect the exit status of the program.
+ my $count = 5;
+ while ($count and $self->alive) {
+ --$count;
+ $self->{_os_obj}->Kill(1<<8);
+ last unless $self->alive;
+ sleep 1;
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Proc::Background::Win32 - Interface to process mangement on Win32 systems
+
+=head1 SYNOPSIS
+
+Do not use this module directly.
+
+=head1 DESCRIPTION
+
+This is a process management class designed specifically for Win32
+operating systems. It is not meant used except through the
+I class. See L for more information.
+
+=head1 IMPLEMENTATION
+
+This package uses the Win32::Process class to manage the objects.
+
+=head1 AUTHOR
+
+Blair Zajac
+
+=head1 COPYRIGHT
+
+Copyright (C) 1998-2005 Blair Zajac. All rights reserved. This
+package is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
diff --git a/tests/lib/Program.pm b/tests/lib/Program.pm
new file mode 100644
index 0000000000..4ad5f6567d
--- /dev/null
+++ b/tests/lib/Program.pm
@@ -0,0 +1,304 @@
+=head1 SYNOPSIS
+
+ use Program;
+ my $m_program = new Program('ls');
+
+=head1 DESCRIPTION
+
+Program is a generic program wrapper that allows easy use of programs on
+multipul platforms with the correct file handle redirection.
+
+=head1 FUNCTIONS
+
+=cut
+
+package Program;
+
+###################
+# Standard Modules
+use strict;
+use Config;
+use FileHandle;
+
+###################
+# Variables
+use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
+@ISA = qw();
+@EXPORT = qw();
+@EXPORT_OK = qw();
+$VERSION = 0.04;
+
+# This hash contains the values for redirection.
+my %m_redirect_values;
+
+# Standard values
+$m_redirect_values{'none'} = '';
+$m_redirect_values{'stdout&stderr2stdout'} = '2>&1';
+$m_redirect_values{'stdout&stderr2stderr'} = '1>&2';
+$m_redirect_values{'stdout2stderr&stderr2stdout'} = '3>&1 1>&2 2>&3 3>&-';
+
+# Platform specific values
+if ($Config{'osname'} eq 'MSWin32')
+{
+ if ($Config{'osvers'} ge 4.0)
+ {
+ $m_redirect_values{'stdout2stdnull'} = '1>NUL';
+ $m_redirect_values{'stderr2stdnull'} = '2>NUL';
+ }
+ else
+ {
+ $m_redirect_values{'stdout2stdnull'} = '1>';
+ $m_redirect_values{'stderr2stdnull'} = '2>';
+ }
+}
+else
+{
+ $m_redirect_values{'stdout2stdnull'} = '1>/dev/null';
+ $m_redirect_values{'stderr2stdnull'} = '2>/dev/null';
+}
+
+###################
+# Functions
+
+################################################################################
+# Function: new()
+
+=head2 B
+
+ Description: This method returns an objective interface the the passed program.
+ Arguments: $program
+ Return: class
+ Usage: my $ls = new Program('ls');
+
+=cut
+
+sub new
+{
+ my $class = shift;
+ my $program = shift;
+
+ my $self = bless {}, $class;
+ $self->{'PROGRAM'} = $program;
+ $self->{'CHOMP'} = 1;
+ $self->{'REDIRECT'} = 'stdout&stderr2stdout';
+ $self->{'LAST_COMMAND'} = "No commands have been executed yet.";
+ $self->{'EXIT_STATUS'} = 0;
+
+ return $self;
+}
+
+################################################################################
+# Function: strip_new_lines()
+
+=head2 B
+
+ Description: This functions is used to set wether the output from a command
+ has trailing new lines removed.
+ Arguments: 0 to turn chomping off
+ 1 to turn chomping on
+ Default: 1
+ Return: true if chomping is on else undef
+ Usage: $ls->strip_new_lines(1);
+ $ls->strip_new_lines(0);
+
+=cut
+
+sub strip_new_lines
+{
+ my $self = shift;
+ my $value = shift;
+
+ if (scalar $value)
+ {
+ $self->{'CHOMP'} = $value;
+ }
+ else
+ {
+ return $self->{'CHOMP'};
+ }
+}
+
+
+################################################################################
+# Function: redirect()
+
+=head2 B
+
+ Description: This functions is used to set the STDOUT and STDERR redirection
+ for commands executed by the program.
+ Arguments: possible values are stdout&stderr2stdout, stdout&stderr2stderr
+ stdout2stderr&stderr2stdout, stdout2stdnull, stderr2stdnull
+ Default: sdtout&stderr2stdout
+ Return: redirection option if nothing passed else set the redirection
+ Usage: $ls->redirect('stderr2sdtnull');
+
+=cut
+
+sub redirect
+{
+ my $self = shift;
+ my $value = shift;
+
+ if (scalar $value)
+ {
+ $self->{'REDIRECT'} = $value;
+ }
+ else
+ {
+ return $m_redirect_values{$self->{'REDIRECT'}};
+ }
+}
+
+################################################################################
+# Function: last_command()
+
+=head2 B
+
+ Description: This function returns the last command executed.
+ Arguments: None.
+ Return: The last command
+ Usage: print $ls->last_command();
+
+=cut
+
+sub last_command
+{
+ my $self = shift;
+ return $self->{'LAST_COMMAND'};
+}
+
+################################################################################
+# Function: output()
+
+=head2 B