1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Application
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 jimport('joomla.filesystem.folder');
13
14 use Joomla\Registry\Registry;
15
16 /**
17 * Class to turn JApplicationCli applications into daemons. It requires CLI and PCNTL support built into PHP.
18 *
19 * @link https://secure.php.net/manual/en/book.pcntl.php
20 * @link https://secure.php.net/manual/en/features.commandline.php
21 * @since 11.1
22 */
23 class JApplicationDaemon extends JApplicationCli
24 {
25 /**
26 * @var array The available POSIX signals to be caught by default.
27 * @link https://secure.php.net/manual/pcntl.constants.php
28 * @since 11.1
29 */
30 protected static $signals = array(
31 'SIGHUP',
32 'SIGINT',
33 'SIGQUIT',
34 'SIGILL',
35 'SIGTRAP',
36 'SIGABRT',
37 'SIGIOT',
38 'SIGBUS',
39 'SIGFPE',
40 'SIGUSR1',
41 'SIGSEGV',
42 'SIGUSR2',
43 'SIGPIPE',
44 'SIGALRM',
45 'SIGTERM',
46 'SIGSTKFLT',
47 'SIGCLD',
48 'SIGCHLD',
49 'SIGCONT',
50 'SIGTSTP',
51 'SIGTTIN',
52 'SIGTTOU',
53 'SIGURG',
54 'SIGXCPU',
55 'SIGXFSZ',
56 'SIGVTALRM',
57 'SIGPROF',
58 'SIGWINCH',
59 'SIGPOLL',
60 'SIGIO',
61 'SIGPWR',
62 'SIGSYS',
63 'SIGBABY',
64 'SIG_BLOCK',
65 'SIG_UNBLOCK',
66 'SIG_SETMASK',
67 );
68
69 /**
70 * @var boolean True if the daemon is in the process of exiting.
71 * @since 11.1
72 */
73 protected $exiting = false;
74
75 /**
76 * @var integer The parent process id.
77 * @since 12.1
78 */
79 protected $parentId = 0;
80
81 /**
82 * @var integer The process id of the daemon.
83 * @since 11.1
84 */
85 protected $processId = 0;
86
87 /**
88 * @var boolean True if the daemon is currently running.
89 * @since 11.1
90 */
91 protected $running = false;
92
93 /**
94 * Class constructor.
95 *
96 * @param JInputCli $input An optional argument to provide dependency injection for the application's
97 * input object. If the argument is a JInputCli object that object will become
98 * the application's input object, otherwise a default input object is created.
99 * @param Registry $config An optional argument to provide dependency injection for the application's
100 * config object. If the argument is a Registry object that object will become
101 * the application's config object, otherwise a default config object is created.
102 * @param JEventDispatcher $dispatcher An optional argument to provide dependency injection for the application's
103 * event dispatcher. If the argument is a JEventDispatcher object that object will become
104 * the application's event dispatcher, if it is null then the default event dispatcher
105 * will be created based on the application's loadDispatcher() method.
106 *
107 * @since 11.1
108 * @throws RuntimeException
109 */
110 public function __construct(JInputCli $input = null, Registry $config = null, JEventDispatcher $dispatcher = null)
111 {
112 // Verify that the process control extension for PHP is available.
113 // @codeCoverageIgnoreStart
114 if (!defined('SIGHUP'))
115 {
116 JLog::add('The PCNTL extension for PHP is not available.', JLog::ERROR);
117 throw new RuntimeException('The PCNTL extension for PHP is not available.');
118 }
119
120 // Verify that POSIX support for PHP is available.
121 if (!function_exists('posix_getpid'))
122 {
123 JLog::add('The POSIX extension for PHP is not available.', JLog::ERROR);
124 throw new RuntimeException('The POSIX extension for PHP is not available.');
125 }
126 // @codeCoverageIgnoreEnd
127
128 // Call the parent constructor.
129 parent::__construct($input, $config, $dispatcher);
130
131 // Set some system limits.
132 @set_time_limit($this->config->get('max_execution_time', 0));
133
134 if ($this->config->get('max_memory_limit') !== null)
135 {
136 ini_set('memory_limit', $this->config->get('max_memory_limit', '256M'));
137 }
138
139 // Flush content immediately.
140 ob_implicit_flush();
141 }
142
143 /**
144 * Method to handle POSIX signals.
145 *
146 * @param integer $signal The received POSIX signal.
147 *
148 * @return void
149 *
150 * @since 11.1
151 * @see pcntl_signal()
152 * @throws RuntimeException
153 */
154 public static function signal($signal)
155 {
156 // Log all signals sent to the daemon.
157 JLog::add('Received signal: ' . $signal, JLog::DEBUG);
158
159 // Let's make sure we have an application instance.
160 if (!is_subclass_of(static::$instance, 'JApplicationDaemon'))
161 {
162 JLog::add('Cannot find the application instance.', JLog::EMERGENCY);
163 throw new RuntimeException('Cannot find the application instance.');
164 }
165
166 // Fire the onReceiveSignal event.
167 static::$instance->triggerEvent('onReceiveSignal', array($signal));
168
169 switch ($signal)
170 {
171 case SIGINT:
172 case SIGTERM:
173 // Handle shutdown tasks
174 if (static::$instance->running && static::$instance->isActive())
175 {
176 static::$instance->shutdown();
177 }
178 else
179 {
180 static::$instance->close();
181 }
182 break;
183 case SIGHUP:
184 // Handle restart tasks
185 if (static::$instance->running && static::$instance->isActive())
186 {
187 static::$instance->shutdown(true);
188 }
189 else
190 {
191 static::$instance->close();
192 }
193 break;
194 case SIGCHLD:
195 // A child process has died
196 while (static::$instance->pcntlWait($signal, WNOHANG || WUNTRACED) > 0)
197 {
198 usleep(1000);
199 }
200 break;
201 case SIGCLD:
202 while (static::$instance->pcntlWait($signal, WNOHANG) > 0)
203 {
204 $signal = static::$instance->pcntlChildExitStatus($signal);
205 }
206 break;
207 default:
208 break;
209 }
210 }
211
212 /**
213 * Check to see if the daemon is active. This does not assume that $this daemon is active, but
214 * only if an instance of the application is active as a daemon.
215 *
216 * @return boolean True if daemon is active.
217 *
218 * @since 11.1
219 */
220 public function isActive()
221 {
222 // Get the process id file location for the application.
223 $pidFile = $this->config->get('application_pid_file');
224
225 // If the process id file doesn't exist then the daemon is obviously not running.
226 if (!is_file($pidFile))
227 {
228 return false;
229 }
230
231 // Read the contents of the process id file as an integer.
232 $fp = fopen($pidFile, 'r');
233 $pid = fread($fp, filesize($pidFile));
234 $pid = (int) $pid;
235 fclose($fp);
236
237 // Check to make sure that the process id exists as a positive integer.
238 if (!$pid)
239 {
240 return false;
241 }
242
243 // Check to make sure the process is active by pinging it and ensure it responds.
244 if (!posix_kill($pid, 0))
245 {
246 // No response so remove the process id file and log the situation.
247 @ unlink($pidFile);
248 JLog::add('The process found based on PID file was unresponsive.', JLog::WARNING);
249
250 return false;
251 }
252
253 return true;
254 }
255
256 /**
257 * Load an object or array into the application configuration object.
258 *
259 * @param mixed $data Either an array or object to be loaded into the configuration object.
260 *
261 * @return JCli Instance of $this to allow chaining.
262 *
263 * @since 11.1
264 */
265 public function loadConfiguration($data)
266 {
267 // Execute the parent load method.
268 parent::loadConfiguration($data);
269
270 /*
271 * Setup some application metadata options. This is useful if we ever want to write out startup scripts
272 * or just have some sort of information available to share about things.
273 */
274
275 // The application author name. This string is used in generating startup scripts and has
276 // a maximum of 50 characters.
277 $tmp = (string) $this->config->get('author_name', 'Joomla Platform');
278 $this->config->set('author_name', (strlen($tmp) > 50) ? substr($tmp, 0, 50) : $tmp);
279
280 // The application author email. This string is used in generating startup scripts.
281 $tmp = (string) $this->config->get('author_email', 'admin@joomla.org');
282 $this->config->set('author_email', filter_var($tmp, FILTER_VALIDATE_EMAIL));
283
284 // The application name. This string is used in generating startup scripts.
285 $tmp = (string) $this->config->get('application_name', 'JApplicationDaemon');
286 $this->config->set('application_name', (string) preg_replace('/[^A-Z0-9_-]/i', '', $tmp));
287
288 // The application description. This string is used in generating startup scripts.
289 $tmp = (string) $this->config->get('application_description', 'A generic Joomla Platform application.');
290 $this->config->set('application_description', filter_var($tmp, FILTER_SANITIZE_STRING));
291
292 /*
293 * Setup the application path options. This defines the default executable name, executable directory,
294 * and also the path to the daemon process id file.
295 */
296
297 // The application executable daemon. This string is used in generating startup scripts.
298 $tmp = (string) $this->config->get('application_executable', basename($this->input->executable));
299 $this->config->set('application_executable', $tmp);
300
301 // The home directory of the daemon.
302 $tmp = (string) $this->config->get('application_directory', dirname($this->input->executable));
303 $this->config->set('application_directory', $tmp);
304
305 // The pid file location. This defaults to a path inside the /tmp directory.
306 $name = $this->config->get('application_name');
307 $tmp = (string) $this->config->get('application_pid_file', strtolower('/tmp/' . $name . '/' . $name . '.pid'));
308 $this->config->set('application_pid_file', $tmp);
309
310 /*
311 * Setup the application identity options. It is important to remember if the default of 0 is set for
312 * either UID or GID then changing that setting will not be attempted as there is no real way to "change"
313 * the identity of a process from some user to root.
314 */
315
316 // The user id under which to run the daemon.
317 $tmp = (int) $this->config->get('application_uid', 0);
318 $options = array('options' => array('min_range' => 0, 'max_range' => 65000));
319 $this->config->set('application_uid', filter_var($tmp, FILTER_VALIDATE_INT, $options));
320
321 // The group id under which to run the daemon.
322 $tmp = (int) $this->config->get('application_gid', 0);
323 $options = array('options' => array('min_range' => 0, 'max_range' => 65000));
324 $this->config->set('application_gid', filter_var($tmp, FILTER_VALIDATE_INT, $options));
325
326 // Option to kill the daemon if it cannot switch to the chosen identity.
327 $tmp = (bool) $this->config->get('application_require_identity', 1);
328 $this->config->set('application_require_identity', $tmp);
329
330 /*
331 * Setup the application runtime options. By default our execution time limit is infinite obviously
332 * because a daemon should be constantly running unless told otherwise. The default limit for memory
333 * usage is 256M, which admittedly is a little high, but remember it is a "limit" and PHP's memory
334 * management leaves a bit to be desired :-)
335 */
336
337 // The maximum execution time of the application in seconds. Zero is infinite.
338 $tmp = $this->config->get('max_execution_time');
339
340 if ($tmp !== null)
341 {
342 $this->config->set('max_execution_time', (int) $tmp);
343 }
344
345 // The maximum amount of memory the application can use.
346 $tmp = $this->config->get('max_memory_limit', '256M');
347
348 if ($tmp !== null)
349 {
350 $this->config->set('max_memory_limit', (string) $tmp);
351 }
352
353 return $this;
354 }
355
356 /**
357 * Execute the daemon.
358 *
359 * @return void
360 *
361 * @since 11.1
362 */
363 public function execute()
364 {
365 // Trigger the onBeforeExecute event.
366 $this->triggerEvent('onBeforeExecute');
367
368 // Enable basic garbage collection.
369 gc_enable();
370
371 JLog::add('Starting ' . $this->name, JLog::INFO);
372
373 // Set off the process for becoming a daemon.
374 if ($this->daemonize())
375 {
376 // Declare ticks to start signal monitoring. When you declare ticks, PCNTL will monitor
377 // incoming signals after each tick and call the relevant signal handler automatically.
378 declare (ticks = 1);
379
380 // Start the main execution loop.
381 while (true)
382 {
383 // Perform basic garbage collection.
384 $this->gc();
385
386 // Don't completely overload the CPU.
387 usleep(1000);
388
389 // Execute the main application logic.
390 $this->doExecute();
391 }
392 }
393 // We were not able to daemonize the application so log the failure and die gracefully.
394 else
395 {
396 JLog::add('Starting ' . $this->name . ' failed', JLog::INFO);
397 }
398
399 // Trigger the onAfterExecute event.
400 $this->triggerEvent('onAfterExecute');
401 }
402
403 /**
404 * Restart daemon process.
405 *
406 * @return void
407 *
408 * @codeCoverageIgnore
409 * @since 11.1
410 */
411 public function restart()
412 {
413 JLog::add('Stopping ' . $this->name, JLog::INFO);
414 $this->shutdown(true);
415 }
416
417 /**
418 * Stop daemon process.
419 *
420 * @return void
421 *
422 * @codeCoverageIgnore
423 * @since 11.1
424 */
425 public function stop()
426 {
427 JLog::add('Stopping ' . $this->name, JLog::INFO);
428 $this->shutdown();
429 }
430
431 /**
432 * Method to change the identity of the daemon process and resources.
433 *
434 * @return boolean True if identity successfully changed
435 *
436 * @since 11.1
437 * @see posix_setuid()
438 */
439 protected function changeIdentity()
440 {
441 // Get the group and user ids to set for the daemon.
442 $uid = (int) $this->config->get('application_uid', 0);
443 $gid = (int) $this->config->get('application_gid', 0);
444
445 // Get the application process id file path.
446 $file = $this->config->get('application_pid_file');
447
448 // Change the user id for the process id file if necessary.
449 if ($uid && (fileowner($file) != $uid) && (!@ chown($file, $uid)))
450 {
451 JLog::add('Unable to change user ownership of the process id file.', JLog::ERROR);
452
453 return false;
454 }
455
456 // Change the group id for the process id file if necessary.
457 if ($gid && (filegroup($file) != $gid) && (!@ chgrp($file, $gid)))
458 {
459 JLog::add('Unable to change group ownership of the process id file.', JLog::ERROR);
460
461 return false;
462 }
463
464 // Set the correct home directory for the process.
465 if ($uid && ($info = posix_getpwuid($uid)) && is_dir($info['dir']))
466 {
467 system('export HOME="' . $info['dir'] . '"');
468 }
469
470 // Change the user id for the process necessary.
471 if ($uid && (posix_getuid($file) != $uid) && (!@ posix_setuid($uid)))
472 {
473 JLog::add('Unable to change user ownership of the proccess.', JLog::ERROR);
474
475 return false;
476 }
477
478 // Change the group id for the process necessary.
479 if ($gid && (posix_getgid($file) != $gid) && (!@ posix_setgid($gid)))
480 {
481 JLog::add('Unable to change group ownership of the proccess.', JLog::ERROR);
482
483 return false;
484 }
485
486 // Get the user and group information based on uid and gid.
487 $user = posix_getpwuid($uid);
488 $group = posix_getgrgid($gid);
489
490 JLog::add('Changed daemon identity to ' . $user['name'] . ':' . $group['name'], JLog::INFO);
491
492 return true;
493 }
494
495 /**
496 * Method to put the application into the background.
497 *
498 * @return boolean
499 *
500 * @since 11.1
501 * @throws RuntimeException
502 */
503 protected function daemonize()
504 {
505 // Is there already an active daemon running?
506 if ($this->isActive())
507 {
508 JLog::add($this->name . ' daemon is still running. Exiting the application.', JLog::EMERGENCY);
509
510 return false;
511 }
512
513 // Reset Process Information
514 $this->safeMode = !!@ ini_get('safe_mode');
515 $this->processId = 0;
516 $this->running = false;
517
518 // Detach process!
519 try
520 {
521 // Check if we should run in the foreground.
522 if (!$this->input->get('f'))
523 {
524 // Detach from the terminal.
525 $this->detach();
526 }
527 else
528 {
529 // Setup running values.
530 $this->exiting = false;
531 $this->running = true;
532
533 // Set the process id.
534 $this->processId = (int) posix_getpid();
535 $this->parentId = $this->processId;
536 }
537 }
538 catch (RuntimeException $e)
539 {
540 JLog::add('Unable to fork.', JLog::EMERGENCY);
541
542 return false;
543 }
544
545 // Verify the process id is valid.
546 if ($this->processId < 1)
547 {
548 JLog::add('The process id is invalid; the fork failed.', JLog::EMERGENCY);
549
550 return false;
551 }
552
553 // Clear the umask.
554 @ umask(0);
555
556 // Write out the process id file for concurrency management.
557 if (!$this->writeProcessIdFile())
558 {
559 JLog::add('Unable to write the pid file at: ' . $this->config->get('application_pid_file'), JLog::EMERGENCY);
560
561 return false;
562 }
563
564 // Attempt to change the identity of user running the process.
565 if (!$this->changeIdentity())
566 {
567 // If the identity change was required then we need to return false.
568 if ($this->config->get('application_require_identity'))
569 {
570 JLog::add('Unable to change process owner.', JLog::CRITICAL);
571
572 return false;
573 }
574 else
575 {
576 JLog::add('Unable to change process owner.', JLog::WARNING);
577 }
578 }
579
580 // Setup the signal handlers for the daemon.
581 if (!$this->setupSignalHandlers())
582 {
583 return false;
584 }
585
586 // Change the current working directory to the application working directory.
587 @ chdir($this->config->get('application_directory'));
588
589 return true;
590 }
591
592 /**
593 * This is truly where the magic happens. This is where we fork the process and kill the parent
594 * process, which is essentially what turns the application into a daemon.
595 *
596 * @return void
597 *
598 * @since 12.1
599 * @throws RuntimeException
600 */
601 protected function detach()
602 {
603 JLog::add('Detaching the ' . $this->name . ' daemon.', JLog::DEBUG);
604
605 // Attempt to fork the process.
606 $pid = $this->fork();
607
608 // If the pid is positive then we successfully forked, and can close this application.
609 if ($pid)
610 {
611 // Add the log entry for debugging purposes and exit gracefully.
612 JLog::add('Ending ' . $this->name . ' parent process', JLog::DEBUG);
613 $this->close();
614 }
615 // We are in the forked child process.
616 else
617 {
618 // Setup some protected values.
619 $this->exiting = false;
620 $this->running = true;
621
622 // Set the parent to self.
623 $this->parentId = $this->processId;
624 }
625 }
626
627 /**
628 * Method to fork the process.
629 *
630 * @return integer The child process id to the parent process, zero to the child process.
631 *
632 * @since 11.1
633 * @throws RuntimeException
634 */
635 protected function fork()
636 {
637 // Attempt to fork the process.
638 $pid = $this->pcntlFork();
639
640 // If the fork failed, throw an exception.
641 if ($pid === -1)
642 {
643 throw new RuntimeException('The process could not be forked.');
644 }
645 // Update the process id for the child.
646 elseif ($pid === 0)
647 {
648 $this->processId = (int) posix_getpid();
649 }
650 // Log the fork in the parent.
651 else
652 {
653 // Log the fork.
654 JLog::add('Process forked ' . $pid, JLog::DEBUG);
655 }
656
657 // Trigger the onFork event.
658 $this->postFork();
659
660 return $pid;
661 }
662
663 /**
664 * Method to perform basic garbage collection and memory management in the sense of clearing the
665 * stat cache. We will probably call this method pretty regularly in our main loop.
666 *
667 * @return void
668 *
669 * @codeCoverageIgnore
670 * @since 11.1
671 */
672 protected function gc()
673 {
674 // Perform generic garbage collection.
675 gc_collect_cycles();
676
677 // Clear the stat cache so it doesn't blow up memory.
678 clearstatcache();
679 }
680
681 /**
682 * Method to attach the JApplicationDaemon signal handler to the known signals. Applications
683 * can override these handlers by using the pcntl_signal() function and attaching a different
684 * callback method.
685 *
686 * @return boolean
687 *
688 * @since 11.1
689 * @see pcntl_signal()
690 */
691 protected function setupSignalHandlers()
692 {
693 // We add the error suppression for the loop because on some platforms some constants are not defined.
694 foreach (self::$signals as $signal)
695 {
696 // Ignore signals that are not defined.
697 if (!defined($signal) || !is_int(constant($signal)) || (constant($signal) === 0))
698 {
699 // Define the signal to avoid notices.
700 JLog::add('Signal "' . $signal . '" not defined. Defining it as null.', JLog::DEBUG);
701 define($signal, null);
702
703 // Don't listen for signal.
704 continue;
705 }
706
707 // Attach the signal handler for the signal.
708 if (!$this->pcntlSignal(constant($signal), array('JApplicationDaemon', 'signal')))
709 {
710 JLog::add(sprintf('Unable to reroute signal handler: %s', $signal), JLog::EMERGENCY);
711
712 return false;
713 }
714 }
715
716 return true;
717 }
718
719 /**
720 * Method to shut down the daemon and optionally restart it.
721 *
722 * @param boolean $restart True to restart the daemon on exit.
723 *
724 * @return void
725 *
726 * @since 11.1
727 */
728 protected function shutdown($restart = false)
729 {
730 // If we are already exiting, chill.
731 if ($this->exiting)
732 {
733 return;
734 }
735 // If not, now we are.
736 else
737 {
738 $this->exiting = true;
739 }
740
741 // If we aren't already daemonized then just kill the application.
742 if (!$this->running && !$this->isActive())
743 {
744 JLog::add('Process was not daemonized yet, just halting current process', JLog::INFO);
745 $this->close();
746 }
747
748 // Only read the pid for the parent file.
749 if ($this->parentId == $this->processId)
750 {
751 // Read the contents of the process id file as an integer.
752 $fp = fopen($this->config->get('application_pid_file'), 'r');
753 $pid = fread($fp, filesize($this->config->get('application_pid_file')));
754 $pid = (int) $pid;
755 fclose($fp);
756
757 // Remove the process id file.
758 @ unlink($this->config->get('application_pid_file'));
759
760 // If we are supposed to restart the daemon we need to execute the same command.
761 if ($restart)
762 {
763 $this->close(exec(implode(' ', $GLOBALS['argv']) . ' > /dev/null &'));
764 }
765 // If we are not supposed to restart the daemon let's just kill -9.
766 else
767 {
768 passthru('kill -9 ' . $pid);
769 $this->close();
770 }
771 }
772 }
773
774 /**
775 * Method to write the process id file out to disk.
776 *
777 * @return boolean
778 *
779 * @since 11.1
780 */
781 protected function writeProcessIdFile()
782 {
783 // Verify the process id is valid.
784 if ($this->processId < 1)
785 {
786 JLog::add('The process id is invalid.', JLog::EMERGENCY);
787
788 return false;
789 }
790
791 // Get the application process id file path.
792 $file = $this->config->get('application_pid_file');
793
794 if (empty($file))
795 {
796 JLog::add('The process id file path is empty.', JLog::ERROR);
797
798 return false;
799 }
800
801 // Make sure that the folder where we are writing the process id file exists.
802 $folder = dirname($file);
803
804 if (!is_dir($folder) && !JFolder::create($folder))
805 {
806 JLog::add('Unable to create directory: ' . $folder, JLog::ERROR);
807
808 return false;
809 }
810
811 // Write the process id file out to disk.
812 if (!file_put_contents($file, $this->processId))
813 {
814 JLog::add('Unable to write proccess id file: ' . $file, JLog::ERROR);
815
816 return false;
817 }
818
819 // Make sure the permissions for the proccess id file are accurate.
820 if (!chmod($file, 0644))
821 {
822 JLog::add('Unable to adjust permissions for the proccess id file: ' . $file, JLog::ERROR);
823
824 return false;
825 }
826
827 return true;
828 }
829
830 /**
831 * Method to handle post-fork triggering of the onFork event.
832 *
833 * @return void
834 *
835 * @since 12.1
836 */
837 protected function postFork()
838 {
839 // Trigger the onFork event.
840 $this->triggerEvent('onFork');
841 }
842
843 /**
844 * Method to return the exit code of a terminated child process.
845 *
846 * @param integer $status The status parameter is the status parameter supplied to a successful call to pcntl_waitpid().
847 *
848 * @return integer The child process exit code.
849 *
850 * @codeCoverageIgnore
851 * @see pcntl_wexitstatus()
852 * @since 11.3
853 */
854 protected function pcntlChildExitStatus($status)
855 {
856 return pcntl_wexitstatus($status);
857 }
858
859 /**
860 * Method to return the exit code of a terminated child process.
861 *
862 * @return integer On success, the PID of the child process is returned in the parent's thread
863 * of execution, and a 0 is returned in the child's thread of execution. On
864 * failure, a -1 will be returned in the parent's context, no child process
865 * will be created, and a PHP error is raised.
866 *
867 * @codeCoverageIgnore
868 * @see pcntl_fork()
869 * @since 11.3
870 */
871 protected function pcntlFork()
872 {
873 return pcntl_fork();
874 }
875
876 /**
877 * Method to install a signal handler.
878 *
879 * @param integer $signal The signal number.
880 * @param callable $handler The signal handler which may be the name of a user created function,
881 * or method, or either of the two global constants SIG_IGN or SIG_DFL.
882 * @param boolean $restart Specifies whether system call restarting should be used when this
883 * signal arrives.
884 *
885 * @return boolean True on success.
886 *
887 * @codeCoverageIgnore
888 * @see pcntl_signal()
889 * @since 11.3
890 */
891 protected function pcntlSignal($signal, $handler, $restart = true)
892 {
893 return pcntl_signal($signal, $handler, $restart);
894 }
895
896 /**
897 * Method to wait on or return the status of a forked child.
898 *
899 * @param integer &$status Status information.
900 * @param integer $options If wait3 is available on your system (mostly BSD-style systems),
901 * you can provide the optional options parameter.
902 *
903 * @return integer The process ID of the child which exited, -1 on error or zero if WNOHANG
904 * was provided as an option (on wait3-available systems) and no child was available.
905 *
906 * @codeCoverageIgnore
907 * @see pcntl_wait()
908 * @since 11.3
909 */
910 protected function pcntlWait(&$status, $options = 0)
911 {
912 return pcntl_wait($status, $options);
913 }
914 }
915