/MTS/Common/Devices/Shells/Base.php

https://github.com/merlinthemagic/MTS · PHP · 254 lines · 199 code · 21 blank · 34 comment · 36 complexity · a4ea23c0de775fa7c5639919877a4ff8 MD5 · raw file

  1. <?php
  2. //© 2016 Martin Madsen
  3. namespace MTS\Common\Devices\Shells;
  4. class Base
  5. {
  6. protected $_childShell=null;
  7. protected $_parentShell=null;
  8. protected $_initialized=null;
  9. protected $_shellPrompt=null;
  10. protected $_shellUUID=null;
  11. protected $_procPID=null;
  12. protected $_debug=false;
  13. protected $_debugData=array();
  14. protected $_defaultExecutionTime=10000;
  15. public function __construct()
  16. {
  17. //on uncaught exception __destruct is not called, this leaves the shell as a zombie running on the system we cant have that.
  18. register_shutdown_function(array($this, '__destruct'));
  19. }
  20. public function __destruct()
  21. {
  22. if ($this->getParentShell() === null) {
  23. //destruction should only be triggered in the initial shell.
  24. //that way we get an orderly shutdown of nested shells
  25. $this->terminate();
  26. }
  27. }
  28. public function setDebug($bool)
  29. {
  30. $this->_debug = $bool;
  31. //make sure the entire chell chain has the same debug.
  32. if ($this->getChildShell() !== null && $this->getChildShell()->getDebug() !== $bool) {
  33. $this->getChildShell()->setDebug($bool);
  34. }
  35. if ($this->getParentShell() !== null && $this->getParentShell()->getDebug() !== $bool) {
  36. $this->getParentShell()->setDebug($bool);
  37. }
  38. }
  39. public function getDebug()
  40. {
  41. return $this->_debug;
  42. }
  43. public function addDebugData($debugData)
  44. {
  45. $parentShell = $this->getParentShell();
  46. if ($parentShell === null) {
  47. $this->_debugData[] = $debugData;
  48. } else {
  49. $parentShell->addDebugData($debugData);
  50. }
  51. }
  52. public function getDebugData()
  53. {
  54. $parentShell = $this->getParentShell();
  55. if ($parentShell === null) {
  56. return $this->_debugData;
  57. } else {
  58. return $parentShell->getDebugData();
  59. }
  60. }
  61. public function exeCmd($strCmd, $delimitor=null, $timeout=null)
  62. {
  63. //$strCmd: string command to execute
  64. //$delimitor: regex when matched ends the command and returns data.
  65. //defaults to a custom shell prompt determined by the shell class
  66. //You should only override the default if the command does not end in a regular prompt, or you want only a partial return from the command.
  67. //If you do not want to use a delimitor at all set to false, this will force a read until $timeout is exceeded
  68. //$timeout: the absolute longest the command is allowed to run
  69. //set to 0 if you do not wish the receive a return from the command
  70. //You should only override if a command takes a very long time or for a command that continues to return data
  71. //i.e ping. Without a timeout on a ping the command would never finish and return
  72. //default is determined by the shell class
  73. //if you know a command will continue to return output and hold up the shell
  74. //you should issue another command to stop it or use the killLastProcess() function
  75. try {
  76. if ($this->getChildShell() !== null) {
  77. //must execute on child as it rides on top of this shell
  78. return $this->getChildShell()->exeCmd($strCmd, $delimitor, $timeout);
  79. } else {
  80. return $this->shellStrExecute($strCmd, $delimitor, $timeout);
  81. }
  82. } catch (\Exception $e) {
  83. switch($e->getCode()){
  84. default;
  85. throw $e;
  86. }
  87. }
  88. }
  89. public function killLastProcess()
  90. {
  91. if ($this->getChildShell() !== null) {
  92. //must execute on child as it rides on top of this shell
  93. $this->getChildShell()->killLastProcess();
  94. } else {
  95. $this->shellKillLastProcess();
  96. }
  97. }
  98. public function terminate()
  99. {
  100. if ($this->getInitialized() !== "terminating" && $this->getInitialized() !== false) {
  101. if ($this->getParentShell() === null) {
  102. //PHP does not allow us to handle exceptions during shutdown. If termination fails on the base shell
  103. //it will hang around forever taking up resources on the server. The only way to avoid this is to
  104. //have a kill wait around to see if terminate completes its job. if not force the process termination
  105. if ($this->getProcessPID() !== null) {
  106. \MTS\Factories::getActions()->getLocalProcesses()->sigTermPid($this->getProcessPID(), 15);
  107. }
  108. if ($this->getDebug() === true) {
  109. $exeTimeout = \MTS\Factories::getActions()->getLocalPhpEnvironment()->getRemainingExecutionTime();
  110. if ($exeTimeout == 0) {
  111. //help debug when commands fail when "max_execution_time" was not long enough.
  112. $this->addDebugData("Shell terminated because 'max_execution_time' value: ".ini_get('max_execution_time').", was exceeded.");
  113. }
  114. $this->addDebugData("Starting Termination of shells. If you dont see a 'Completed' message something went wrong");
  115. }
  116. }
  117. $childError = null;
  118. $ownError = null;
  119. try {
  120. //child shells must be shutdown before this
  121. if ($this->getChildShell() !== null) {
  122. $this->getChildShell()->terminate();
  123. }
  124. } catch (\Exception $e) {
  125. switch($e->getCode()){
  126. default;
  127. $childError = $e;
  128. }
  129. }
  130. try {
  131. $this->shellTerminate();
  132. } catch (\Exception $e) {
  133. switch($e->getCode()){
  134. default;
  135. $ownError = $e;
  136. }
  137. }
  138. //tell the parent we are shutdown
  139. $parentShell = $this->getParentShell();
  140. if ($this->getParentShell() !== null) {
  141. $this->getParentShell()->setChildShell(null);
  142. //clean up
  143. $this->getParentShell()->exeCmd("");
  144. }
  145. //finish by throwing the errors. It is crucial that the parent
  146. //is informed it no longer has a child, even though it may have failed
  147. //some part of the termination process. otherwise commands are still passed upstream
  148. //and the initial shell will never get a chance to terminate
  149. if ($childError !== null) {
  150. throw $childError;
  151. } elseif ($ownError !== null) {
  152. throw $ownError;
  153. }
  154. if ($this->getDebug() === true && $this->getParentShell() === null) {
  155. $this->addDebugData("Completed Termination of shells");
  156. }
  157. if ($parentShell !== null) {
  158. //the user may still have some use of the parent
  159. return $parentShell;
  160. } else {
  161. return null;
  162. }
  163. }
  164. }
  165. public function setChildShell($shellObj)
  166. {
  167. if ($shellObj === null) {
  168. //this is a child destructing it self and letting its parent know it is done
  169. $this->_childShell = null;
  170. } else {
  171. if ($this->getChildShell() !== null) {
  172. $this->getChildShell()->setChildShell($shellObj);
  173. } else {
  174. $this->_childShell = $shellObj;
  175. $this->_childShell->setDebug($this->getDebug());
  176. $this->_childShell->setParentShell($this);
  177. }
  178. }
  179. }
  180. public function getChildShell()
  181. {
  182. return $this->_childShell;
  183. }
  184. public function getActiveShell()
  185. {
  186. if ($this->getChildShell() === null) {
  187. return $this;
  188. } else {
  189. return $this->getChildShell()->getActiveShell();
  190. }
  191. }
  192. public function setParentShell($shellObj)
  193. {
  194. //this should only be set by the parent shell itself
  195. $this->_parentShell = $shellObj;
  196. }
  197. public function getParentShell()
  198. {
  199. return $this->_parentShell;
  200. }
  201. public function getInitialized()
  202. {
  203. return $this->_initialized;
  204. }
  205. public function getShellPrompt()
  206. {
  207. //needed on exit from child shell
  208. return $this->_shellPrompt;
  209. }
  210. public function getShellUUID()
  211. {
  212. if ($this->_shellUUID === null) {
  213. $this->_shellUUID = uniqid("", true);
  214. }
  215. return $this->_shellUUID;
  216. }
  217. public function setDefaultExecutionTime($mSecs)
  218. {
  219. $this->_defaultExecutionTime = intval($mSecs);
  220. }
  221. public function getDefaultExecutionTime()
  222. {
  223. return $this->_defaultExecutionTime;
  224. }
  225. public function getProcessPID()
  226. {
  227. if ($this->getParentShell() !== null) {
  228. return $this->getParentShell()->getProcessPID();
  229. } else {
  230. return $this->_procPID;
  231. }
  232. }
  233. }