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