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