GIT repositories

Index page of all the GIT repositories that are clonable form this server via HTTPS. Übersichtsseite aller GIT-Repositories, die von diesem Server aus über git clone (HTTPS) erreichbar sind.

Services

A bunch of service scripts to convert, analyse and generate data. Ein paar Services zum Konvertieren, Analysieren und Generieren von Daten.

GNU octave web interface

A web interface for GNU Octave, which allows to run scientific calculations from netbooks, tables or smartphones. The interface provides a web form generator for Octave script parameters with pre-validation, automatic script list generation, as well presenting of output text, figures and files in a output HTML page. Ein Webinterface für GNU-Octave, mit dem wissenschaftliche Berechnungen von Netbooks, Tablets oder Smartphones aus durchgeführt werden können. Die Schnittstelle beinhaltet einen Formulargenerator für Octave-Scriptparameter, mit Einheiten und Einfabevalidierung. Textausgabe, Abbildungen und generierte Dateien werden abgefangen und in einer HTML-Seite dem Nutzer als Ergebnis zur Verfügung gestellt.

Klasse zum Ausführen von Shellprogrammen

Shell execution management class

Shell processes are normally executed using the functions exec, shell_exec, system etc - depending on which return or output is desired. proc_open and Co have the advantage that every output can be fetched: exit code, standard output (stdout) and error output (stderr). All this is managed by the ShellProcess class. You can run programs, fetch the outputs just-in-time via inherited callbacks, and send text input (stdin) to the process. The script can stop the process if the browser connection is closed.

Shell-Prozesse werden in PHP normalerweise mit den Funktionen exec, shell_exec, system usw. ausgeführt, jeweils für einen bestimmten Anwendungszweck bzw. Rückgabe. Die Funktionen proc_open und Co. haben den Vorteil, dass alle Rückgaben erfasst werden können, d.h. Exit-Code, Standardausgabe (stdout) und Standard Fehlerausgabe (stderr). Die Klasse ShellProcess bietet die Möglichkeit, auf einfache Weise diese erweiterten Funktionen zu verwenden. Es ist möglich, stdout und stderr mit vererbten Callbacks zu erfassen, Daten über stdin zu senden, sowie den Prozess zu stoppen, wenn die Browserverbindung abgebrochen ist.

Sample source code 1

Anwendungsbeispiel 1

<?php
require_once 'swlib/swlib.class.php';
use sw\ShellProcess;
use \Exception;
 
//
// This is the usage without inheritance. You can directly execute
// a program and fetch its output, error output and exit code.
//
try {
  $result = ShellProcess::exec("ls -lsia");
  print "Exit code:" . $result['exitcode'] . "\n";
  print "[STDOUT]\n" . $result['stdout'] . "\n";
  print "[STDERR]\n" . $result['stderr'] . "\n";
} catch(Exception $e) {
  print  "Exception: " . $e->getMessage() . "\n";
}

Sample source code 2

Anwendungsbeispiel 2

<?php
require_once 'swlib/swlib.class.php';
use sw\ShellProcess;
use \Exception;
 
//
// Next possibility, you instantiate the Object to get more control:
//
$sh = new ShellProcess(
  "ls -lsia",     // The command
  array(          // An array containing callbacks
    'onstdout' => function($text) { print "STDOUT: $text\n"; },
    'onstderr' => function($text) { print "STDERR: $text\n"; },
    'onprocessstarted' => function() { print "[STARTED]\n"; },
    'onprocessrunning' => function() { /* THINGS TO DO WHILE RUNNING */ },
    'onprocessstopped' => function() { print "[FINISHED]\n"; },
  ),
  false           // Don't fetch output in internal variables to save memory
);
 
// You can also set the command with this function instead in the constructor
$sh->setCommand('ls -ls');
 
// Same here we set it to false already in the constructor, but we can do
// it like this as well:
$sh->setFetchOutput(false);
 
// Timeout for the process, here 10s. The process will be terminated after
// this time elapsed.
$sh->setTimeout(10);
 
// This means the process shall be killed when this script ends. You can set
// this to false as well to let the child process live.
// Termination on script abort is the default, you don't need to set this
// explicitly.
$sh->setTerminateOnAbort(true);
 
