push  v0.0.1
PHP shell
LineReader.php
1 <?php
2 
3 namespace fpoirotte\push;
4 
5 class LineReader
6 {
7  protected $manager;
8  protected $stop;
9 
10  public function __construct(\fpoirotte\push\Manager $manager)
11  {
12  $this->manager = $manager;
13  $this->stop = false;
14  $this->prompt = '>>> ';
15  }
16 
17  protected function filter($data)
18  {
19  if (substr($data, -1) === "\n") {
20  $data = (string) substr($data, 0, -1);
21  }
22 
23  $output = array();
24  $lines = explode("\n", $data);
25  $pattern = $this->manager->getEvalLocation();
26  $pattern = " in $pattern : eval()'d code on line ";
27  $pLen = strlen($pattern);
28  foreach ($lines as $line) {
29  $pos = strpos($line, $pattern);
30  if ($pos !== false) {
31  $matchEnd = strspn(substr($line, $pos + $pLen), '1234567890');
32  if ($pos + $pLen + $matchEnd === strlen($line)) {
33  $output[] = (string) substr($line, 0, $pos);
34  } else {
35  $output[] = $line;
36  }
37  } else {
38  $output[] = $line;
39  }
40  }
41  return implode("\n", $output) . "\n";
42  }
43 
44  protected function signalHandler($signo)
45  {
46  switch ($signo) {
47  case SIGINT:
48  // This is silly, but required to provide feedback.
49  echo "^C\n";
50  if (!$this->manager->isWorking()) {
51  return;
52  }
53  break;
54  }
55 
56  $this->manager->sendSignal($signo);
57  }
58 
59  protected function lineHandler($line)
60  {
61  if ($line === "") {
62  return;
63  }
64 
65  readline_callback_handler_remove();
66 
67  if ($line === null || $line == "exit") {
68  if ($line === null) {
69  echo "^D\n";
70  }
71 
72  // Explicit call to "exit" or Ctrl+D
73  $this->stop = true;
74  return;
75  }
76 
77  readline_add_history($line);
78  $this->manager->sendCommands($line);
79  $this->prompt = '';
80  }
81 
82  public function run()
83  {
84  declare(ticks=1);
85 
86  $stdout = $stderr = $control = null;
87  $this->manager->prepare($stdout, $stderr, $control);
88 
89  $signals = array(
90  'SIGHUP',
91  'SIGINT',
92  'SIGQUIT',
93  'SIGILL',
94  'SIGTRAP',
95  'SIGABRT',
96  'SIGIOT',
97  'SIGBUS',
98  'SIGFPE',
99  //'SIGKILL', // SIGKILL cannot be intercepted.
100  'SIGUSR1',
101  'SIGSEGV',
102  'SIGUSR2',
103  'SIGPIPE',
104  'SIGALRM',
105  'SIGTERM',
106  'SIGSTKFLT',
107  'SIGCLD',
108  'SIGCHLD',
109  //'SIGCONT', // Signals that suspend/resume execution
110  //'SIGSTOP', // are left intact. We want the manager
111  //'SIGTSTP', // to suspend its execution, not its children.
112  'SIGTTIN',
113  'SIGTTOU',
114  'SIGURG',
115  'SIGXCPU',
116  'SIGXFSZ',
117  'SIGVTALRM',
118  'SIGPROF',
119  'SIGWINCH',
120  'SIGPOLL',
121  'SIGIO',
122  'SIGPWR',
123  'SIGSYS',
124  'SIGBABY',
125  );
126 
127  foreach ($signals as $signal) {
128  if (!defined($signal)) {
129  continue;
130  }
131 
132  if (!pcntl_signal(constant($signal), array($this, 'signalHandler'), true)) {
133  throw new \RuntimeException('Unable to set up signal handler for ' . $signal);
134  }
135  }
136 
137  while (true) {
138  if ($this->stop) {
139  readline_write_history();
140  echo "Exiting...\n";
141  break;
142  }
143 
144  $r = array(STDIN, $stdout, $stderr, $control);
145  $w = $e = array();
146 
147  $nb = @stream_select($r, $w, $e, null);
148  pcntl_signal_dispatch();
149 
150  if ($nb === false) {
151  continue;
152  }
153 
154  if (in_array(STDIN, $r)) {
155  if ($this->manager->isWorking()) {
156  $tmp = fgetc(STDIN);
157  } else {
158  readline_callback_read_char();
159  }
160  }
161 
162  if (in_array($stdout, $r)) {
163  $read = fread($stdout, 8192);
164  if ($read !== false) {
165  fwrite(STDOUT, $this->filter($read));
166  }
167  }
168 
169  if (in_array($stderr, $r)) {
170  $read = fread($stderr, 8192);
171  if ($read !== false) {
172  fwrite(STDERR, $this->filter($read));
173  }
174  }
175 
176  if (in_array($control, $r)) {
177  try {
178  $this->manager->runOnce();
179  } catch (\RuntimeException $e) {
180  throw $e;
181  break;
182  }
183 
184  if (!$this->manager->isWorking()) {
185  $this->prompt = '>>> ';
186  }
187 
188  if (!readline_callback_handler_install($this->prompt, array($this, 'lineHandler'))) {
189  throw new \RuntimeException();
190  }
191  }
192  }
193  }
194 }