読者です 読者をやめる 読者になる 読者になる

はてダ

はてブロなのにはてダ!!!!!

実を言うとはこのブログはもうだめです。

突然こんなこと言ってごめんね。 でも本当です。

PHP でシェルを介さずプロセスを起動させたいと思った

PHP

TL;DR;

処理の流れとしては、 popen(3) とほぼ同じで、 fork(2) して exec(3) させるだけである。 ただし、 pipe(2) が使えないため、プロセス間通信には名前付きパイプ (fifo) を使う。 素直に C Extension 書いた方がよのだろう。

実装

Debian 8.5 で動作確認を行った。

<?php

function fileno ( $path ) {
    // fileno(3) がないのでそれっぽいもの
    $pid = getmypid();
    foreach ( scandir("/proc/{$pid}/fd") as $fileno ) {
        $realpath = @readlink("/proc/{$pid}/fd/{$fileno}");
        if ( $path === $realpath ) {
            return (int)$fileno;
        }
    }
    return -1;
}


function popen_( $path, $args ) {
    // pipe(2) が呼び出せないので、 fifo を作る
    // なおこの fifo は自動的に消えない
    $pipe = tempnam(sys_get_temp_dir(), '');

    // exec されたことを検知するために使うランダムなプロセス名
    $process_name = substr(
        str_shuffle( '1234567890abcdefghijklmnopqrstuvwxyz' ), 0, 16
    );

    // 子プロセスを立ち上げるために fork する
    $pid = pcntl_fork();

    if ( $pid < 0 ) {  // 失敗
        return null;

    } else if ( $pid === 0 ) {  // child
        // exec されたことを親プロセスに検知させるため
        // プロセスを一度ランダム文字列にする
        cli_set_process_title( $process_name );

        // fifo を作る
        unlink( $pipe );
        posix_mkfifo( $pipe, 0600 );
        $fp = fopen( $pipe, 'r+' );
        $fd = fileno( $pipe );

        // dup2
        eio_dup2( $fd, 0 );
        sleep( 1 );  // wait dup2

        // ストリームを閉じる
        fclose( $fp );

        // exec
        pcntl_exec( $path, $args );

    } else {
        while (true) {
            // exec されて子プロセスの名前が変わるのを待つ
            if ( $process_name !== file_get_contents( "/proc/{$pid}/cmdline" ) ) {
                break;
            }
        }
        sleep(1);  // wait pipe open
        return fopen( $pipe, 'w+' );
    }
}

// デモ
$stdin = fopen( 'php://stdin', 'w' );
$pipe = popen_( '/bin/cat', [] );
while ( ($line = fgets( $stdin ) ) !== false ) {
    fputs( $pipe, $line );
}

もう説明書く気力が残ってない。

基本的に PHP は嫌いではないんだけど、無理なことは無理ということで素直に切り分ける気持ちが大切だなという気持ちになる気持ちでした。気持ち