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 import org.slf4j.Logger;
044 import org.slf4j.LoggerFactory;
045
046 import java.io.Closeable;
047 import java.io.File;
048 import java.io.FileDescriptor;
049 import java.io.FileInputStream;
050 import java.io.IOException;
051 import java.io.PrintWriter;
052 import java.net.*;
053 import java.util.List;
054 import java.util.Properties;
055
056 public class CRaSH {
057
058 /** . */
059 private static Logger log = LoggerFactory.getLogger(CRaSH.class);
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.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.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.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 finally {
276
277 //
278 if (closeable != null) {
279 Safe.close(closeable);
280 }
281
282 // Force exit
283 System.exit(0);
284 }
285 }
286 }
287
288 public static void main(String[] args) throws Exception {
289
290 StringBuilder line = new StringBuilder();
291 for (int i = 0;i < args.length;i++) {
292 if (i > 0) {
293 line.append(' ');
294 }
295 Delimiter.EMPTY.escape(args[i], line);
296 }
297
298 //
299 CRaSH main = new CRaSH();
300 Matcher<CRaSH> matcher = main.descriptor.matcher("main");
301 CommandMatch<CRaSH, ?, ?> match = matcher.match(line.toString());
302 match.invoke(new CRaSH());
303 }
304 }