* @copyright Copyright (C) 2021 Uwe Steinmann * @version Release: @package_version@ */ require_once("inc/inc.ClassConversionServiceBase.php"); /** * Implementation of conversion service exec class * * @category DMS * @package SeedDMS * @author Uwe Steinmann * @copyright Copyright (C) 2021 Uwe Steinmann * @version Release: @package_version@ */ class SeedDMS_ConversionServiceExec extends SeedDMS_ConversionServiceBase { /** * shell cmd */ public $cmd; /** * timeout */ public $timeout; /** * Run a shell command * * @param $cmd * @param int $timeout * @return array * @throws Exception */ static function execWithTimeout($cmd, $timeout=5) { /* {{{ */ $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w") ); $pipes = array(); $timeout += time(); // Putting an 'exec' before the command will not fork the command // and therefore not create any child process. proc_terminate will // then reliably terminate the cmd and not just shell. See notes of // https://www.php.net/manual/de/function.proc-terminate.php $process = proc_open('exec '.$cmd, $descriptorspec, $pipes); if (!is_resource($process)) { throw new Exception("proc_open failed on: " . $cmd); } stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); $output = $error = ''; $timeleft = $timeout - time(); $read = array($pipes[1], $pipes[2]); $write = NULL; $exeptions = NULL; do { $num_changed_streams = stream_select($read, $write, $exeptions, $timeleft, 200000); if ($num_changed_streams === false) { proc_terminate($process); throw new Exception("stream select failed on: " . $cmd); } elseif ($num_changed_streams > 0) { $output .= fread($pipes[1], 8192); $error .= fread($pipes[2], 8192); } $timeleft = $timeout - time(); } while (!feof($pipes[1]) && $timeleft > 0); fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); if ($timeleft <= 0) { proc_terminate($process); throw new Exception("command timeout on: " . $cmd); } else { $return_value = proc_close($process); return array('stdout'=>$output, 'stderr'=>$error, 'return'=>$return_value); } } /* }}} */ public function __construct($from, $to, $cmd) { $this->from = $from; $this->to = $to; $this->cmd = $cmd; $this->timeout = 5; } public function convert($infile, $target = null, $params = array()) { $start = microtime(true); if(!$target) $tmpfile = tempnam(sys_get_temp_dir(), 'convert'); else $tmpfile = $target; $cmd = str_replace(array('%w', '%f', '%o', '%m'), array(isset($params['width']) ? $params['width'] : '', $infile, $tmpfile, $this->from), $this->cmd); try { self::execWithTimeout($cmd, $this->timeout); } catch(Exception $e) { return false; } $end = microtime(true); if($this->logger) { $this->logger->log('Conversion from '.$this->from.' to '.$this->to.' with cmd "'.$this->cmd.'" took '.($end-$start).' sec.', PEAR_LOG_INFO); } if(!$target) { $content = file_get_contents($tmpfile); unlink($tmpfile); return $content; } else { return true; } } }