1 /*
2  * Copyright (c) 1999, 2013, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.jdi;
27 
28 import com.sun.tools.jdi.*;
29 import com.sun.jdi.connect.*;
30 import com.sun.jdi.connect.spi.*;
31 import com.sun.jdi.*;
32 
33 import java.util.Map;
34 import java.util.StringTokenizer;
35 import java.util.List;
36 import java.util.ArrayList;
37 import java.io.IOException;
38 import java.io.InterruptedIOException;
39 
40 abstract class AbstractLauncher extends ConnectorImpl implements LaunchingConnector {
41 
42     abstract public VirtualMachine
launch(Map<String,? extends Connector.Argument> arguments)43         launch(Map<String,? extends Connector.Argument> arguments)
44                                  throws IOException,
45                                         IllegalConnectorArgumentsException,
46                                         VMStartException;
name()47     abstract public String name();
description()48     abstract public String description();
49 
50     ThreadGroup grp;
51 
AbstractLauncher()52     AbstractLauncher() {
53         super();
54 
55         grp = Thread.currentThread().getThreadGroup();
56         ThreadGroup parent = null;
57         while ((parent = grp.getParent()) != null) {
58             grp = parent;
59         }
60     }
61 
tokenizeCommand(String command, char quote)62     String[] tokenizeCommand(String command, char quote) {
63         String quoteStr = String.valueOf(quote); // easier to deal with
64 
65         /*
66          * Tokenize the command, respecting the given quote character.
67          */
68         StringTokenizer tokenizer = new StringTokenizer(command,
69                                                         quote + " \t\r\n\f",
70                                                         true);
71         String quoted = null;
72         String pending = null;
73         List<String> tokenList = new ArrayList<String>();
74         while (tokenizer.hasMoreTokens()) {
75             String token = tokenizer.nextToken();
76             if (quoted != null) {
77                 if (token.equals(quoteStr)) {
78                     tokenList.add(quoted);
79                     quoted = null;
80                 } else {
81                     quoted += token;
82                 }
83             } else if (pending != null) {
84                 if (token.equals(quoteStr)) {
85                     quoted = pending;
86                 } else if ((token.length() == 1) &&
87                            Character.isWhitespace(token.charAt(0))) {
88                     tokenList.add(pending);
89                 } else {
90                     throw new InternalException("Unexpected token: " + token);
91                 }
92                 pending = null;
93             } else {
94                 if (token.equals(quoteStr)) {
95                     quoted = "";
96                 } else if ((token.length() == 1) &&
97                            Character.isWhitespace(token.charAt(0))) {
98                     // continue
99                 } else {
100                     pending = token;
101                 }
102             }
103         }
104 
105         /*
106          * Add final token.
107          */
108         if (pending != null) {
109             tokenList.add(pending);
110         }
111 
112         /*
113          * An unclosed quote at the end of the command. Do an
114          * implicit end quote.
115          */
116         if (quoted != null) {
117             tokenList.add(quoted);
118         }
119 
120         String[] tokenArray = new String[tokenList.size()];
121         for (int i = 0; i < tokenList.size(); i++) {
122             tokenArray[i] = tokenList.get(i);
123         }
124         return tokenArray;
125     }
126 
launch(String[] commandArray, String address, TransportService.ListenKey listenKey, TransportService ts)127     protected VirtualMachine launch(String[] commandArray, String address,
128                                     TransportService.ListenKey listenKey,
129                                     TransportService ts)
130                                     throws IOException, VMStartException {
131         Helper helper = new Helper(commandArray, address, listenKey, ts);
132         helper.launchAndAccept();
133 
134         VirtualMachineManager manager =
135             Bootstrap.virtualMachineManager();
136 
137         return manager.createVirtualMachine(helper.connection(),
138                                             helper.process());
139     }
140 
141     /**
142      * This class simply provides a context for a single launch and
143      * accept. It provides instance fields that can be used by
144      * all threads involved. This stuff can't be in the Connector proper
145      * because the connector is a singleton and is not specific to any
146      * one launch.
147      */
148     private class Helper {
149         private final String address;
150         private TransportService.ListenKey listenKey;
151         private TransportService ts;
152         private final String[] commandArray;
153         private Process process = null;
154         private Connection connection = null;
155         private IOException acceptException = null;
156         private boolean exited = false;
157 
Helper(String[] commandArray, String address, TransportService.ListenKey listenKey, TransportService ts)158         Helper(String[] commandArray, String address, TransportService.ListenKey listenKey,
159             TransportService ts) {
160             this.commandArray = commandArray;
161             this.address = address;
162             this.listenKey = listenKey;
163             this.ts = ts;
164         }
165 
commandString()166         String commandString() {
167             String str = "";
168             for (int i = 0; i < commandArray.length; i++) {
169                 if (i > 0) {
170                     str += " ";
171                 }
172                 str += commandArray[i];
173             }
174             return str;
175         }
176 
launchAndAccept()177         synchronized void launchAndAccept() throws
178                                 IOException, VMStartException {
179 
180             process = Runtime.getRuntime().exec(commandArray);
181 
182             Thread acceptingThread = acceptConnection();
183             Thread monitoringThread = monitorTarget();
184             try {
185                 while ((connection == null) &&
186                        (acceptException == null) &&
187                        !exited) {
188                     wait();
189                 }
190 
191                 if (exited) {
192                     throw new VMStartException(
193                         "VM initialization failed for: " + commandString(), process);
194                 }
195                 if (acceptException != null) {
196                     // Rethrow the exception in this thread
197                     throw acceptException;
198                 }
199             } catch (InterruptedException e) {
200                 throw new InterruptedIOException("Interrupted during accept");
201             } finally {
202                 acceptingThread.interrupt();
203                 monitoringThread.interrupt();
204             }
205         }
206 
process()207         Process process() {
208             return process;
209         }
210 
connection()211         Connection connection() {
212             return connection;
213         }
214 
notifyOfExit()215         synchronized void notifyOfExit() {
216             exited = true;
217             notify();
218         }
219 
notifyOfConnection(Connection connection)220         synchronized void notifyOfConnection(Connection connection) {
221             this.connection = connection;
222             notify();
223         }
224 
notifyOfAcceptException(IOException acceptException)225         synchronized void notifyOfAcceptException(IOException acceptException) {
226             this.acceptException = acceptException;
227             notify();
228         }
229 
monitorTarget()230         Thread monitorTarget() {
231             Thread thread = new Thread(grp,
232                                        "launched target monitor") {
233                 public void run() {
234                     try {
235                         process.waitFor();
236                         /*
237                          * Notify waiting thread of VM error termination
238                          */
239                         notifyOfExit();
240                     } catch (InterruptedException e) {
241                         // Connection has been established, stop monitoring
242                     }
243                 }
244             };
245             thread.setDaemon(true);
246             thread.start();
247             return thread;
248         }
249 
acceptConnection()250         Thread acceptConnection() {
251             Thread thread = new Thread(grp,
252                                        "connection acceptor") {
253                 public void run() {
254                     try {
255                         Connection connection = ts.accept(listenKey, 0, 0);
256                         /*
257                          * Notify waiting thread of connection
258                          */
259                         notifyOfConnection(connection);
260                     } catch (InterruptedIOException e) {
261                         // VM terminated, stop accepting
262                     } catch (IOException e) {
263                         // Report any other exception to waiting thread
264                         notifyOfAcceptException(e);
265                     }
266                 }
267             };
268             thread.setDaemon(true);
269             thread.start();
270             return thread;
271         }
272     }
273 }
274