<?phperror_reporting(E_ALL);/** * Marc Quinton - may 2007 * Licence : LGPL * * a pipe is a "channel" built to talk between your php script and an external command (shell, script or what ever) * - you can "talk" to this command : just write to file descriptor (see PipeIO::write()) (stdin) * - you can read from this command, (stdout) * - there is also an channel for errors : stderr * */class Pipe{ protected $proc_id; # proc_id from proc_open call ; needed for many proc_* functions protected $descriptor_spec; # defaults descriptors (FD) for a pipe with reading and writing protected $watch_ids; # watch ids : when terminating pipe, need to call Gtk::input_remove($id); protected $terminated; # triggered to true when child terminate (hup signal) protected $has_run; # can run a pipe more that one time. public function __construct(){ $this->descriptor_spec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a file to write to ); $this->terminated = false; $this->proc_id = null; $this->watch_ids = array(); $this->has_run = false; } # auto clean this pipe when object is destroyed. # You need to take care of this if you don't keep reference to object in a loop for example. public function __destruct(){ $this->terminate(); } public function terminate(){ if($this->proc_id != null){ # terminate each FD watch, using right ID foreach ($this->watch_ids as $id) Gtk::input_remove($id); # close opened pipes. fclose($this->pipes[0]); fclose($this->pipes[1]); fclose($this->pipes[2]); # kill running child process $this->kill_child(); # close pipe proc_close($this->proc_id); $this->proc_id = null; $this->pipes = null; } } public function is_running(){ $status = $this->get_status(); if($status == false) return false; return $status['running'] == 1; } public function get_pid(){ $status = $this->get_status(); if($status == false) return false; return $status['pid']; } public function get_exitcode(){ $status = $this->get_status(); if($status == false) return false; return $status['exitcode']; } /* Array ( [command] => php -q ... [pid] => 13208 [running] => 1 [signaled] => [stopped] => [exitcode] => -1 [termsig] => 0 [stopsig] => 0 ) */ public function get_status(){ if($this->proc_id == null) return false; return proc_get_status($this->proc_id); } public function kill_child(){ if($this->proc_id == null || $this->terminated) return false; if($this->is_running()){ posix_kill($this->get_pid(), SIGILL); return true; } return false; } public function write($txt){ return fwrite($this->pipes[0], $txt); } public function flush(){ return fflush($this->pipes[0]); } # read a large block avoiding read block public function read($size=32000){ return fread($this->pipes[1], $size); } public function run($cmd){ if($this->has_run) throw new Exception("Pipe::run() : can't run this pipe instance twice or more."); $this->has_run = true; $this->proc_id = proc_open($cmd, $this->descriptor_spec, $this->pipes); # could set non blocking IO here # stream_set_blocking($this->pipes[1], false); # stdout # stream_set_blocking($this->pipes[2], false); # stderr if ($this->proc_id !== false) { $this->watch_ids[] = Gtk::io_add_watch($this->pipes[0], Gtk::IO_IN, array($this, 'stdin')); $this->watch_ids[] = Gtk::io_add_watch($this->pipes[1], Gtk::IO_OUT, array($this, 'stdout')); $this->watch_ids[] = Gtk::io_add_watch($this->pipes[2], Gtk::IO_OUT, array($this, 'stderr')); # signals can be : Gtk::IO_IN, IO_OUT, IO_PRI, IO_ERR, IO_HUP, IO_NVAL $this->watch_ids[] = Gtk::io_add_watch($this->pipes[1], Gtk::IO_HUP, array($this, 'io_hup')); $this->watch_ids[] = Gtk::io_add_watch($this->pipes[0], Gtk::IO_ERR, array($this, 'io_err')); $this->watch_ids[] = Gtk::io_add_watch($this->pipes[1], Gtk::IO_ERR, array($this, 'io_err')); $this->watch_ids[] = Gtk::io_add_watch($this->pipes[2], Gtk::IO_ERR, array($this, 'io_err')); } else throw new Exception("PipeIO::run() : proc_open error"); echo "Pipe::run : cmd is running ($cmd)\n"; } # process terminated ; pipe is broken public function io_hup($pipe){ echo "Pipe::io_hup()\n"; $this->terminated = true; $this->terminate(); return false; } # process terminated ; pipe is broken public function io_err($pipe){ echo "Pipe::io_err()\n"; $this->terminated = true; $this->terminate(); return false; } public function stdin($pipe){ echo "Pipe::stdin()\n"; $this->write("test me\n"); return true; } public function stdout($pipe){ if($this->pipes == null) return; # here avoid blocking read : don't do fread until "\n" for example or set stream_set_blocking() $data = trim($this->read()); echo "Pipe::stdout($data)\n"; if(!$this->terminated) return true; else return false; } public function stderr($pipe){ if($this->pipes == null) return; # here avoid blocking read : don't do fread until "\n" for example. $data = trim($this->read()); echo "Pipe::stderr($data)\n"; return true; }}$pipe = null;function run_a_pipe(){ global $pipe; if($pipe != null) $pipe->terminate(); # $pipe = new Pipe2('/bin/traceroute', array('-n', 'intrux')); # php -r '$i=0;while(1) { echo "$i\n" ; sleep(1); $i++;}' $pipe = new Pipe(); # $pipe = new Pipe2('php', array('-i')); $pipe->run('php -q ' . __FILE__ . ' slave'); $pipe->write("test me\n");}function slave_function(){ $count = rand(1, 6); for($i=0 ; $i< $count ; $i++){ fprintf(STDOUT, "stdout $i\n"); fprintf(STDERR, "stderr $i\n"); sleep(1); } exit(0);}$args = $_SERVER['argv'];if(count($args) == 2){ if($args[1] == 'slave'){ slave_function(); exit(0); }}run_a_pipe();Gtk::timeout_add(3000, 'on_timeout');Gtk::timeout_add(500, 'on_timeout2');Gtk::main();function on_timeout(){ echo "I am alive\n"; global $pipe; $pipe->terminate(); if($pipe->is_running()) echo "pipe is running\n"; else echo "pipe is not running\n"; # $pipe->run('ls'); # this throw an exception ... can't run more that one time. run_a_pipe(); return true;}function on_timeout2(){ global $pipe; if($pipe->is_running()) echo "pipe is running\n"; else echo "pipe is not running\n"; return true;}?>