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