Reading from STDIN pipe when using proc_open

后端 未结 1 362
礼貌的吻别
礼貌的吻别 2020-12-29 15:25

I am trying to make a website where people can compile and run their code online, thus we need to find an interactive way for users to send instructions.

Actually,

1条回答
  •  -上瘾入骨i
    2020-12-29 15:51

    It is more a C or a glibc problem. You'll have to use fflush(stdout).

    Why? And what's the difference between running a.out in a terminal and calling it from PHP?

    Answer: If you run a.out in a terminal (being stdin a tty) then the glibc will use line buffered IO. But if you run it from another program (PHP in this case) and it's stdin is a pipe (or whatever but not a tty) than the glibc will use internal IO buffering. That's why the first fgets() blocks if uncommented. For more info check this article.

    Good news: You can control this buffering using the stdbuf command. Change $run_string to:

    $run_string = "cd ".$addr_base.";stdbuf -o0 ./a.out 2>&1";
    

    Here comes a working example. Working even if the C code don't cares about fflush() as it is using the stdbuf command:

    Starting subprocess

    $cmd = 'stdbuf -o0 ./a.out 2>&1';
    
    // what pipes should be used for STDIN, STDOUT and STDERR of the child
    $descriptorspec = array (
        0 => array("pipe", "r"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w")
     );
    
    // open the child
    $proc = proc_open (
        $cmd, $descriptorspec, $pipes, getcwd()
    );
    

    set all streams to non blocking mode

    // set all streams to non blockin mode
    stream_set_blocking($pipes[1], 0);
    stream_set_blocking($pipes[2], 0);
    stream_set_blocking(STDIN, 0);
    
    // check if opening has succeed
    if($proc === FALSE){
        throw new Exception('Cannot execute child process');
    }
    

    get child pid. we need it later

    // get PID via get_status call
    $status = proc_get_status($proc);
    if($status === FALSE) {
        throw new Exception (sprintf(
            'Failed to obtain status information '
        ));
    }
    $pid = $status['pid'];
    

    poll until child terminates

    // now, poll for childs termination
    while(true) {
        // detect if the child has terminated - the php way
        $status = proc_get_status($proc);
        // check retval
        if($status === FALSE) {
            throw new Exception ("Failed to obtain status information for $pid");
        }
        if($status['running'] === FALSE) {
            $exitcode = $status['exitcode'];
            $pid = -1;
            echo "child exited with code: $exitcode\n";
            exit($exitcode);
        }
    
        // read from childs stdout and stderr
        // avoid *forever* blocking through using a time out (50000usec)
        foreach(array(1, 2) as $desc) {
            // check stdout for data
            $read = array($pipes[$desc]);
            $write = NULL;
            $except = NULL;
            $tv = 0;
            $utv = 50000;
    
            $n = stream_select($read, $write, $except, $tv, $utv);
            if($n > 0) {
                do {
                    $data = fread($pipes[$desc], 8092);
                    fwrite(STDOUT, $data);
                } while (strlen($data) > 0);
            }
        }
    
    
        $read = array(STDIN);
        $n = stream_select($read, $write, $except, $tv, $utv);
        if($n > 0) {
            $input = fread(STDIN, 8092);
            // inpput to program
            fwrite($pipes[0], $input);
        }
    }
    

    0 讨论(0)
提交回复
热议问题