| | |
| | | |
| | | package com.gitblit.transport.ssh.commands; |
| | | |
| | | import java.io.BufferedWriter; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InterruptedIOException; |
| | | import java.io.OutputStream; |
| | | import java.io.OutputStreamWriter; |
| | | import java.io.PrintWriter; |
| | | import java.io.StringWriter; |
| | | import java.util.concurrent.Future; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | |
| | | import org.apache.sshd.server.Command; |
| | | import org.apache.sshd.server.Environment; |
| | | import org.apache.sshd.server.ExitCallback; |
| | | import org.apache.sshd.server.SessionAware; |
| | | import org.apache.sshd.server.session.ServerSession; |
| | | import org.kohsuke.args4j.Argument; |
| | | import org.kohsuke.args4j.CmdLineException; |
| | | import org.kohsuke.args4j.Option; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import com.gitblit.transport.ssh.AbstractSshCommand; |
| | | import com.gitblit.transport.ssh.SshCommandContext; |
| | | import com.gitblit.utils.IdGenerator; |
| | | import com.gitblit.utils.WorkQueue; |
| | |
| | | import com.google.common.base.Charsets; |
| | | import com.google.common.util.concurrent.Atomics; |
| | | |
| | | public abstract class BaseCommand extends AbstractSshCommand { |
| | | private static final Logger log = LoggerFactory |
| | | .getLogger(BaseCommand.class); |
| | | public abstract class BaseCommand implements Command, SessionAware { |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(BaseCommand.class); |
| | | |
| | | /** Ssh context */ |
| | | protected SshCommandContext ctx; |
| | | |
| | | protected InputStream in; |
| | | |
| | | protected OutputStream out; |
| | | |
| | | protected OutputStream err; |
| | | |
| | | protected ExitCallback exit; |
| | | |
| | | protected ServerSession session; |
| | | |
| | | /** Text of the command line which lead up to invoking this instance. */ |
| | | private String commandName = ""; |
| | | |
| | | /** Unparsed command line options. */ |
| | | private String[] argv; |
| | | |
| | | /** Ssh context */ |
| | | protected SshCommandContext ctx; |
| | | |
| | | /** The task, as scheduled on a worker thread. */ |
| | | private final AtomicReference<Future<?>> task; |
| | |
| | | this.executor = w.getDefaultQueue(); |
| | | } |
| | | |
| | | @Override |
| | | public void setSession(final ServerSession session) { |
| | | this.session = session; |
| | | } |
| | | |
| | | @Override |
| | | public void destroy() { |
| | | } |
| | | |
| | | protected static PrintWriter toPrintWriter(final OutputStream o) { |
| | | return new PrintWriter(new BufferedWriter(new OutputStreamWriter(o, Charsets.UTF_8))); |
| | | } |
| | | |
| | | @Override |
| | | public abstract void start(Environment env) throws IOException; |
| | | |
| | | public void setContext(SshCommandContext ctx) { |
| | | this.ctx = ctx; |
| | | } |
| | | |
| | | @Override |
| | | public void setInputStream(final InputStream in) { |
| | | this.in = in; |
| | | } |
| | | |
| | | @Override |
| | | public void setOutputStream(final OutputStream out) { |
| | | this.out = out; |
| | | } |
| | | |
| | | @Override |
| | | public void setErrorStream(final OutputStream err) { |
| | | this.err = err; |
| | | } |
| | | |
| | | @Override |
| | | public void setExitCallback(final ExitCallback callback) { |
| | | this.exit = callback; |
| | | } |
| | |
| | | * <p> |
| | | * This method must be explicitly invoked to cause a parse. |
| | | * |
| | | * @throws UnloggedFailure if the command line arguments were invalid. |
| | | * @throws UnloggedFailure |
| | | * if the command line arguments were invalid. |
| | | * @see Option |
| | | * @see Argument |
| | | */ |
| | |
| | | * <p> |
| | | * This method must be explicitly invoked to cause a parse. |
| | | * |
| | | * @param options object whose fields declare Option and Argument annotations |
| | | * to describe the parameters of the command. Usually {@code this}. |
| | | * @throws UnloggedFailure if the command line arguments were invalid. |
| | | * @param options |
| | | * object whose fields declare Option and Argument annotations to |
| | | * describe the parameters of the command. Usually {@code this}. |
| | | * @throws UnloggedFailure |
| | | * if the command line arguments were invalid. |
| | | * @see Option |
| | | * @see Argument |
| | | */ |
| | |
| | | public void run() throws Exception; |
| | | } |
| | | |
| | | |
| | | /** Runnable function which can retrieve a project name related to the task */ |
| | | public static interface RepositoryCommandRunnable extends CommandRunnable { |
| | | public String getRepository(); |
| | |
| | | /** |
| | | * Spawn a function into its own thread. |
| | | * <p> |
| | | * Typically this should be invoked within {@link Command#start(Environment)}, |
| | | * such as: |
| | | * Typically this should be invoked within |
| | | * {@link Command#start(Environment)}, such as: |
| | | * |
| | | * <pre> |
| | | * startThread(new Runnable() { |
| | |
| | | * }); |
| | | * </pre> |
| | | * |
| | | * @param thunk the runnable to execute on the thread, performing the |
| | | * @param thunk |
| | | * the runnable to execute on the thread, performing the |
| | | * command's logic. |
| | | */ |
| | | protected void startThread(final Runnable thunk) { |
| | |
| | | * lose access to request based resources as any callbacks previously |
| | | * registered with {@link RequestCleanup} will fire. |
| | | * |
| | | * @param rc exit code for the remote client. |
| | | * @param rc |
| | | * exit code for the remote client. |
| | | */ |
| | | protected void onExit(final int rc) { |
| | | exit.onExit(rc); |
| | |
| | | } |
| | | |
| | | private int handleError(final Throwable e) { |
| | | if ((e.getClass() == IOException.class |
| | | && "Pipe closed".equals(e.getMessage())) |
| | | || // |
| | | (e.getClass() == SshException.class |
| | | && "Already closed".equals(e.getMessage())) |
| | | || // |
| | | if ((e.getClass() == IOException.class && "Pipe closed".equals(e.getMessage())) || // |
| | | (e.getClass() == SshException.class && "Already closed".equals(e.getMessage())) || // |
| | | e.getClass() == InterruptedIOException.class) { |
| | | // This is sshd telling us the client just dropped off while |
| | | // we were waiting for a read or a write to complete. Either |
| | |
| | | /** |
| | | * Spawn a function into its own thread. |
| | | * <p> |
| | | * Typically this should be invoked within {@link Command#start(Environment)}, |
| | | * such as: |
| | | * Typically this should be invoked within |
| | | * {@link Command#start(Environment)}, such as: |
| | | * |
| | | * <pre> |
| | | * startThread(new CommandRunnable() { |
| | |
| | | * If the function throws an exception, it is translated to a simple message |
| | | * for the client, a non-zero exit code, and the stack trace is logged. |
| | | * |
| | | * @param thunk the runnable to execute on the thread, performing the |
| | | * @param thunk |
| | | * the runnable to execute on the thread, performing the |
| | | * command's logic. |
| | | */ |
| | | protected void startThread(final CommandRunnable thunk) { |
| | |
| | | /** |
| | | * Create a new failure. |
| | | * |
| | | * @param exitCode exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and 255, |
| | | * inclusive. |
| | | * @param msg message to also send to the client's stderr. |
| | | * @param exitCode |
| | | * exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and |
| | | * 255, inclusive. |
| | | * @param msg |
| | | * message to also send to the client's stderr. |
| | | */ |
| | | public Failure(final int exitCode, final String msg) { |
| | | this(exitCode, msg, null); |
| | |
| | | /** |
| | | * Create a new failure. |
| | | * |
| | | * @param exitCode exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and 255, |
| | | * inclusive. |
| | | * @param msg message to also send to the client's stderr. |
| | | * @param why stack trace to include in the server's log, but is not sent to |
| | | * the client's stderr. |
| | | * @param exitCode |
| | | * exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and |
| | | * 255, inclusive. |
| | | * @param msg |
| | | * message to also send to the client's stderr. |
| | | * @param why |
| | | * stack trace to include in the server's log, but is not |
| | | * sent to the client's stderr. |
| | | */ |
| | | public Failure(final int exitCode, final String msg, final Throwable why) { |
| | | super(msg, why); |
| | |
| | | /** |
| | | * Create a new failure. |
| | | * |
| | | * @param msg message to also send to the client's stderr. |
| | | * @param msg |
| | | * message to also send to the client's stderr. |
| | | */ |
| | | public UnloggedFailure(final String msg) { |
| | | this(1, msg); |
| | |
| | | /** |
| | | * Create a new failure. |
| | | * |
| | | * @param exitCode exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and 255, |
| | | * inclusive. |
| | | * @param msg message to also send to the client's stderr. |
| | | * @param exitCode |
| | | * exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and |
| | | * 255, inclusive. |
| | | * @param msg |
| | | * message to also send to the client's stderr. |
| | | */ |
| | | public UnloggedFailure(final int exitCode, final String msg) { |
| | | this(exitCode, msg, null); |
| | |
| | | /** |
| | | * Create a new failure. |
| | | * |
| | | * @param exitCode exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and 255, |
| | | * inclusive. |
| | | * @param msg message to also send to the client's stderr. |
| | | * @param why stack trace to include in the server's log, but is not sent to |
| | | * the client's stderr. |
| | | * @param exitCode |
| | | * exit code to return the client, which indicates the |
| | | * failure status of this command. Should be between 1 and |
| | | * 255, inclusive. |
| | | * @param msg |
| | | * message to also send to the client's stderr. |
| | | * @param why |
| | | * stack trace to include in the server's log, but is not |
| | | * sent to the client's stderr. |
| | | */ |
| | | public UnloggedFailure(final int exitCode, final String msg, |
| | | final Throwable why) { |
| | | public UnloggedFailure(final int exitCode, final String msg, final Throwable why) { |
| | | super(exitCode, msg, why); |
| | | } |
| | | } |