├── index.php ├── README.md ├── tests └── TestExecuter.php └── source └── ShellExecuter.php /index.php: -------------------------------------------------------------------------------- 1 | execute(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-shell-executer 2 | ================== 3 | 4 | A simple shell executer script that kills processes that run for too long -------------------------------------------------------------------------------- /tests/TestExecuter.php: -------------------------------------------------------------------------------- 1 | execute(); 18 | 19 | } 20 | 21 | public function testOutput() { 22 | 23 | $se = new ShellExecuter("echo hi"); 24 | $result = $se->execute(); 25 | $this->assertEquals("hi", $result, "Testing shell output"); 26 | 27 | } 28 | 29 | 30 | /** 31 | * Prepares the environment before running a test. 32 | */ 33 | protected function setUp() { 34 | 35 | parent::setUp (); 36 | 37 | } 38 | 39 | /** 40 | * Cleans up the environment after running a test. 41 | */ 42 | protected function tearDown() { 43 | // TODO Auto-generated Events::tearDown() 44 | 45 | parent::tearDown (); 46 | } 47 | 48 | /** 49 | * Constructs the test case. 50 | */ 51 | public function __construct() { 52 | // TODO Auto-generated constructor 53 | } 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /source/ShellExecuter.php: -------------------------------------------------------------------------------- 1 | command = $command; 42 | $this->timeout = $timeout; 43 | } 44 | /** 45 | * Executes the command (blocking) 46 | * @throws Exception when timeout reached 47 | */ 48 | public function execute() { 49 | 50 | $unique_id = uniqid(); 51 | 52 | $sleeptime = 100000; 53 | $looptime = $this->timeout * 1000000 / $sleeptime; 54 | 55 | $this->files["pid"] = sys_get_temp_dir() . '/shellexecuter_pid' . $unique_id . '.txt'; 56 | $this->files["success"] = sys_get_temp_dir() . '/shellexecuter_success' . $unique_id . '.txt'; 57 | 58 | $descriptorspec = array( 59 | 1 => array('pipe', 'w'), 60 | 2 => array('pipe', 'w'), 61 | ); 62 | $this->resource = proc_open("(" . $this->command . " && touch ".$this->files['success'].") & echo $! > ".$this->files['pid']." &" , $descriptorspec, $this->pipes, null, $_ENV); 63 | /** 64 | * Lets get the actual pid of the process we're backgrounding 65 | * Since PHP doesn't run our process right away, lets sleep until we actually have a pid 66 | **/ 67 | while(!$this->pid) { 68 | if(file_exists($this->files["pid"])) { 69 | $pid = (int)file_get_contents($this->files["pid"]); 70 | if($pid > 0) 71 | $this->pid = $pid; 72 | } 73 | else 74 | usleep($sleeptime); 75 | } 76 | 77 | for($i = 0; $i <= $looptime; $i++) { 78 | 79 | if($this->isRunning()) { 80 | if($i == $looptime) { 81 | $this->kill(); 82 | } 83 | usleep($sleeptime); 84 | } 85 | else 86 | break; 87 | } 88 | /** 89 | * Lets gather some info from the pipes 90 | **/ 91 | $stdout = stream_get_contents($this->pipes[1]); 92 | $stderr = stream_get_contents($this->pipes[2]); 93 | 94 | /** 95 | * If we didn't touch the success file, the processes executed with a failure 96 | **/ 97 | if(!file_exists($this->files["success"])) { 98 | throw new Exception("Command executed with failure: " . $stderr); 99 | } 100 | 101 | return $stdout; 102 | } 103 | 104 | /** 105 | * Cleans up and throws an exception 106 | * @param string $reason 107 | * @throws Exception 108 | */ 109 | private function fail($reason) { 110 | $this->cleanup(); 111 | throw new Exception($reason); 112 | } 113 | 114 | /** 115 | * Determines if the pid is running 116 | */ 117 | private function isRunning(){ 118 | if($this->pid == null) 119 | $this->fail("Pid not defined"); 120 | 121 | return file_exists( "/proc/$this->pid" ); 122 | } 123 | /** 124 | * Kills this process 125 | */ 126 | private function kill() { 127 | proc_terminate($this->resource); 128 | shell_exec("kill -9 " . $this->pid); 129 | $this->fail("Exec timeout reached, process (".$this->pid.") killed. Command: " .$this->command); 130 | } 131 | /** 132 | * Lets remove all our created files 133 | **/ 134 | private function cleanup() { 135 | foreach($this->files as $file) { 136 | @unlink($file); 137 | } 138 | } 139 | 140 | 141 | 142 | } --------------------------------------------------------------------------------