PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/code/classes/pinetd/IPC.class.php

https://github.com/blekkzor/pinetd2
PHP | 649 lines | 467 code | 47 blank | 135 comment | 120 complexity | 24b69131027815a2d074a78eb23e6286 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /* Portable INET daemon v2 in PHP
  3. * Copyright (C) 2007 Mark Karpeles <mark@kinoko.fr>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. /**
  20. * \file IPC.class.php
  21. * \brief IPC (Inter-Process Communications) code
  22. */
  23. namespace pinetd;
  24. /**
  25. * \class IPC
  26. * \brief IPC (Inter-Process Communications) class, used by parents and children
  27. */
  28. class IPC {
  29. private $pipe; /*!< Pipe to my child/parent */
  30. private $ischld; /*!< Am I a child?? */
  31. private $buf = ''; /*!< Input buffer (reads are non blocking) */
  32. private $parent = NULL; /*!< My "parent" class, where functions are called */
  33. private $parentipc = NULL; /*!< My "parent IPC" class, for some specific stuff */
  34. private $fds = array(); /*!< List of fds to listens, and callbacks */
  35. private $ports = array(); /*!< Port routing table */
  36. private $bcast_listen = array(); /*!< list of broadcast listeners */
  37. private $parent_ipc = NULL;
  38. private $wfds = array();
  39. const CMD_PING = 'PING'; /*!< PING command, should receive RES_PING from peer */
  40. const CMD_NOOP = 'NOOP'; /*!< NOOP command, just to NOOP (NB: this is virtual, should never be sent) */
  41. const CMD_STOP = 'STOP'; /*!< STOP a child, only parent can send this */
  42. const CMD_LOG = 'LOG'; /*!< LOG data to the log subsystem */
  43. const CMD_ERROR = 'ERR'; /*!< Some kind of error occured, for example the system died */
  44. const CMD_CALL = 'CALL'; /*!< CALL a peer function. This will call a function on peer's class */
  45. const CMD_NEWPORT = 'NEWPORT'; /*!< Create a new port, and announce to parent (if any) */
  46. const CMD_CALLPORT = 'CALLPORT'; /*!< Call a function on a port */
  47. const CMD_BCAST = 'BCAST'; /*!< broadcast event */
  48. const RES_PING = 'PONG'; /*!< PONG, the reply to ping */
  49. const RES_CALL = 'RCALL'; /*!< Reply to a CMD_CALL, containing the result of the called function */
  50. const RES_CALL_EXCEPT = 'RCALLX'; /*!< A CMD_CALL resulted in an exception to be thrown */
  51. const RES_NEWPORT = 'RNEWPORT'; /*!< A CMD_NEWPORT reply */
  52. const RES_CALLPORT = 'RCALLPORT'; /*!< A CMD_CALLPORT reply */
  53. const RES_CALLPORT_EXCEPT = 'RCALLPORTX'; /*!< Transport for exception when calling ports */
  54. /**
  55. * \brief IPC constructor
  56. * \param $pipe Pipe to remote parent/child
  57. * \param $ischld bool Am I a child?
  58. * \param $parent If not a child, put $this here (reference to parent when parent)
  59. */
  60. function __construct($pipe, $ischld, &$parent, &$parentipc) {
  61. $this->pipe = $pipe;
  62. $this->ischld = $ischld;
  63. if (!$this->ischld) { // parent
  64. stream_set_blocking($this->pipe, false); // parent should not hang
  65. $this->parent = &$parent;
  66. $this->parentipc = &$parentipc;
  67. } else {
  68. $this->registerSocketWait($pipe, array($this, 'run'), $foobar = array(null));
  69. }
  70. }
  71. /**
  72. * \brief call a function on remote parent/child
  73. * \param $func Function name (will be prepended with some text)
  74. * \param $args Argument list for this function
  75. * \return mixed data, depending on called function
  76. */
  77. function __call($func, $args) {
  78. return $this->doCall($func, $args, $this->ischld);
  79. }
  80. function doCall($func, $args, $waitanswer = true) {
  81. array_unshift($args, $func);
  82. $this->sendcmd(self::CMD_CALL, $args);
  83. if (!$waitanswer) return null;
  84. while(!feof($this->pipe)) {
  85. @stream_select($r = array($this->pipe), $w = null, $e = null, 1); // wait
  86. pcntl_signal_dispatch();
  87. $cmd = $this->readcmd();
  88. if ($cmd[0] == self::RES_CALL_EXCEPT) {
  89. throw new \Exception($cmd[1]); // throw exception again in this process
  90. } elseif ($cmd[0] != self::RES_CALL) {
  91. $this->handlecmd($cmd, $foo = null);
  92. } else {
  93. $res = $cmd[1];
  94. break;
  95. }
  96. }
  97. return $res;
  98. }
  99. /**
  100. * \brief register a wait on a socket, with a callback function and some data by reference
  101. * \param $fd File descriptor to be select()ed
  102. * \param $callback Callback function (can be a class, see PHP doc)
  103. * \param $data Data to be passed to the callback, by reference
  104. */
  105. public function registerSocketWait($fd, $callback, &$data) {
  106. if ((is_array($callback)) && ($callback[0] instanceof self) && ($callback[0] != $this)) {
  107. $callback[0]->parent_ipc = $this;
  108. }
  109. if (!is_array($data)) throw new \Exception('No data defined :(');
  110. $this->fds[(int)$fd] = array('fd'=>$fd, 'last_activity' => time(), 'callback' => $callback, 'data' => &$data);
  111. }
  112. public function setTimeOut($fd, $timeout, $callback, &$data) {
  113. if (!is_array($data)) throw new \Exception('No data defined!');
  114. $this->fds[(int)$fd]['timeout'] = $timeout;
  115. $this->fds[(int)$fd]['timeout_callback'] = $callback;
  116. $this->fds[(int)$fd]['timeout_data'] = &$data;
  117. }
  118. /**
  119. * \brief Get callback data by socket
  120. * \param $fd The fd to be checked
  121. * \return mixed data or null if fd not known
  122. */
  123. public function getSocketInfo($fd) {
  124. return $this->fds[(int)$fd]['data'];
  125. }
  126. /**
  127. * \brief Stop listening on a stream
  128. * \param $fd to stop listening on
  129. */
  130. public function removeSocket($fd) {
  131. unset($this->fds[(int)$fd]);
  132. unset($this->wfds[(int)$fd]);
  133. }
  134. /**
  135. * \brief Define parent class for this IPC. Works only once
  136. * \param $parent The parent class
  137. */
  138. public function setParent(&$parent) {
  139. if (is_null($this->parent)) $this->parent = &$parent;
  140. }
  141. /**
  142. * \brief Creates a logical IPC port
  143. * \param $port_name The port's name
  144. * \param $class Object handling calls to this port
  145. */
  146. public function createPort($port_name, &$class) {
  147. if (!$this->ischld) throw new \Exception('This is not possible, man!');
  148. $this->sendcmd(self::CMD_NEWPORT, $port_name);
  149. while(!feof($this->pipe)) {
  150. @stream_select($r = array($this->pipe), $w = null, $e = null, 1); // wait
  151. pcntl_signal_dispatch();
  152. $cmd = $this->readcmd();
  153. // TODO: handle exceptions too?
  154. if ($cmd[0] != self::RES_NEWPORT) {
  155. $this->handlecmd($cmd, $foo = null);
  156. } else {
  157. $res = $cmd[1];
  158. break;
  159. }
  160. }
  161. if ($res) {
  162. $this->ports[$port_name] = array('type' => 'class', 'class' => &$class);
  163. }
  164. return $res;
  165. }
  166. public function openPort($port) {
  167. return new IPC_Port($this, $port);
  168. }
  169. /**
  170. * \brief Call a port method
  171. * \param $port_name Name of the port to call
  172. * \param $method Port method to call
  173. * \param $args Arguements to be passed to this port
  174. * \param $wait Shall we wait for reply [default=true]
  175. * \return Port call result, or NULL if \a $wait is false
  176. *
  177. * This method will call an IPC port's method. See createPort
  178. * for more details.
  179. */
  180. public function callPort($port_name, $method, array $args, $wait = true) {
  181. if (!$this->ischld) throw new \Exception('This is not possible either, man!');
  182. $this->sendcmd(self::CMD_CALLPORT, array($port_name, array(), $method, $args));
  183. if (!$wait) return NULL;
  184. while(!feof($this->pipe)) {
  185. @stream_select($r = array($this->pipe), $w = null, $e = null, 1); // wait
  186. pcntl_signal_dispatch();
  187. $cmd = $this->readcmd();
  188. if ($cmd[0] == self::RES_CALLPORT_EXCEPT) {
  189. throw new \Exception($cmd[1][2]);
  190. } elseif ($cmd[0] != self::RES_CALLPORT) {
  191. $this->handlecmd($cmd, $foo = null);
  192. } else {
  193. return $cmd[1][2];
  194. }
  195. }
  196. }
  197. /**
  198. * \brief Send a command to peer
  199. * \param $cmd Command name
  200. * \param ... Parameters
  201. * \internal
  202. */
  203. public function sendcmd() {
  204. $cmd = func_get_args();
  205. $cmd = serialize($cmd);
  206. @fwrite($this->pipe, pack('L', strlen($cmd)).$cmd);
  207. //@fflush($this->pipe);
  208. }
  209. /**
  210. * \brief Read data from buffer and decode it
  211. * \return First packet in queue
  212. * \internal
  213. */
  214. private function readbuf() {
  215. if (strlen($this->buf) < 4) return array(self::CMD_NOOP);
  216. list(,$len) = unpack('L', substr($this->buf, 0, 4));
  217. if ($len > (strlen($this->buf)-4)) return array(self::CMD_NOOP);
  218. $tmp = substr($this->buf, 4, $len);
  219. $tmp = unserialize($tmp);
  220. $this->buf = (string)substr($this->buf, 4+$len);
  221. return $tmp;
  222. }
  223. /**
  224. * \brief Read data from remote buffer if local buffer is empty, and return last packet
  225. * \return First packet in queue
  226. * \internal
  227. */
  228. private function readcmd() {
  229. $res = $this->readbuf();
  230. if ($res[0] != self::CMD_NOOP) return $res;
  231. if (feof($this->pipe)) {
  232. if ($this->ischld) exit;
  233. return NULL;
  234. }
  235. stream_set_blocking($this->pipe, false);
  236. $tmp = @fread($this->pipe, 1024);
  237. if (($tmp === '') && feof($this->pipe)) return null;
  238. if (!is_string($tmp)) return null;
  239. if (strlen($tmp) == 0) {
  240. return $this->readbuf();
  241. }
  242. $this->buf .= $tmp;
  243. return $this->readbuf();
  244. }
  245. public function listenBroadcast($code, $key, $callback) {
  246. $this->bcast_listen[$code][$key] = $callback;
  247. }
  248. public function unlistenBroadcast($code, $key) {
  249. unset($this->bcast_listen[$code][$key]);
  250. if (!$this->bcast_listen[$code]) unset($this->bcast_listen[$code]);
  251. }
  252. public function broadcast($code, $data = null, $except = 0) {
  253. if (isset($this->bcast_listen[$code]))
  254. foreach($this->bcast_listen[$code] as $key => $callback) call_user_func($callback, $data, $code, $key);
  255. if ($this->ischld) {
  256. foreach($this->fds as $id => $info) {
  257. $class = $info['callback'];
  258. if (!is_array($class)) continue;
  259. $class = $class[0];
  260. if (!($class instanceof self)) continue;
  261. if ($id == $except) continue;
  262. if ($class == $this) continue;
  263. $class->broadcast($code, $data);
  264. }
  265. if ($except != (int)$this->pipe) {
  266. $this->sendcmd(self::CMD_BCAST, array($code, $data));
  267. }
  268. return true;
  269. }
  270. if (!$except) {
  271. $this->sendcmd(self::CMD_BCAST, array($code, $data));
  272. } else if ($this->parent instanceof Core) {
  273. $this->parent->broadcast($code, $data, (int)$this->pipe);
  274. } else if (!is_null($this->parent_ipc)) {
  275. $this->parent_ipc->broadcast($code, $data, (int)$this->pipe);
  276. }
  277. return true;
  278. }
  279. protected function internalCallPort($call) {
  280. $port = $call[0];
  281. $method = $call[2];
  282. $class = &$this->ports[$call[0]]['class']; // at this point, ports are only class type
  283. if ($class instanceof IPC) {
  284. $class->sendcmd(IPC::CMD_CALLPORT, $call);
  285. } else {
  286. $method = $call[2];
  287. try {
  288. if (method_exists($class, '_asyncPort_'.$method)) {
  289. $reply = array($call[0], $call[1]);
  290. $res = call_user_func(array($class, '_asyncPort_'.$method), $call[3], $reply);
  291. return;
  292. }
  293. $res = call_user_func_array(array($class, $method), $call[3]);
  294. } catch(\Exception $e) {
  295. $exception = array(
  296. $call[0],
  297. $call[1],
  298. $e->getMessage(),
  299. );
  300. $this->routePortReply($exception, true);
  301. return;
  302. }
  303. $result = array(
  304. $call[0],
  305. $call[1],
  306. $res,
  307. );
  308. $this->routePortReply($result);
  309. }
  310. }
  311. public function routePortReply($reply, $exception = false) {
  312. $target = array_pop($reply[1]);
  313. if ($target == '@parent') {
  314. $this->sendcmd($exception ? self::RES_CALLPORT_EXCEPT : self::RES_CALLPORT, $reply);
  315. } else {
  316. var_dump($target, $reply);
  317. exit;
  318. }
  319. }
  320. /**
  321. * \brief Handle a command request
  322. * \param $cmd array The command to handle
  323. * \param $daemon Which child caused this call (if child), for CMD_CALL
  324. * \internal
  325. */
  326. private function handlecmd($cmd, &$daemon, $fd = -1) {
  327. if ($cmd === false) return;
  328. if (is_null($cmd)) {
  329. if (!$this->parent) throw new \Exception('Peer got away');
  330. if ($this->ischld) exit();
  331. $this->removeSocket($this->pipe);
  332. // unset($this->fds[$this->pipe]);
  333. $this->parent->IPCDied($this->pipe);
  334. return;
  335. }
  336. switch($cmd[0]) {
  337. case self::CMD_NOOP:
  338. return true;
  339. case self::CMD_PING:
  340. $this->sendcmd(self::RES_PING, $cmd[1]);
  341. break;
  342. case self::CMD_BCAST:
  343. // send to all children except (int)$fd
  344. $this->broadcast($cmd[1][0], $cmd[1][1], (int)$fd);
  345. break;
  346. case self::CMD_CALL:
  347. // $cmd[1] = array(function, args)
  348. $key = ($this->ischld)?'_ParentIPC_':'_ChildIPC_';
  349. $func = array($this->parent, $key.$cmd[1][0]);
  350. if (!is_callable($func)) {
  351. Logger::log(Logger::LOG_ERR, 'Tried to call '.get_class($func[0]).'::'.$func[1].' but failed!');
  352. if (!$this->ischld) $this->sendcmd(self::RES_CALL, null); // avoid deadlock
  353. break;
  354. }
  355. $cmd[1][0] = &$daemon;
  356. try {
  357. $res = call_user_func_array($func, $cmd[1]);
  358. } catch(\Exception $e) {
  359. $this->sendcmd(self::RES_CALL_EXCEPT, $e->getMessage());
  360. break;
  361. }
  362. $this->sendcmd(self::RES_CALL, $res);
  363. break;
  364. case self::RES_CALL:
  365. // nothing to care about this?
  366. break;
  367. case self::CMD_ERROR:
  368. if ($cmd[1][1] > 0) {
  369. $daemon['deadline'] = time()+$cmd[1][1];
  370. }
  371. break;
  372. case self::CMD_STOP:
  373. $this->parent->shutdown();
  374. exit;
  375. case self::CMD_LOG:
  376. call_user_func_array(array('pinetd\\Logger', 'log_full'), $cmd[1]);
  377. break;
  378. case self::CMD_NEWPORT:
  379. $port = $cmd[1];
  380. if (isset($this->ports[$port])) {
  381. // return false!
  382. $this->sendcmd(self::RES_NEWPORT, false);
  383. break;
  384. }
  385. $res = $this->parentipc->createPort($port, $this);
  386. if ($res) {
  387. $this->ports[$port] = array('type' => 'child');
  388. }
  389. $this->sendcmd(self::RES_NEWPORT, $res);
  390. break;
  391. case self::CMD_CALLPORT:
  392. $port = $cmd[1][0];
  393. // do we have this port in our routing table?
  394. if (isset($this->ports[$port])) {
  395. $cmd[1][1][] = '@parent';
  396. $this->internalCallPort($cmd[1]);
  397. break;
  398. }
  399. // if not, follow to parent
  400. $cmd[1][1][] = (int)$fd;
  401. if ($this->parentipc instanceof IPC) {
  402. $this->parentipc->sendcmd(self::CMD_CALLPORT, $cmd[1]);
  403. } else {
  404. $this->parentipc->callPort($cmd[1]);
  405. }
  406. break;
  407. case self::RES_CALLPORT:
  408. case self::RES_CALLPORT_EXCEPT:
  409. $port = $cmd[1][0];
  410. if (!$cmd[1][1]) break; // this result reached us, but we don't care anymore
  411. $next = array_pop($cmd[1][1]);
  412. if ($next == '@parent') {
  413. if ($this->parentipc instanceof IPC) {
  414. $this->parentipc->sendcmd($cmd[0], $cmd[1]);
  415. } else {
  416. $this->parentipc->routePortReply($cmd[1], $cmd[0] == self::RES_CALLPORT_EXCEPT);
  417. }
  418. break;
  419. }
  420. if (!isset($this->fds[$next])) break; // child has died while doing an IPC call?
  421. $info = &$this->fds[$next];
  422. if ( (is_array($info['callback'])) && ($info['callback'][1] == 'run') && ($info['callback'][0] instanceof IPC)) {
  423. $class = $info['callback'][0];
  424. $class->sendcmd($cmd[0], $cmd[1]);
  425. } else {
  426. var_dump('huh?');
  427. }
  428. break;
  429. default:
  430. throw new \Exception('Unknown command '.$cmd[0]);
  431. }
  432. return true;
  433. }
  434. /**
  435. * \brief Pass log messages to parent
  436. * \internal
  437. */
  438. public function log() {
  439. if (!$this->ischld) throw new \Exception('Parent sending logs to child makes no sense');
  440. $n = func_get_args();
  441. return $this->sendcmd(self::CMD_LOG, $n);
  442. }
  443. /**
  444. * /brief Stop attached child
  445. */
  446. public function stop() {
  447. if ($this->ischld) throw new \Exception('Children can\'t tell their parents to go to sleep');
  448. return $this->sendcmd(self::CMD_STOP);
  449. }
  450. /**
  451. * \brief Send a CMD_PING. Will wait for RES_PING only if child
  452. */
  453. public function ping() {
  454. $this->sendcmd(self::CMD_PING, microtime(true));
  455. if (!$this->ischld) return true;
  456. while(!feof($this->pipe)) {
  457. $cmd = $this->readcmd();
  458. if ($cmd[0] != self::RES_PING) {
  459. $this->handlecmd($cmd, $foo = null);
  460. } else {
  461. break;
  462. }
  463. }
  464. return true;
  465. }
  466. public function addWriteBuffer($fd, &$buffer) {
  467. if (isset($this->wfds[(int)$fd])) return;
  468. $this->wfds[(int)$fd] = array(
  469. 'fd' => $fd,
  470. 'buffer' => &$buffer,
  471. );
  472. }
  473. /**
  474. * \brief Wait for something to happen (or for timeout) and handle it
  475. * \param $timeout Timeout for select() in microseconds
  476. * \return int Number of processed sockets
  477. */
  478. public function selectSockets($timeout) {
  479. $r = array();
  480. $w = array();
  481. $now = time();
  482. foreach($this->fds as &$c) {
  483. if ( (isset($c['timeout'])) && ($c['timeout'] > 0)) {
  484. if ($c['last_activity'] < ($now - $c['timeout'])) {
  485. $c['last_activity'] = $now;
  486. call_user_func_array($c['timeout_callback'], &$c['timeout_data']);
  487. }
  488. }
  489. $r[] = $c['fd'];
  490. }
  491. foreach($this->wfds as &$c) {
  492. $w[] = $c['fd'];
  493. }
  494. $n = @stream_select($r, $w, $e=null, 0, $timeout);
  495. pcntl_signal_dispatch();
  496. if (($n==0) && ((count($r)>0) || (count($w) > 0))) $n = count($r) + count($w); // stream_select returns weird values sometimes Oo
  497. if ($n<=0) {
  498. // nothing has happened, let's collect garbage collector cycles
  499. gc_collect_cycles();
  500. $this->waitChildren();
  501. return $n;
  502. }
  503. foreach($r as $fd) {
  504. $info = &$this->fds[(int)$fd];
  505. $info['last_activity'] = $now;
  506. // somewhat dirty (but not so dirty) workaround for a weird PHP 5.3 behaviour
  507. if ( (is_array($info['callback'])) && ($info['callback'][1] == 'run')) {
  508. // This function expects one parameter, and by ref
  509. $ref = &$info['data'][0];
  510. call_user_func($info['callback'], &$ref, $fd);
  511. } else {
  512. call_user_func_array($info['callback'], &$info['data']);
  513. }
  514. }
  515. foreach($w as $fd) {
  516. $info = &$this->wfds[(int)$fd];
  517. $res = fwrite($fd, $info['buffer']);
  518. if ($res == strlen($info['buffer'])) { // write completed?
  519. $info['buffer'] = '';
  520. unset($this->wfds[(int)$fd]);
  521. continue;
  522. }
  523. $info['buffer'] = substr($info['buffer'], $res);
  524. }
  525. $this->waitChildren();
  526. return $n;
  527. }
  528. public function waitChildren() {
  529. if (!PINETD_CAN_FORK) return;
  530. if (!function_exists('pcntl_wait')) return;
  531. $res = pcntl_wait($status, WNOHANG);
  532. if ($res == -1) return; // something bad happened
  533. if ($res == 0) return; // no process terminated
  534. if (pcntl_wifstopped($status)) {
  535. Logger::log(Logger::LOG_INFO, 'Waking up stopped child on pid '.$res);
  536. posix_kill($res, SIGCONT);
  537. return;
  538. }
  539. // dispatch signal (if possible)
  540. if (is_callable(array($this->parent, 'childSignaled'))) {
  541. if (pcntl_wifexited($status)) {
  542. $code = pcntl_wexitstatus($status);
  543. return $this->parent->childSignaled($res, $code, NULL);
  544. }
  545. if (pcntl_wifsignaled($status)) {
  546. $signal = pcntl_wtermsig($status);
  547. $code = pcntl_wexitstatus($status);
  548. $const = get_defined_constants(true);
  549. $const = $const['pcntl'];
  550. foreach($const as $var => $val) {
  551. if (substr($var, 0, 3) != 'SIG') continue;
  552. if (substr($var, 0, 4) == 'SIG_') continue;
  553. if ($val != $signal) continue;
  554. $signal = $var;
  555. break;
  556. }
  557. $this->parent->childSignaled($res, $code, $signal); // pid, status, signal
  558. }
  559. }
  560. }
  561. /**
  562. * \brief Send an error to parent.
  563. * \internal
  564. */
  565. public function Error() {
  566. $n = func_get_args();
  567. return $this->sendcmd(self::CMD_ERROR, $n);
  568. }
  569. /**
  570. * \brief Send an exception report to parent
  571. * \param $e Exception
  572. * \internal
  573. */
  574. public function Exception(\Exception $e) {
  575. Logger::log(Logger::LOG_ERR, 'Got error: '.$e->getMessage());
  576. $this->killSelf();
  577. $this->ping();
  578. }
  579. /**
  580. * \brief Run a daemon until there's nothing to run
  581. * \param $daemon Daemon to run
  582. */
  583. public function run(&$daemon, $fd) {
  584. $cmd = $this->readcmd();
  585. while($cmd[0] != self::CMD_NOOP) {
  586. $this->handlecmd($cmd, $daemon, $fd);
  587. $cmd = $this->readbuf();
  588. if (is_null($cmd)) break;
  589. }
  590. }
  591. }
  592. class IPC_Port {
  593. private $IPC;
  594. private $port;
  595. public function __construct(&$IPC, $port) {
  596. $this->IPC = &$IPC;
  597. $this->port = $port;
  598. }
  599. public function __call($method, $args) {
  600. return $this->IPC->callPort($this->port, $method, $args);
  601. }
  602. }