#!/usr/bin/perl -w # # IvyLaunch, a perl script to launch applications on ivy # # Copyright (C) 1997-1999 # Centre d'Études de la Navigation Aérienne # # Main # # Authors: Michelle Jacomi # Johnny Accot # Modified by: Christophe Mertz Mertz@cena.fr # Daniel Etienne # # $Id$ # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU GPL General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # Or look at http://www.gnu.org/copyleft/gpl.html # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. our $VERSION = 1.8; BEGIN { if (-d "/usr/lib/ivylaunch") { # comment this line if FugueConfig is located elsewhere unshift(@INC, "/usr/lib/ivylaunch"); print "/usr/lib/ivylaunch is prepend to \@INC\n"; } } $SIG{INT} = 'RunEndCmd'; use Ivy; use strict; use Sys::Hostname; use File::Basename; use Getopt::Long qw(:config pass_through); use POSIX 'sys_wait_h'; use FugueConfig; my $appliname = "ivylaunch"; my $HEADER = "$appliname"; my %OPTIONS = ("b=s" => "ivy bus"); my %opt; my @apps; Getopt::Long::GetOptions(\%opt, "b=s", 'help', "override") ; &usage if $opt{help}; my $bus = defined $ENV{"IVYBUS"} ? $ENV{"IVYBUS"} : '127.255.255.255:2010'; $bus = $opt{'b'} if defined $opt{'b'}; my $file = pop @ARGV; &usage unless defined($file); my @cppopts = @ARGV; my (%child); my %cmd = ('begin' => [], 'end' => []); my %agent = ('global' => [], 'local' => []); $| = 1; print "$HEADER Reading configuration file $file...\n"; my ($type, %ready); for (FugueConfig::parse($file, @cppopts)) { my ($type, $host, $name, $command, $params) = @$_; next if $type eq 'comment'; if ($type eq 'begin' or $type eq 'end') { push @{$cmd{$type}}, [$host, $command, $params]; } else { # add filename to ivycontrolpanel command (not destructive) $params .= join(' ', @cppopts) . " $file " if ($name eq 'ivycontrolpanel'); push @{$agent{$type}}, [$name, $host, $command, $params, $bus]; push (@apps, $name); } } print "$HEADER needs ", join (",", @apps), "\n"; Ivy->init(-loopMode => 'LOCAL', -onDieFunc => [\&Quit], -appName => $appliname, -ivyBus => $bus); my $ivy = Ivy->new (-appName => $appliname, -ivyBus => $bus, -statusFunc => \&Status, -neededApp => [@apps]); print "$HEADER $appliname is being launched using bus $bus (pid=$$)...\n"; # Now execute commands and launch the necessary agents foreach my $app (@{$cmd{'begin'}}) { my $pid = FugueConfig::launchCommand ('begin', @$app); $child{$pid} = $app->[1]; $child{$pid} .= " ".$app->[2] if $app->[2] !~ /^\s*$/; } foreach my $app (@{$agent{'local'}}) { my $pid = FugueConfig::launchAgent(@$app); $child{$pid} = $app->[0]; } # if after 5 seconds the "global" agents are not detected, launch them. if (@{$agent{'global'}}) { print "$HEADER Wait 5 seconds before global apps checking...\n"; Ivy::after (5000, [\&Global]); } my $host = hostname; $SIG{CHLD} = 'Child'; $ivy->start; $ivy->mainLoop; sub Global { print "$HEADER Now looking at global apps...\n"; my $n = 0; foreach my $app (@{$agent{'global'}}) { my $name = @$app[0]; unless (grep { $name eq $_ } (keys(%ready))) { print ("$HEADER appli $name not found. Launching it.\n"); my $pid = FugueConfig::launchAgent(@$app); $child{$pid} = $app->[0]; $n++; } } print "$HEADER $n applis launched.\n"; } # function called when a child exits sub Child { $SIG{CHLD} = 'Child'; if (my $pid = waitpid (-1, WNOHANG)) { return if $pid == -1; return unless $child{$pid}; my $status = $? & 255; print "$HEADER Warning! $child{$pid}". " (pid=$pid) exited with status $status.\n"; delete $child{$pid}; } } # function called on a die message sub Quit { print "$HEADER Quit (on Die message)\n"; # kill begin commands (if running) and agents foreach (keys %child) { print "$HEADER Stopping '$child{$_}' (pid=$_)...\n"; kill INT => $_; } &RunEndCmd; } sub RunEndCmd { # execute end commands foreach my $app (@{$cmd{'end'}}) { FugueConfig::launchCommand('end',@$app); } $ivy->stop(); exit; } sub Status { my ($ref_array_present, $ref_array_absent, $ref_hash_present, $agent, $status, $host) = @_; if ($status eq 'new') { print ("$HEADER $agent connected from host $host.\n"); print ("$HEADER All agents are running.\n") unless @$ref_array_absent; $ready{$agent}++; } elsif ($status eq 'died') { print ("$HEADER $agent disconnected from host $host..\n"); print ("$HEADER Agents are missing.\n") if @$ref_array_absent; $ready{$agent}--; delete $ready{$agent} if $ready{$agent} <= 0; } } sub usage { print "Version : $VERSION\n"; print "Usage: $appliname [-b bus] [-override] [cpp options] \n"; exit; } __END__ =head1 NAME ivylaunch - a script which launches ivy agents and commands, according to a configuration file. =head1 SYNOPSIS ivylaunch [-help] [-b bus] [-override] [cpp options] configfile =head1 DESCRIPTION ivylaunch forks ivy agents and commands described in configuration file. ivylaunch is also an ivy agent : it reports agents connection and disconnection, and can be killed by an Ivy die message. =head1 OPTIONS =over =item B<-b> bus Specify the ivy bus address on which ivylaunch will communicate. If this option is not provided, the value of the environment variable IVYBUS is used. If the variable is not defined, the default value is 127:2010, that is port 2010 on localhost. =item B<-override> Infer ivylaunch behavior when a B agent is detected on the bus. See below for explanation. =back =head1 CONFIGURATION FILE FORMAT Each line of configuration file should contain at least 4 mandatory fields separated by space or tabulation : B, B, B and B and its options. =over =item B the type of forked commands should take one of the following values : begin, local, global, end. =over =item B links up with simple commands (not ivy agents) which are executed at first, =item B links up with ivy agents which can have one or more instances on the bus. =item B links up with ivy agents which must be single. Before launching a global agent, ivylaunch checks for it on the bus; if this agent is not present, ivylaunch launches it. If it's already connected, ivylaunch preserves it if the B<-override> option is false (the default value), or kills it (by sending an ivy die message) and forks a new one if -override is true. global agents are launched after local ones. =item B links up with simple commands which are executed after agents have been killed, just before exiting. These commands are kept alive when ivylaunch exits. =back =item B the host where the command will be executed. =item B This indicative field is used to detect double agents. Insure that this name corresponds to the real ivy name detected on the bus. This field make sense only for ivy agent; usage is to set 'none' to other commands. =item B the command to execute and its options. =back Before being analysed, the configuration file is parsed by the preprocessor B, which provides some facilities like macro expansion. cpp options can be passed on command line, or be defined in configfile using the #cpp_options keyword. Options defined in configfile overload those that are passed on command line. See the example below. =head1 EXAMPLE % ivylaunch -b 127:3456 -D ACC=reims -D WP=WP0 configfile where configfile contains : #ifndef ACC #define ACC paris #endif #ifndef WP #define WP WP1 #endif #ifndef POSITION #define POSITION --acc ACC --wp WP #endif #cpp_options: -traditional begin lasra none xset fp+ tcp/10.192.36.68:7100 global anglo Rejeu rejeu -s 9:10 STR_ATH_01_06_26.rej global tibot ivycontrolpanel ivycontrolpanel -nocursor local lasra twinkle:ACC:WP:TC twinkle POSITION --role TC local astik IvyMon ivymon end lasra none xset fp- tcp/10.192.36.68:7100 =head1 SEE ALSO ivycontrolpanel(1), ivybanner(1), cpp(1) =head1 AUTHORS Daniel Etienne Michelle Jacomi Johnny Accot