// We overwrite the callbacks to show sendKeys()
$sh->setCallbacks(array(
 
  'onprocessstarted' => function() {
    print "My process ID is " . $sh->getPID() . "\n";
    print "My working directory is " . $sh->getWorkingDirectory() . "\n";
    print "I have " . $sh->getTimeout() . " seconds to finish up.\n";
    print "\n";
  },
 
  'onstdout' => function($text) {
    if(stripos($text, 'Type a number') !== false) {
      // We send something to STDIN of the process
      $sh->sendKeys("3.1415");
    }
  },
));
 
// There we go
$sh->run();
 
// Exit code can be obtained after the process is done.
print "Exit code: " . $sh->getExitCode() . "\n";
 
// This woule be if you would set $sh->setFetchOutput(true);
// $stderr = $sh->getStdErr();
// $stdout = $sh->getStdOut();

Sample source code 3

Anwendungsbeispiel 3

<?php
 
require_once 'swlib/swlib.class.php';
 
use sw\ShellProcess;
use \Exception;
 
//
// We overload on<Something> methods of the the ShellProcess class.
// These are used like event methods. In the example, we add the
// fetched text to variables and support methods to get these variables.
// I think you don't need no further description ...
//
class MyShellProcess extends ShellProcess {
 
  private $stdout = '';
  private $stderr = '';
 
  protected function onStdErr($text) {
    $this->stderr .= $text;
  }
 
  protected function onStdOut($text) {
    $this->stdout .= $text;
  }
 
  protected function onProcessStarted() {
    $this->stdout .= "[Process started]\n\n";
  }
 
  protected function onProcessStopped() {
    $this->stdout .= "\n\n[Process stopped]\n";
  }
 
  public function getOutput() {
    return $this->stdout;
  }
 
  public function getErrorOutput() {
    return $this->stderr;
  }
 
}
 
try {
  // Ok, lets run something:
  $sh = new MyShellProcess('tree -C');
 
  // You can set a timeout
  $sh->setTimeout(10.0);
 
  // That's default - if the script times out or the connection is aborted,
  // then the child process will be terminated.
  $sh->setTerminateOnAbort(true);
 
  // You can change the command (no need to set the command in the constructor)
  $sh->setCommand('tree -C');
 
  // And run ...
  $sh->run();
 
  // Handle output ...
  if ($sh->getErrorOutput() == '' && $sh->getOutput() == '') {
    // We want to see if the script worked but just has no output
    print "THERE IS NO OUTPUT\n";
  } else {
    // Check if we have errors outputs...
    if ($sh->getOutput() != '') {
      print "[STDOUT]\n" . $sh->getOutput() . "\n\n";
    }
 
    // Check if we have errors outputs...
    if ($sh->getErrorOutput() != '') {
      print "[STDERR]\n" . $sh->getErrorOutput() . "\n\n";
    }
  }
} catch (Exception $e) {
  print "EXCEPTION: " . $e->getMessage() . "\n" . $e->getTraceAsString();
}

Output of sample 3

Well, that's context dependent, but for me it was:

Ausgabe von Beispiel 3

Es ist kontextabhängig, aber bei mir war es:

[STDOUT]
[Process started]

