Hi everyone,
Whole last day I've tried to implement a managed SSH access for Git. The
problem I faced was not quite trivial: whenever I try to `git clone` something
over SSH transport I either get successfully cloned repository or `fatal: early
EOF`. After hours of investigating I finally managed to identify a problem. It
is in `InvertedShellWrapper.pumpStreams`:
for (;;) {
if (!shell.isAlive()) {
callback.onExit(shell.exitValue());
// risk of loosing process data
return;
}
if (pumpStream(in, shellIn, buffer)) {
continue;
}
if (pumpStream(shellOut, out, buffer)) {
continue;
}
if (pumpStream(shellErr, err, buffer)) {
continue;
}
Thread.sleep(1);
}
In most Unix systems a process may exit as soon as it writes everything into a
stream. If the streams are buffered, then there is a chance of dropping
everything that is left in STDOUT and STDERR (`shellOut` and `shellErr`) after
the `shell.isAlive` has reported that the underlying process has finished.
The solution is simple: you just pump streams one more time until no more data
is left in STDOUT or STDERR. Here is what I came up with (sorry for Scala):
class GitShellFactory(val verb: String, val path: String)
extends ProcessShellFactory(Array[String](verb, path)) {
override def create() = new SshShellWrapper(new ProcessShell())
}
class SshShellWrapper(shell: InvertedShell) extends SimpleCommand {
var buffer = new Array[Byte](4096)
var stdin: OutputStream = _
var stdout: InputStream = _
var stderr: InputStream = _
def start(env: Environment): Unit = {
shell.start(env.getEnv)
stdin = shell.getInputStream
stdout = shell.getOutputStream
stderr = shell.getErrorStream
new Thread() {
override def run() = try {
while (shell.isAlive) {
copyStream(in, stdin)
copyStream(stdout, out)
copyStream(stderr, err)
Thread.sleep(1)
}
copyStream(stdout, out)
copyStream(stderr, err)
} finally {
shell.destroy()
exit(shell.exitValue)
}
}.start()
}
def destroy = if (shell != null) shell.destroy
def copyStream(in: InputStream, out: OutputStream): Unit =
while (in.available > 0) {
val len = in.read(buffer)
if (len > 0) {
out.write(buffer, 0, len)
out.flush
}
}
}
Then I simply use GitShellFactory instead of ProcessShellFactory and everything
works perfectly.
Hope it will save some time for those who stuck with the same issue. It would
be great to have this fixed in later releases, though.
Best regards,
Boris Okunskiy