@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add an optional protocol log to `git` SSH workflows

Summary:
Ref T8093. Support dumping the protocol bytes to a side channel logfile, as a precursor to parsing the protocol and rewriting protocol frames to virtualize refs.

The protocol itself is mostly ASCII text so the raw protocol bytes are pretty comprehensible.

Test Plan:
{F6363221}

{F6363222}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T8093

Differential Revision: https://secure.phabricator.com/D20380

+268 -1
+2
src/__phutil_library_map__.php
··· 4284 4284 'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php', 4285 4285 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php', 4286 4286 'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php', 4287 + 'PhabricatorProtocolLog' => 'infrastructure/log/PhabricatorProtocolLog.php', 4287 4288 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 4288 4289 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', 4289 4290 'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php', ··· 10491 10492 'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 10492 10493 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 10493 10494 'PhabricatorPronounSetting' => 'PhabricatorSelectSetting', 10495 + 'PhabricatorProtocolLog' => 'Phobject', 10494 10496 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 10495 10497 'PhabricatorQuery' => 'Phobject', 10496 10498 'PhabricatorQueryConstraint' => 'Phobject',
+12 -1
src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php
··· 28 28 ->setRepository($repository) 29 29 ->setLog($this); 30 30 31 - if ($this->shouldProxy()) { 31 + $is_proxy = $this->shouldProxy(); 32 + if ($is_proxy) { 32 33 $command = $this->getProxyCommand(true); 33 34 $did_write = false; 34 35 ··· 51 52 } 52 53 } 53 54 55 + $log = $this->newProtocolLog($is_proxy); 56 + if ($log) { 57 + $this->setProtocolLog($log); 58 + $log->didStartSession($command); 59 + } 60 + 54 61 $caught = null; 55 62 try { 56 63 $err = $this->executeRepositoryCommand($command); 57 64 } catch (Exception $ex) { 58 65 $caught = $ex; 66 + } 67 + 68 + if ($log) { 69 + $log->didEndSession(); 59 70 } 60 71 61 72 // We've committed the write (or rejected it), so we can release the lock
+51
src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php
··· 5 5 implements DiffusionRepositoryClusterEngineLogInterface { 6 6 7 7 private $engineLogProperties = array(); 8 + private $protocolLog; 8 9 9 10 protected function writeError($message) { 10 11 // Git assumes we'll add our own newlines. ··· 53 54 'interact with this repository.', 54 55 $repository->getDisplayName(), 55 56 $repository->getVersionControlSystem())); 57 + } 58 + 59 + protected function newPassthruCommand() { 60 + return parent::newPassthruCommand() 61 + ->setWillWriteCallback(array($this, 'willWriteMessageCallback')) 62 + ->setWillReadCallback(array($this, 'willReadMessageCallback')); 63 + } 64 + 65 + protected function newProtocolLog($is_proxy) { 66 + if ($is_proxy) { 67 + return null; 68 + } 69 + 70 + // While developing, do this to write a full protocol log to disk: 71 + // 72 + // return new PhabricatorProtocolLog('/tmp/git-protocol.log'); 73 + 74 + return null; 75 + } 76 + 77 + protected function getProtocolLog() { 78 + return $this->protocolLog; 79 + } 80 + 81 + protected function setProtocolLog(PhabricatorProtocolLog $log) { 82 + $this->protocolLog = $log; 83 + } 84 + 85 + public function willWriteMessageCallback( 86 + PhabricatorSSHPassthruCommand $command, 87 + $message) { 88 + 89 + $log = $this->getProtocolLog(); 90 + if ($log) { 91 + $log->didWriteBytes($message); 92 + } 93 + 94 + return $message; 95 + } 96 + 97 + public function willReadMessageCallback( 98 + PhabricatorSSHPassthruCommand $command, 99 + $message) { 100 + 101 + $log = $this->getProtocolLog(); 102 + if ($log) { 103 + $log->didReadBytes($message); 104 + } 105 + 106 + return $message; 56 107 } 57 108 58 109 }
+10
src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php
··· 54 54 $future = id(new ExecFuture('%C', $command)) 55 55 ->setEnv($this->getEnvironment()); 56 56 57 + $log = $this->newProtocolLog($is_proxy); 58 + if ($log) { 59 + $this->setProtocolLog($log); 60 + $log->didStartSession($command); 61 + } 62 + 57 63 $err = $this->newPassthruCommand() 58 64 ->setIOChannel($this->getIOChannel()) 59 65 ->setCommandChannelFromExecFuture($future) 60 66 ->execute(); 67 + 68 + if ($log) { 69 + $log->didEndSession(); 70 + } 61 71 62 72 if ($err) { 63 73 $pull_event
+193
src/infrastructure/log/PhabricatorProtocolLog.php
··· 1 + <?php 2 + 3 + final class PhabricatorProtocolLog 4 + extends Phobject { 5 + 6 + private $logfile; 7 + private $mode; 8 + private $buffer = array(); 9 + 10 + public function __construct($logfile) { 11 + $this->logfile = $logfile; 12 + } 13 + 14 + public function didStartSession($session_name) { 15 + $this->setMode('!'); 16 + $this->buffer[] = $session_name; 17 + $this->flush(); 18 + } 19 + 20 + public function didEndSession() { 21 + $this->setMode('_'); 22 + $this->buffer[] = pht('<End of Session>'); 23 + $this->flush(); 24 + } 25 + 26 + public function didWriteBytes($bytes) { 27 + if (!strlen($bytes)) { 28 + return; 29 + } 30 + 31 + $this->setMode('>'); 32 + $this->buffer[] = $bytes; 33 + } 34 + 35 + public function didReadBytes($bytes) { 36 + if (!strlen($bytes)) { 37 + return; 38 + } 39 + 40 + $this->setMode('<'); 41 + $this->buffer[] = $bytes; 42 + } 43 + 44 + private function setMode($mode) { 45 + if ($this->mode === $mode) { 46 + return $this; 47 + } 48 + 49 + if ($this->mode !== null) { 50 + $this->flush(); 51 + } 52 + 53 + $this->mode = $mode; 54 + 55 + return $this; 56 + } 57 + 58 + private function flush() { 59 + $mode = $this->mode; 60 + $bytes = $this->buffer; 61 + 62 + $this->mode = null; 63 + $this->buffer = array(); 64 + 65 + $bytes = implode('', $bytes); 66 + 67 + if (strlen($bytes)) { 68 + $this->writeBytes($mode, $bytes); 69 + } 70 + } 71 + 72 + private function writeBytes($mode, $bytes) { 73 + $header = $mode; 74 + $len = strlen($bytes); 75 + 76 + $out = array(); 77 + switch ($mode) { 78 + case '<': 79 + $out[] = pht('%s Write [%s bytes]', $header, new PhutilNumber($len)); 80 + break; 81 + case '>': 82 + $out[] = pht('%s Read [%s bytes]', $header, new PhutilNumber($len)); 83 + break; 84 + default: 85 + $out[] = pht( 86 + '%s %s', 87 + $header, 88 + $this->escapeBytes($bytes)); 89 + break; 90 + } 91 + 92 + switch ($mode) { 93 + case '<': 94 + case '>': 95 + $out[] = $this->renderBytes($header, $bytes); 96 + break; 97 + } 98 + 99 + $out = implode("\n", $out)."\n\n"; 100 + 101 + $this->writeMessage($out); 102 + } 103 + 104 + private function renderBytes($header, $bytes) { 105 + $bytes_per_line = 48; 106 + $bytes_per_chunk = 4; 107 + 108 + // Compute the width of the "bytes" display section, which looks like 109 + // this: 110 + // 111 + // > 00112233 44556677 abcdefgh 112 + // ^^^^^^^^^^^^^^^^^ 113 + // 114 + // We need to figure this out so we can align the plain text in the far 115 + // right column appropriately. 116 + 117 + // The character width of the "bytes" part of a full display line. If 118 + // we're rendering 48 bytes per line, we'll need 96 characters, since 119 + // each byte is printed as a 2-character hexadecimal code. 120 + $display_bytes = ($bytes_per_line * 2); 121 + 122 + // The character width of the number of spaces in between the "bytes" 123 + // chunks. If we're rendering 12 chunks per line, we'll put 11 spaces 124 + // in between them to separate them. 125 + $display_spaces = (($bytes_per_line / $bytes_per_chunk) - 1); 126 + 127 + $pad_bytes = $display_bytes + $display_spaces; 128 + 129 + // When the protocol is plaintext, try to break it on newlines so it's 130 + // easier to read. 131 + $pos = 0; 132 + $lines = array(); 133 + while (true) { 134 + $next_break = strpos($bytes, "\n", $pos); 135 + if ($next_break === false) { 136 + $len = strlen($bytes) - $pos; 137 + } else { 138 + $len = ($next_break - $pos) + 1; 139 + } 140 + $len = min($bytes_per_line, $len); 141 + 142 + $next_bytes = substr($bytes, $pos, $len); 143 + 144 + $chunk_parts = array(); 145 + foreach (str_split($next_bytes, $bytes_per_chunk) as $chunk) { 146 + $chunk_display = ''; 147 + for ($ii = 0; $ii < strlen($chunk); $ii++) { 148 + $chunk_display .= sprintf('%02x', ord($chunk[$ii])); 149 + } 150 + $chunk_parts[] = $chunk_display; 151 + } 152 + $chunk_parts = implode(' ', $chunk_parts); 153 + 154 + $chunk_parts = str_pad($chunk_parts, $pad_bytes, ' '); 155 + 156 + 157 + $lines[] = $header.' '.$chunk_parts.' '.$this->escapeBytes($next_bytes); 158 + 159 + $pos += $len; 160 + 161 + if ($pos >= strlen($bytes)) { 162 + break; 163 + } 164 + } 165 + 166 + $lines = implode("\n", $lines); 167 + 168 + return $lines; 169 + } 170 + 171 + private function escapeBytes($bytes) { 172 + $result = ''; 173 + for ($ii = 0; $ii < strlen($bytes); $ii++) { 174 + $c = $bytes[$ii]; 175 + $o = ord($c); 176 + 177 + if ($o >= 0x20 && $o <= 0x7F) { 178 + $result .= $c; 179 + } else { 180 + $result .= '.'; 181 + } 182 + } 183 + return $result; 184 + } 185 + 186 + private function writeMessage($message) { 187 + $f = fopen($this->logfile, 'a'); 188 + fwrite($f, $message); 189 + fflush($f); 190 + fclose($f); 191 + } 192 + 193 + }