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