Safely Using External System Commands In PHP

Posted by Tres Sun, 20 Apr 2008 07:26:00 GMT

Sometimes you just need to hand control over to an external command via the exec built-in function in PHP. But whenever you transfer control of a process to an external command, there’s a chance of your app hanging.

To safely run system commands you should wrap your exec call in the process control functions. The following is an example class that uses process control functions to ensure that a call to an external system command gets returned back.

/**
 * 
 *  
 *
 * @copyright 2008
 * @package ProjectX
 * @subpackage SystemResources
 * @author Tres Wong-Godfrey
 * 
 */

/**
 * Sys handles system execution, ensuring that the external call will be returned to us.
 */
//set up signal callback mechanism
declare(ticks=1);
class Sys {

    /**
     * Handles the signal alarm timer for the process
     * 
     * @throws TimeoutException when called
     * @param integer the signal that will be 
     */
    public static function alarm( $signal )
    {
        throw new TimeoutException(__CLASS__.'::'.__FUNCTION__ . ":: a Timeout occured while attempting to run the external system call.");
    }

    /**
     * This sets the timeout for the external command so that we can ensure control gets returned
     * to us.
     *
     * @param int The timeout for the external command in seconds
     * @access public
     */
    public static function setAlarm($timeout)
    {
        pcntl_signal(SIGALRM, array($this, 'alarm'));
        pcntl_alarm($timeout);  
    }

    /**
     * Cancels the previously set alarm
     */
    public static function unsetAlarm() { pcntl_alarm(0); }


    /**
     * This is a modified exec().  It is responsible for setting the
     * timeout alarm and handling any invalid returns.
     *
     * <code>
     * $command = "ls /";
     * try {
     * Sys::execute( $command, 90 );
     * } catch( TimeoutException $e ) {
     * //handle timeout
     * } catch( ExternalApplicationException $e ) {
     * //handle problems with external app running
     * }
     * </code>
     *
     * @param string $cmd
     * @param int Optional. Time in seconds until the process alarm timer expires. Defaults to 10.
     * @throws ExternalApplicationException if the external call does not return 0.
     * @return string The output from the external command
     */
    public static function execute( $cmd, $timeout=10) 
    {
        self::setAlarm($timeout);
        $last_line=exec("$cmd 2>&1", $output, $return_value);
        self::unsetAlarm();
        if ($return_value != 0) 
            throw new ExternalApplicationException(__CLASS__.'::'.__FUNCTION__ . ":: command $cmd failed. :: output = $output" );
        else
            return implode("\n", $output);
    }
}

Posted in ,  | Tags , , , , ,