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.Terminal;
024    import jline.TerminalFactory;
025    import jline.console.ConsoleReader;
026    import org.crsh.cmdline.ClassDescriptor;
027    import org.crsh.cmdline.CommandFactory;
028    import org.crsh.cmdline.Delimiter;
029    import org.crsh.cmdline.IntrospectionException;
030    import org.crsh.cmdline.annotations.Argument;
031    import org.crsh.cmdline.annotations.Command;
032    import org.crsh.cmdline.annotations.Option;
033    import org.crsh.cmdline.annotations.Usage;
034    import org.crsh.cmdline.matcher.CommandMatch;
035    import org.crsh.cmdline.matcher.Matcher;
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.InterruptHandler;
042    import org.crsh.util.Safe;
043    
044    import java.io.Closeable;
045    import java.io.File;
046    import java.io.FileDescriptor;
047    import java.io.FileInputStream;
048    import java.io.IOException;
049    import java.io.PrintWriter;
050    import java.net.*;
051    import java.util.List;
052    import java.util.Properties;
053    import java.util.logging.Level;
054    import java.util.logging.Logger;
055    
056    public class CRaSH {
057    
058      /** . */
059      private static Logger log = Logger.getLogger(CRaSH.class.getName());
060    
061      /** . */
062      private final ClassDescriptor<CRaSH> descriptor;
063    
064      public CRaSH() throws IntrospectionException {
065        this.descriptor = CommandFactory.DEFAULT.create(CRaSH.class);
066      }
067    
068      @Command
069      public void main(
070        @Option(names = {"h","help"})
071        @Usage("display standalone mode help")
072        Boolean help,
073        @Option(names={"j","jar"})
074        @Usage("specify a file system path of a jar added to the class path")
075        List<String> jars,
076        @Option(names={"c","cmd"})
077        @Usage("specify a file system path of a dir added to the command path")
078        List<String> cmds,
079        @Option(names={"conf"})
080        @Usage("specify a file system path of a dir added to the configuration path")
081        List<String> confs,
082        @Option(names={"p","property"})
083        @Usage("specify a configuration property of the form a=b")
084        List<String> properties,
085        @Argument(name = "pid")
086        @Usage("the optional JVM process id to attach to")
087        Integer pid) throws Exception {
088    
089        //
090        if (Boolean.TRUE.equals(help)) {
091          descriptor.printUsage(System.out);
092        } else {
093    
094          CloseableList closeable = new CloseableList();
095          Shell shell;
096          if (pid != null) {
097    
098            // Standalone
099            URL url = CRaSH.class.getProtectionDomain().getCodeSource().getLocation();
100            java.io.File f = new java.io.File(url.toURI());
101            log.log(Level.INFO, "Attaching to remote process " + pid);
102            final VirtualMachine vm = VirtualMachine.attach("" + pid);
103    
104            //
105            RemoteServer server = new RemoteServer(0);
106            int port = server.bind();
107            log.log(Level.INFO, "Callback server set on port " + port);
108    
109            // Build the options
110            StringBuilder sb = new StringBuilder();
111    
112            // Rewrite canonical path
113            if (cmds != null) {
114              for (String cmd : cmds) {
115                File cmdPath = new File(cmd);
116                if (cmdPath.exists()) {
117                  sb.append("--cmd ");
118                  Delimiter.EMPTY.escape(cmdPath.getCanonicalPath(), sb);
119                  sb.append(' ');
120                }
121              }
122            }
123    
124            // Rewrite canonical path
125            if (confs != null) {
126              for (String conf : confs) {
127                File confPath = new File(conf);
128                if (confPath.exists()) {
129                  sb.append("--conf ");
130                  Delimiter.EMPTY.escape(confPath.getCanonicalPath(), sb);
131                  sb.append(' ');
132                }
133              }
134            }
135    
136            // Rewrite canonical path
137            if (jars != null) {
138              for (String jar : jars) {
139                File jarPath = new File(jar);
140                if (jarPath.exists()) {
141                  sb.append("--jar ");
142                  Delimiter.EMPTY.escape(jarPath.getCanonicalPath(), sb);
143                  sb.append(' ');
144                }
145              }
146            }
147    
148            // Propagate canonical config
149            if (properties != null) {
150              for (String property : properties) {
151                sb.append("--property ");
152                Delimiter.EMPTY.escape(property, sb);
153                sb.append(' ');
154              }
155            }
156    
157            // Append callback port
158            sb.append(port);
159    
160            //
161            String options = sb.toString();
162            log.log(Level.INFO, "Loading agent with command " + options);
163            vm.loadAgent(f.getCanonicalPath(), options);
164    
165            //
166            server.accept();
167    
168            //
169            shell = server.getShell();
170            closeable.add(new Closeable() {
171              public void close() throws IOException {
172                vm.detach();
173              }
174            });
175          } else {
176            final Bootstrap bootstrap = new Bootstrap(Thread.currentThread().getContextClassLoader());
177    
178            //
179            if (cmds != null) {
180              for (String cmd : cmds) {
181                File cmdPath = new File(cmd);
182                bootstrap.addCmdPath(cmdPath);
183              }
184            }
185    
186            //
187            if (confs != null) {
188              for (String conf : confs) {
189                File confPath = new File(conf);
190                bootstrap.addConfPath(confPath);
191              }
192            }
193    
194            //
195            if (jars != null) {
196              for (String jar : jars) {
197                File jarPath = new File(jar);
198                bootstrap.addJarPath(jarPath);
199              }
200            }
201    
202            //
203            if (properties != null) {
204              Properties config = new Properties();
205              for (String property : properties) {
206                int index = property.indexOf('=');
207                if (index == -1) {
208                  config.setProperty(property, "");
209                } else {
210                  config.setProperty(property.substring(0, index), property.substring(index + 1));
211                }
212              }
213              bootstrap.setConfig(config);
214            }
215    
216            // Register shutdown hook
217            Runtime.getRuntime().addShutdownHook(new Thread() {
218              @Override
219              public void run() {
220                // Should trigger some kind of run interruption
221              }
222            });
223    
224            // Do bootstrap
225            bootstrap.bootstrap();
226            Runtime.getRuntime().addShutdownHook(new Thread(){
227              @Override
228              public void run() {
229                bootstrap.shutdown();
230              }
231            });
232    
233            //
234            ShellFactory factory = bootstrap.getContext().getPlugin(ShellFactory.class);
235            shell = factory.create(null);
236            closeable = null;
237          }
238    
239          // Start crash for this command line
240          final Terminal term = TerminalFactory.create();
241          term.init();
242          ConsoleReader reader = new ConsoleReader(null, new FileInputStream(FileDescriptor.in), System.out, term);
243          Runtime.getRuntime().addShutdownHook(new Thread(){
244            @Override
245            public void run() {
246              try {
247                term.restore();
248              }
249              catch (Exception ignore) {
250              }
251            }
252          });
253    
254          //
255          final PrintWriter out = new PrintWriter(System.out);
256          final JLineProcessor processor = new JLineProcessor(
257            shell,
258            reader,
259            out
260          );
261          reader.addCompleter(processor);
262    
263          // Install signal handler
264          InterruptHandler ih = new InterruptHandler(new Runnable() {
265            public void run() {
266              processor.cancel();
267            }
268          });
269          ih.install();
270    
271          //
272          try {
273            processor.run();
274          }
275          catch (Throwable t) {
276            t.printStackTrace();
277          }
278          finally {
279    
280            //
281            if (closeable != null) {
282              Safe.close(closeable);
283            }
284    
285            // Force exit
286            System.exit(0);
287          }
288        }
289      }
290    
291      public static void main(String[] args) throws Exception {
292    
293        StringBuilder line = new StringBuilder();
294        for (int i = 0;i < args.length;i++) {
295          if (i  > 0) {
296            line.append(' ');
297          }
298          Delimiter.EMPTY.escape(args[i], line);
299        }
300    
301        //
302        CRaSH main = new CRaSH();
303        Matcher<CRaSH> matcher = main.descriptor.matcher("main");
304        CommandMatch<CRaSH, ?, ?> match = matcher.match(line.toString());
305        match.invoke(new CRaSH());
306      }
307    }