PageRenderTime 417ms CodeModel.GetById 282ms app.highlight 51ms RepoModel.GetById 80ms app.codeStats 0ms

/test/netbatch.php

https://github.com/blekkzor/pinetd2
PHP | 447 lines | 320 code | 85 blank | 42 comment | 56 complexity | 82c5c284900d57e1a7bb9140a7511f76 MD5 | raw file
  1<?php
  2
  3class NetBatch_Process {
  4	private $pid;
  5	private $parent;
  6	private $blocking;
  7	private $resume;
  8
  9	public function __construct($parent, $pid, $resume, $persist) {
 10		$this->parent = $parent;
 11		$this->pid = $pid;
 12		$this->blocking = true;
 13		$this->resume = $resume;
 14		$this->persist = $persist;
 15	}
 16
 17	public function __destruct() {
 18		if ($this->running())
 19			$this->kill(9); // SIGKILL
 20		$this->parent->freePid($this->pid);
 21	}
 22
 23	public function setBlocking($blocking) {
 24		$this->blocking = (bool)$blocking;
 25	}
 26
 27	public function dump() {
 28		return $this->parent->dump($this->pid);
 29	}
 30
 31	public function eof($fd) {
 32		return $this->parent->eof($fd, $this->pid);
 33	}
 34
 35	public function read($fd, $size) {
 36		return $this->parent->read($fd, $size, $this->pid, $this->blocking);
 37	}
 38
 39	public function gets($fd, $size = NULL) {
 40		return $this->parent->gets($fd, $size, $this->pid, $this->blocking);
 41	}
 42
 43	public function wait() {
 44		return $this->parent->wait($this->pid);
 45	}
 46
 47	public function write($fd, $buf) {
 48		return $this->parent->write($fd, $buf, $this->pid);
 49	}
 50
 51	public function kill($signal = 15) {
 52		return $this->parent->kill($signal, $this->pid);
 53	}
 54
 55	public function close($fd) {
 56		return $this->parent->close($fd, $this->pid);
 57	}
 58
 59	public function getPid() {
 60		return $this->pid;
 61	}
 62
 63	public function poll() {
 64		return $this->parent->poll($this->pid);
 65	}
 66
 67	public function isResumed() {
 68		return (bool)$this->resume;
 69	}
 70
 71	public function running() {
 72		return $this->parent->isRunning($this->pid);
 73	}
 74
 75	public function exitCode() {
 76		return $this->parent->getExitCode($this->pid);
 77	}
 78}
 79
 80class NetBatch {
 81	private $fp;
 82	private $name;
 83	private $salt;
 84	private $type;
 85	private $pipes = array();
 86	private $pipes_buf = array();
 87	private $running = array();
 88	private $returnCode = array();
 89	private $persist = array();
 90	private $last_pid;
 91
 92	const PKT_STANDARD = 0;
 93	const PKT_LOGIN = 1;
 94	const PKT_EXIT = 2;
 95	const PKT_RUN = 3;
 96	const PKT_RETURNCODE = 4;
 97	const PKT_EOF = 5;
 98	const PKT_CLOSE = 6;
 99	const PKT_DATA = 7;
100	const PKT_NOPIPES = 8;
101	const PKT_KILL = 9;
102	const PKT_POLL = 10;
103
104	public function __construct($host, $port = 65432) {
105		$this->fp = fsockopen($host, $port);
106		if (!$this->fp) throw new Exception('Could not connect');
107
108		$this->name = $this->readPacket();
109		$this->salt = $this->readPacket();
110	}
111
112	public function __destruct() {
113		$this->sendPacket('', self::PKT_EXIT);
114		fclose($this->fp);
115	}
116
117	public function freePid($pid) {
118		unset($this->pipes[$pid]);
119		unset($this->pipes_buf[$pid]);
120		unset($this->returnCode[$pid]);
121	}
122
123	public function ident($login, $key) {
124		$this->sendPacket(sha1($key.$this->salt, true).$login, self::PKT_LOGIN);
125		$result = $this->readPacket();
126		return (bool)$result;
127	}
128
129	public function run($cmd, $pipes = NULL, $env = NULL, $persist = false) {
130		if (is_null($pipes)) 
131			$pipes = array(0=>'r', 1=>'w', 2=>'w');
132
133		$packet = array(
134			'cmd' => $cmd,
135			'pipes' => $pipes,
136		);
137		if ($persist !== false)
138			$packet['persist'] = $persist;
139
140		if (!is_null($env)) $packet['env'] = $env;
141
142		$this->sendPacket(serialize($packet), self::PKT_RUN);
143
144		$pid = $this->readPacket();
145		if ($pid === '0') return false;
146
147		list(,$pid, $resume) = unpack('N2', $pid);
148		$this->running[$pid] = true;
149		$this->last_pid = $pid;
150		$this->pipes[$pid] = $pipes;
151		$this->pipes_buf[$pid] = array();
152		if ($persist !== false) $this->persist[$pid] = true;
153
154		return new NetBatch_Process($this, $pid, $resume, $persist);
155	}
156
157	public function dump($pid = NULL) {
158		if (is_null($pid)) $pid = $this->last_pid;
159		var_dump($this->pipes_buf[$pid]);
160	}
161
162	public function eof($fd, $pid = NULL) {
163		if (is_null($pid)) $pid = $this->last_pid;
164		// still has a buffer => not EOF
165		if (isset($this->pipes_buf[$pid][$fd])) return false;
166
167		// if the stream exists it's not EOF yet
168		return !isset($this->pipes[$pid][$fd]);
169	}
170
171	public function read($fd, $size, $pid = NULL, $blocking) {
172		if (is_null($pid)) 
173			$pid = $this->last_pid;
174
175		while(1) {
176			if (isset($this->pipes_buf[$pid][$fd])) {
177				if (strlen($this->pipes_buf[$pid][$fd]) >= $size) {
178					$ret = substr($this->pipes_buf[$pid][$fd], 0, $size);
179					if ($size == strlen($this->pipes_buf[$pid][$fd])) {
180						unset($this->pipes_buf[$pid][$fd]);
181					} else {
182						$this->pipes_buf[$pid][$fd] = substr($this->pipes_buf[$pid][$fd], $size);
183					}
184
185					return $ret;
186				}
187
188				if (!isset($this->pipes[$pid][$fd])) {
189					// reached EOF, flush buffer first
190					$res = $this->pipes_buf[$pid][$fd];
191					unset($this->pipes_buf[$pid][$fd]);
192					return $res;
193				}
194			}
195
196			if (!isset($this->pipes[$pid][$fd])) return false;
197
198			if (!$this->getEvent($pid, $blocking)) break;
199		}
200		return false;
201	}
202
203	public function gets($fd, $size = NULL, $pid = NULL, $blocking) {
204		if (is_null($pid))
205			$pid = $this->last_pid;
206
207		while(1) {
208			if (isset($this->pipes_buf[$pid][$fd])) {
209				$pos = strpos($this->pipes_buf[$pid][$fd], "\n");
210
211				if ($pos !== false) {
212					$pos++;
213					$ret = substr($this->pipes_buf[$pid][$fd], 0, $pos);
214					if ($pos == strlen($this->pipes_buf[$pid][$fd])) {
215						unset($this->pipes_buf[$pid][$fd]);
216					} else {
217						$this->pipes_buf[$pid][$fd] = substr($this->pipes_buf[$pid][$fd], $pos);
218					}
219					return $ret;
220				}
221
222				if ((!is_null($size)) && (strlen($this->pipes_buf[$pid][$fd]) >= $size)) {
223					$ret = substr($this->pipes_buf[$pid][$fd], 0, $size);
224					if ($size == strlen($this->pipes_buf[$pid][$fd])) {
225						unset($this->pipes_buf[$pid][$fd]);
226					} else {
227						$this->pipes_buf[$pid][$fd] = substr($this->pipes_buf[$pid][$fd], $size);
228					}
229
230					return $ret;
231				}
232
233				if (!isset($this->pipes[$pid][$fd])) {
234					// reached EOF, flush buffer first
235					$res = $this->pipes_buf[$pid][$fd];
236					unset($this->pipes_buf[$pid][$fd]);
237					return $res;
238				}
239			}
240
241			if (!isset($this->pipes[$pid][$fd])) return false;
242
243			if (!$this->getEvent($pid, $blocking)) break;
244		}
245		return false;
246	}
247
248	public function wait($pid = NULL) {
249		if (is_null($pid)) $pid = $this->last_pid;
250
251		while($this->running[$pid])
252			$this->getEvent($pid);
253
254		return $this->returnCode[$pid];
255	}
256
257	public function getExitCode($pid) {
258		return $this->returnCode[$pid];
259	}
260
261	public function isRunning($pid) {
262		return isset($this->running[$pid]);
263	}
264
265	public function hasProcesses() {
266		return (bool)count($this->running);
267	}
268
269	public function write($fd, $data, $pid = NULL) {
270		if (is_null($pid)) $pid = $this->last_pid;
271		$this->sendPacket(pack('NN', $pid, $fd).$data, self::PKT_DATA);
272	}
273
274	public function kill($signal = 15, $pid = NULL) {
275		if (is_null($pid)) $pid = $this->last_pid;
276		$this->sendPacket(pack('NN', $pid, $signal), self::PKT_KILL);
277	}
278
279	public function poll($pid) {
280		$this->sendPacket(pack('N', $pid), self::PKT_POLL);
281	}
282
283	protected function getEvent($pid, $blocking = true) {
284		$pkt = $this->readPacket($pid, $blocking);
285
286		if ($pkt === false) return false;
287
288		switch($this->type) {
289			case self::PKT_EOF:
290				list(,$pid, $fd) = unpack('N2', $pkt);
291				unset($this->pipes[$pid][$fd]);
292				break;
293			case self::PKT_DATA:
294				list(,$pid,$fd) = unpack('N2', substr($pkt, 0, 8));
295				$pkt = (string)substr($pkt, 8);
296				$this->pipes_buf[$pid][$fd] .= $pkt;
297				break;
298			case self::PKT_NOPIPES:
299				// mmh?
300				break;
301			case self::PKT_RETURNCODE:
302				list(,$pid, $rc) = unpack('N2', $pkt);
303				unset($this->running[$pid]);
304				unset($this->pipes[$pid]);
305				$this->returnCode[$pid] = $rc;
306				break;
307			default:
308				var_dump($this->type);
309				break;
310		}
311		return true;
312	}
313
314	public function getActive(array $tmp) {
315		if (!$tmp)
316			throw new Exception('getActive() called without params');
317
318		$this->getEvent(0, false);
319
320		$list = array();
321		$keys = array();
322		foreach($tmp as $key => $process) {
323			$list[$process->getPid()] = $process;
324			$keys[$process->getPid()] = $key;
325		}
326
327		$final_list = array();
328		while(!$final_list) {
329			foreach($list as $pid => $process) {
330				if ($this->pipes_buf[$pid]) {
331					$final_list[$keys[$pid]] = $process;
332				}
333				
334				if (!$process->running())
335					$final_list[$keys[$pid]] = $process;
336			}
337
338			if (!$final_list)
339				$this->getEvent(0);
340		}
341
342		return $final_list;
343	}
344
345	public function close($fd, $pid = NULL) {
346		if (is_null($pid)) $pid = $this->last_pid;
347		$this->sendPacket(pack('NN', $pid, $fd), self::PKT_CLOSE);
348	}
349
350	protected function sendPacket($data, $type = self::PKT_STANDARD) {
351		return fwrite($this->fp, pack('nn', $type, strlen($data)).$data);
352	}
353
354	protected function readPacket($pid = 0, $blocking = true) {
355		if (feof($this->fp)) throw new Exception('Connection lost');
356
357		if ($this->persist[$pid]) {
358			$now = time();
359			if ($this->persist[$pid] != $now) {
360				$this->poll($pid);
361				$this->persist[$pid] = $now;
362			}
363			if ($blocking)
364				while (!stream_select($r = array($this->fp), $w = NULL, $e = NULL, 1)) {
365					$this->poll($pid);
366				}
367		}
368
369		if (!$blocking) {
370			if (!stream_select($r = array($this->fp), $w = NULL, $e = NULL, 0))
371				return false;
372		}
373
374		$len = fread($this->fp, 4);
375		if (strlen($len) != 4) throw new Exception('Connection lost');
376		list(,$type,$len) = unpack('n2', $len);
377
378		$this->type = $type;
379
380		if ($len == 0) return '';
381
382		return fread($this->fp, $len);
383	}
384}
385
386$netbatch = new NetBatch('127.0.0.1');
387$netbatch->ident('test','test');
388
389$process = $netbatch->run(array('php'), NULL, NULL, 'PhpEval');
390
391if (!$process->isResumed()) {
392	$process->write(0, '<?php while(1) { echo "BLAH\n"; sleep(1); }');
393	$process->close(0);
394}
395
396$c = 10;
397
398while(!$process->eof(1)) {
399	var_dump(rtrim($process->gets(1)));
400	if ($c-- < 0) {
401		$process->kill();
402		$c = 99;
403	}
404}
405
406while(!$process->eof(2))
407	var_dump(rtrim($process->gets(2)));
408
409/*
410$ping = array();
411
412$ping[] = $netbatch->run(array('ping', '127.0.0.1', '-c', '5'));
413$ping[] = $netbatch->run(array('ping', 'google.fr', '-c', '5'));
414
415foreach($ping as $process)
416	$process->setBlocking(false);
417
418while($netbatch->hasProcesses()) {
419	foreach($netbatch->getActive($ping) as $key => $process) {
420		$line = $process->gets(1);
421		while($line !== false) {
422			echo 'PID#'.$process->getPid().': '.rtrim($line)."\n";
423			$line = $process->gets(1);
424		}
425
426		if (!$process->running()) {
427			echo 'PID#'.$process->getPid().' has exited with exit code '.$process->exitCode()."\n";
428			unset($ping[$key]);
429		}
430	}
431}
432*/
433
434/*
435echo "Running: php\n";
436
437$process = $netbatch->run(array('php'), NULL, array('foo' => 'bougaga!'));
438$process->write(0, '<?php print_r($_ENV);');
439$process->close(0); // close stdin
440
441while(!$process->eof(1))
442	var_dump(rtrim($process->gets(1)));
443
444$process->wait();
445$process->dump();
446*/
447