| | |
| | | // Copyright (C) 2009 The Android Open Source Project |
| | | // |
| | | // Licensed under the Apache License, Version 2.0 (the "License"); |
| | | // you may not use this file except in compliance with the License. |
| | | // You may obtain a copy of the License at |
| | | // |
| | | // http://www.apache.org/licenses/LICENSE-2.0 |
| | | // |
| | | // Unless required by applicable law or agreed to in writing, software |
| | | // distributed under the License is distributed on an "AS IS" BASIS, |
| | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | // See the License for the specific language governing permissions and |
| | | // limitations under the License. |
| | | |
| | | /* |
| | | * Copyright (C) 2009 The Android Open Source Project |
| | | * Copyright 2014 gitblit.com. |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | | * you may not use this file except in compliance with the License. |
| | | * You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | */ |
| | | package com.gitblit.transport.ssh.commands; |
| | | |
| | | import java.io.BufferedWriter; |
| | |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import com.gitblit.transport.ssh.SshCommandContext; |
| | | import com.gitblit.utils.IdGenerator; |
| | | import com.gitblit.Keys; |
| | | import com.gitblit.utils.StringUtils; |
| | | import com.gitblit.utils.WorkQueue; |
| | | import com.gitblit.utils.WorkQueue.CancelableRunnable; |
| | | import com.gitblit.utils.cli.CmdLineParser; |
| | |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(BaseCommand.class); |
| | | |
| | | /** Ssh context */ |
| | | protected SshCommandContext ctx; |
| | | private static final int PRIVATE_STATUS = 1 << 30; |
| | | |
| | | public final static int STATUS_CANCEL = PRIVATE_STATUS | 1; |
| | | |
| | | public final static int STATUS_NOT_FOUND = PRIVATE_STATUS | 2; |
| | | |
| | | public final static int STATUS_NOT_ADMIN = PRIVATE_STATUS | 3; |
| | | |
| | | protected InputStream in; |
| | | |
| | |
| | | |
| | | protected ServerSession session; |
| | | |
| | | /** Ssh command context */ |
| | | private SshCommandContext ctx; |
| | | |
| | | /** Text of the command line which lead up to invoking this instance. */ |
| | | private String commandName = ""; |
| | | |
| | |
| | | /** The task, as scheduled on a worker thread. */ |
| | | private final AtomicReference<Future<?>> task; |
| | | |
| | | private final WorkQueue.Executor executor; |
| | | private WorkQueue workQueue; |
| | | |
| | | public BaseCommand() { |
| | | task = Atomics.newReference(); |
| | | IdGenerator gen = new IdGenerator(); |
| | | WorkQueue w = new WorkQueue(gen); |
| | | this.executor = w.getDefaultQueue(); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public void destroy() { |
| | | log.debug("destroying " + getClass().getName()); |
| | | Future<?> future = task.getAndSet(null); |
| | | if (future != null && !future.isDone()) { |
| | | future.cancel(true); |
| | | } |
| | | session = null; |
| | | ctx = null; |
| | | } |
| | | |
| | | protected static PrintWriter toPrintWriter(final OutputStream o) { |
| | |
| | | @Override |
| | | public abstract void start(Environment env) throws IOException; |
| | | |
| | | protected void provideStateTo(final BaseCommand cmd) { |
| | | cmd.setContext(ctx); |
| | | cmd.setWorkQueue(workQueue); |
| | | cmd.setInputStream(in); |
| | | cmd.setOutputStream(out); |
| | | cmd.setErrorStream(err); |
| | | cmd.setExitCallback(exit); |
| | | } |
| | | |
| | | public WorkQueue getWorkQueue() { |
| | | return workQueue; |
| | | } |
| | | |
| | | public void setWorkQueue(WorkQueue workQueue) { |
| | | this.workQueue = workQueue; |
| | | } |
| | | |
| | | public void setContext(SshCommandContext ctx) { |
| | | this.ctx = ctx; |
| | | } |
| | | |
| | | public SshCommandContext getContext() { |
| | | return ctx; |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Override |
| | | public void setExitCallback(final ExitCallback callback) { |
| | | this.exit = callback; |
| | | } |
| | | |
| | | protected void provideBaseStateTo(final Command cmd) { |
| | | if (cmd instanceof BaseCommand) { |
| | | ((BaseCommand) cmd).setContext(ctx); |
| | | } |
| | | cmd.setInputStream(in); |
| | | cmd.setOutputStream(out); |
| | | cmd.setErrorStream(err); |
| | | cmd.setExitCallback(exit); |
| | | } |
| | | |
| | | protected String getName() { |
| | |
| | | } |
| | | |
| | | if (clp.wasHelpRequestedByOption()) { |
| | | CommandMetaData meta = getClass().getAnnotation(CommandMetaData.class); |
| | | String title = meta.name().toUpperCase() + ": " + meta.description(); |
| | | String b = com.gitblit.utils.StringUtils.leftPad("", title.length() + 2, '═'); |
| | | StringWriter msg = new StringWriter(); |
| | | clp.printDetailedUsage(commandName, msg); |
| | | msg.write(usage()); |
| | | msg.write('\n'); |
| | | msg.write(b); |
| | | msg.write('\n'); |
| | | msg.write(' '); |
| | | msg.write(title); |
| | | msg.write('\n'); |
| | | msg.write(b); |
| | | msg.write("\n\n"); |
| | | msg.write("USAGE\n"); |
| | | msg.write("─────\n"); |
| | | msg.write(' '); |
| | | msg.write(commandName); |
| | | msg.write('\n'); |
| | | msg.write(" "); |
| | | clp.printSingleLineUsage(msg, null); |
| | | msg.write("\n\n"); |
| | | String txt = getUsageText(); |
| | | if (!StringUtils.isEmpty(txt)) { |
| | | msg.write(txt); |
| | | msg.write("\n\n"); |
| | | } |
| | | msg.write("ARGUMENTS & OPTIONS\n"); |
| | | msg.write("───────────────────\n"); |
| | | clp.printUsage(msg, null); |
| | | msg.write('\n'); |
| | | String examples = usage().trim(); |
| | | if (!StringUtils.isEmpty(examples)) { |
| | | msg.write('\n'); |
| | | msg.write("EXAMPLES\n"); |
| | | msg.write("────────\n"); |
| | | msg.write(examples); |
| | | msg.write('\n'); |
| | | } |
| | | |
| | | throw new UnloggedFailure(1, msg.toString()); |
| | | } |
| | | } |
| | |
| | | return new CmdLineParser(options); |
| | | } |
| | | |
| | | protected String usage() { |
| | | public String usage() { |
| | | Class<? extends BaseCommand> clazz = getClass(); |
| | | if (clazz.isAnnotationPresent(UsageExamples.class)) { |
| | | return examples(clazz.getAnnotation(UsageExamples.class).examples()); |
| | | } else if (clazz.isAnnotationPresent(UsageExample.class)) { |
| | | return examples(clazz.getAnnotation(UsageExample.class)); |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | protected String getUsageText() { |
| | | return ""; |
| | | } |
| | | |
| | | protected String examples(UsageExample... examples) { |
| | | int sshPort = getContext().getGitblit().getSettings().getInteger(Keys.git.sshPort, 29418); |
| | | String username = getContext().getClient().getUsername(); |
| | | String hostname = "localhost"; |
| | | String ssh = String.format("ssh -l %s -p %d %s", username, sshPort, hostname); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (UsageExample example : examples) { |
| | | sb.append(example.description()).append("\n\n"); |
| | | String syntax = example.syntax(); |
| | | syntax = syntax.replace("${ssh}", ssh); |
| | | syntax = syntax.replace("${username}", username); |
| | | syntax = syntax.replace("${cmd}", commandName); |
| | | sb.append(" ").append(syntax).append("\n\n"); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | protected void showHelp() throws UnloggedFailure { |
| | | argv = new String [] { "--help" }; |
| | | parseCommandLine(); |
| | | } |
| | | |
| | | private final class TaskThunk implements CancelableRunnable { |
| | |
| | | public void cancel() { |
| | | synchronized (this) { |
| | | try { |
| | | // onExit(/*STATUS_CANCEL*/); |
| | | onExit(STATUS_CANCEL); |
| | | } finally { |
| | | ctx = null; |
| | | } |
| | |
| | | } |
| | | |
| | | /** Runnable function which can throw an exception. */ |
| | | public static interface CommandRunnable { |
| | | public void run() throws Exception; |
| | | public interface CommandRunnable { |
| | | 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(); |
| | | public interface RepositoryCommandRunnable extends CommandRunnable { |
| | | String getRepository(); |
| | | } |
| | | |
| | | /** |
| | |
| | | /** |
| | | * Terminate this command and return a result code to the remote client. |
| | | * <p> |
| | | * Commands should invoke this at most once. Once invoked, the command may |
| | | * lose access to request based resources as any callbacks previously |
| | | * registered with {@link RequestCleanup} will fire. |
| | | * Commands should invoke this at most once. |
| | | * |
| | | * @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); |
| | | // if (cleanup != null) { |
| | | // cleanup.run(); |
| | | // } |
| | | } |
| | | |
| | | 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 |
| | |
| | | } else { |
| | | final StringBuilder m = new StringBuilder(); |
| | | m.append("Internal server error"); |
| | | // if (userProvider.get().isIdentifiedUser()) { |
| | | // final IdentifiedUser u = (IdentifiedUser) userProvider.get(); |
| | | // m.append(" (user "); |
| | | // m.append(u.getAccount().getUserName()); |
| | | // m.append(" account "); |
| | | // m.append(u.getAccountId()); |
| | | // m.append(")"); |
| | | // } |
| | | // m.append(" during "); |
| | | // m.append(contextProvider.get().getCommandLine()); |
| | | String user = ctx.getClient().getUsername(); |
| | | if (user != null) { |
| | | m.append(" (user "); |
| | | m.append(user); |
| | | m.append(")"); |
| | | } |
| | | m.append(" during "); |
| | | m.append(ctx.getCommandLine()); |
| | | log.error(m.toString(), e); |
| | | } |
| | | |
| | |
| | | */ |
| | | protected void startThread(final CommandRunnable thunk) { |
| | | final TaskThunk tt = new TaskThunk(thunk); |
| | | task.set(executor.submit(tt)); |
| | | task.set(workQueue.getDefaultQueue().submit(tt)); |
| | | } |
| | | |
| | | /** Thrown from {@link CommandRunnable#run()} with client message and code. */ |