package MMM::Daemon; use strict; use warnings; use Sys::Syslog; use MMM::Utils; use MMM::Config; use Fcntl qw(:flock); use POSIX qw(:sys_wait_h); use base qw(MMM); use base qw(MMM::Report::Mail); =head1 NAME MMM::Daemon =head1 SYNOPSIS use MMM::Daemon; my $mmm = MMM::Daemon->new() or die "Cannot find MMM installation"; $mmm->run(); =head1 DESCRIPTION A daemon for mmm system =head1 METHODS =cut sub new { my ($class, @args) = @_; my $mmm = $class->SUPER::new(@args) or return; if (!$mmm->{nofork}) { Sys::Syslog::openlog('mmm', 'pid', $mmm->configval('default', 'syslog_facilities', 'daemon')); $mmm->{use_syslog} = 1; } $mmm } sub _create_pid_file { my ($self) = @_; if (my $pidf = $self->configval('default', 'pidfile', PIDFILE)) { if (open(my $h, '>>', $pidf)) { autoflush $h 1; if (flock($h, LOCK_EX | LOCK_NB)) { truncate($h, 0); print $h "$$\n"; $self->{lockfh} = $h; } else { close($h); my $pid; if (open($h, '<', $pidf)) { $pid = <$h> || ''; close($h); } chomp($pid); $self->log('WARNING', 'Another mmm seems running pid `%s\'', $pid || 'N/A'); return 0; } return 1; } else { $self->log('WARNING', 'Cannot create pid file `%s\' %s', $pidf, $!); return 0 } } else { $self->log('WARNING', 'No pid file configured'); return 0; } 0 } sub _delete_pid_file { my ($self) = @_; if ($self->{lockfh}) { close($self->{lockfh}); } if (my $pidf = $self->configval('default', 'pidfile', PIDFILE)) { unlink($pidf) or $self->log('WARNING', 'Cannot delete pid file `%s\' %s', $pidf, $!); } } sub _reload_config { my ($self) = @_; my $config = Config::IniFiles->new( -file => $self->{configfile}, -default => 'default', ); if ($config) { $self->{config} = $config; $self->_parse_config(); $self->log('NOTICE', 'Configuration reload'); return 1; } else { $self->log('ERROR', 'Failure will reloading configuration'); return 0; } } sub run { my ($self) = @_; if (!$self->{nofork}) { $self->log('DEBUG', 'Going into background'); my $pid = fork; if (!defined($pid)) { return; } if ($pid) { sleep 1; my $ret = waitpid($pid, &WNOHANG); if ($ret) { die "Daemon has stopped, check log (exit with $ret status)\n"; } exit(0); } } $self->_create_pid_file() or return; $0 = 'mmm (DAEMON)'; $self->log('NOTICE', 'MMM::Daemon started at pid %d', $$); $self->{dontdie} = 1; $SIG{'ALRM'} = sub { $self->{next_alarm} = 0; $self->_start_pending_queue() }; $SIG{'TERM'} = sub { $self->{dontdie} = 0 }; $SIG{'INT'} = sub { $self->log('INFO', 'Ctrl+C received, stoping'); $self->{dontdie} = 0; }; $SIG{'HUP'} = sub { alarm(0); $self->_reload_config; $self->{next_alarm} = 0; $self->_start_pending_queue(); }; $SIG{'CHLD'} = sub { $self->log('DEBUG', 'SIG CHILD received'); $self->_reap_child(); }; $self->_start_pending_queue(); while ($self->{dontdie}) { $self->_reap_child(); $self->log('DEBUG', 'next alarm in %ss', ($self->{next_alarm} ? $self->{next_alarm} - scalar(time): 'N/A')); sleep(3600) if ($self->{dontdie}); } foreach (keys %{ $self->{process} || {} }) { kill 15, $_; } $self->_delete_pid_file(); 1; } sub post_process { my ($self, $job) = @_; $self->log('DEBUG', 'Post process %s', $job->name); $self->_start_pending_queue(); } sub _set_alarm { my ($self, $when) = @_; ($self->{next_alarm} || 0) != 0 && $when >= $self->{next_alarm} and return; $self->log('DEBUG', 'Re-Alarm for %d, in %ds', $when, $when - scalar(time)); $self->{next_alarm} = $when; my $s_alarm = $when - scalar(time); alarm($s_alarm > 0 ? $s_alarm : 1); } sub _start_pending_queue { my ($self) = @_; $self->{dontdie} or return; my $waitdelay = 0; foreach my $job ($self->get_tasks_by_name($self->list_tasks)) { $job->is_disable and next; $job->frequency or next; my $next_run_time = $job->next_run_time; if ($next_run_time <= scalar(time)) { $self->_run_fork($job, 'NOTRAPLOG'); sleep(5) if($waitdelay); # small wait to avoid excessive load $waitdelay = 1; } else { $self->log('DEBUG', '$when < 0: %d %s', $next_run_time, $job->name) if ($next_run_time <= 0); $self->_set_alarm($next_run_time); } } } 1; __END__ =head1 SEE ALSO L =head1 AUTHOR Olivier Thauvin =head1 COPYRIGHT AND LICENSE Copyright (C) 2006 Olivier Thauvin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =cut