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 (copyCmd) {
202 sb.append("--cmd-mode copy ");
203 } else {
204 sb.append("--cmd-mode read ");
205 }
206 if (cmds != null) {
207 for (String cmd : cmds) {
208 File cmdPath = new File(cmd);
209 if (cmdPath.exists()) {
210 sb.append("--cmd ");
211 Delimiter.EMPTY.escape(cmdPath.getCanonicalPath(), sb);
212 sb.append(' ');
213 }
214 }
215 }
216
217 // Rewrite canonical path
218 if (copyCmd) {
219 sb.append("--conf-mode copy ");
220 } else {
221 sb.append("--conf-mode read ");
222 }
223 if (confs != null) {
224 for (String conf : confs) {
225 File confPath = new File(conf);
226 if (confPath.exists()) {
227 sb.append("--conf ");
228 Delimiter.EMPTY.escape(confPath.getCanonicalPath(), sb);
229 sb.append(' ');
230 }
231 }
232 }
233
234 // Propagate canonical config
235 if (properties != null) {
236 for (String property : properties) {
237 sb.append("--property ");
238 Delimiter.EMPTY.escape(property, sb);
239 sb.append(' ');
240 }
241 }
242
243 // Append callback port
244 sb.append(port);
245
246 //
247 String options = sb.toString();
248 log.log(Level.INFO, "Loading agent with command " + options + " as agent " + agentFile.getCanonicalPath());
249 vm.loadAgent(agentFile.getCanonicalPath(), options);
250
251 //
252 server.accept();
253
254 //
255 shell = server.getShell();
256 closeable.add(new Closeable() {
257 public void close() throws IOException {
258 vm.detach();
259 }
260 });
261 } else {
262 final Bootstrap bootstrap = new Bootstrap(Thread.currentThread().getContextClassLoader());
263
264 //
265 if (!copyCmd) {
266 bootstrap.addToCmdPath(Path.get("/crash/commands/"));
267 }
268 if (cmds != null) {
269 for (String cmd : cmds) {
270 File cmdPath = new File(cmd);
271 bootstrap.addToCmdPath(cmdPath);
272 }
273 }
274
275 //
276 if (!copyConf) {
277 bootstrap.addToConfPath(Path.get("/crash/"));
278 }
279 if (confs != null) {
280 for (String conf : confs) {
281 File confPath = new File(conf);
282 bootstrap.addToConfPath(confPath);
283 }
284 }
285
286 //
287 if (properties != null) {
288 Properties config = new Properties();
289 for (String property : properties) {
290 int index = property.indexOf('=');
291 if (index == -1) {
292 config.setProperty(property, "");
293 } else {
294 config.setProperty(property.substring(0, index), property.substring(index + 1));
295 }
296 }
297 bootstrap.setConfig(config);
298 }
299
300 // Register shutdown hook
301 Runtime.getRuntime().addShutdownHook(new Thread() {
302 @Override
303 public void run() {
304 // Should trigger some kind of run interruption
305 }
306 });
307
308 // Do bootstrap
309 bootstrap.bootstrap();
310 Runtime.getRuntime().addShutdownHook(new Thread(){
311 @Override
312 public void run() {
313 bootstrap.shutdown();
314 }
315 });
316
317 //
318 ShellFactory factory = bootstrap.getContext().getPlugin(ShellFactory.class);
319 shell = factory.create(null);
320 closeable = null;
321 }
322
323 // Start crash for this command line
324 final Terminal term = TerminalFactory.create();
325 term.init();
326 ConsoleReader reader = new ConsoleReader(null, new FileInputStream(FileDescriptor.in), System.out, term);
327 Runtime.getRuntime().addShutdownHook(new Thread(){
328 @Override
329 public void run() {
330 try {
331 term.restore();
332 }
333 catch (Exception ignore) {
334 }
335 }
336 });
337
338 //
339 final PrintWriter out = new PrintWriter(System.out);
340 final JLineProcessor processor = new JLineProcessor(
341 shell,
342 reader,
343 out
344 );
345 reader.addCompleter(processor);
346
347 // Install signal handler
348 InterruptHandler ih = new InterruptHandler(new Runnable() {
349 public void run() {
350 processor.cancel();
351 }
352 });
353 ih.install();
354
355 //
356 try {
357 processor.run();
358 }
359 catch (Throwable t) {
360 t.printStackTrace();
361 }
362 finally {
363
364 //
365 if (closeable != null) {
366 Safe.close(closeable);
367 }
368
369 // Force exit
370 System.exit(0);
371 }
372 }
373
374 public static void main(String[] args) throws Exception {
375
376 StringBuilder line = new StringBuilder();
377 for (int i = 0;i < args.length;i++) {
378 if (i > 0) {
379 line.append(' ');
380 }
381 Delimiter.EMPTY.escape(args[i], line);
382 }
383
384 //
385 CRaSH main = new CRaSH();
386 InvocationMatcher<CRaSH> matcher = main.descriptor.invoker("main");
387 InvocationMatch<CRaSH> match = matcher.match(line.toString());
388 match.invoke(new CRaSH());
389 }
390 }