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    }