/*
* Copyright (C) 2012 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.crsh.standalone;
import com.sun.tools.attach.VirtualMachine;
import jline.AnsiWindowsTerminal;
import jline.Terminal;
import jline.TerminalFactory;
import jline.console.ConsoleReader;
import jline.internal.Configuration;
import org.crsh.cli.Argument;
import org.crsh.cli.Command;
import org.crsh.cli.Named;
import org.crsh.cli.Option;
import org.crsh.cli.Usage;
import org.crsh.cli.descriptor.CommandDescriptor;
import org.crsh.cli.impl.Delimiter;
import org.crsh.cli.impl.descriptor.IntrospectionException;
import org.crsh.cli.impl.invocation.InvocationMatch;
import org.crsh.cli.impl.invocation.InvocationMatcher;
import org.crsh.cli.impl.lang.CommandFactory;
import org.crsh.cli.impl.lang.Instance;
import org.crsh.cli.impl.lang.Util;
import org.crsh.console.jline.JLineProcessor;
import org.crsh.plugin.ResourceManager;
import org.crsh.shell.Shell;
import org.crsh.shell.ShellFactory;
import org.crsh.shell.impl.remoting.RemoteServer;
import org.crsh.util.CloseableList;
import org.crsh.util.InterruptHandler;
import org.crsh.util.Utils;
import org.crsh.vfs.FS;
import org.crsh.vfs.Path;
import org.crsh.vfs.Resource;
import org.crsh.vfs.spi.Mount;
import org.crsh.vfs.spi.file.FileMountFactory;
import org.crsh.vfs.spi.url.ClassPathMountFactory;
import org.fusesource.jansi.AnsiConsole;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
@Named("crash")
public class CRaSH {
/** . */
private static Logger log = Logger.getLogger(CRaSH.class.getName());
/** . */
private final CommandDescriptor<Instance<CRaSH>> descriptor;
public CRaSH() throws IntrospectionException {
this.descriptor = CommandFactory.DEFAULT.create(CRaSH.class);
}
private void copyCmd(org.crsh.vfs.File src, File dst) throws IOException {
if (src.hasChildren()) {
if (!dst.exists()) {
if (dst.mkdir()) {
log.fine("Could not create dir " + dst.getCanonicalPath());
}
}
if (dst.exists() && dst.isDirectory()) {
for (org.crsh.vfs.File child : src.children()) {
copyCmd(child, new File(dst, child.getName()));
}
}
} else {
if (!dst.exists()) {
Resource resource = src.getResource();
if (resource != null) {
log.info("Copied command " + src.getPath().getValue() + " to " + dst.getCanonicalPath());
Utils.copy(new ByteArrayInputStream(resource.getContent()), new FileOutputStream(dst));
}
}
}
}
private void copyConf(org.crsh.vfs.File src, File dst) throws IOException {
if (!src.hasChildren()) {
if (!dst.exists()) {
Resource resource = ResourceManager.loadConf(src);
if (resource != null) {
log.info("Copied resource " + src.getPath().getValue() + " to " + dst.getCanonicalPath());
Utils.copy(new ByteArrayInputStream(resource.getContent()), new FileOutputStream(dst));
}
}
}
}
private String toString(FS.Builder builder) {
StringBuilder sb = new StringBuilder();
List<Mount<?>> mounts = builder.getMounts();
for (int i = 0;i < mounts.size();i++) {
Mount<?> mount = mounts.get(i);
if (i > 0) {
sb.append(';');
}
sb.append(mount.getValue());
}
return sb.toString();
}
private FS.Builder createBuilder() throws IOException {
FileMountFactory fileDriver = new FileMountFactory(Utils.getCurrentDirectory());
ClassPathMountFactory classpathDriver = new ClassPathMountFactory(Thread.currentThread().getContextClassLoader());
return new FS.Builder().register("file", fileDriver).register("classpath", classpathDriver);
}
@Command
public void main(
@Option(names= {"non-interactive"})
@Usage("non interactive mode, the JVM io will not be used")
Boolean nonInteractive,
@Option(names={"c","cmd"})
@Usage("the command mounts")
String cmd,
@Option(names={"conf"})
@Usage("the conf mounts")
String conf,
@Option(names={"p","property"})
@Usage("set a property of the form a=b")
List<String> properties,
@Option(names = {"cmd-folder"})
@Usage("a folder in which commands should be extracted")
String cmdFolder,
@Option(names = {"conf-folder"})
@Usage("a folder in which configuration should be extracted")
String confFolder,
@Argument(name = "pid")
@Usage("the optional list of JVM process id to attach to")
List<Integer> pids) throws Exception {
//
boolean interactive = nonInteractive == null || !nonInteractive;
//
if (conf == null) {
conf = "classpath:/crash/";
}
FS.Builder confBuilder = createBuilder().mount(conf);
if (confFolder != null) {
File dst = new File(confFolder);
if (!dst.isDirectory()) {
throw new Exception("Directory " + dst.getAbsolutePath() + " does not exist");
}
org.crsh.vfs.File f = confBuilder.build().get(Path.get("/"));
log.info("Extracting conf resources to " + dst.getAbsolutePath());
for (org.crsh.vfs.File child : f.children()) {
if (!child.hasChildren()) {
copyConf(child, new File(dst, child.getName()));
}
}
confBuilder = createBuilder().mount("file", Path.get(dst));
}
//
if (cmd == null) {
cmd = "classpath:/crash/commands/";
}
FS.Builder cmdBuilder = createBuilder().mount(cmd);
if (cmdFolder != null) {
File dst = new File(cmdFolder);
if (!dst.isDirectory()) {
throw new Exception("Directory " + dst.getAbsolutePath() + " does not exist");
}
org.crsh.vfs.File f = cmdBuilder.build().get(Path.get("/"));
log.info("Extracting command resources to " + dst.getAbsolutePath());
copyCmd(f, dst);
cmdBuilder = createBuilder().mount("file", Path.get(dst));
}
//
log.log(Level.INFO, "conf mounts: " + confBuilder.toString());
log.log(Level.INFO, "cmd mounts: " + cmdBuilder.toString());
//
CloseableList closeable = new CloseableList();
Shell shell;
if (pids != null && pids.size() > 0) {
//
if (interactive && pids.size() > 1) {
throw new Exception("Cannot attach to more than one JVM in interactive mode");
}
// Compute classpath
String classpath = System.getProperty("java.class.path");
String sep = System.getProperty("path.separator");
StringBuilder buffer = new StringBuilder();
for (String path : classpath.split(Pattern.quote(sep))) {
File file = new File(path);
if (file.exists()) {
if (buffer.length() > 0) {
buffer.append(' ');
}
buffer.append(file.getCanonicalPath());
}
}
// Create manifest
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.putValue("Agent-Class", Agent.class.getName());
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(Attributes.Name.CLASS_PATH, buffer.toString());
// Create jar file
File agentFile = File.createTempFile("agent", ".jar");
agentFile.deleteOnExit();
JarOutputStream out = new JarOutputStream(new FileOutputStream(agentFile), manifest);
out.close();
log.log(Level.INFO, "Created agent jar " + agentFile.getCanonicalPath());
// Build the options
StringBuilder sb = new StringBuilder();
// Path configuration
sb.append("--cmd ");
Delimiter.EMPTY.escape(toString(cmdBuilder), sb);
sb.append(' ');
sb.append("--conf ");
Delimiter.EMPTY.escape(toString(confBuilder), sb);
sb.append(' ');
// Propagate canonical config
if (properties != null) {
for (String property : properties) {
sb.append("--property ");
Delimiter.EMPTY.escape(property, sb);
sb.append(' ');
}
}
//
if (interactive) {
RemoteServer server = new RemoteServer(0);
int port = server.bind();
log.log(Level.INFO, "Callback server set on port " + port);
sb.append(port);
String options = sb.toString();
Integer pid = pids.get(0);
final VirtualMachine vm = VirtualMachine.attach("" + pid);
log.log(Level.INFO, "Loading agent with command " + options + " as agent " + agentFile.getCanonicalPath());
vm.loadAgent(agentFile.getCanonicalPath(), options);
server.accept();
shell = server.getShell();
closeable.add(new Closeable() {
public void close() throws IOException {
vm.detach();
}
});
} else {
for (Integer pid : pids) {
log.log(Level.INFO, "Attaching to remote process " + pid);
VirtualMachine vm = VirtualMachine.attach("" + pid);
String options = sb.toString();
log.log(Level.INFO, "Loading agent with command " + options + " as agent " + agentFile.getCanonicalPath());
vm.loadAgent(agentFile.getCanonicalPath(), options);
}
shell = null;
}
} else {
final Bootstrap bootstrap = new Bootstrap(
Thread.currentThread().getContextClassLoader(),
confBuilder.build(),
cmdBuilder.build());
//
if (properties != null) {
Properties config = new Properties();
for (String property : properties) {
int index = property.indexOf('=');
if (index == -1) {
config.setProperty(property, "");
} else {
config.setProperty(property.substring(0, index), property.substring(index + 1));
}
}
bootstrap.setConfig(config);
}
// Register shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Should trigger some kind of run interruption
}
});
// Do bootstrap
bootstrap.bootstrap();
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
bootstrap.shutdown();
}
});
//
if (interactive) {
ShellFactory factory = bootstrap.getContext().getPlugin(ShellFactory.class);
shell = factory.create(null);
} else {
shell = null;
}
closeable = null;
}
//
if (shell != null) {
//
final Terminal term = TerminalFactory.create();
//
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
term.restore();
}
catch (Exception ignore) {
}
}
});
//
String encoding = Configuration.getEncoding();
// Use AnsiConsole only if term doesn't support Ansi
PrintStream out;
PrintStream err;
boolean ansi;
if (term.isAnsiSupported()) {
out = new PrintStream(new BufferedOutputStream(term.wrapOutIfNeeded(new FileOutputStream(FileDescriptor.out)), 16384), false, encoding);
err = new PrintStream(new BufferedOutputStream(term.wrapOutIfNeeded(new FileOutputStream(FileDescriptor.err)), 16384), false, encoding);
ansi = true;
} else {
out = AnsiConsole.out;
err = AnsiConsole.err;
ansi = false;
}
//
FileInputStream in = new FileInputStream(FileDescriptor.in);
ConsoleReader reader = new ConsoleReader(null, in, out, term);
//
final JLineProcessor processor = new JLineProcessor(ansi, shell, reader, out);
//
InterruptHandler interruptHandler = new InterruptHandler(new Runnable() {
@Override
public void run() {
processor.interrupt();
}
});
interruptHandler.install();
//
Thread thread = new Thread(processor);
thread.setDaemon(true);
thread.start();
//
try {
processor.closed();
}
catch (Throwable t) {
t.printStackTrace();
}
finally {
//
if (closeable != null) {
Utils.close(closeable);
}
// Force exit
System.exit(0);
}
}
}
public static void main(String[] args) throws Exception {
StringBuilder line = new StringBuilder();
for (int i = 0;i < args.length;i++) {
if (i > 0) {
line.append(' ');
}
Delimiter.EMPTY.escape(args[i], line);
}
//
CRaSH main = new CRaSH();
InvocationMatcher<Instance<CRaSH>> matcher = main.descriptor.matcher();
InvocationMatch<Instance<CRaSH>> match = matcher.parse(line.toString());
match.invoke(Util.wrap(main));
}
}