.
├── LICENSE
├── VERSION
├── core
│   ├── EException.class.php
│   ├── Exception.class.php
│   ├── LException.class.php
│   ├── Localizer.class.php
│   ├── OutputBuffer.class.php
│   ├── Session.class.php
│   └── Tracer.class.php
├── date
│   ├── GregorianCalendar.class.php
│   ├── GregorianCalendarEvent.class.php
│   ├── GregorianCalendarEventController.class.php
│   ├── GregorianDayCalendarRenderer.class.php
│   ├── GregorianMonthCalendarRenderer.class.php
│   ├── IDate.class.php
│   ├── LocalDate.class.php
│   └── UtcDate.class.php
├── db
│   ├── MySql.class.php
│   ├── MySqlAdministration.class.php
│   └── MySqlException.class.php
├── index.php
├── mail
│   ├── EMailContact.class.php
│   ├── IEMailContact.class.php
│   └── Mailer.class.php
├── math
│   ├── Math.class.php
│   └── MathException.class.php
├── media
│   ├── FfmpegException.class.php
│   ├── FfmpegFile.class.php
│   ├── FfmpegFormula.class.php
│   ├── MediaStreamLoader.class.php
│   ├── MediaStreamLoaderException.class.php
│   ├── YoutubeDownloader.class.php
│   ├── YoutubeDownloaderException.class.php
│   └── formulas
│       ├── ffmpeg-avi-mpeg4-mp3.php
│       ├── ffmpeg-copy.php
│       └── ffmpeg-mp4-h264-aac.php
├── net
│   ├── AutoPodcast.class.php
│   ├── HttpAuthentication.class.php
│   ├── HttpAuthenticationException.class.php
│   ├── HttpRequest.class.php
│   ├── PodcastFeed.class.php
│   ├── PodcastFeedItem.class.php
│   ├── ResourceFile.class.php
│   └── Rss2Feed.class.php
├── readme
├── swlib.class.php
├── sys
│   ├── CommandLineInterface.class.php
│   ├── FileSystem.class.php
│   ├── FileSystemException.class.php
│   ├── IniFile.class.php
│   ├── Json.class.php
│   ├── ShellProcess.class.php
│   ├── ZipException.class.php
│   └── ZipFile.class.php
├── util
│   ├── ArrayFilter.class.php
│   ├── GeoIpLookup.class.php
│   ├── HtmlDocument.class.php
│   ├── LaTexRenderer.class.php
│   ├── PasswordCheck.class.php
│   ├── PhpCode.class.php
│   ├── SshAuthorizedKeysFile.class.php
│   ├── SshKey.class.php
│   ├── SshKeyManager.class.php
│   ├── UnitTest.class.php
│   ├── UserVerificationImage.class.php
│   ├── Versioning.class.php
│   ├── Xml.class.php
│   ├── XmlConverter.class.php
│   └── menu
└── verc
    ├── GitProject.class.php
    └── GitProjectException.class.php

12 directories, 70 files

[Process stopped]

Class source code

Klassen-Quelltext

<?php
 
/**
 * Shell execution wrapper. Provides executing a shell program, passing input
 * to the STDIN of the process (virtually typing in the console window), and
 * fetching STDOUT (normal output) and STDERR (error output). Furthermore the
 * child process is terminated if the script is finished. This prevents higher
 * cpu load for no reason. You can specify not to terminate the process as well.
 * The output is passed to the callback methods onStdOut() and onStdErr(), which
 * just print the contents. Overload these methods to handle the output yourself.
 * All overloadable methods:
 *  - onProcessStarted(): Called when the process was just started
 *  - onProcessFinished(): Called when the process exists
 *  - onProcessRunning(): Called regularely when the process is running
 *  - onStdOut(): STDOUT processing
 *  - onStdErr(): STDERR processing
 *
 * @gpackage de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2009-2010
 * @license GPL
 * @version 1.0
 */
 
namespace sw;
 
class ShellProcess {
 
  /**
   * The command to be executed
   * @var string
   */
  private $command = '';
 
  /**
   * The command to be executed
   * @var string
   */
  private $hProcess = false;
 
  /**
   * Environment variables for the process
   * @var array
   */
  private $environment = null;
 
  /**
   * The directory to execute the program in
   * @var string
   */
  private $directory = null;
 
  /**
   * The pipes for stdin, stdout, stderr
   * @var array
   */
  private $pipes = array();
 
  /**
   * Assoc array, contains the callbacks
   * @var array
   */
  private $callbacks = array();
 
  /**
   * The process id of the started child process. Only valid during
   * execution (or after that if the process shall continue after
   * connection abort / timeout).
   * @var int
   */
  private $pid = -1;
 
  /**
   * Child process exit code, null if no exit code determined yet.
   * @var int
   */
  private $exitcode = null;
 
  /**
   * If fetched, contains STDOUT of the process
   * @var string
   */
  protected $stdout = '';
 
  /**
   * If fetched, contains STDERR of the process
   * @var string
   */
  protected $stderr = '';
 
  /**
   * Process timeout
   * @var int
   */
  private $timeout = 10000;
 
  /**
   * If true, the output will be fetched
   * @var bool
   */
  private $fetchOutput = false;
 
  /**
   * If true, then the process will be stopped/closed if the http connection
   * is broken
   * @var bool
   */
  private $abortOnConnectionClosed = true;
 
