Initial Commit
This commit is contained in:
48
dependencies/amphp/process/lib/Internal/Posix/Handle.php
vendored
Normal file
48
dependencies/amphp/process/lib/Internal/Posix/Handle.php
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Process\Internal\Posix;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Loop;
|
||||
use WP_Ultimo\Dependencies\Amp\Process\Internal\ProcessHandle;
|
||||
/** @internal */
|
||||
final class Handle extends ProcessHandle
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->pidDeferred = new Deferred();
|
||||
$this->joinDeferred = new Deferred();
|
||||
$this->originalParentPid = \getmypid();
|
||||
}
|
||||
/** @var Deferred */
|
||||
public $joinDeferred;
|
||||
/** @var resource */
|
||||
public $proc;
|
||||
/** @var resource */
|
||||
public $extraDataPipe;
|
||||
/** @var string */
|
||||
public $extraDataPipeWatcher;
|
||||
/** @var string */
|
||||
public $extraDataPipeStartWatcher;
|
||||
/** @var int */
|
||||
public $originalParentPid;
|
||||
/** @var int */
|
||||
public $shellPid;
|
||||
public function wait()
|
||||
{
|
||||
if ($this->shellPid === 0) {
|
||||
return;
|
||||
}
|
||||
$pid = $this->shellPid;
|
||||
$this->shellPid = 0;
|
||||
Loop::unreference(Loop::repeat(100, static function (string $watcherId) use($pid) {
|
||||
if (!\extension_loaded('pcntl') || \pcntl_waitpid($pid, $status, \WNOHANG) !== 0) {
|
||||
Loop::cancel($watcherId);
|
||||
}
|
||||
}));
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
$this->wait();
|
||||
}
|
||||
}
|
207
dependencies/amphp/process/lib/Internal/Posix/Runner.php
vendored
Normal file
207
dependencies/amphp/process/lib/Internal/Posix/Runner.php
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Process\Internal\Posix;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\ResourceInputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\ResourceOutputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Loop;
|
||||
use WP_Ultimo\Dependencies\Amp\Process\Internal\ProcessHandle;
|
||||
use WP_Ultimo\Dependencies\Amp\Process\Internal\ProcessRunner;
|
||||
use WP_Ultimo\Dependencies\Amp\Process\Internal\ProcessStatus;
|
||||
use WP_Ultimo\Dependencies\Amp\Process\ProcessException;
|
||||
use WP_Ultimo\Dependencies\Amp\Process\ProcessInputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Process\ProcessOutputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/** @internal */
|
||||
final class Runner implements ProcessRunner
|
||||
{
|
||||
const FD_SPEC = [
|
||||
["pipe", "r"],
|
||||
// stdin
|
||||
["pipe", "w"],
|
||||
// stdout
|
||||
["pipe", "w"],
|
||||
// stderr
|
||||
["pipe", "w"],
|
||||
];
|
||||
/** @var string|null */
|
||||
private static $fdPath;
|
||||
public static function onProcessEndExtraDataPipeReadable($watcher, $stream, Handle $handle)
|
||||
{
|
||||
Loop::cancel($watcher);
|
||||
$handle->extraDataPipeWatcher = null;
|
||||
$handle->status = ProcessStatus::ENDED;
|
||||
if (!\is_resource($stream) || \feof($stream)) {
|
||||
$handle->joinDeferred->fail(new ProcessException("Process ended unexpectedly"));
|
||||
} else {
|
||||
$handle->joinDeferred->resolve((int) \rtrim(@\stream_get_contents($stream)));
|
||||
}
|
||||
$handle->wait();
|
||||
}
|
||||
public static function onProcessStartExtraDataPipeReadable($watcher, $stream, $data)
|
||||
{
|
||||
Loop::cancel($watcher);
|
||||
$pid = \rtrim(@\fgets($stream));
|
||||
/** @var $deferreds Deferred[] */
|
||||
list($handle, $pipes, $deferreds) = $data;
|
||||
if (!$pid || !\is_numeric($pid)) {
|
||||
$error = new ProcessException("Could not determine PID");
|
||||
$handle->pidDeferred->fail($error);
|
||||
foreach ($deferreds as $deferred) {
|
||||
/** @var $deferred Deferred */
|
||||
$deferred->fail($error);
|
||||
}
|
||||
if ($handle->status < ProcessStatus::ENDED) {
|
||||
$handle->status = ProcessStatus::ENDED;
|
||||
$handle->joinDeferred->fail($error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$handle->status = ProcessStatus::RUNNING;
|
||||
$handle->pidDeferred->resolve((int) $pid);
|
||||
$deferreds[0]->resolve($pipes[0]);
|
||||
$deferreds[1]->resolve($pipes[1]);
|
||||
$deferreds[2]->resolve($pipes[2]);
|
||||
if ($handle->extraDataPipeWatcher !== null) {
|
||||
Loop::enable($handle->extraDataPipeWatcher);
|
||||
}
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function start(string $command, string $cwd = null, array $env = [], array $options = []) : ProcessHandle
|
||||
{
|
||||
$command = \sprintf('{ (%s) <&3 3<&- 3>/dev/null & } 3<&0; trap "" INT TERM QUIT HUP;' . 'pid=$!; echo $pid >&3; wait $pid; RC=$?; echo $RC >&3; exit $RC', $command);
|
||||
$handle = new Handle();
|
||||
$handle->proc = @\proc_open($command, $this->generateFds(), $pipes, $cwd ?: null, $env ?: null, $options);
|
||||
if (!\is_resource($handle->proc)) {
|
||||
$message = "Could not start process";
|
||||
if ($error = \error_get_last()) {
|
||||
$message .= \sprintf(" Errno: %d; %s", $error["type"], $error["message"]);
|
||||
}
|
||||
throw new ProcessException($message);
|
||||
}
|
||||
$status = \proc_get_status($handle->proc);
|
||||
if (!$status) {
|
||||
\proc_close($handle->proc);
|
||||
throw new ProcessException("Could not get process status");
|
||||
}
|
||||
$handle->shellPid = \proc_get_status($handle->proc)['pid'];
|
||||
$stdinDeferred = new Deferred();
|
||||
$handle->stdin = new ProcessOutputStream($stdinDeferred->promise());
|
||||
$stdoutDeferred = new Deferred();
|
||||
$handle->stdout = new ProcessInputStream($stdoutDeferred->promise());
|
||||
$stderrDeferred = new Deferred();
|
||||
$handle->stderr = new ProcessInputStream($stderrDeferred->promise());
|
||||
$handle->extraDataPipe = $pipes[3];
|
||||
\stream_set_blocking($pipes[3], \false);
|
||||
$handle->extraDataPipeStartWatcher = Loop::onReadable($pipes[3], [self::class, 'onProcessStartExtraDataPipeReadable'], [$handle, [new ResourceOutputStream($pipes[0]), new ResourceInputStream($pipes[1]), new ResourceInputStream($pipes[2])], [$stdinDeferred, $stdoutDeferred, $stderrDeferred]]);
|
||||
$handle->extraDataPipeWatcher = Loop::onReadable($pipes[3], [self::class, 'onProcessEndExtraDataPipeReadable'], $handle);
|
||||
Loop::unreference($handle->extraDataPipeWatcher);
|
||||
Loop::disable($handle->extraDataPipeWatcher);
|
||||
return $handle;
|
||||
}
|
||||
private function generateFds() : array
|
||||
{
|
||||
if (self::$fdPath === null) {
|
||||
self::$fdPath = \file_exists("/dev/fd") ? "/dev/fd" : "/proc/self/fd";
|
||||
}
|
||||
$fdList = @\scandir(self::$fdPath, \SCANDIR_SORT_NONE);
|
||||
if ($fdList === \false) {
|
||||
throw new ProcessException("Unable to list open file descriptors");
|
||||
}
|
||||
$fdList = \array_filter($fdList, function (string $path) : bool {
|
||||
return $path !== "." && $path !== "..";
|
||||
});
|
||||
$fds = [];
|
||||
foreach ($fdList as $id) {
|
||||
$fds[(int) $id] = ["file", "/dev/null", "r"];
|
||||
}
|
||||
return self::FD_SPEC + $fds;
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function join(ProcessHandle $handle) : Promise
|
||||
{
|
||||
/** @var Handle $handle */
|
||||
if ($handle->extraDataPipeWatcher !== null) {
|
||||
Loop::reference($handle->extraDataPipeWatcher);
|
||||
}
|
||||
return $handle->joinDeferred->promise();
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function kill(ProcessHandle $handle)
|
||||
{
|
||||
/** @var Handle $handle */
|
||||
if ($handle->extraDataPipeWatcher !== null) {
|
||||
Loop::cancel($handle->extraDataPipeWatcher);
|
||||
$handle->extraDataPipeWatcher = null;
|
||||
}
|
||||
/** @var Handle $handle */
|
||||
if ($handle->extraDataPipeStartWatcher !== null) {
|
||||
Loop::cancel($handle->extraDataPipeStartWatcher);
|
||||
$handle->extraDataPipeStartWatcher = null;
|
||||
}
|
||||
if (!\proc_terminate($handle->proc, 9)) {
|
||||
// Forcefully kill the process using SIGKILL.
|
||||
throw new ProcessException("Terminating process failed");
|
||||
}
|
||||
$handle->pidDeferred->promise()->onResolve(function ($error, $pid) {
|
||||
// The function should not call posix_kill() if $pid is null (i.e., there was an error starting the process).
|
||||
if ($error) {
|
||||
return;
|
||||
}
|
||||
// ignore errors because process not always detached
|
||||
@\posix_kill($pid, 9);
|
||||
});
|
||||
if ($handle->status < ProcessStatus::ENDED) {
|
||||
$handle->status = ProcessStatus::ENDED;
|
||||
$handle->joinDeferred->fail(new ProcessException("The process was killed"));
|
||||
}
|
||||
$this->free($handle);
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function signal(ProcessHandle $handle, int $signo)
|
||||
{
|
||||
$handle->pidDeferred->promise()->onResolve(function ($error, $pid) use($signo) {
|
||||
if ($error) {
|
||||
return;
|
||||
}
|
||||
@\posix_kill($pid, $signo);
|
||||
});
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function destroy(ProcessHandle $handle)
|
||||
{
|
||||
/** @var Handle $handle */
|
||||
if ($handle->status < ProcessStatus::ENDED && \getmypid() === $handle->originalParentPid) {
|
||||
try {
|
||||
$this->kill($handle);
|
||||
return;
|
||||
} catch (ProcessException $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
$this->free($handle);
|
||||
}
|
||||
private function free(Handle $handle)
|
||||
{
|
||||
/** @var Handle $handle */
|
||||
if ($handle->extraDataPipeWatcher !== null) {
|
||||
Loop::cancel($handle->extraDataPipeWatcher);
|
||||
$handle->extraDataPipeWatcher = null;
|
||||
}
|
||||
/** @var Handle $handle */
|
||||
if ($handle->extraDataPipeStartWatcher !== null) {
|
||||
Loop::cancel($handle->extraDataPipeStartWatcher);
|
||||
$handle->extraDataPipeStartWatcher = null;
|
||||
}
|
||||
if (\is_resource($handle->extraDataPipe)) {
|
||||
\fclose($handle->extraDataPipe);
|
||||
}
|
||||
$handle->stdin->close();
|
||||
$handle->stdout->close();
|
||||
$handle->stderr->close();
|
||||
if (\is_resource($handle->proc)) {
|
||||
\proc_close($handle->proc);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user