001 /* 002 * Copyright (C) 2012 eXo Platform SAS. 003 * 004 * This is free software; you can redistribute it and/or modify it 005 * under the terms of the GNU Lesser General Public License as 006 * published by the Free Software Foundation; either version 2.1 of 007 * the License, or (at your option) any later version. 008 * 009 * This software is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this software; if not, write to the Free 016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 018 */ 019 020 package org.crsh.standalone; 021 022 import com.sun.tools.attach.VirtualMachine; 023 import org.crsh.cli.impl.descriptor.CommandDescriptorImpl; 024 import jline.Terminal; 025 import jline.TerminalFactory; 026 import jline.console.ConsoleReader; 027 import org.crsh.cli.impl.Delimiter; 028 import org.crsh.cli.impl.descriptor.IntrospectionException; 029 import org.crsh.cli.Argument; 030 import org.crsh.cli.Command; 031 import org.crsh.cli.Option; 032 import org.crsh.cli.Usage; 033 import org.crsh.cli.impl.lang.CommandFactory; 034 import org.crsh.cli.impl.invocation.InvocationMatch; 035 import org.crsh.cli.impl.invocation.InvocationMatcher; 036 import org.crsh.processor.jline.JLineProcessor; 037 import org.crsh.shell.Shell; 038 import org.crsh.shell.ShellFactory; 039 import org.crsh.shell.impl.remoting.RemoteServer; 040 import org.crsh.util.CloseableList; 041 import org.crsh.util.IO; 042 import org.crsh.util.InterruptHandler; 043 import org.crsh.util.Safe; 044 import org.crsh.vfs.FS; 045 import org.crsh.vfs.Path; 046 import org.crsh.vfs.Resource; 047 import org.fusesource.jansi.AnsiConsole; 048 049 import java.io.ByteArrayInputStream; 050 import java.io.Closeable; 051 import java.io.File; 052 import java.io.FileDescriptor; 053 import java.io.FileInputStream; 054 import java.io.FileOutputStream; 055 import java.io.IOException; 056 import java.io.PrintWriter; 057 import java.util.List; 058 import java.util.Properties; 059 import java.util.jar.Attributes; 060 import java.util.jar.JarOutputStream; 061 import java.util.jar.Manifest; 062 import java.util.logging.Level; 063 import java.util.logging.Logger; 064 import java.util.regex.Pattern; 065 066 public class CRaSH { 067 068 /** . */ 069 private static Logger log = Logger.getLogger(CRaSH.class.getName()); 070 071 /** . */ 072 private final CommandDescriptorImpl<CRaSH> descriptor; 073 074 public CRaSH() throws IntrospectionException { 075 this.descriptor = CommandFactory.DEFAULT.create(CRaSH.class); 076 } 077 078 private void copy(org.crsh.vfs.File src, File dst) throws IOException { 079 if (src.isDir()) { 080 if (!dst.exists()) { 081 if (dst.mkdir()) { 082 log.fine("Could not create dir " + dst.getCanonicalPath()); 083 } 084 } 085 if (dst.exists() && dst.isDirectory()) { 086 for (org.crsh.vfs.File child : src.children()) { 087 copy(child, new File(dst, child.getName())); 088 } 089 } 090 } else { 091 if (!dst.exists()) { 092 Resource resource = src.getResource(); 093 if (resource != null) { 094 log.info("Copied resource " + src.getPath().getValue() + " to " + dst.getCanonicalPath()); 095 IO.copy(new ByteArrayInputStream(resource.getContent()), new FileOutputStream(dst)); 096 } 097 } 098 } 099 } 100 101 @Command 102 public void main( 103 @Option(names= {"non-interactive"}) 104 @Usage("non interactive mode, the JVM io will not be used") 105 Boolean nonInteractive, 106 @Option(names={"c","cmd"}) 107 @Usage("adds a dir to the command path") 108 List<String> cmds, 109 @Option(names={"conf"}) 110 @Usage("adds a dir to the conf path") 111 List<String> confs, 112 @Option(names={"p","property"}) 113 @Usage("set a property of the form a=b") 114 List<String> properties, 115 @Option(names = {"cmd-mode"}) 116 @Usage("the cmd mode (read or copy), copy mode requires at least one cmd path to be specified") 117 ResourceMode cmdMode, 118 @Option(names = {"conf-mode"}) 119 @Usage("the conf mode (read of copy), copy mode requires at least one conf path to be specified") 120 ResourceMode confMode, 121 @Argument(name = "pid") 122 @Usage("the optional list of JVM process id to attach to") 123 List<Integer> pids) throws Exception { 124 125 // 126 boolean copyCmd = cmdMode != ResourceMode.read && cmds != null && cmds.size() > 0; 127 boolean copyConf = confMode != ResourceMode.read && confs != null && confs.size() > 0; 128 boolean interactive = nonInteractive == null || !nonInteractive; 129 130 // 131 if (copyCmd) { 132 File dst = new File(cmds.get(0)); 133 if (!dst.isDirectory()) { 134 throw new Exception("Directory " + dst.getAbsolutePath() + " does not exist"); 135 } 136 FS fs = new FS(); 137 fs.mount(Thread.currentThread().getContextClassLoader(), Path.get("/crash/commands/")); 138 org.crsh.vfs.File f = fs.get(Path.get("/")); 139 log.info("Copying command classpath resources"); 140 copy(f, dst); 141 } 142 143 // 144 if (copyConf) { 145 File dst = new File(confs.get(0)); 146 if (!dst.isDirectory()) { 147 throw new Exception("Directory " + dst.getAbsolutePath() + " does not exist"); 148 } 149 FS fs = new FS(); 150 fs.mount(Thread.currentThread().getContextClassLoader(), Path.get("/crash/")); 151 org.crsh.vfs.File f = fs.get(Path.get("/")); 152 log.info("Copying conf classpath resources"); 153 for (org.crsh.vfs.File child : f.children()) { 154 if (!child.isDir()) { 155 copy(child, new File(dst, child.getName())); 156 } 157 } 158 } 159 160 // 161 CloseableList closeable = new CloseableList(); 162 Shell shell; 163 if (pids != null && pids.size() > 0) { 164 165 // 166 if (interactive && pids.size() > 1) { 167 throw new Exception("Cannot attach to more than one JVM in interactive mode"); 168 } 169 170 // Compute classpath 171 String classpath = System.getProperty("java.class.path"); 172 String sep = System.getProperty("path.separator"); 173 StringBuilder buffer = new StringBuilder(); 174 for (String path : classpath.split(Pattern.quote(sep))) { 175 File file = new File(path); 176 if (file.exists()) { 177 if (buffer.length() > 0) { 178 buffer.append(' '); 179 } 180 buffer.append(file.getCanonicalPath()); 181 } 182 } 183 184 // Create manifest 185 Manifest manifest = new Manifest(); 186 Attributes attributes = manifest.getMainAttributes(); 187 attributes.putValue("Agent-Class", Agent.class.getName()); 188 attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 189 attributes.put(Attributes.Name.CLASS_PATH, buffer.toString()); 190 191 // Create jar file 192 File agentFile = File.createTempFile("agent", ".jar"); 193 agentFile.deleteOnExit(); 194 JarOutputStream out = new JarOutputStream(new FileOutputStream(agentFile), manifest); 195 out.close(); 196 log.log(Level.INFO, "Created agent jar " + agentFile.getCanonicalPath()); 197 198 // Build the options 199 StringBuilder sb = new StringBuilder(); 200 201 // Rewrite canonical path 202 if (copyCmd) { 203 sb.append("--cmd-mode copy "); 204 } else { 205 sb.append("--cmd-mode read "); 206 } 207 if (cmds != null) { 208 for (String cmd : cmds) { 209 File cmdPath = new File(cmd); 210 if (cmdPath.exists()) { 211 sb.append("--cmd "); 212 Delimiter.EMPTY.escape(cmdPath.getCanonicalPath(), sb); 213 sb.append(' '); 214 } 215 } 216 } 217 218 // Rewrite canonical path 219 if (copyCmd) { 220 sb.append("--conf-mode copy "); 221 } else { 222 sb.append("--conf-mode read "); 223 } 224 if (confs != null) { 225 for (String conf : confs) { 226 File confPath = new File(conf); 227 if (confPath.exists()) { 228 sb.append("--conf "); 229 Delimiter.EMPTY.escape(confPath.getCanonicalPath(), sb); 230 sb.append(' '); 231 } 232 } 233 } 234 235 // Propagate canonical config 236 if (properties != null) { 237 for (String property : properties) { 238 sb.append("--property "); 239 Delimiter.EMPTY.escape(property, sb); 240 sb.append(' '); 241 } 242 } 243 244 // 245 if (interactive) { 246 RemoteServer server = new RemoteServer(0); 247 int port = server.bind(); 248 log.log(Level.INFO, "Callback server set on port " + port); 249 sb.append(port); 250 String options = sb.toString(); 251 Integer pid = pids.get(0); 252 final VirtualMachine vm = VirtualMachine.attach("" + pid); 253 log.log(Level.INFO, "Loading agent with command " + options + " as agent " + agentFile.getCanonicalPath()); 254 vm.loadAgent(agentFile.getCanonicalPath(), options); 255 server.accept(); 256 shell = server.getShell(); 257 closeable.add(new Closeable() { 258 public void close() throws IOException { 259 vm.detach(); 260 } 261 }); 262 } else { 263 for (Integer pid : pids) { 264 log.log(Level.INFO, "Attaching to remote process " + pid); 265 VirtualMachine vm = VirtualMachine.attach("" + pid); 266 String options = sb.toString(); 267 log.log(Level.INFO, "Loading agent with command " + options + " as agent " + agentFile.getCanonicalPath()); 268 vm.loadAgent(agentFile.getCanonicalPath(), options); 269 } 270 shell = null; 271 } 272 } else { 273 final Bootstrap bootstrap = new Bootstrap(Thread.currentThread().getContextClassLoader()); 274 275 // 276 if (!copyCmd) { 277 bootstrap.addToCmdPath(Path.get("/crash/commands/")); 278 } 279 if (cmds != null) { 280 for (String cmd : cmds) { 281 File cmdPath = new File(cmd); 282 bootstrap.addToCmdPath(cmdPath); 283 } 284 } 285 286 // 287 if (!copyConf) { 288 bootstrap.addToConfPath(Path.get("/crash/")); 289 } 290 if (confs != null) { 291 for (String conf : confs) { 292 File confPath = new File(conf); 293 bootstrap.addToConfPath(confPath); 294 } 295 } 296 297 // 298 if (properties != null) { 299 Properties config = new Properties(); 300 for (String property : properties) { 301 int index = property.indexOf('='); 302 if (index == -1) { 303 config.setProperty(property, ""); 304 } else { 305 config.setProperty(property.substring(0, index), property.substring(index + 1)); 306 } 307 } 308 bootstrap.setConfig(config); 309 } 310 311 // Register shutdown hook 312 Runtime.getRuntime().addShutdownHook(new Thread() { 313 @Override 314 public void run() { 315 // Should trigger some kind of run interruption 316 } 317 }); 318 319 // Do bootstrap 320 bootstrap.bootstrap(); 321 Runtime.getRuntime().addShutdownHook(new Thread(){ 322 @Override 323 public void run() { 324 bootstrap.shutdown(); 325 } 326 }); 327 328 // 329 if (interactive) { 330 ShellFactory factory = bootstrap.getContext().getPlugin(ShellFactory.class); 331 shell = factory.create(null); 332 } else { 333 shell = null; 334 } 335 closeable = null; 336 } 337 338 // 339 if (shell != null) { 340 341 // Start crash for this command line 342 final Terminal term = TerminalFactory.create(); 343 term.init(); 344 ConsoleReader reader = new ConsoleReader(null, new FileInputStream(FileDescriptor.in), System.out, term); 345 Runtime.getRuntime().addShutdownHook(new Thread(){ 346 @Override 347 public void run() { 348 try { 349 term.restore(); 350 } 351 catch (Exception ignore) { 352 } 353 } 354 }); 355 356 AnsiConsole.systemInstall(); 357 358 final PrintWriter out = new PrintWriter(AnsiConsole.out); 359 final JLineProcessor processor = new JLineProcessor( 360 shell, 361 reader, 362 out 363 ); 364 reader.addCompleter(processor); 365 366 // Install signal handler 367 InterruptHandler ih = new InterruptHandler(new Runnable() { 368 public void run() { 369 processor.cancel(); 370 } 371 }); 372 ih.install(); 373 374 // 375 try { 376 processor.run(); 377 } 378 catch (Throwable t) { 379 t.printStackTrace(); 380 } 381 finally { 382 383 // 384 AnsiConsole.systemUninstall(); 385 386 // 387 if (closeable != null) { 388 Safe.close(closeable); 389 } 390 391 // Force exit 392 System.exit(0); 393 } 394 } 395 } 396 397 public static void main(String[] args) throws Exception { 398 399 StringBuilder line = new StringBuilder(); 400 for (int i = 0;i < args.length;i++) { 401 if (i > 0) { 402 line.append(' '); 403 } 404 Delimiter.EMPTY.escape(args[i], line); 405 } 406 407 // 408 CRaSH main = new CRaSH(); 409 InvocationMatcher<CRaSH> matcher = main.descriptor.invoker("main"); 410 InvocationMatch<CRaSH> match = matcher.match(line.toString()); 411 match.invoke(new CRaSH()); 412 } 413 }