  /**
   * If true, then the process will terminated after the connection has been
   * closed or the timeout was reached.
   * @var bool
   */
  private $killProcessOnAbort = true;
 
  /**
   * Contains all process IDs that have to be killed when the script
   * shuts down. The variable will be initialized when the method
   * run() is called.
   * @staticvar array
   */
  private static $processToKillOnAbort = null;
 
  /**
   * Returns if a process is running. This feature requires access to the
   * shell command "ps".
   * @param string/int $processIdOrName
   * @param bool $processNameOnly = false
   * @param bool $matchAnywhere = false
   * @return bool
   */
  public static function isProcessRunning($processIdOrName, $processNameOnly = false, $matchAnywhere = false) {
    if ($processNameOnly) {
      $processIdOrName = FileSystem::getBasename($processIdOrName);
    }
    $processIdOrName = trim($processIdOrName);
    $ob = new OutputBuffer();
    try {
      if (empty($processIdOrName)) {
        return false;
      } else if (is_numeric($processIdOrName)) {
        system("ps ax -o \"%p\""); //,'%P','%U','%G','%c','%a','%t','%C'"
        foreach (explode("\n", $ob->getOutput()) as $v) {
          if (trim($v, "\r\t ") == $processIdOrName) {
            return true;
          }
        }
      } else if ($processNameOnly) {
        system("ps ax -o \"%c\"");
        foreach (explode("\n", $ob->getOutput()) as $v) {
          if (trim($v, "\r\t ") == $processIdOrName) {
            return true;
          }
        }
      } else {
        system("ps ax -o \"%a\"");
        $o = $ob->getOutput();
        foreach (explode("\n", $o) as $v) {
          $v = trim(str_replace("\t", ' ', $v), " \r");
          if ($matchAnywhere && strpos($v, $processIdOrName) !== false) {
            return true;
          }
          if (stripos($v, $processIdOrName) !== false) {
            // Check if the first character after the command is a space
            $v = trim(str_ireplace($processIdOrName, '', substr($v, stripos($v, $processIdOrName))));
            if (strlen($v) == 0 || substr($v, 0, 1) == ' ') {
              return true;
            }
          }
        }
      }
    } catch (\Exception $e) {
      Tracer::traceLException($e);
    }
    return false;
  }
 
  /**
   * Runs a shell command and optionally fetches the output or runs callbacks
   * @param string $command
   * @return array
   */
  public static final function exec($command) {
    $o = new self($command, array(), true);
    $o = $o->run();
    return array(
        'exitcode' => $o->exitcode,
        'stdout' => $o->stdout,
        'stderr' => $o->stderr
    );
  }
 
  /**
   * Overload this method to write your own STDERR handler
   * @param string $text
   */
  protected function onStdErr(&$text) {
    if (isset($this->callbacks['onstderr'])) {
      call_user_func($this->callbacks['onstderr'], $text);
    }
    $this->stderr .= $text;
  }
 
  /**
   * Overload this method to write your own STDOUT handler
   * @param string $text
   */
  protected function onStdOut(&$text) {
    if (isset($this->callbacks['onstdout'])) {
      call_user_func($this->callbacks['onstdout'], $text);
    }
    $this->stdout .= $text;
  }
 
  /**
   * Overload this to be informed when the process was started
   * @param string $text
   */
  protected function onProcessStarted() {
    if (isset($this->callbacks['onprocessstarted'])) {
      call_user_func($this->callbacks['onprocessstarted']);
    }
  }
 
  /**
   * Overload this to be informed when the process was stopped
   * @param string $text
   */
  protected function onProcessStopped() {
    if (isset($this->callbacks['onprocessstopped'])) {
      call_user_func($this->callbacks['onprocessstopped']);
    }
  }
 
  /**
   * Overload this to be informed every time the process monitoring loop
   * was executed and want to start the next cycle, but minimum after 250ms.
   * This means it is not sure that the method is called after 250ms, but
   * definitly not earlier.
   * Return false if you want to exit the loop.
   * @return bool
   */
  protected function onProcessRunning() {
    if (isset($this->callbacks['onprocessrunning'])) {
      return call_user_func($this->callbacks['onprocessrunning']);
    }
  }
 
