Package com.gitblit.transport.ssh.commands

Source Code of com.gitblit.transport.ssh.commands.SshCommandFactory$Trampoline

/*
* 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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitblit.Keys;
import com.gitblit.manager.IGitblit;
import com.gitblit.transport.ssh.SshDaemonClient;
import com.gitblit.utils.WorkQueue;
import com.google.common.util.concurrent.Atomics;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

public class SshCommandFactory implements CommandFactory {
  private static final Logger logger = LoggerFactory.getLogger(SshCommandFactory.class);

  private final WorkQueue workQueue;
  private final IGitblit gitblit;
  private final ScheduledExecutorService startExecutor;
  private final ExecutorService destroyExecutor;

  public SshCommandFactory(IGitblit gitblit, WorkQueue workQueue) {
    this.gitblit = gitblit;
    this.workQueue = workQueue;

    int threads = gitblit.getSettings().getInteger(Keys.git.sshCommandStartThreads, 2);
    startExecutor = workQueue.createQueue(threads, "SshCommandStart");
    destroyExecutor = Executors.newSingleThreadExecutor(
        new ThreadFactoryBuilder()
          .setNameFormat("SshCommandDestroy-%s")
          .setDaemon(true)
          .build());
  }

  public void stop() {
    destroyExecutor.shutdownNow();
  }

  public RootDispatcher createRootDispatcher(SshDaemonClient client, String commandLine) {
    return new RootDispatcher(gitblit, client, commandLine, workQueue);
  }

  @Override
  public Command createCommand(final String commandLine) {
    return new Trampoline(commandLine);
  }

  private class Trampoline implements Command, SessionAware {
    private final String[] argv;
    private ServerSession session;
    private InputStream in;
    private OutputStream out;
    private OutputStream err;
    private ExitCallback exit;
    private Environment env;
    private String cmdLine;
    private DispatchCommand cmd;
    private final AtomicBoolean logged;
    private final AtomicReference<Future<?>> task;

    Trampoline(String line) {
      if (line.startsWith("git-")) {
        line = "git " + line;
      }
      cmdLine = line;
      argv = split(line);
      logged = new AtomicBoolean();
      task = Atomics.newReference();
    }

    @Override
    public void setSession(ServerSession session) {
      this.session = session;
    }

    @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;
    }

    @Override
    public void start(final Environment env) throws IOException {
      this.env = env;
      task.set(startExecutor.submit(new Runnable() {
        @Override
        public void run() {
          try {
            onStart();
          } catch (Exception e) {
            logger.warn("Cannot start command ", e);
          }
        }

        @Override
        public String toString() {
          return "start (user " + session.getUsername() + ")";
        }
      }));
    }

    private void onStart() throws IOException {
      synchronized (this) {
        SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
        try {
          cmd = createRootDispatcher(client, cmdLine);
          cmd.setArguments(argv);
          cmd.setInputStream(in);
          cmd.setOutputStream(out);
          cmd.setErrorStream(err);
          cmd.setExitCallback(new ExitCallback() {
            @Override
            public void onExit(int rc, String exitMessage) {
              exit.onExit(translateExit(rc), exitMessage);
              log(rc);
            }

            @Override
            public void onExit(int rc) {
              exit.onExit(translateExit(rc));
              log(rc);
            }
          });
          cmd.start(env);
        } finally {
          client = null;
        }
      }
    }

    private int translateExit(final int rc) {
      switch (rc) {
      case BaseCommand.STATUS_NOT_ADMIN:
        return 1;

      case BaseCommand.STATUS_CANCEL:
        return 15 /* SIGKILL */;

      case BaseCommand.STATUS_NOT_FOUND:
        return 127 /* POSIX not found */;

      default:
        return rc;
      }
    }

    private void log(final int rc) {
      if (logged.compareAndSet(false, true)) {
        logger.info("onExecute: {} exits with: {}", cmd.getClass().getSimpleName(), rc);
      }
    }

    @Override
    public void destroy() {
      Future<?> future = task.getAndSet(null);
      if (future != null) {
        future.cancel(true);
        destroyExecutor.execute(new Runnable() {
          @Override
          public void run() {
            onDestroy();
          }
        });
      }
    }

    private void onDestroy() {
      synchronized (this) {
        if (cmd != null) {
          try {
            cmd.destroy();
          } finally {
            cmd = null;
          }
        }
      }
    }
  }

  /** Split a command line into a string array. */
  static public String[] split(String commandLine) {
    final List<String> list = new ArrayList<String>();
    boolean inquote = false;
    boolean inDblQuote = false;
    StringBuilder r = new StringBuilder();
    for (int ip = 0; ip < commandLine.length();) {
      final char b = commandLine.charAt(ip++);
      switch (b) {
      case '\t':
      case ' ':
        if (inquote || inDblQuote)
          r.append(b);
        else if (r.length() > 0) {
          list.add(r.toString());
          r = new StringBuilder();
        }
        continue;
      case '\"':
        if (inquote)
          r.append(b);
        else
          inDblQuote = !inDblQuote;
        continue;
      case '\'':
        if (inDblQuote)
          r.append(b);
        else
          inquote = !inquote;
        continue;
      case '\\':
        if (inquote || ip == commandLine.length())
          r.append(b); // literal within a quote
        else
          r.append(commandLine.charAt(ip++));
        continue;
      default:
        r.append(b);
        continue;
      }
    }
    if (r.length() > 0) {
      list.add(r.toString());
    }
    return list.toArray(new String[list.size()]);
  }
}
TOP

Related Classes of com.gitblit.transport.ssh.commands.SshCommandFactory$Trampoline

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.