#!/usr/bin/perl

# runEmbeddedScript:
# This script illustrates how to run an embedded Perl script (source embedded
# in the current script) as a separate process.
# The new process (running the embedded Perl script) is completely independent
# of the parent process. It will continue to run even after the parent dies.
#
# Try the following tests:
# - doing Control-C on the parent
# - sending a TERM signal to the parent (with 'kill')
# - sending a USR1 signal to the parent (with 'kill -USR1')
# - let the parent finish normally
#
# If you discover any problems with this script or have a better way to do this,
# please send me email about it.
#
# Cameron Hayne (macdev@hayne.net)  Nov 2006

use strict;
use warnings;
use POSIX qw(setsid);

sub runPerlScriptInSeparateProcess($@)
{
    my ($script, @args) = @_;

    $SIG{CHLD} = 'IGNORE'; # to avoid zombies
    pipe(READER, WRITER);
    my $pid; 
    if ($pid = fork())
    {
        # parent process
        close(READER); # not needed
        print WRITER $script;
        close(WRITER);
    }
    elsif (defined($pid))
    {
        # child process
        close(WRITER); # not needed

        # close the stdio file descriptors inherited from the parent
        close(STDIN);
        close(STDOUT);
        close(STDERR);

        # we dup READER to STDIN so that the child gets the script from STDIN
        open(STDIN, "<&READER");
        close(READER);

        # start a new process session so as to disassociate
        # from the parent process and its controlling terminal
        setsid()
            or die "Can't start a new session: $!\n";

        my $command = "/usr/bin/perl";
        exec($command, "-", @args)
            or die "Failed to exec \"$command\": $!\n";
    }
    else
    {
        warn "Can't fork: $!\n";
        $pid = 0;
    }

    # return the pid of the child process so the parent can kill it later
    return $pid;
}

sub terminateProcess($)
{
    my ($pid) = @_;

    print "Terminating process $pid\n";
    if (kill('TERM', $pid) < 1)
    {
        print "Failed to terminate process $pid\n";
    }
}

my $scriptText = <<'EOT';
$0 = "scriptFromRunEmbeddedScript";
my $logfile = "foo";
$SIG{INT}  = sub { print FILE "rec'd INT signal\n"; exit; };
$SIG{TERM} = sub { print FILE "rec'd TERM signal\n"; exit; };
open(FILE, ">$logfile") or die "Can't open file \"$logfile\": $!\n";
select((select(FILE), $| = 1)[0]);  # auto-flush so we can see progress
foreach my $arg (@ARGV)
{
    print FILE "$arg\n";
}
print FILE "pid is $$\n";
foreach my $i (0..10000)
{
    print FILE time(), "\n";
    sleep 1;
}
close(FILE);
exit;
EOT

MAIN:
{
    print "parent pid is $$\n";
    my @args = ("hello world", "glad to be here");
    my $childPid = runPerlScriptInSeparateProcess($scriptText, @args);
    print "childPid is $childPid\n";

    $SIG{INT}  = sub { print "parent rec'd INT signal\n"; exit; };
    $SIG{TERM} = sub { print "parent rec'd TERM signal\n"; exit; };
    $SIG{USR1} = sub { print "parent rec'd USR1 signal\n";
                       terminateProcess($childPid); };

    print "Press 'Return' to continue\n";
    my $input = <STDIN>;
    my $n = 5;
    for (my $i = 0; $i < $n; $i++)
    {
        print "This is the parent ($i)\n";
        sleep 4;
    }
    print "parent is finished\n";
}

