1 /*
  2  * Copyright (c) 1999, 2021, 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     public abstract VirtualMachine
 50         launch(Map<String, ? extends Connector.Argument> arguments)
 51         throws IOException, IllegalConnectorArgumentsException,
 52                VMStartException;
 53 
 54     public abstract String name();
 55 
 56     public abstract String description();
 57 
 58     ThreadGroup grp;
 59 
 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 
 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 
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 
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 
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 
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 
216         Process process() {
217             return process;
218         }
219 
220         Connection connection() {
221             return connection;
222         }
223 
224         synchronized void notifyOfExit() {
225             exited = true;
226             notify();
227         }
228 
229         synchronized void notifyOfConnection(Connection connection) {
230             this.connection = connection;
231             notify();
232         }
233 
234         synchronized void notifyOfAcceptException(IOException acceptException) {
235             this.acceptException = acceptException;
236             notify();
237         }
238 
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 
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 }