  /**
   * Constructor
   * @param string $command=''
   * @param array $callbacks=array()
   * @param bool $fetchOutput=false
   */
  public final function __construct($command='', $callbacks=array(), $fetchOutput=false) {
    $this->setCommand($command);
    $this->setCallbacks($callbacks);
    $this->setFetchOutput($fetchOutput);
  }
 
  /**
   * Destructor
   */
  public final function __destruct() {
    if (is_resource($this->hProcess)) {
      @proc_close($this->hProcess);
    }
    if (is_array($this->pipes) && !empty($this->pipes)) {
      if (is_resource($this->pipes[0]))
        @fclose($this->pipes[0]);
      if (is_resource($this->pipes[1]))
        @fclose($this->pipes[1]);
      if (is_resource($this->pipes[2]))
        @fclose($this->pipes[2]);
    }
  }
 
  /**
   * Returns the command to be executed
   * @return string
   */
  public final function getCommand() {
    return $this->command;
  }
 
  /**
   * Sets the new command to be executed.
   * @param string $command
   */
  public final function setCommand($command) {
    $this->command = trim($command);
  }
 
  /**
   * Returns the callbacks
   * @return array
   */
  public final function getCallbacks() {
    return $this->callbacks;
  }
 
  /**
   * Sets the callbacks
   * @param array $callbacks
   */
  public final function setCallbacks($callbacks) {
    $this->callbacks = array();
    if (!is_array($callbacks)) {
      throw new LException("The callbacks has to be specified as array");
    }
    foreach ($callbacks as $k => $v) {
      $k = strtolower($k);
      if (!in_array($k, array('onstdout', 'onstderr', 'onprocessstarted', 'onprocessrunning', 'onprocessstopped'))) {
        throw new LException("Callback array key ':k' is not known", array(':k' => $k));
      } else if (!is_callable($v)) {
        if (!empty($v)) {
          throw new LException("Callback ':v' is not callable", array(':v' => $v));
        }
      } else {
        $this->callbacks[$k] = $v;
      }
    }
  }
 
  /**
   * Returns if the process STDOUT and STDERR shall be fetched
   * @return bool
   */
  public final function getFetchOutput() {
    return $this->fetchOutput;
  }
 
  /**
   * Sets if the process STDOUT and STDERR shall be fetched. If true, the
   * output is fetched after the callback is performed. The callback can
   * modify the output.
   * @param bool $fetchOutput
   */
  public final function setFetchOutput($fetchOutput) {
    $this->fetchOutput = (bool) $fetchOutput;
  }
 
  /**
   * Returns the timeout in seconds
   * @return double
   */
  public final function getTimeout() {
    return $this->timeout;
  }
 
  /**
   * Sets the new process timeout in seconds
   * @param double $seconds
   */
  public final function setTimeout($seconds) {
    if (!is_numeric($seconds)) {
      throw new LException('Timeout must be an integer value in seconds.');
    } else {
      $this->timeout = intval($seconds);
    }
  }
 
  /**
   * Returns if the process has to be terminated (killed) if it still runs
   * after a timeout, break or connection abort.
   * @return bool
   */
  public final function getTerminateOnAbort() {
    return $this->killProcessOnAbort;
  }
 
  /**
   * Sets if the process has to be terminated (killed) if it still runs
   * after a timeout, break or connection abort.
   * @param bool $terminate
   */
  public final function setTerminateOnAbort($terminate) {
    if (!is_bool($terminate)) {
      throw new LException('Terminate-on-abort must be a boolean value.');
    } else {
      $this->killProcessOnAbort = $terminate ? true : false;
    }
  }
 
  /**
   * Returns the process ID of the child process, -1 if not yet started.
   * @return int
   */
  public final function getPID() {
    return $this->pid;
  }
 
  /**
   * Returns the process exit code after the process was stopped, null if
   * process not started or still running.
   * @return int
   */
  public final function getExitCode() {
    return $this->exitcode;
  }
 
  /**
   * Returns the process STDERR output of output fetching is enabled
   * @return string
   */
  public final function getStdErr() {
    return $this->stderr;
  }
 
  /**
   * Returns the process STDOUT output of output fetching is enabled
   * @return string
   */
  public final function getStdOut() {
    return $this->stdout;
  }
 
