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