PHP Classes

File: Shell.php

Recommend this page to a friend!
  Classes of Igor Dyshlenko   Apc Switch   Shell.php   Download  
File: Shell.php
Role: Class source
Content type: text/plain
Description: Shell class
Class: Apc Switch
Manage an APC Rack PDU over ssh
Author: By
Last change:
Date: 6 years ago
Size: 8,080 bytes
 

Contents

Class file image Download
<?php
/***
 * Copyright 2016 Igor Dyshlenko
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Shell performs operations with the connected server (ssh2) - connect, execute
 * commands, obtain execution results, etc.
 * The possibility of working on other protocols (for example, telnet) is
 * provided for the implementation of the corresponding classes with the
 * ShellConnector interface.
 *
 * The concept is taken from the class Net_Telnet,
 * Copyright 2012 Jesse Norell <jesse@kci.net>
 * Copyright 2012 Kentec Communications, Inc.
 *
 * @author Igor Dyshlenko
 * @category Console
 * @license https://opensource.org/licenses/MIT MIT
 */

class Shell {
    protected
       
$connector, //
       
$eol = PHP_EOL, // End of line code
       
$timeout = 5, // Timeout for io operations (sec.)
       
$useSleep = 0, // Use usleep (timeout in ms)
       
$prompt, // Terminal command prompt
       
$logger; // Logger - object Log class

   
protected
       
$readBuffer,
       
$writeBuffer;

   
/**
     * Constructor
     * @param ShellConnector $connector
     * @param string $prompt - Command interpreter prompt
     * @param Log $logger - PEAR Log object or null
     * @throws LogicException
     */
   
public function __construct(ShellConnector $connector, $prompt='$ ', $timeout=null, $logger=null) {
       
$this->logger = new LogWrapper($logger);

        if ((
$tOut = intval($timeout)) > 0) {
           
$this->timeout = $tOut;
           
$this->logger->debug(__METHOD__ . ': Timeout setted to ' . $tOut . ' sec.');
        }
        unset(
$tOut);

        if (!
$connector->isConnected()) {
           
$this->logger->err(__METHOD__ . ': Fail: connector state "Disconnected"!');
            throw new
LogicException('Fail: connector state "Disconnected"!');
        }
       
$this->connector = $connector;
       
$this->prompt = $prompt;
    }

    public function
__destruct(){
       
$this->logout();
        if (
$this->connector->isLoggedIn()) {
           
$this->connector->logout();
        }
        if (
$this->connector->isConnected()) {
           
$this->connector->disconnect();
        }
    }

   
/**
     * Login function
     * @param string $userName
     * @param string $pass
     * @return bool true if success.
     * @throws LogicException if authentication error.
     */
   
public function login($username, $pass) {
        return
$this->connector->login($username, $pass);
    }

   
/**
     * Logout function
     * @return bool true if success, false if fail.
     */
   
public function logout() {
        return
$this->connector->logout();
    }

   
/**
     * Execute the command.
     * @param string $command
     * @return string result.
     */
   
public function exec($command) {
       
$this->write($command . $this->eol);
       
$this->read($this->prompt);
        return
$this->getResult();
    }

   
/**
     * @todo Abort execution of command
     */
/* public function stop() {
       
    }
*/