  /**
   * Returns the process working directory
   * @return string
   */
  public final function getWorkingDirectory() {
    return strval($this->directory);
  }
 
  /**
   * Sets the process working directory
   * @param string $dir
   */
  public final function setWorkingDirectory($dir) {
    $dir = trim($dir);
    if (!FileSystem::isDirectory($dir)) {
      throw new LException("Cannot set working directory to nonexisting directory");
    }
    $this->directory = $dir;
  }
 
  /**
   * Writes a $text to the STDIN (terminal input) of the process
   * @param string $text
   */
  public final function sendKeys($text) {
    if (!is_resource($this->hProcess)) {
      throw new LException('Shell command not running, cannot send characters to STDIN');
    } else if (!fwrite($this->pipes[0], $text)) {
      throw new LException('Failed to write to STDIN pipe of shell command');
    }
  }
 
  /**
   * Terminates the process using the kill command.
   */
  public final function terminate() {
    // That's to ensure that the pid is ok
    $pid = $this->pid;
    if ($pid > 0) {
      Tracer::trace("KILL executed process $pid");
      @exec("ps ax -o  \"%p,%P\"", $return, $exitcode);
      if (is_array($return)) {
        foreach ($return as $cpid) {
          $cpid = explode(',', $cpid);
          if (trim($cpid[1]) == $pid) {
            Tracer::trace("KILL child process " . $cpid[0]);
            @exec('kill ' . $cpid[0] . ' 2>/dev/null >&- >/dev/null');
          }
        }
      }
      @exec('kill ' . $pid . ' 2>/dev/null >&- >/dev/null');
    } else {
      throw new LException('Could not fetch a valid pid to terminate process', 1001);
    }
  }
 
