[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Proxy an IO channel to an underlying command, with optional callbacks. This 5 * is a mostly a more general version of @{class:PhutilExecPassthru}. This 6 * class is used to proxy Git, SVN and Mercurial traffic to the commands which 7 * can actually serve it. 8 * 9 * Largely, this just reads an IO channel (like stdin from SSH) and writes 10 * the results into a command channel (like a command's stdin). Then it reads 11 * the command channel (like the command's stdout) and writes it into the IO 12 * channel (like stdout from SSH): 13 * 14 * IO Channel Command Channel 15 * stdin -> stdin 16 * stdout <- stdout 17 * stderr <- stderr 18 * 19 * You can provide **read and write callbacks** which are invoked as data 20 * is passed through this class. They allow you to inspect and modify traffic. 21 * 22 * IO Channel Passthru Command Channel 23 * stdout -> willWrite -> stdin 24 * stdin <- willRead <- stdout 25 * stderr <- (identity) <- stderr 26 * 27 * Primarily, this means: 28 * 29 * - the **IO Channel** can be a @{class:PhutilProtocolChannel} if the 30 * **write callback** can convert protocol messages into strings; and 31 * - the **write callback** can inspect and reject requests over the channel, 32 * e.g. to enforce policies. 33 * 34 * In practice, this is used when serving repositories to check each command 35 * issued over SSH and determine if it is a read command or a write command. 36 * Writes can then be checked for appropriate permissions. 37 */ 38 final class PhabricatorSSHPassthruCommand extends Phobject { 39 40 private $commandChannel; 41 private $ioChannel; 42 private $errorChannel; 43 private $execFuture; 44 private $willWriteCallback; 45 private $willReadCallback; 46 47 public function setCommandChannelFromExecFuture(ExecFuture $exec_future) { 48 $exec_channel = new PhutilExecChannel($exec_future); 49 $exec_channel->setStderrHandler(array($this, 'writeErrorIOCallback')); 50 51 $this->execFuture = $exec_future; 52 $this->commandChannel = $exec_channel; 53 54 return $this; 55 } 56 57 public function setIOChannel(PhutilChannel $io_channel) { 58 $this->ioChannel = $io_channel; 59 return $this; 60 } 61 62 public function setErrorChannel(PhutilChannel $error_channel) { 63 $this->errorChannel = $error_channel; 64 return $this; 65 } 66 67 public function setWillReadCallback($will_read_callback) { 68 $this->willReadCallback = $will_read_callback; 69 return $this; 70 } 71 72 public function setWillWriteCallback($will_write_callback) { 73 $this->willWriteCallback = $will_write_callback; 74 return $this; 75 } 76 77 public function writeErrorIOCallback(PhutilChannel $channel, $data) { 78 $this->errorChannel->write($data); 79 } 80 81 public function execute() { 82 $command_channel = $this->commandChannel; 83 $io_channel = $this->ioChannel; 84 $error_channel = $this->errorChannel; 85 86 if (!$command_channel) { 87 throw new Exception('Set a command channel before calling execute()!'); 88 } 89 90 if (!$io_channel) { 91 throw new Exception('Set an IO channel before calling execute()!'); 92 } 93 94 if (!$error_channel) { 95 throw new Exception('Set an error channel before calling execute()!'); 96 } 97 98 $channels = array($command_channel, $io_channel, $error_channel); 99 100 // We want to limit the amount of data we'll hold in memory for this 101 // process. See T4241 for a discussion of this issue in general. 102 103 $buffer_size = (1024 * 1024); // 1MB 104 $io_channel->setReadBufferSize($buffer_size); 105 $command_channel->setReadBufferSize($buffer_size); 106 107 // TODO: This just makes us throw away stderr after the first 1MB, but we 108 // don't currently have the support infrastructure to buffer it correctly. 109 // It's difficult to imagine this causing problems in practice, though. 110 $this->execFuture->getStderrSizeLimit($buffer_size); 111 112 while (true) { 113 PhutilChannel::waitForAny($channels); 114 115 $io_channel->update(); 116 $command_channel->update(); 117 $error_channel->update(); 118 119 // If any channel is blocked on the other end, wait for it to flush before 120 // we continue reading. For example, if a user is running `git clone` on 121 // a 1GB repository, the underlying `git-upload-pack` may 122 // be able to produce data much more quickly than we can send it over 123 // the network. If we don't throttle the reads, we may only send a few 124 // MB over the I/O channel in the time it takes to read the entire 1GB off 125 // the command channel. That leaves us with 1GB of data in memory. 126 127 while ($command_channel->isOpen() && 128 $io_channel->isOpenForWriting() && 129 ($command_channel->getWriteBufferSize() >= $buffer_size || 130 $io_channel->getWriteBufferSize() >= $buffer_size || 131 $error_channel->getWriteBufferSize() >= $buffer_size)) { 132 PhutilChannel::waitForActivity(array(), $channels); 133 $io_channel->update(); 134 $command_channel->update(); 135 $error_channel->update(); 136 } 137 138 // If the subprocess has exited and we've read everything from it, 139 // we're all done. 140 $done = !$command_channel->isOpenForReading() && 141 $command_channel->isReadBufferEmpty(); 142 143 $in_message = $io_channel->read(); 144 if ($in_message !== null) { 145 $in_message = $this->willWriteData($in_message); 146 if ($in_message !== null) { 147 $command_channel->write($in_message); 148 } 149 } 150 151 $out_message = $command_channel->read(); 152 if ($out_message !== null) { 153 $out_message = $this->willReadData($out_message); 154 if ($out_message !== null) { 155 $io_channel->write($out_message); 156 } 157 } 158 159 // If we have nothing left on stdin, close stdin on the subprocess. 160 if (!$io_channel->isOpenForReading()) { 161 $command_channel->closeWriteChannel(); 162 } 163 164 if ($done) { 165 break; 166 } 167 168 // If the client has disconnected, kill the subprocess and bail. 169 if (!$io_channel->isOpenForWriting()) { 170 $this->execFuture 171 ->setStdoutSizeLimit(0) 172 ->setStderrSizeLimit(0) 173 ->setReadBufferSize(null) 174 ->resolveKill(); 175 break; 176 } 177 } 178 179 list($err) = $this->execFuture 180 ->setStdoutSizeLimit(0) 181 ->setStderrSizeLimit(0) 182 ->setReadBufferSize(null) 183 ->resolve(); 184 185 return $err; 186 } 187 188 public function willWriteData($message) { 189 if ($this->willWriteCallback) { 190 return call_user_func($this->willWriteCallback, $this, $message); 191 } else { 192 if (strlen($message)) { 193 return $message; 194 } else { 195 return null; 196 } 197 } 198 } 199 200 public function willReadData($message) { 201 if ($this->willReadCallback) { 202 return call_user_func($this->willReadCallback, $this, $message); 203 } else { 204 if (strlen($message)) { 205 return $message; 206 } else { 207 return null; 208 } 209 } 210 } 211 212 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |