#! /usr/bin/perl
# Copyright (c) 2012 Faith Ekstrand and Iowa State University
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the
# following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
# USE OR OTHER DEALINGS IN THE SOFTWARE.

use strict;

my $daemon = 0;
my $help = 0;
my $pid_file = '';
my $command = '';
my $infile = '';
my $outfile = '';
my $errfile = '';
my $waittime = 5;

use POSIX;
use Getopt::Long;

my $good_opts = GetOptions ('help|h' => \$help, 'daemon|d!' => \$daemon,
        'pid-file|p=s' => \$pid_file, 'command|c=s' => \$command,
        'in-file=s' => \$infile, 'out-file=s' => \$outfile,
        'err-file=s' => \$errfile, 'wait-time|w=f' => \$waittime);

if ($help or not $good_opts) {
    print << "EOT";

Usage:
 daemonizer.pl [options] [-- command]

Options:
 -h, --help             print this help
 -d, --(no)daemon       run as a daemon (default: off)
 -c, --command=command  execute the given command
 -p, --pid-file=file    print the PID to the given file
 -w, --wait-time=time   specify how long (in minutes) we should wait to
 			make sure that the child started correctly.
 --in-file=file         redirect standard input to a file
 --out-file=file        redirect standard output to a file
 --err-file=file        redirect standard error to a file (default: if
			standard output is redirected, redirect standard
			error to the same file)

EOT
    exit 0;
}

###
# Subroutines to handle daemonizing.
###

if (! $command) {
    $command = join(' ', @ARGV);
}

my $daemon_pgid;
sub daemon_signal_handler {
    my $signame = shift;

    print "Recieved SIG$signame, passing on to children...\n";

    if ($signame == 'INT') {
        kill -2, $daemon_pgid;
    } elsif ($signame == 'QUIT') {
        kill -3, $daemon_pgid;
    } elsif ($signame == 'TERM') {
        kill -15, $daemon_pgid;
    }
}

sub run_daemon {
    # Redirect standard input, output, and error as instructed (We want to
    # do this first so that even the daemonizer messages will get logged)
    if ($infile) {
        open STDIN, '<', $infile;
    }
    if ($outfile) {
        open STDOUT, '>', $outfile;
    }

    # If stderr is not specified, simply dump it to the same place as
    # stdout
    if ($errfile and $errfile != $outfile) {
        open STDERR, '>', $errfile;
    } elsif ($outfile) {
        open STDERR, '>&STDOUT';
    }

    # Now we can fork
    my $pid = fork;
    if (! $pid) {
        # Everything from here on out has this group id.
        setpgid(0, 0);
        exec $command;
    }

    # The child process will never get here
    $daemon_pgid = $pid;

    # We'll gracefully handle these two
    $SIG{'INT'} = \&daemon_signal_handler;
    $SIG{'QUIT'} = \&daemon_signal_handler;
    $SIG{'TERM'} = \&daemon_signal_handler;

    # We'll make the rash assumption that what we call won't exit until
    # everything else has
    waitpid $pid, 0;
    exit $?;
}

###
# Actually executing the process
###
if ($daemon) {
    my $pid = fork();
    if ($pid) {
        # Wait 5 seconds to make sure the server has properly started
        sleep $waittime;

        if (waitpid $pid, WNOHANG > 0) {
            exit $?;
        }

        if ($pid_file) {
            # If we have a pid file specified, record the pid of the daemon
            open PIDFILE, '>', $pid_file;
            print PIDFILE "$pid";
            close PIDFILE;
        }

        exit 0;
    } else {
        # Run the daemon
        run_daemon();
    }
} else {
    run_daemon();
}