  /**
   * Executes the specified command line and returns a reference to itself.
   * @return ShellProcess
   */
  public final function run() {
    $this->stderr = $this->stdout = '';
    $ignoreUserAbort = ignore_user_abort();
    if (!$this->abortOnConnectionClosed) {
      ignore_user_abort(false);
    }
    set_time_limit(60);
    ini_set('memory_limit', '256M');
    if ($this->command == '') {
      throw new LException('No shell command specified to execute');
    } else {
      $descriptors = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"));
      $this->hProcess = proc_open("exec " . $this->command, $descriptors, $this->pipes, $this->directory, $this->environment);
      if (!is_resource($this->hProcess)) {
        $exception = new LException('Executing shell command failed');
      } else {
        $status = proc_get_status($this->hProcess);
        if (!is_array($status)) {
          throw new LException("Failed to determine process status of started process");
        }
        $this->pid = $status['pid'];
 
        if ($this->abortOnConnectionClosed) {
          if (!is_array(self::$processToKillOnAbort)) {
            self::$processToKillOnAbort = array();
            register_shutdown_function(array(__CLASS__, 'shutdownTermination'), 'inside');
          }
          self::$processToKillOnAbort[strval($this->pid)] = $this->pid;
        }
 
        stream_set_blocking($this->pipes[1], 0);
        stream_set_blocking($this->pipes[2], 0);
        $stdoutOpen = $stderrOpen = true;
        $timeout = microtime(true) + $this->timeout;
        $exception = null;
        $nextOnRunningInterval = 0.1;
        $nextOnRunning = microtime(true) + $nextOnRunningInterval;
 
        try {
          $this->onProcessStarted();
        } catch (\Exception $e) {
          $exception = $e;
        }
 
        if (!$exception) {
 
          while ($stdoutOpen || $stderrOpen) {
            // Check if streams have new data
            $stream_r = array();
            $stream_w = null;
            $stream_x = null;
            if ($stdoutOpen) {
              $stream_r[] = $this->pipes[1];
            }
            if ($stderrOpen) {
              $stream_r[] = $this->pipes[2];
            }
 
            $stream_c = stream_select($stream_r, $stream_w, $stream_x, 100);
            if ($stream_c === false) {
              $exception = new LException('Failed to check STDOUT and STDERR for new data');
              break;
            } else if ($stream_c > 0) {
 
              // Check STDOUT for new data
              if ($stdoutOpen) {
                if (!feof($this->pipes[1])) {
                  $txt = fread($this->pipes[1], 4096);
                  $l = strlen($txt);
                  if ($l > 0) {
                    try {
                      $this->onStdOut($txt);
                    } catch (\Exception $e) {
                      $exception = $e;
                      break;
                    }
                  }
                } else {
                  $stdoutOpen = false;
                }
              }
 
              // Check STDERR for new data
              if ($stderrOpen) {
                if (!feof($this->pipes[2])) {
                  $txt = fread($this->pipes[2], 4096);
                  $l = strlen($txt);
                  if ($l > 0) {
                    try {
                      $this->onStdErr($txt);
                    } catch (\Exception $e) {
                      $exception = $e;
                      break;
                    }
                  }
                } else {
                  $stderrOpen = false;
                }
              }
            }
 
            // Check if connection has been closed by the client
            // and ignore_userabort() was set to true
            if ($this->abortOnConnectionClosed) {
              if (connection_aborted()) {
                $exception = new LException('Client connection closed');
                break;
              }
            }
 
            // Check for script timeout
            if ($this->timeout > 0) {
              if (microtime(true) > $timeout) {
                $exception = new LException('Script timed out');
                break;
              } else {
                set_time_limit(60);
              }
            }
 
            // Only to be sure not to block other proceses even if
            // each loop results new data.
            usleep(10);
 
            if (microtime(true) >= $nextOnRunning) {
              $nextOnRunning = microtime(true) + $nextOnRunningInterval;
              try {
                if ($this->onProcessRunning() === false) {
                  break;
                }
              } catch (\Exception $e) {
                $exception = $e;
                break;
              }
            }
 
            $status = proc_get_status($this->hProcess);
            if (!is_array($status) || $status['running'] == false) {
              break;
            }
          }
        }
 
        // Get the status agein
        $status = proc_get_status($this->hProcess);
        if (!is_array($status)) {
          $exception = new LException("Failed to determine process status");
        } else if ($status['running'] == true) {
          if ($this->killProcessOnAbort) {
            try {
              $this->terminate();
            } catch (\Exception $e) {
              if (!$exception instanceof LException) {
                $exception = $e;
              } else {
                // ok, do it this way, not using $previous ...
                $exception = new LException($exception->getMessage() . ', AND ADITIONALLY: ' . $exception->getMessage());
              }
            }
          }
        }
 
        // Get exit code
        if (is_array($status) && !$status['running']) {
          $this->exitcode = $status['exitcode'];
        }
 
        // clean up
        @proc_close($this->hProcess);
        if (is_resource($this->pipes[0]))
          @fclose($this->pipes[0]);
        if (is_resource($this->pipes[1]))
          @fclose($this->pipes[1]);
        if (is_resource($this->pipes[2]))
          @fclose($this->pipes[2]);
        $this->hProcess = null;
        $this->pipes = array();
 
        try {
          $this->onProcessStopped($this->exitcode);
        } catch (\Exception $e) {
          $exception = $e;
        }
      }
 
      // Unregister the process id to kill at shutdown time.
      if (isset(self::$processToKillOnAbort[strval($this->pid)])) {
        unset(self::$processToKillOnAbort[strval($this->pid)]);
      }
 
      // Restore the original ignore_user_abort.
      ignore_user_abort($ignoreUserAbort);
 
      if ($exception != null) {
        throw $exception;
      }
    }
    return $this;
  }
 
  /**
   * Registered shutdown function, terminates all child processes,
   * which are registered as terminate on abort.
   * @return void
   */
  public static final function shutdownTermination() {
    if (is_array(self::$processToKillOnAbort)) {
      foreach (self::$processToKillOnAbort as $pid) {
        $return = array();
        $exitcode = -1;
        @exec("ps ax -o  \"%p,%P\"", $return, $exitcode);
        if (is_array($return)) {
          foreach ($return as $cpid) {
            $cpid = explode(',', $cpid);
            if (trim($cpid[1]) == $pid) {
              Tracer::trace("KILL child process " . $cpid[0]);
              @exec('kill ' . $cpid[0] . ' 2>/dev/null >&- >/dev/null');
            }
          }
        }
        @exec('kill ' . $pid . ' 2>/dev/null >&- >/dev/null');
      }
    }
  }
 
}