#!/usr/bin/perl # # Copyright (c) 2004-2007 - Consultas, PKG.fr # # This file is part of A2P. # # A2P is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # A2P 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 A2P; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # $Id$ # # Simple service to synchronize Job STATUS with a DB # BEGIN { use strict ; use warnings ; use POSIX 'setsid' ; use A2P::Globals ; our $VERSION = sprintf "%s", q$Rev: 1185 $ =~ /(\d[0-9.]+)\s+/ ; if (defined($ENV{JAVA_HOME}) and $ENV{JAVA_HOME}) { # Don't fork if launch by Eclipse, script launcher unset JAVA_HOME $Progname = "a2p-eclipse" ; $MUSTQUIT = 1 ; } else { # Daemonize early my $forkpid = 0 ; our $argument = $ARGV[0] || "" ; chdir '/' or die "Can't chdir to /: $!"; open *STDIN , '<', '/dev/null' or die "Can't read from /dev/null: $!"; open *STDOUT, '>', $StdoutFile or die "Can't write to $StdoutFile: $!"; defined($forkpid = fork) or die "Can't fork: $!"; # Eventually save son PID in system file provided as argument and exit if ( $forkpid > 0 ) { if ( length($argument) > 0 ) { open *PID, '>', $argument or die "Can't open PID file '$argument': $!"; print PID $forkpid ; close(PID); } exit(0); } } } our $VERSION ; our $argument ; our $Progname ; our $MUSTQUIT ; #------------------------------------------------------------------------------- # Here we import any needed modules only after we are in background #------------------------------------------------------------------------------- use strict ; use warnings ; use integer ; use POSIX qw( :signal_h :sys_wait_h setsid ); use Time::HiRes qw( usleep gettimeofday tv_interval ); use A2P::Init ; use A2P::Globals; use A2P::Syslog ; use A2P::Signal ; use A2P::Thread ; use A2P::JobStatus qw( sync_dbm_to_db get_status ); use A2P::Signal 'LogSigMessage' ; use A2P::DB ; use A2P::DB qw( CheckJobsStatusTables CheckJobsQueueTable get_serviceid_info ); use A2P::Infos ; use A2P::AfpJob ; &Debug(" Module load error: $! $? $@") if ( $! or $? or $@ ); &Debug(" =============================START=============================="); &setsid or die "Can't set a new session: $!"; open *STDERR, '>&', *STDOUT or die "Can't redirect STDERR to STDOUT: $!"; #------------------------------------- # Here we are in a daemonized process #------------------------------------- &Debug("Current progname = $0, version $VERSION, started"); &Debug("Setting service name to $Progname"); # Update system name of this daemon to be pretty with ps command $0 = $Progname ; # Reset error code $! = 0 ; ################################################################################ sub InitCheck { # Read conf &Init ; # Check needed folders my %Folders = ( SERVICE_TMP => $SERVICE_TMP, SHMDIR => $SHMDIR ); while ( my ($var, $Path) = each(%Folders) ) { unless ( -d $Path ) { &Info("Creating $Path folder as $var"); my $Folder = "" ; foreach my $tmp_path ( split(m{[/]+}, $Path) ) { $Folder .= "/$tmp_path" ; mkdir $Folder , oct(775) unless ( -d $Folder ); &Error("Can't create '$Path' folder as $var: $!"), last unless ( -d $Folder ); } } } # Initialize SERVICE_TMP folder if ( -d $SERVICE_TMP ) { unless ( system("touch $SERVICE_TMP/init_$Progname") == 0 ) { &Error("Can't initialize $SERVICE_TMP folder: $!"); $MUSTQUIT = 1 ; } } else { &Error("Can't initialize unavailable $SERVICE_TMP folder: $!"); $MUSTQUIT = 1 ; } # Test opening logfile in case of file logging if ( $LOGFILENAME and ( $LOGFILE_VS_SYSLOG or $DEBUG_IN_FILE )) { $! = 0 ; unless (open(*LOG, '>', $LOGFILENAME)) { $LOGFILE_VS_SYSLOG = 0 ; $DEBUG_IN_FILE = 0 ; return &Error("Can't open '$LOGFILENAME': $!"); } if ( $LOGFILE_PURGE ) { &UPSTAT('PURGE-LOG'); truncate LOG , 0 ; $LOGFILE_PURGE = 0 ; } close(LOG); } # Reset Debug comportement &ResetDebug() ; &Info("Configuration reloaded") if ($do_init); $do_init = 0 ; } ################################################################################ &InitCheck( $do_init = 0 ); ################################################################################ ### Local variables ################################################################################ my @MarkTimer = &gettimeofday() ; # Status objects hash ref my $STATUS = {} ; my $factor = 0 ; my $mode = 0 ; my $afpjob = undef ; my $try_connect = 1 ; my $connected = 0 ; my $not_connected_timer = undef ; my $delay = 60 ; my %lockid_list = () ; my $event_timer = undef ; # Initialization from DB my $services_infos ; $services_infos = &get_serviceid_info() if (&a2p_db_connect() and &CheckJobsStatusTables()); &Info("$Progname service v" . A2P_RPM_VERSION . " started"); $STATS{'A2P-STATUS-VERSION'} = A2P_RPM_VERSION ; # Main loop while ( ! $MUSTQUIT ) { &TIMESTAT('MainLoop'); &LogSigMessage(); A2P::Thread::self_TEST() if $do_test ; @MarkTimer = &gettimeofday() , &Info("---MARK---") unless ( &tv_interval(\@MarkTimer) < 1800 ); ################################################################## # 1. Get a DB Access and check db tables ################################################################## $connected = 1 if (( ! $connected or $try_connect-- > 0 ) and &a2p_db_connect()); if ( $connected > 0 and &CheckJobsStatusTables and &CheckJobsQueueTable ) { ############################# # 2. Read all status objects ############################# &sync_dbm_to_db ; ########################################################### # 3. Check for events in jobs_queue table for current mode ########################################################### my @list = keys(%lockid_list) ; if ( defined($services_infos) and @list ) { my $count = 5 ; my $lockid = shift @list ; while ( $count-- ) { my ( $serviceid, $name ) = @{$lockid_list{$lockid}} ; #&Debug("Getting events for $name service") # if ($ADVANCED_DEBUGGING) ; if (defined($afpjob) and $afpjob) { $afpjob = $afpjob->get_next_from_db($serviceid) ; if (ref($afpjob) =~ /^A2P::AfpJob/) { my $job = $afpjob->jobname ; # Got an event &UPSTAT('A2P-STATUS-GOT-EVENT-'.$lockid); &Debug("Got event for $name service on $job") if ($ADVANCED_DEBUGGING) ; # Update the Status from our memory my $status = &get_status($job); $afpjob->set_status_object($status) if ($status); # Update afpspool/service name my @service = ( $lockid, $name ) ; my $spool = $services_infos->get_afpspool_of(@service); my @spools = $services_infos->get_spools_of(@service) ; &Debug("Setting afpspool to $spool on $job") if ($ADVANCED_DEBUGGING) ; $afpjob->afpspool($spool); $afpjob->otherspools(@spools); $afpjob->service($name); # Handle event getting if deleting (chained events case) my $neg_is_deleted = $afpjob->handle_event or &Warn("Bad event handling on " . $job . " A2P::AfpJob object"); # Synchronize us in jobs_queue table unless deleted &Warn("Bad event synchro on $job A2P::AfpJob") unless ( $neg_is_deleted < 0 or $afpjob->sync_with_jobs_queue ); } else { # Select next mode and/or next lockid to check if ($mode) { &UPSTAT('A2P-STATUS-ADV-EVENT-CHECKED-'.$lockid); $mode = 0 ; delete $lockid_list{$lockid} ; last unless @list ; # All possible event checked $lockid = shift @list ; } else { &UPSTAT('A2P-STATUS-USER-EVENT-CHECKED-'.$lockid); $mode = 1 ; } $afpjob = new A2P::AfpJob($mode) ; &Debug("Getting events for $name service with mode " . $mode ) if ($ADVANCED_DEBUGGING) ; $count ++ ; next ; } } else { # Initialize with a fake afpjob $afpjob = new A2P::AfpJob($mode) ; $count ++ ; next ; } } } elsif ( ref($services_infos) !~ /^A2P::Infos/i and ( ! defined($event_timer) or &tv_interval($event_timer) > 2 )) { # Reset event check timer $event_timer = [ &gettimeofday() ]; $services_infos = &get_serviceid_info ; } elsif ( ! defined($event_timer) or &tv_interval($event_timer) > 2 ) { # Reset lockid list to check for events foreach my $lockid ($services_infos->get_lockid_list()) { my $sname = $services_infos->get_servicename_of($lockid) || '' ; next unless $sname ; $lockid_list{$lockid} = [ $services_infos->get_serviceid_of($lockid), $sname ]; } # Control list @list = keys(%lockid_list) ; &Debug("Checking events for services: " . join( ', ', map { "$_ (@{$lockid_list{$_}})" } @list )) if ($ADVANCED_DEBUGGING); # Reset event check timer $event_timer = [ &gettimeofday() ]; } # TODO Check SHMDIR for not finished work # Check if still connected to DB, this reconnect also to DB if a DB # error is detected or if a reconnection DB timer is set unless (&a2p_db_connected) { &Info("Just disconnected from DB", "Retrying DB connection"); $connected = 0 ; } } elsif ( $connected > 0 ) { # Log any pending warning from DBI API &LogSigMessage(); # Not a connection error but a checktable error just warn and retry # connection in 10 minutes &Warn("Disconnecting from DB for 10 minutes"); &a2p_db_disconnect ; $not_connected_timer = [ &gettimeofday() ]; $connected = $try_connect = -10 ; } elsif ( ! $connected ) { &Info("Disconnected from DB"); $not_connected_timer = [ &gettimeofday() ]; $connected-- ; # Anyway reset delay to one minute $delay = 60 ; } elsif ( $connected <= 10 ) { # Retry only after 10 min when more than 10 connections have failed $delay = 600 ; } # Check disconnection timing when not connected if ( $connected < 0 and &tv_interval($not_connected_timer) >= $delay ) { my $delta = abs($connected) ; $delta += 9 * ($connected - 10) if ( $delta >= 10 ); &Info("Retrying DB connection after $delta minutes disconnection"); $try_connect = 1 ; $not_connected_timer = [ &gettimeofday() ]; $connected-- ; } ####################### # Check flags ####################### # true after QUIT/TERM signal has been received &TIMESTAT('MainLoop'), last if ( $do_quit ); # true after HUP signal has been received &InitCheck() if ( $do_init ); # Check to do last commits &check_commit() if ( $connected > 0 ); # Adapt USLEEP factor to be 1 when JobStatus has been found # and grow up to 10 otherwise if (keys(%{$STATUS})) { $factor = 1 ; } elsif ( $factor < 10 ) { $factor ++ ; } # Avoid zombies sub-processes waitpid(-1,&WNOHANG); &TIMESTAT('MainLoop'); usleep $factor * $USLEEP ; &UPSTAT('SLEEP-IN-MAINLOOP'); } # We can safely activate debugging when quitting is requested $NO_SYSLOG_DEBUG = 0 if $SERVICE_DEBUG ; &a2p_db_disconnect(); &LogSigMessage ; &Info("$Progname service is stopping"); # Wait until every child has quit while ( my $pid = wait ) { last if ( $pid < 0 ); &Debug("T$pid has terminated"); } &Debug("PID file = '$argument'"); if (defined($argument)) { # Delete our PID in PID file open *RET , '>', $argument or die "Can't open PID file '$argument': $!"; print RET "0" ; close(RET); } &LogSigMessage ; &Info("$Progname service stopped"); exit(0); END { # Output statistics when quitting from a diagnostic service A2P::Thread::self_TEST() if ($Progname =~ /^diagnostics/); &Debug("=============================QUIT==============================="); &LogSigMessage ; }