summaryrefslogtreecommitdiff
path: root/includes/daemonize/daemonize.php
blob: 1f517085f26d021b98a7bac5f413c23aa52a40f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<?php
/* This script daemonizes a PHP CLI script.

Based on code from Dewi Morgan, http://www.thudgame.com/node/254
Modified by bitweaver.org - spider@viovio.com, et al

This code is explicitly released onto the public domain by myself,
Dewi Morgan, the author. As such, it is not subject to copyright.
You may do with it as you will. Credit, while appreciated, need not
be given. This notice does not need to be included.

Call from cron every minute as:
* * * * * /path/to/bin/php /path/to/script/daemonize.php /path/to/script/daemon.php 2>&1 >> /path/to/logfile
Could be trivially modified to take the script and other globals
as arguments. But that seems a  bit pointless to me.
*/

function usage_die( $pError ) {
	die( "ERROR: $pError\nUsage: /path/to/bin/php /path/to/script/daemonize.php /path/to/script/daemon.php [--debug] [--nohup] [--pidfile=/path/to/lock/daemon.pid]\n\n" );
}

$PHP = $_SERVER['_']; # Remember to change the #! line above, too.
if( empty( $argv[1] ) || !file_exists( $argv[1] ) ) {
	usage_die( 'daemon script not found' );	
}
$daemonScript = $argv[1];

$debug = FALSE;
if( !empty( $argv[2] ) ) {
	// convert any remaining arguments into local variables
	for( $i=2; $i < count( $argv ); $i++ ) {
		$arg = preg_replace( '/^[-]*/', '', $argv[$i] ); 
		@list( $name, $val ) = @split( '=', $arg );
		${$name} = (!empty( $val ) ? $val : TRUE);
	}
}


if( empty( $pidfile ) ) {
	$pidfile = '/var/lock/php-' . basename( $daemonScript,'.php' ).'.pid';
}

# Must contain any of nohup, perl, daemonise.pl, is_up.php and this script
# That you intend to use.
# Get the following values from "kill -l" or /usr/include/linux/signal.h
# Should be correct for most unixes.
$SIGTERM = 15;
$SIGKILL = 9;

# If we've self-bootstrapped, then load the bot and run with it.
if ( !empty( $nohup ) ) {
  to_log("arg detected, bootstrapping daemon through require.");
  require( $daemonScript );
  to_log("killing daemon child.");
  exit(0);
}

# If we're already started, then don't bother running.
if( empty( $nohup ) ) {
  $test = 0;
  $lines = array(0, 0);
  $pid_data = "unread";

  #to_log("Testing for pre-existing... $pidfile"); # Can get spammy.
  if (file_exists($pidfile)) {
    $pid_data = file_get_contents($pidfile);
    $lines = explode("\n", $pid_data);
    if (!empty($lines[0]) && is_numeric($lines[0]) && !empty($lines[1]) && is_numeric($lines[1])) {
      # Kill hung processes.
      if ($lines[1] + 300 < time()) { // If it's an OLD pidfile...
        to_log("$pidfile OLD pidfile found from $lines[1] - ".date('d/M/Y:H:i:s O',$lines[1]).": $pid_data");
        zero_file($pidfile);

        # If found, kill. If it won't die, kill it harder. Then give up.
        # In practice, it can take a bit longer than 20 seconds to die, but it
        # tries again in a minute's time, and works.
        if (is_alive($lines[0])) {
          to_log("killing and waiting a few secs...");
          posix_kill($lines[0], $SIGTERM);
          # Loop for 10 seconds, or until it dies.
          for ($i=0; $i<20 && is_alive($lines[0]); $i++) {
            sleep(1);
            if ($i == 10) { # After ten seconds, kill it harder.
              to_log("Failed to kill it. Killing it harder!");
              posix_kill($lines[0], $SIGKILL);
            }
          }
          if (is_alive($lines[0])) {
            to_log("Failed to kill it. Giving up.");
            exit(0);
          }
        }
      } else {
        #to_log("Young pidfile found.");  # Can get spammy.
      }
      if (is_alive($lines[0])) {
//		to_log( "DAEMON $lines[0] : $daemonScript running. Last seen at ".date('d/M/Y:H:i:s O', $lines[1] ) );
        exit(0);
      } else {
        to_log("$pidfile pidfile $lines[0] found, but process is dead.");
      }
    }
    else {
      to_log("$pidfile Bad format pidfile found, zeroing, restarting...");
      zero_file($pidfile);
    }
  }
  else {
    to_log("pidfile not found: $pidfile");
  }
  to_log("NOT found running.");
}

# If pcntl_fork() doesn't exist, we need to load ourselves in the background, then die.
if (!function_exists("pcntl_fork")) {
  to_log("no pcntl, calling self with nohup and param");
  system("nohup $PHP $argv[1] -nohup &", $return);
  if ($return == 0) { exit(0); }
  # If there was a problem, we have one more ace in the hole - perl!
  to_log("nohup failed with return $return, calling self with perl");
  system("perl ".dirname( $argv[0] )."daemonize.pl &", $return);
  to_log("killing daemon parent with return $return");
  exit(0);
}

# If we've got the right functions to play with, all the above becomes moot.
to_log("we have pcntl! Doing it all ourselves!");

# Replace file handles
$fh_unused = array(STDIN, STDOUT);

ob_implicit_flush();

# Daemon Rule 1) Fork and exit the parent.
$pid = pcntl_fork();
if ($pid == -1) {
  die("could not fork");
}
else if ($pid) {
  exit();  # Kill the parent
}

# Daemon Rule 2) become session leader, pg leader, no term
$session_id = posix_setsid();
if (!$session_id) {
  die("Could not detach from terminal.");
}

# Daemon Rule 3) cd to /
if (!chdir('/')) { die("Could not cd to rootfs"); }

# Daemon Rule 4) set file creation mask to 0
$oldmask = umask(00);

# Daemon Rule 5) Close unneeded file handles
foreach ($fh_unused as $fh) {
  if (!fclose($fh)) { die("Unable to close $fh"); }
}

# Daemon Rule 6) Set up signal handlers where necessary.
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");


function sig_handler($signo) {
  switch ($signo) {
    case SIGTERM:
      # handle shutdown tasks
      die("Caught thud SIGTERM");
      break;
    case SIGHUP:
      # handle restart tasks
      die("Caught thud SIGHUP");
      break;
    default:
      # handle all other signals
  }
}

# Meat of the daemon goes here.
to_log("requiring $daemonScript");
chdir( dirname( $daemonScript ) );
require_once( $daemonScript );

exit(0);


# Access functions.
function is_alive($pid) {
	$output = array();
	exec( dirname( __FILE__ )."/is_up.sh $pid", $output );
	$result = $output[0];
	return (0 < $result);
}

function to_log($string) {
	error_log( date( 'd/M/Y:H:i:s O' )." - $string");
}

function zero_file($pidfile) {
  $fp = fopen($pidfile, "w");
  fwrite($fp, ""); # Zero the pidfile.
  fclose($fp);
}
?>