1/*
2 * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import com.sun.jdi.*;
25import com.sun.jdi.connect.*;
26import com.sun.jdi.request.EventRequestManager;
27
28import java.util.*;
29import java.io.*;
30
31
32/**
33 * Manages a VM conection for the JDI test framework.
34 */
35class VMConnection {
36    private VirtualMachine vm;
37    private Process process = null;
38    private int outputCompleteCount = 0;
39
40    private final Connector connector;
41    private final Map connectorArgs;
42    private final int traceFlags;
43
44    /**
45     * Return a String containing VM Options to pass to the debugee
46     * or an empty string if there are none.
47     * These are read from TESTVMOPTS and/or TESTJAVAOPTS.
48     */
49    static public String getDebuggeeVMOptions() {
50        String retVal = "";
51
52        // When we run under jtreg, test.classes contains the pathname of
53        // the dir in which the .class files will be placed.
54        String testClasses = System.getProperty("test.classes");
55        if (testClasses == null) {
56            return retVal;
57        }
58        retVal += "-classpath " + testClasses;
59
60        String vmOpts = System.getProperty("test.vm.opts");
61        System.out.println("vmOpts: '" + vmOpts + "'");
62        if (vmOpts != null && !vmOpts.trim().isEmpty()) {
63            retVal += " " + vmOpts;
64        }
65        String javaOpts = System.getProperty("test.java.opts");
66        System.out.println("javaOpts: '" + javaOpts + "'");
67        if (javaOpts != null && !javaOpts.trim().isEmpty()) {
68            retVal += " " + javaOpts;
69        }
70
71        return retVal;
72    }
73
74    static public String[] insertDebuggeeVMOptions(String[] cmdLine) {
75        String opts = getDebuggeeVMOptions();
76        if (opts.equals("")) {
77            return cmdLine;
78        }
79        // Insert the options at position 1.  Blanks in args are not allowed!
80        String[] v1 = opts.split(" +");
81        String[] retVal = new String[cmdLine.length + v1.length];
82        retVal[0] = cmdLine[0];
83        System.arraycopy(v1, 0, retVal, 1, v1.length);
84        System.arraycopy(cmdLine, 1, retVal, v1.length + 1, cmdLine.length - 1);
85        return retVal;
86    }
87
88
89    private Connector findConnector(String name) {
90        List connectors = Bootstrap.virtualMachineManager().allConnectors();
91        Iterator iter = connectors.iterator();
92        while (iter.hasNext()) {
93            Connector connector = (Connector)iter.next();
94            if (connector.name().equals(name)) {
95                return connector;
96            }
97        }
98        return null;
99    }
100
101    private Map parseConnectorArgs(Connector connector, String argString) {
102        StringTokenizer tokenizer = new StringTokenizer(argString, ",");
103        Map arguments = connector.defaultArguments();
104
105        while (tokenizer.hasMoreTokens()) {
106            String token = tokenizer.nextToken();
107            int index = token.indexOf('=');
108            if (index == -1) {
109                throw new IllegalArgumentException("Illegal connector argument: " +
110                                                   token);
111            }
112            String name = token.substring(0, index);
113            String value = token.substring(index + 1);
114            Connector.Argument argument = (Connector.Argument)arguments.get(name);
115            if (argument == null) {
116                throw new IllegalArgumentException("Argument " + name +
117                                               "is not defined for connector: " +
118                                               connector.name());
119            }
120            argument.setValue(value);
121        }
122        return arguments;
123    }
124
125    VMConnection(String connectSpec, int traceFlags) {
126        String nameString;
127        String argString;
128        int index = connectSpec.indexOf(':');
129        if (index == -1) {
130            nameString = connectSpec;
131            argString = "";
132        } else {
133            nameString = connectSpec.substring(0, index);
134            argString = connectSpec.substring(index + 1);
135        }
136
137        connector = findConnector(nameString);
138        if (connector == null) {
139            throw new IllegalArgumentException("No connector named: " +
140                                               nameString);
141        }
142
143        connectorArgs = parseConnectorArgs(connector, argString);
144        this.traceFlags = traceFlags;
145    }
146
147    synchronized VirtualMachine open() {
148        if (connector instanceof LaunchingConnector) {
149            vm = launchTarget();
150        } else if (connector instanceof AttachingConnector) {
151            vm = attachTarget();
152        } else if (connector instanceof ListeningConnector) {
153            vm = listenTarget();
154        } else {
155            throw new InternalError("Invalid connect type");
156        }
157        vm.setDebugTraceMode(traceFlags);
158        System.out.println("JVM version:" + vm.version());
159        System.out.println("JDI version: " + Bootstrap.virtualMachineManager().majorInterfaceVersion() +
160                           "." + Bootstrap.virtualMachineManager().minorInterfaceVersion());
161        System.out.println("JVM description: " + vm.description());
162
163        return vm;
164    }
165
166    boolean setConnectorArg(String name, String value) {
167        /*
168         * Too late if the connection already made
169         */
170        if (vm != null) {
171            return false;
172        }
173
174        Connector.Argument argument = (Connector.Argument)connectorArgs.get(name);
175        if (argument == null) {
176            return false;
177        }
178        argument.setValue(value);
179        return true;
180    }
181
182    String connectorArg(String name) {
183        Connector.Argument argument = (Connector.Argument)connectorArgs.get(name);
184        if (argument == null) {
185            return "";
186        }
187        return argument.value();
188    }
189
190    public synchronized VirtualMachine vm() {
191        if (vm == null) {
192            throw new InternalError("VM not connected");
193        } else {
194            return vm;
195        }
196    }
197
198    boolean isOpen() {
199        return (vm != null);
200    }
201
202    boolean isLaunch() {
203        return (connector instanceof LaunchingConnector);
204    }
205
206    Connector connector() {
207        return connector;
208    }
209
210    boolean isListen() {
211        return (connector instanceof ListeningConnector);
212    }
213
214    boolean isAttach() {
215        return (connector instanceof AttachingConnector);
216    }
217
218    private synchronized void notifyOutputComplete() {
219        outputCompleteCount++;
220        notifyAll();
221    }
222
223    private synchronized void waitOutputComplete() {
224        // Wait for stderr and stdout
225        if (process != null) {
226            while (outputCompleteCount < 2) {
227                try {wait();} catch (InterruptedException e) {}
228            }
229        }
230    }
231
232    public void disposeVM() {
233        try {
234            if (vm != null) {
235                vm.dispose();
236                vm = null;
237            }
238        } finally {
239            if (process != null) {
240                process.destroy();
241                process = null;
242            }
243            waitOutputComplete();
244        }
245    }
246
247    private void dumpStream(InputStream stream) throws IOException {
248                PrintStream outStream = System.out;
249                BufferedReader in =
250                        new BufferedReader(new InputStreamReader(stream));
251                String line;
252                while(true){
253                      try{
254                          line = in.readLine();
255                          if( line == null ){
256                              break;
257                          }
258                          outStream.println(line);
259                      }
260                      catch(IOException ieo){
261                           /**
262                            * IOException with "Bad file number..." can happen
263                            * when the debuggee process is destroyed. Ignore such exception.
264                            *
265                           */
266                           String s = ieo.getMessage();
267                           if( s.startsWith("Bad file number") ){
268                               break;
269                           }
270                           throw ieo;
271                      }
272                      catch(NullPointerException npe){
273                          throw new IOException("Bug 4728096 in Java io may cause in.readLine() to throw a NULL pointer exception");
274                      }
275                }
276    }
277
278    /**
279     *  Create a Thread that will retrieve and display any output.
280     *  Needs to be high priority, else debugger may exit before
281     *  it can be displayed.
282     */
283    private void displayRemoteOutput(final InputStream stream) {
284        Thread thr = new Thread("output reader") {
285            public void run() {
286                try {
287                    dumpStream(stream);
288                } catch (IOException ex) {
289                    System.err.println("IOException reading output of child java interpreter:"
290                                       + ex.getMessage());
291                } finally {
292                    notifyOutputComplete();
293                }
294            }
295        };
296        thr.setPriority(Thread.MAX_PRIORITY-1);
297        thr.start();
298    }
299
300    private void dumpFailedLaunchInfo(Process process) {
301        try {
302            dumpStream(process.getErrorStream());
303            dumpStream(process.getInputStream());
304        } catch (IOException e) {
305            System.err.println("Unable to display process output: " +
306                               e.getMessage());
307        }
308    }
309
310    /* launch child target vm */
311    private VirtualMachine launchTarget() {
312        LaunchingConnector launcher = (LaunchingConnector)connector;
313        try {
314            VirtualMachine vm = launcher.launch(connectorArgs);
315            process = vm.process();
316            displayRemoteOutput(process.getErrorStream());
317            displayRemoteOutput(process.getInputStream());
318            return vm;
319        } catch (IOException ioe) {
320            ioe.printStackTrace();
321            System.err.println("\n Unable to launch target VM.");
322        } catch (IllegalConnectorArgumentsException icae) {
323            icae.printStackTrace();
324            System.err.println("\n Internal debugger error.");
325        } catch (VMStartException vmse) {
326            System.err.println(vmse.getMessage() + "\n");
327            dumpFailedLaunchInfo(vmse.process());
328            System.err.println("\n Target VM failed to initialize.");
329        }
330        return null; // Shuts up the compiler
331    }
332
333    /* attach to running target vm */
334    private VirtualMachine attachTarget() {
335        AttachingConnector attacher = (AttachingConnector)connector;
336        try {
337            return attacher.attach(connectorArgs);
338        } catch (IOException ioe) {
339            ioe.printStackTrace();
340            System.err.println("\n Unable to attach to target VM.");
341        } catch (IllegalConnectorArgumentsException icae) {
342            icae.printStackTrace();
343            System.err.println("\n Internal debugger error.");
344        }
345        return null; // Shuts up the compiler
346    }
347
348    /* listen for connection from target vm */
349    private VirtualMachine listenTarget() {
350        ListeningConnector listener = (ListeningConnector)connector;
351        try {
352            String retAddress = listener.startListening(connectorArgs);
353            System.out.println("Listening at address: " + retAddress);
354            vm = listener.accept(connectorArgs);
355            listener.stopListening(connectorArgs);
356            return vm;
357        } catch (IOException ioe) {
358            ioe.printStackTrace();
359            System.err.println("\n Unable to attach to target VM.");
360        } catch (IllegalConnectorArgumentsException icae) {
361            icae.printStackTrace();
362            System.err.println("\n Internal debugger error.");
363        }
364        return null; // Shuts up the compiler
365    }
366}
367