    /**
     * Get the result (screen buffer).
     * @return string
     */
   
public function getResult() {
       
$result = $this->readBuffer;
       
$this->readBuffer = '';
        return
$result;
    }

   
/**
     * Get Error Message.
     * @return string error message if error, empty string ('') otherwise
     */
   
public function getError() {
        return
$this->connector->getError();
    }

   
/**
     * Get Error number.
     * @return mixed int error code if error, NULL otherwise
     */
   
public function getErrno() {
        return
$this->connector->getErrno();
    }

   
/**
     * Get "is connected" state
     * @return bool
     */
   
public function isConnested() {
        return
$this->connector->isConnected();
    }

   
/**
     * Get "is logged in" state
     * @return bool
     */
   
public function isLoggedIn() {
        return
$this->connector->isLoggedIn();
    }

   
/**
     * Can execute the operation?
     * @return boolean
     */
   
public function isOnLine() {
        return
$this->isConnested() && $this->isLoggedIn();
    }

   
/***
     * @todo Is the previous command completed normally?
     */
/* public function isOk() {
        return false;
    }
*/
    /**
     * Set / get end-of-line value
     * @param mixed $eol if NULL - do nothing, else - set new end-of-line value.
     * @return string current end-of-line value.
     */
   
public function eol($eol=null) {
        if (!
is_null($eol)){
           
$this->eol = strval($eol);
           
$this->logger->debug(__METHOD__ . ': End of line setted to "' .
               
str_replace(array("\n", "\r", "\t"), array('\n', '\r', '\t'), $eol) . '"');
        }
        return
$this->eol;
    }

   
/**
     * Set / get the value of the command prompt.
     * @param mixed $prompt if NULL - do nothing, else - set new command line
     * prompt value.
     * @return string current prompt command line value.
     */
   
public function prompt($prompt=null) {
        if (!
is_null($prompt)){
           
$this->prompt = strval($prompt);
           
$this->logger->debug(__METHOD__ . ': Prompt setted to "' .
               
str_replace(array("\n", "\r", "\t"), array('\n', '\r', '\t'), $prompt) . '"');
        }
        return
$this->prompt;
    }

   
/**
     * Skip all data before the command prompt.
     * @return mixed FALSE if error, (int) count readed data bytes otherwise
     */
   
public function goAhead() {
        return
$this->read($this->prompt);
    }

   
/**
     * Skip data from output stream
     * @param string $searchFor - skip data to the desired value inclusive.
     * @param int $numChars - skip a maximum of $numChars characters.
     * @return mixed FALSE if error, (int) count readed data bytes otherwise
     */
   
public function read($searchFor=null, $numChars=null) {
       
$buffer = '';
       
$nums = intval($numChars);
       
$search = strval($searchFor);
       
$found = false;
       
$started = time();
       
$timedOut = false;

        while ( !
$found && !$timedOut &&
                ((
$nums == 0) || ($nums && (strlen($buffer) < $nums))) &&
                ((
$char= $this->readStream()) !== false) ) {

           
$buffer .= $char;

            if ((
$searchFor !== null) && (substr($buffer, 0 - strlen($search)) === $search)) {
               
$this->lastmatch = $search;
               
$found = true;
                continue;
            }

           
$timedOut = ((time() - $started) > $this->timeout);
           
$found = ($nums && (strlen($buffer) >= $nums));
        }

       
$this->readBuffer .= $buffer;

        return (
$found) ? strlen($buffer) : false;
    }

   
/**
     * Get symbol from connector input stream
     * @return mixed string or FALSE if eof().
     */
   
protected function readStream() {
        return
$this->connector->read();
    }

   
/**
     * Write data to stream
     * @param string $data - data for write to input stream
     * @return int number of written chars
     */
   
public function write($data) {
       
$this->writeBuffer .= $data;
        return
$this->writeStream();
    }

   
/**
     * Put data to connector output stream
     * @param type $data
     * @return int
     * @throws RuntimeException
     */
   
protected function writeStream($data=null) {
       
$written = 0;
       
$n = 0;

        if (!
$this->isOnLine()){
            return
0;
        }

        if ((
$data !== null) and (strlen($data) > 0)){
           
$buf = $data;
           
$total = strlen($data);
        } else {
           
$buf = $this->writeBuffer;
           
$total = strlen($this->writeBuffer);
           
$this->writeBuffer = null;
        }

        while (
$written < $total) {
           
$buf = substr($buf, $n);
            if ((
$n = $this->connector->write($buf)) === false) {
                if (!
$this->isConnested()){
                   
$this->logger->debug(__METHOD__ . ': Disconnected.');
                    break;
                } else {
                   
$this->logger->err(__METHOD__ . ': Error writing to socket.');
                    throw new
RuntimeException('Error writing to socket.');
                }
            }
           
$written += $n;
        }

        return
$written;
    }
}