Files
percona-toolkit/lib/Daemon.pm

320 lines
10 KiB
Perl

# This program is copyright 2008-2013 Percona Ireland Ltd.
# Feedback and improvements are welcome.
#
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# 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, version 2; OR the Perl Artistic License. On UNIX and similar
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
# licenses.
#
# 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.
# ###########################################################################
# Daemon package
# ###########################################################################
package Daemon;
use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use POSIX qw(setsid);
use Fcntl qw(:DEFAULT);
sub new {
my ($class, %args) = @_;
my $self = {
log_file => $args{log_file},
pid_file => $args{pid_file},
daemonize => $args{daemonize},
};
return bless $self, $class;
}
sub run {
my ($self, %args) = @_;
my $pid ||= $PID;
my $pid_file ||= $self->{pid_file};
my $log_file ||= $self->{log_file};
if ( $self->{daemonize} ) {
$self->_daemonize(
pid => $pid,
pid_file => $pid_file,
log_file => $log_file,
);
}
elsif ( $pid_file ) {
$self->_make_pid_file(
pid => $pid,
pid_file => $pid_file,
);
$self->{pid_file_owner} = $pid;
}
else {
PTDEBUG && _d('Neither --daemonize nor --pid was specified');
}
return;
}
sub _daemonize {
my ($self, %args) = @_;
my $pid = $args{pid};
my $pid_file = $args{pid_file};
my $log_file = $args{log_file};
PTDEBUG && _d('Daemonizing');
# First obtain the pid file or die trying. NOTE: we're still the parent
# so the pid file will contain the parent's pid at first. This is done
# to avoid a race condition between the parent checking for the pid file,
# forking, and the child actually obtaining the pid file. This way, if
# the parent obtains the pid file, the child is guaranteed to be the only
# process running.
if ( $pid_file ) {
eval {
$self->_make_pid_file(
pid => $pid, # parent's pid
pid_file => $pid_file,
);
};
if ( $EVAL_ERROR ) {
die "Cannot daemonize: $EVAL_ERROR\n";
}
}
# Fork, exit parent, continue as child process.
defined (my $child_pid = fork())
or die "Cannot fork: $OS_ERROR";
if ( $child_pid ) {
# I'm the parent.
PTDEBUG && _d('Forked child', $child_pid);
exit 0;
}
# I'm the child. First, open the log file, if any. Do this first
# so that all daemon/child output goes there.
# We used to only reopen STDIN to /dev/null if it's a tty because
# otherwise it may be a pipe, in which case we didn't want to break
# it. However, Perl -t is not reliable. This is true and false on
# various boxes even when the same code is ran, or it depends on if
# the code is ran via cron, Jenkins, etc. Since there should be no
# sane reason to `foo | pt-tool --daemonize` for a tool that reads
# STDIN, we now just always close STDIN.
PTDEBUG && _d('Redirecting STDIN to /dev/null');
close STDIN;
open STDIN, '/dev/null'
or die "Cannot reopen STDIN to /dev/null: $OS_ERROR";
if ( $log_file ) {
PTDEBUG && _d('Redirecting STDOUT and STDERR to', $log_file);
close STDOUT;
open STDOUT, '>>', $log_file
or die "Cannot open log file $log_file: $OS_ERROR";
# If we don't close STDERR explicitly, then prove Daemon.t fails
# because STDERR gets written before STDOUT even though we print
# to STDOUT first in the tests. I don't know why, but it's probably
# best that we just explicitly close all fds before reopening them.
close STDERR;
open STDERR, ">&STDOUT"
or die "Cannot dupe STDERR to STDOUT: $OS_ERROR";
}
else {
if ( -t STDOUT ) {
PTDEBUG && _d('No log file and STDOUT is a terminal;',
'redirecting to /dev/null');
close STDOUT;
open STDOUT, '>', '/dev/null'
or die "Cannot reopen STDOUT to /dev/null: $OS_ERROR";
}
if ( -t STDERR ) {
PTDEBUG && _d('No log file and STDERR is a terminal;',
'redirecting to /dev/null');
close STDERR;
open STDERR, '>', '/dev/null'
or die "Cannot reopen STDERR to /dev/null: $OS_ERROR";
}
}
# XXX: I don't think we need this?
# $OUTPUT_AUTOFLUSH = 1;
PTDEBUG && _d('I am child', $PID);
# Now update the pid file to contain the correct pid, i.e. the child's pid.
if ( $pid_file ) {
$self->_update_pid_file(
pid => $PID, # child's pid
pid_file => $pid_file,
);
$self->{pid_file_owner} = $PID;
}
# Last: other misc daemon stuff.
POSIX::setsid() or die "Cannot start a new session: $OS_ERROR";
chdir '/' or die "Cannot chdir to /: $OS_ERROR";
# We're not fully daemonized.
return;
}
# Call this for non-daemonized scripts to make a PID file.
sub _make_pid_file {
my ($self, %args) = @_;
my @required_args = qw(pid pid_file);
foreach my $arg ( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
};
my $pid = $args{pid};
my $pid_file = $args{pid_file};
# "If O_CREAT and O_EXCL are set, open() shall fail if the file exists.
# The check for the existence of the file and the creation of the file
# if it does not exist shall be atomic with respect to other threads
# executing open() naming the same filename in the same directory with
# O_EXCL and O_CREAT set.
eval {
sysopen(PID_FH, $pid_file, O_RDWR|O_CREAT|O_EXCL) or die $OS_ERROR;
print PID_FH $PID, "\n";
close PID_FH;
};
if ( my $e = $EVAL_ERROR ) {
if ( $e =~ m/file exists/i ) {
# Check if the existing pid is running. If yes, then die,
# else this returns and we overwrite the pid file.
my $old_pid = $self->_check_pid_file(
pid_file => $pid_file,
);
if ( $old_pid ) {
warn "Overwriting PID file $pid_file because PID $old_pid "
. "is not running.\n";
}
$self->_update_pid_file(
pid => $PID,
pid_file => $pid_file
);
}
else {
die "Error creating PID file $pid_file: $e\n";
}
}
return;
}
sub _check_pid_file {
my ($self, %args) = @_;
my @required_args = qw(pid_file);
foreach my $arg ( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
};
my $pid_file = $args{pid_file};
PTDEBUG && _d('Checking if PID in', $pid_file, 'is running');
if ( ! -f $pid_file ) {
PTDEBUG && _d('PID file', $pid_file, 'does not exist');
return;
}
open my $fh, '<', $pid_file
or die "Error opening $pid_file: $OS_ERROR";
my $pid = do { local $/; <$fh> };
chomp($pid) if $pid;
close $fh
or die "Error closing $pid_file: $OS_ERROR";
if ( $pid ) {
PTDEBUG && _d('Checking if PID', $pid, 'is running');
my $pid_is_alive = kill 0, $pid;
if ( $pid_is_alive ) {
die "PID file $pid_file exists and PID $pid is running\n";
}
}
else {
# PID file but no PID: not sure what to do, so be safe and die;
# let the user figure it out (i.e. rm the pid file).
die "PID file $pid_file exists but it is empty. Remove the file "
. "if the process is no longer running.\n";
}
return $pid;
}
sub _update_pid_file {
my ($self, %args) = @_;
my @required_args = qw(pid pid_file);
foreach my $arg ( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
};
my $pid = $args{pid};
my $pid_file = $args{pid_file};
open my $fh, '>', $pid_file
or die "Cannot open $pid_file: $OS_ERROR";
print { $fh } $pid, "\n"
or die "Cannot print to $pid_file: $OS_ERROR";
close $fh
or warn "Cannot close $pid_file: $OS_ERROR";
return;
}
sub remove_pid_file {
my ($self, $pid_file) = @_;
$pid_file ||= $self->{pid_file};
if ( $pid_file && -f $pid_file ) {
unlink $self->{pid_file}
or warn "Cannot remove PID file $pid_file: $OS_ERROR";
PTDEBUG && _d('Removed PID file');
}
else {
PTDEBUG && _d('No PID to remove');
}
return;
}
sub DESTROY {
my ($self) = @_;
# Remove the PID file only if we created it. There's two cases where
# it might be removed wrongly. 1) When the obj first daemonizes itself,
# the parent's copy of the obj will call this sub when it exits. We
# don't remove it then because the child that continues to run won't
# have it. 2) When daemonized code forks its children get copies of
# the Daemon obj which will also call this sub when they exit. We
# don't remove it then because the daemonized parent code won't have it.
# This trick works because $self->{pid_file_owner}=$PID is set once to the
# owner's $PID then this value is copied on fork. But the "== $PID"
# here is the forked copy's PID which won't match the owner's PID.
if ( ($self->{pid_file_owner} || 0) == $PID ) {
$self->remove_pid_file();
}
return;
}
sub _d {
my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
map { defined $_ ? $_ : 'undef' }
@_;
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
}
1;
# ###########################################################################
# End Daemon package
# ###########################################################################