1/*
2 * Copyright (c) 1998, 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
26package com.sun.tools.jdi;
27
28import java.io.File;
29import java.io.IOException;
30import java.lang.reflect.InvocationTargetException;
31import java.util.Map;
32import java.util.Random;
33
34import com.sun.jdi.VirtualMachine;
35import com.sun.jdi.connect.Connector;
36import com.sun.jdi.connect.IllegalConnectorArgumentsException;
37import com.sun.jdi.connect.Transport;
38import com.sun.jdi.connect.VMStartException;
39import com.sun.jdi.connect.spi.TransportService;
40
41public class SunCommandLineLauncher extends AbstractLauncher {
42
43    static private final String ARG_HOME = "home";
44    static private final String ARG_OPTIONS = "options";
45    static private final String ARG_MAIN = "main";
46    static private final String ARG_INIT_SUSPEND = "suspend";
47    static private final String ARG_QUOTE = "quote";
48    static private final String ARG_VM_EXEC = "vmexec";
49
50    TransportService transportService;
51    Transport transport;
52    boolean usingSharedMemory = false;
53
54    TransportService transportService() {
55        return transportService;
56    }
57
58    public Transport transport() {
59        return transport;
60    }
61
62    public SunCommandLineLauncher() {
63        super();
64
65        /**
66         * By default this connector uses either the shared memory
67         * transport or the socket transport
68         */
69        try {
70            transportService = (TransportService)Class.
71                forName("com.sun.tools.jdi.SharedMemoryTransportService").
72                getDeclaredConstructor().newInstance();
73            transport = new Transport() {
74                public String name() {
75                    return "dt_shmem";
76                }
77            };
78            usingSharedMemory = true;
79        } catch (ClassNotFoundException |
80                 UnsatisfiedLinkError |
81                 InstantiationException |
82                 InvocationTargetException |
83                 IllegalAccessException |
84                 NoSuchMethodException x) {
85        };
86        if (transportService == null) {
87            transportService = new SocketTransportService();
88            transport = new Transport() {
89                public String name() {
90                    return "dt_socket";
91                }
92            };
93        }
94
95        addStringArgument(
96                ARG_HOME,
97                getString("sun.home.label"),
98                getString("sun.home"),
99                System.getProperty("java.home"),
100                false);
101        addStringArgument(
102                ARG_OPTIONS,
103                getString("sun.options.label"),
104                getString("sun.options"),
105                "",
106                false);
107        addStringArgument(
108                ARG_MAIN,
109                getString("sun.main.label"),
110                getString("sun.main"),
111                "",
112                true);
113
114        addBooleanArgument(
115                ARG_INIT_SUSPEND,
116                getString("sun.init_suspend.label"),
117                getString("sun.init_suspend"),
118                true,
119                false);
120
121        addStringArgument(
122                ARG_QUOTE,
123                getString("sun.quote.label"),
124                getString("sun.quote"),
125                "\"",
126                true);
127        addStringArgument(
128                ARG_VM_EXEC,
129                getString("sun.vm_exec.label"),
130                getString("sun.vm_exec"),
131                "java",
132                true);
133    }
134
135    static boolean hasWhitespace(String string) {
136        int length = string.length();
137        for (int i = 0; i < length; i++) {
138            if (Character.isWhitespace(string.charAt(i))) {
139                return true;
140            }
141        }
142        return false;
143    }
144
145    public VirtualMachine
146        launch(Map<String, ? extends Connector.Argument> arguments)
147        throws IOException, IllegalConnectorArgumentsException,
148               VMStartException
149    {
150        VirtualMachine vm;
151
152        String home = argument(ARG_HOME, arguments).value();
153        String options = argument(ARG_OPTIONS, arguments).value();
154        String mainClassAndArgs = argument(ARG_MAIN, arguments).value();
155        boolean wait = ((BooleanArgumentImpl)argument(ARG_INIT_SUSPEND,
156                                                  arguments)).booleanValue();
157        String quote = argument(ARG_QUOTE, arguments).value();
158        String exe = argument(ARG_VM_EXEC, arguments).value();
159        String exePath = null;
160
161        if (quote.length() > 1) {
162            throw new IllegalConnectorArgumentsException("Invalid length",
163                                                         ARG_QUOTE);
164        }
165
166        if ((options.indexOf("-Djava.compiler=") != -1) &&
167            (options.toLowerCase().indexOf("-djava.compiler=none") == -1)) {
168            throw new IllegalConnectorArgumentsException("Cannot debug with a JIT compiler",
169                                                         ARG_OPTIONS);
170        }
171
172        /*
173         * Start listening.
174         * If we're using the shared memory transport then we pick a
175         * random address rather than using the (fixed) default.
176         * Random() uses System.currentTimeMillis() as the seed
177         * which can be a problem on windows (many calls to
178         * currentTimeMillis can return the same value), so
179         * we do a few retries if we get an IOException (we
180         * assume the IOException is the filename is already in use.)
181         */
182        TransportService.ListenKey listenKey;
183        if (usingSharedMemory) {
184            Random rr = new Random();
185            int failCount = 0;
186            while(true) {
187                try {
188                    String address = "javadebug" +
189                        String.valueOf(rr.nextInt(100000));
190                    listenKey = transportService().startListening(address);
191                    break;
192                } catch (IOException ioe) {
193                    if (++failCount > 5) {
194                        throw ioe;
195                    }
196                }
197            }
198        } else {
199            listenKey = transportService().startListening();
200        }
201        String address = listenKey.address();
202
203        try {
204            if (home.length() > 0) {
205                exePath = home + File.separator + "bin" + File.separator + exe;
206            } else {
207                exePath = exe;
208            }
209            // Quote only if necessary in case the quote arg value is bogus
210            if (hasWhitespace(exePath)) {
211                exePath = quote + exePath + quote;
212            }
213
214            String xrun = "transport=" + transport().name() +
215                          ",address=" + address +
216                          ",suspend=" + (wait? 'y' : 'n');
217            // Quote only if necessary in case the quote arg value is bogus
218            if (hasWhitespace(xrun)) {
219                xrun = quote + xrun + quote;
220            }
221
222            String command = exePath + ' ' +
223                             options + ' ' +
224                             "-Xdebug " +
225                             "-Xrunjdwp:" + xrun + ' ' +
226                             mainClassAndArgs;
227
228            // System.err.println("Command: \"" + command + '"');
229            vm = launch(tokenizeCommand(command, quote.charAt(0)), address, listenKey,
230                        transportService());
231        } finally {
232            transportService().stopListening(listenKey);
233        }
234
235        return vm;
236    }
237
238    public String name() {
239        return "com.sun.jdi.CommandLineLaunch";
240    }
241
242    public String description() {
243        return getString("sun.description");
244    }
245}
246