| /* |
| * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package nsk.share.jpda; |
| |
| import java.io.*; |
| import java.net.*; |
| import java.util.*; |
| |
| import nsk.share.*; |
| import nsk.share.jpda.*; |
| |
| /** |
| * <code>BindServer</code> is an utility to perform JPDA tests |
| * in remote mode across network. |
| * <p> |
| * This utility should be started on remote host. It listens for connection |
| * from JPDA tests and launches debuggee VM on this host. |
| * <p> |
| * <code>BindServer</code> works together with <code>Binder</code> used in |
| * the tests to incapsulate actions required for launching debuggee VM. |
| * See <code>ProcessBinder</code> and <code>DebugeeArgumentHandler</code> |
| * to know how run tests in local or remote mode across network or |
| * on an single host. |
| * <p> |
| * <code>BindServer</code> is started on the debuggee host. |
| * It recognizes following command line options: |
| * <ul> |
| * <li><code>-bind.file=<i>filename</i></code> - configuration file |
| * <li><code>-verbose</code> - print verbose messages |
| * </ul> |
| * <p> |
| * Only required option is <code>-bind.file</code>, which points to the file |
| * where pairs of particular pathes are presented as they are seen from |
| * both hosts along with some other <code>BindServer</code> options. |
| * See <i>execution.html</i> to read more about format of bind-file. |
| * |
| * @see DebugeeBinder |
| * @see DebugeeArgumentHandler |
| */ |
| public class BindServer implements Finalizable { |
| |
| /** Version of <code>BindServer</code> implementation. */ |
| public static final long VERSION = 2; |
| |
| /** Timeout in milliseconds used for waiting for inner threads. */ |
| private static long THREAD_TIMEOUT = DebugeeBinder.THREAD_TIMEOUT; // milliseconds |
| |
| private static int PASSED = 0; |
| private static int FAILED = 2; |
| private static int JCK_BASE = 95; |
| |
| private static int TRACE_LEVEL_PACKETS = 10; |
| private static int TRACE_LEVEL_THREADS = 20; |
| private static int TRACE_LEVEL_ACTIONS = 30; |
| private static int TRACE_LEVEL_SOCKETS = 40; |
| private static int TRACE_LEVEL_IO = 50; |
| |
| private static String pathSeparator = System.getProperty("path.separator"); |
| private static String fileSeparator = System.getProperty("file.separator"); |
| |
| private static char pathSeparatorChar = pathSeparator.charAt(0); |
| private static char fileSeparatorChar = fileSeparator.charAt(0); |
| |
| private static Log log = null; |
| private static Log.Logger logger = null; |
| private static ArgumentHandler argHandler = null; |
| |
| private static String pathConvertions[][] = null; |
| |
| private ListeningThread listeningThread = null; |
| |
| private int totalRequests = 0; |
| private int acceptedRequests = 0; |
| private int unauthorizedRequests = 0; |
| private int busyRequests = 0; |
| |
| /** |
| * Start <code>BindServer</code> utility from command line. |
| * This method invokes <code>run()</code> and redirects output |
| * to <code>System.err</code>. |
| * |
| * @param argv list of command line arguments |
| */ |
| public static void main (String argv[]) { |
| System.exit(run(argv,System.err) + JCK_BASE); |
| } |
| |
| /** |
| * Start <code>BindServer</code> utility from JCK-compatible |
| * environment. |
| * |
| * @param argv list of command line arguments |
| * @param out outpur stream for log messages |
| * |
| * @return FAILED if error occured |
| * PASSED oterwise |
| */ |
| public static int run(String argv[], PrintStream out) { |
| return new BindServer().runIt(argv, out); |
| } |
| |
| /** |
| * Perform execution of <code>BindServer</code>. |
| * This method handles command line arguments, starts seperate |
| * thread for listening connection from test on remote host, |
| * and waits for command "exit" from a user. |
| * Finally it closes all conections and prints connections |
| * statiscs. |
| * |
| * @param argv list of command line arguments |
| * @param out outpur stream for log messages |
| * |
| * @return FAILED if error occured |
| * PASSED oterwise |
| */ |
| private int runIt(String argv[], PrintStream out) { |
| try { |
| argHandler = new ArgumentHandler(argv); |
| } catch (ArgumentHandler.BadOption e) { |
| out.println("ERROR: " + e.getMessage()); |
| return FAILED; |
| } |
| |
| if (argHandler.getArguments().length > 0) { |
| out.println("ERROR: " + "Too many positional arguments in command line"); |
| return FAILED; |
| } |
| |
| log = new Log(out, argHandler); |
| log.enableErrorsSummary(false); |
| log.enableVerboseOnError(false); |
| logger = new Log.Logger(log, ""); |
| |
| Finalizer bindFinalizer = new Finalizer(this); |
| bindFinalizer.activate(); |
| |
| logger.trace(TRACE_LEVEL_THREADS, "BindServer: starting main thread"); |
| |
| logger.display("Listening to port: " + argHandler.getBindPortNumber()); |
| logger.display("Authorizing host: " + argHandler.getDebuggerHost()); |
| |
| pathConvertions = new String[][] { |
| { "TESTED_JAVA_HOME", argHandler.getDebuggerJavaHome(), argHandler.getDebugeeJavaHome() }, |
| { "TESTBASE", argHandler.getDebuggerTestbase(), argHandler.getDebugeeTestbase() }, |
| { "WORKDIR", argHandler.getDebuggerWorkDir(), argHandler.getDebugeeWorkDir() } |
| }; |
| |
| logger.display("Translating pathes:"); |
| for (int i = 0; i < pathConvertions.length; i++) { |
| logger.display(pathConvertions[i][0] + ":" +"\n" |
| + " " + pathConvertions[i][1] + "\n" |
| + " =>" + "\n" |
| + " " + pathConvertions[i][2]); |
| } |
| |
| String windir = argHandler.getDebugeeWinDir(); |
| if (!(windir == null || windir.equals(""))) { |
| logger.display("Using WINDIR: \n" |
| + " " + argHandler.getDebugeeWinDir()); |
| } |
| |
| BufferedReader stdIn = new BufferedReader( |
| new InputStreamReader(System.in)); |
| |
| listeningThread = new ListeningThread(this); |
| listeningThread.bind(); |
| listeningThread.start(); |
| |
| System.out.println("\n" |
| + "BindServer started" + "\n" |
| + "Type \"exit\" to shut down BindServer" |
| + "\n"); |
| |
| for (;;) { |
| try { |
| String userInput = stdIn.readLine(); |
| if (userInput == null || userInput.equals("exit") |
| || userInput.equals("quit")) { |
| logger.display("Shutting down BindServer"); |
| stdIn.close(); |
| stdIn = null; |
| break; |
| } else if (userInput.trim().equals("")) { |
| continue; |
| } else { |
| System.out.println("ERROR: Unknown command: " + userInput); |
| } |
| } catch(IOException e) { |
| e.printStackTrace(log.getOutStream()); |
| throw new Failure("Caught exception while reading console command:\n\t" |
| + e); |
| } |
| } |
| |
| printSummary(System.out); |
| |
| logger.trace(TRACE_LEVEL_THREADS, "BindServer: exiting main thread"); |
| try { |
| finalize(); |
| } catch (Throwable e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while finalization of BindServer:\n\t" + e); |
| } |
| |
| return PASSED; |
| } |
| |
| /** |
| * Print usefull summary statistics about connections occured. |
| * |
| * @param out output stream for printing statistics |
| */ |
| private void printSummary(PrintStream out) { |
| out.println("\n" |
| + "Connections summary:" + "\n" |
| + " Tolal connections: " + totalRequests + "\n" |
| + " Accepted authorized: " + acceptedRequests + "\n" |
| + " Rejected unauthorized " + unauthorizedRequests + "\n" |
| + " Rejected being busy: " + busyRequests + "\n"); |
| }; |
| |
| /** |
| * Check if given <code>path</code> starts with the specified prefix taking |
| * into account difference between <code>slashChar<code> used in <code>path</code> |
| * and <code>fileSeparatorChar</code> used in <code>prefix</code>. |
| * |
| * @param path path to check |
| * @param prefix prefix to compare with |
| * @param slashChar file separator used in <code>path</code> |
| */ |
| private static boolean checkPathPrefix(String path, String prefix, char slashChar) { |
| int prefixLength = prefix.length(); |
| if (prefixLength > path.length()) { |
| return false; |
| } |
| for (int i = 0; i < prefixLength; i++) { |
| char pathChar = path.charAt(i); |
| char prefixChar = prefix.charAt(i); |
| |
| if (pathChar != prefixChar) { |
| if ((pathChar == slashChar || pathChar == fileSeparatorChar |
| || pathChar == '\\' || pathChar == '/') |
| && (prefixChar == slashChar || prefixChar == fileSeparatorChar |
| || prefixChar == '\\' || prefixChar == '/')) { |
| // do nothing |
| } else { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Convert given path according to list of prefixes from |
| * <code>pathConvertions</code> table. |
| * |
| * @param path path for converting |
| * @param slash file separator used in <code>path</code> |
| * @param name path identifier used for error messages |
| * @param strict force throwing Failure if path is not matched |
| * |
| * @return string with the converted path |
| * |
| * @throws Failure if path does not matched for translation |
| */ |
| private static String convertPath(String path, String slash, String name, boolean strict) { |
| if (path == null) |
| return null; |
| |
| char slashChar = slash.charAt(0); |
| |
| for (int i = 0; i < pathConvertions.length; i++) { |
| String from = pathConvertions[i][1]; |
| String to = pathConvertions[i][2]; |
| if (checkPathPrefix(path, from, slashChar)) { |
| return (to + path.substring(from.length())).replace(slashChar, fileSeparatorChar); |
| } |
| } |
| if (strict) { |
| throw new Failure("Path not matched for translation " + name + ":\n\t" + path); |
| } |
| return path; |
| } |
| |
| /** |
| * Convert given list of pathes according to list of prefixes from |
| * <code>pathConvertions</code> table by invoking <code>convertPath()</code> |
| * for each path from the list. |
| * |
| * @param list list of pathes for converting |
| * @param slash file separator used in pathes |
| * @param name path identifier used for error messages |
| * @param strict force throwing Failure if some path is not matched |
| * |
| * @return list of strings with converted pathes |
| * |
| * @throws Failure if some path does not matched for translation |
| * |
| * @see #convertPath() |
| */ |
| private static String[] convertPathes(String[] list, String slash, String name, boolean strict) { |
| String[] converted = new String[list.length]; |
| for (int i = 0; i < list.length; i++) { |
| converted[i] = convertPath(list[i], slash, name, strict); |
| } |
| return converted; |
| } |
| |
| /** |
| * Pause current thread for specified amount of time in milliseconds, |
| * This method uses <code>Object.wait(long)</code> method as a reliable |
| * method which prevents whole VM from suspending. |
| * |
| * @param millisecs - amount of time in milliseconds |
| */ |
| private static void sleeping(int millisecs) { |
| Object obj = new Object(); |
| |
| synchronized(obj) { |
| try { |
| obj.wait(millisecs); |
| } catch (InterruptedException e) { |
| e.printStackTrace(log.getOutStream()); |
| new Failure("Thread interrupted while sleeping:\n\t" + e); |
| } |
| } |
| } |
| |
| /** |
| * Wait for given thread finished for specified timeout or |
| * interrupt this thread if not finished. |
| * |
| * @param thr thread to wait for |
| * @param millisecs timeout in milliseconds |
| */ |
| private static void waitInterruptThread(Thread thr, long millisecs) { |
| if (thr != null) { |
| String name = thr.getName(); |
| try { |
| if (thr.isAlive()) { |
| logger.trace(TRACE_LEVEL_THREADS, "Waiting for thread: " + name); |
| thr.join(millisecs); |
| } |
| } catch (InterruptedException e) { |
| e.printStackTrace(log.getOutStream()); |
| throw new Failure ("Thread interrupted while waiting for another thread:\n\t" |
| + e); |
| } finally { |
| if (thr.isAlive()) { |
| logger.trace(TRACE_LEVEL_THREADS, "Interrupting not finished thread: " + name); |
| thr.interrupt(); |
| /* |
| logger.display("Stopping not finished thread: " + thr); |
| thr.stop(); |
| */ |
| } |
| } |
| } |
| } |
| |
| /** |
| * Wait for given thread finished for default timeout |
| * <code>THREAD_TIMEOUT</code> and |
| * interrupt this thread if not finished. |
| * |
| * @param thr thread to wait for |
| */ |
| private static void waitInterruptThread(Thread thr) { |
| waitInterruptThread(thr, THREAD_TIMEOUT); |
| } |
| |
| /** |
| * Close <code>BindServer</code> by finishing all threads and closing |
| * all conections. |
| */ |
| public synchronized void close() { |
| if (listeningThread != null) { |
| listeningThread.close(); |
| listeningThread = null; |
| } |
| } |
| |
| /** |
| * Make finalization of <code>BindServer</code> object by invoking |
| * method <code>close()</code>. |
| * |
| * @see #close() |
| */ |
| protected void finalize() throws Throwable { |
| close(); |
| super.finalize(); |
| } |
| |
| /** |
| * Make finalization of <code>BindServer</code> object at program exit |
| * by invoking method <code>finalize()</code>. |
| * |
| * @see #finalize() |
| */ |
| public void finalizeAtExit() throws Throwable { |
| finalize(); |
| logger.trace(TRACE_LEVEL_THREADS, "BindServer: finalization at exit completed"); |
| } |
| |
| ///////// Thread listening a TCP/IP socket ////////// |
| |
| /** |
| * An inner thread used for listening connection from remote test |
| * and starting separate serving thread for each accepted connection. |
| * |
| * @see ServingThread |
| */ |
| private static class ListeningThread extends Thread { |
| private volatile boolean shouldStop = false; |
| private volatile boolean closed = false; |
| |
| private BindServer owner = null; |
| private volatile ServingThread servingThread = null; |
| private volatile int taskCount = 0; |
| |
| private ObjectOutputStream socOut = null; |
| private ObjectInputStream socIn = null; |
| |
| private String autorizedHostName = argHandler.getDebuggerHost(); |
| private InetAddress autorizedInetAddresses[] = null; |
| private int port = argHandler.getBindPortNumber(); |
| private Socket socket = null; |
| private ServerSocket serverSocket = null; |
| private InetAddress clientInetAddr = null; |
| private String clientHostName = null; |
| private SocketConnection connection = null; |
| |
| /** |
| * Make listening thread for given <code>BindServer</code> object |
| * as an owner and bind it to listening port by invoking method |
| * <code>bind()</code>. |
| * |
| * @see bind() |
| */ |
| public ListeningThread(BindServer owner) { |
| super("ListeningThread"); |
| this.owner = owner; |
| try { |
| autorizedInetAddresses = InetAddress.getAllByName(autorizedHostName); |
| } catch (UnknownHostException e) { |
| e.printStackTrace(log.getOutStream()); |
| throw new Failure("Cannot resolve DEBUGGER_HOST value: " + autorizedHostName); |
| } |
| } |
| |
| /** |
| * Bind ServerSocket to the specified port. |
| */ |
| public void bind() { |
| for (int i = 0; !shouldStop && i < DebugeeBinder.CONNECT_TRIES; i++) { |
| try { |
| logger.trace(TRACE_LEVEL_SOCKETS, "ListeningThread: binding to server socket ..."); |
| // length of the queue = 2 |
| serverSocket = new ServerSocket(port, 2); |
| // timeout for the ServerSocket.accept() |
| serverSocket.setSoTimeout(DebugeeBinder.CONNECT_TRY_DELAY); |
| logger.trace(TRACE_LEVEL_SOCKETS, "ListeningThread: socket bound: " + serverSocket); |
| logger.display("Bound to listening port"); |
| return; |
| } catch (BindException e) { |
| logger.display("Socket binding try #" + i + " failed:\n\t" + e); |
| sleeping(DebugeeBinder.CONNECT_TRY_DELAY); |
| } catch (IOException e) { |
| e.printStackTrace(log.getOutStream()); |
| throw new Failure("Caught exception while binding to socket:\n\t" |
| + e); |
| } |
| } |
| throw new Failure("Unable to bind to socket after " |
| + DebugeeBinder.CONNECT_TRIES + " tries"); |
| } |
| |
| /** |
| * Accept socket connection from authorized remote host and |
| * start separate <code>SrvingThread</code> to handle each connection. |
| * Connection from unauthorized hosts or connections made while |
| * current connection is alive are rejected. |
| * |
| * @see ServingThread |
| * @see #llowConnection() |
| * @see allowServing() |
| */ |
| public void run() { |
| String reply = null; |
| |
| logger.trace(TRACE_LEVEL_THREADS, "ListeningThread: started"); |
| logger.display("Listening for connection from remote host"); |
| while(!(shouldStop || isInterrupted())) { |
| try { |
| try { |
| logger.trace(TRACE_LEVEL_SOCKETS, "ListeningThread: waiting for connection from test"); |
| socket = serverSocket.accept(); |
| logger.trace(TRACE_LEVEL_SOCKETS, "ListeningThread: connection accepted"); |
| } catch(InterruptedIOException e) { |
| // logger.trace(TRACE_LEVEL_SOCKETS, "ListeningThread: timeout of waiting for connection from test"); |
| continue; |
| } |
| owner.totalRequests++; |
| logger.display(""); |
| clientInetAddr = socket.getInetAddress(); |
| clientHostName = clientInetAddr.getHostName(); |
| logger.display("Connection #" + owner.totalRequests |
| + " requested from host: " + clientHostName); |
| connection = new SocketConnection(logger, "BindServer"); |
| // connection.setPingTimeout(DebugeeBinder.PING_TIMEOUT); |
| connection.setSocket(socket); |
| socket = null; |
| if (allowConnection()) { |
| if (allowServing()) { |
| owner.acceptedRequests++; |
| reply = "host authorized: " + clientHostName; |
| logger.display("Accepting connection #" + owner.acceptedRequests |
| + ": " + reply); |
| servingThread = new ServingThread(this, connection); |
| servingThread.start(); |
| cleanHostConnection(); |
| } else { |
| owner.busyRequests++; |
| reply = "BindServer is busy"; |
| logger.complain("Rejecting connection #" + owner.busyRequests |
| + ": " + reply); |
| connection.writeObject(new RequestFailed(reply)); |
| closeHostConnection(); |
| } |
| } else { |
| owner.unauthorizedRequests++; |
| reply = "host unauthorized: " + clientHostName; |
| logger.complain("Rejecting connection #" + owner.unauthorizedRequests |
| + ": " + reply); |
| connection.writeObject(new RequestFailed(reply)); |
| closeHostConnection(); |
| } |
| } catch (Exception e) { |
| logger.complain("Caught exception while accepting connection:\n" + e); |
| e.printStackTrace(log.getOutStream()); |
| } |
| } |
| logger.trace(TRACE_LEVEL_THREADS, "ListeningThread: exiting"); |
| closeConnection(); |
| } |
| |
| /** |
| * Check if the connection made is from authorized host. |
| * |
| * @return true if connection is allowed because host authorized |
| * false if connection is rejected because host unauthorized |
| */ |
| private boolean allowConnection() { |
| // check if local host from loopback address |
| if (autorizedHostName.equals("localhost")) |
| return clientInetAddr.isLoopbackAddress(); |
| |
| // check if equal hostname |
| if (autorizedHostName.equals(clientHostName)) |
| return true; |
| |
| // check if equal host address |
| for (int i = 0; i < autorizedInetAddresses.length; i++) { |
| if (clientInetAddr.equals(autorizedInetAddresses[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check if no current connection exists or it is dead. |
| * If current connection presents it will be tested by pinging |
| * remote host and aborted if host sends no reply. If an alive |
| * connection exists, new connection will be rejected. |
| * |
| * @return true if no alive connection exists |
| * false otherwise |
| */ |
| private boolean allowServing() { |
| if (servingThread == null) { |
| return true; |
| } |
| if (servingThread.done) { |
| return true; |
| } |
| if (!servingThread.isConnectionAlive()) { |
| logger.display("# WARNING: Previous connection from remote host is dead: aborting connection"); |
| servingThread.close(); |
| servingThread = null; |
| return true; |
| } |
| |
| /* |
| logger.complain("Previous connection from remote host is alive: starting new connection"); |
| servingThread = null; |
| return true; |
| */ |
| logger.complain("Previous connection from remote host is alive: reject new connection"); |
| return false; |
| } |
| |
| /** |
| * Wait for this thread finished |
| * for specified timeout or interrupt it. |
| * |
| * @param millis timeout in milliseconds |
| */ |
| public void waitForThread(long millis) { |
| shouldStop = true; |
| waitInterruptThread(this, millis); |
| } |
| |
| /** |
| * Close socket connection from remote host. |
| */ |
| private void closeHostConnection() { |
| if (connection != null) { |
| connection.close(); |
| } |
| if (socket != null) { |
| try { |
| socket.close(); |
| } catch (IOException e) { |
| logger.complain("Caught IOException while closing socket:\n\t" |
| + e); |
| } |
| socket = null; |
| } |
| } |
| |
| /** |
| * Assign <null> to connection and socket objects |
| * but do not close them. |
| */ |
| private void cleanHostConnection() { |
| connection = null; |
| socket = null; |
| } |
| |
| /** |
| * Close all connections and sockets. |
| */ |
| private void closeConnection() { |
| closeHostConnection(); |
| if (serverSocket != null) { |
| try { |
| serverSocket.close(); |
| } catch (IOException e) { |
| logger.complain("Caught IOException while closing ServerSocket:\n\t" |
| + e); |
| } |
| serverSocket = null; |
| } |
| } |
| |
| /** |
| * Close thread by closing all connections and waiting |
| * foor thread finished. |
| * |
| * @see #closeConnection() |
| */ |
| public synchronized void close() { |
| if (closed) { |
| return; |
| } |
| closeHostConnection(); |
| if (servingThread != null) { |
| servingThread.close(); |
| servingThread = null; |
| } |
| waitForThread(THREAD_TIMEOUT); |
| closeConnection(); |
| closed = true; |
| logger.trace(TRACE_LEVEL_THREADS, "ListeningThread closed"); |
| } |
| |
| } // ListeningThread |
| |
| ///////// Thread working with a communication channel ////////// |
| |
| /** |
| * An internal thread for handling each connection from a test |
| * on remote host. It reads requests from test and starts separate |
| * <code>LaunchingThread</code> to execute each request. |
| * |
| * @see LaunchingThread |
| */ |
| private static class ServingThread extends Thread { |
| private volatile boolean shouldStop = false; |
| private volatile boolean closed = false; |
| private volatile boolean done = false; |
| |
| private ListeningThread owner = null; |
| private LaunchingThread launchingThread = null; |
| |
| private SocketConnection connection = null; |
| |
| /** |
| * Make serving thread with specified input/output connection streams |
| * and given <code>Listenerthread</code> as an owner. |
| * |
| * @param owner owner of this thread |
| * @param connection established socket connection with test |
| */ |
| public ServingThread(ListeningThread owner, SocketConnection connection) { |
| super("ServingThread"); |
| this.owner = owner; |
| this.connection = connection; |
| } |
| |
| /** |
| * Read requests from socket connection and start <code>LaunchingThread</code> |
| * to perform each requested action. |
| */ |
| public void run() { |
| logger.trace(TRACE_LEVEL_THREADS, "ServingThread: starting handling requests from debugger"); |
| try { |
| // sending OK(version) |
| logger.trace(TRACE_LEVEL_ACTIONS, "ServingThread: sending initial OK(VERSION) to debugger"); |
| connection.writeObject(new OK(VERSION)); |
| |
| // receiving TaskID(id) |
| logger.trace(TRACE_LEVEL_IO, "ServingThread: waiting for TaskID from debugger"); |
| Object taskID = connection.readObject(); |
| logger.trace(TRACE_LEVEL_IO, "ServingThread: received TaskID from debugger: " + taskID); |
| if (taskID instanceof TaskID) { |
| String id = ((TaskID)taskID).id; |
| owner.taskCount++; |
| logger.println("[" + owner.taskCount + "/" + owner.owner.totalRequests + "]: " + id); |
| } else { |
| throw new Failure("Unexpected TaskID received form debugger: " + taskID); |
| } |
| |
| // starting launching thread |
| launchingThread = new LaunchingThread(this, connection); |
| launchingThread.start(); |
| |
| // receiving and handling requests |
| while(!(shouldStop || isInterrupted())) { |
| logger.trace(TRACE_LEVEL_IO, "ServingThread: waiting for request from debugger"); |
| Object request = connection.readObject(); |
| logger.trace(TRACE_LEVEL_IO, "ServingThread: received request from debugger: " + request); |
| if (request == null) { |
| logger.display("Connection closed"); |
| break; |
| } else if (request instanceof Disconnect) { |
| logger.display("Closing connection by request"); |
| request = null; |
| break; |
| } else { |
| boolean success = false; |
| long timeToFinish = System.currentTimeMillis() + THREAD_TIMEOUT; |
| while (System.currentTimeMillis() < timeToFinish) { |
| if (launchingThread.doneRequest()) { |
| success = true; |
| logger.trace(TRACE_LEVEL_ACTIONS, "ServingThread: asking launching thread to handle request: " + request); |
| launchingThread.handleRequest(request); |
| break; |
| } |
| try { |
| launchingThread.join(DebugeeBinder.TRY_DELAY); |
| } catch (InterruptedException e) { |
| throw new Failure("ServingThread interrupted while waiting for LaunchingThread:\n\t" |
| + e); |
| } |
| } |
| if (!success) { |
| logger.complain("Rejecting request because of being busy:\n" + request); |
| connection.writeObject( |
| new RequestFailed("Busy with handling previous request")); |
| } |
| } |
| } |
| } catch (Exception e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while handling request:\n\t" + e); |
| } finally { |
| logger.trace(TRACE_LEVEL_THREADS, "ServingThread: exiting"); |
| closeConnection(); |
| done = true; |
| } |
| } |
| |
| /** |
| * Check if present socket connection is alive. |
| */ |
| private boolean isConnectionAlive() { |
| return (connection != null && connection.isConnected()); |
| } |
| |
| /** |
| * Wait for this thread finished |
| * for specified timeout or interrupt it. |
| * |
| * @param millis timeout in milliseconds |
| */ |
| public void waitForThread(long millis) { |
| shouldStop = true; |
| waitInterruptThread(this, millis); |
| } |
| |
| /** |
| * Close socket connection from remote host. |
| */ |
| private void closeConnection() { |
| if (connection != null) { |
| connection.close(); |
| } |
| if (launchingThread != null) { |
| launchingThread.handleRequest(null); |
| } |
| } |
| |
| /** |
| * Close thread closing socket connection and |
| * waiting for thread finished. |
| */ |
| public synchronized void close() { |
| if (closed) { |
| return; |
| } |
| closeConnection(); |
| if (launchingThread != null) { |
| launchingThread.close(); |
| launchingThread = null; |
| } |
| waitForThread(THREAD_TIMEOUT); |
| closed = true; |
| logger.trace(TRACE_LEVEL_THREADS, "ServingThread closed"); |
| } |
| |
| } // ServingThread |
| |
| ///////// Thread serving a particular Binder's request ////////// |
| |
| /** |
| * An internal thread to execute each request from a test on remote host. |
| * Requests are coming from ServingThread by invoking handleRequest(Object) |
| * method. |
| */ |
| private static class LaunchingThread extends Thread { |
| private volatile boolean shouldStop = false; |
| private volatile boolean closed = false; |
| public volatile boolean done = false; |
| |
| private ServingThread owner = null; |
| // private ProcessWaitingThread waitingThread = null; |
| private Process process = null; |
| |
| private StreamRedirectingThread stdoutRedirectingThread = null; |
| private StreamRedirectingThread stderrRedirectingThread = null; |
| |
| /** Notification about request occurence. */ |
| private volatile Object notification = new Object(); |
| /** Request to execute. */ |
| private volatile Object request = null; |
| /** Socket stream to send replies to. */ |
| private SocketConnection connection = null; |
| |
| /** |
| * Make thread for executing requests from a test and |
| * send reply. |
| * |
| * @param owner owner of this thread |
| * @connection socket connection for sending replies |
| */ |
| public LaunchingThread(ServingThread owner, SocketConnection connection) { |
| super("LaunchingThread"); |
| this.owner = owner; |
| this.connection = connection; |
| } |
| |
| /** |
| * Notify this thread that new request has come. |
| * |
| * @param request request to execute |
| */ |
| public void handleRequest(Object request) { |
| synchronized (notification) { |
| this.request = request; |
| notification.notifyAll(); |
| } |
| } |
| |
| /** |
| * Check if request has been executed. |
| */ |
| public boolean doneRequest() { |
| return done; |
| } |
| |
| /** |
| * Wait for request notification from <code>ServingThread</code> |
| * and execute an action according to the request. |
| * Request <i>null</code> means thread should finish. |
| */ |
| public void run() { |
| logger.trace(TRACE_LEVEL_THREADS, "LaunchingThread: started to handle request"); |
| done = true; |
| while (!isInterrupted()) { |
| // wait for new request notification |
| logger.trace(TRACE_LEVEL_ACTIONS, "LaunchingThread: waiting for request"); |
| synchronized (notification) { |
| try { |
| notification.wait(); |
| } catch (InterruptedException e) { |
| logger.complain("LaunchingThread interrupted while waiting for request:\n\t" |
| + e); |
| break; |
| } |
| } |
| |
| // execute the request |
| try { |
| logger.trace(TRACE_LEVEL_ACTIONS, "LaunchingThread: handling request: " + request); |
| if (request == null) { |
| break; |
| } else if (request instanceof LaunchDebugee) { |
| launchDebugee((LaunchDebugee)request); |
| } else if (request instanceof WaitForDebugee) { |
| waitForDebugee((WaitForDebugee)request); |
| } else if (request instanceof DebugeeExitCode) { |
| debugeeExitCode((DebugeeExitCode)request); |
| } else if (request instanceof KillDebugee) { |
| killDebugee((KillDebugee)request); |
| } else { |
| String reason = "Unknown request: " + request; |
| logger.complain(reason); |
| sendReply(new RequestFailed(reason)); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while handling request:\n\t" + e); |
| } |
| done = true; |
| } |
| done = true; |
| logger.trace(TRACE_LEVEL_THREADS, "LaunchingThread: exiting"); |
| closeConnection(); |
| } |
| |
| /** |
| * Send given reply to remote test. |
| * |
| * @param reply reply object to send |
| */ |
| public void sendReply(Object reply) throws IOException { |
| connection.writeObject(reply); |
| } |
| |
| /** |
| * Send given output line to remote test. |
| * |
| * @param reply wrapper object for output line to send |
| */ |
| public void sendStreamMessage(RedirectedStream wrapper) throws IOException { |
| logger.trace(TRACE_LEVEL_ACTIONS, "Sending output line wrapper to debugger: " + wrapper); |
| if (connection.isConnected()) { |
| sendReply(wrapper); |
| } else { |
| logger.complain("NOT redirected: " + wrapper.line); |
| } |
| } |
| |
| /** |
| * Launch two <code>StreamRedirectingThread</code> threads to redirect |
| * stdin/stderr output of debuggee VM process via <code>BindServer</code> |
| * connection. |
| * |
| * @param process debuggee VM process |
| */ |
| private void launchStreamRedirectors(Process process) { |
| stdoutRedirectingThread = |
| new StdoutRedirectingThread(this, process.getInputStream(), |
| DebugeeProcess.DEBUGEE_STDOUT_LOG_PREFIX); |
| stdoutRedirectingThread.start(); |
| stderrRedirectingThread = |
| new StderrRedirectingThread(this, process.getErrorStream(), |
| DebugeeProcess.DEBUGEE_STDERR_LOG_PREFIX); |
| stderrRedirectingThread.start(); |
| } |
| |
| /** |
| * Execute request for launching debuggee. |
| * |
| * @param request request to execute |
| */ |
| private void launchDebugee(LaunchDebugee request) throws IOException { |
| logger.trace(TRACE_LEVEL_ACTIONS, "LaunchDebugee: handle request: " + request); |
| |
| if (process != null) { |
| logger.complain("Unable to launch debuggee: process already launched"); |
| sendReply(new RequestFailed("Debuggee process already launched")); |
| return; |
| } |
| |
| try { |
| String[] cmd = request.cmd; |
| cmd[0] = convertPath(cmd[0], request.slash, "TESTED_JAVA_HOME", true); |
| for (int i = 1; i < cmd.length; i++) { |
| cmd[i] = convertPath(cmd[i], request.slash, "JAVA_ARGS", false); |
| } |
| String workDir = convertPath(request.workDir, request.slash, "WORKDIR", true); |
| String[] classPathes = convertPathes(request.classPathes, request.slash, "CLASSPATH", true); |
| String windir = argHandler.getDebugeeWinDir(); |
| |
| boolean win = (!(windir == null || windir.equals(""))); |
| String[] envp = new String[win ? 3 : 1] ; |
| envp[0] = "CLASSPATH=" + ArgumentParser.joinArguments(classPathes, "", pathSeparator); |
| if (win) { |
| envp[1] = "WINDIR=" + windir; |
| envp[2] = "SystemRoot=" + windir; |
| } |
| |
| logger.display("Setting environment:\n" |
| + " " + ArgumentHandler.joinArguments(envp, "", "\n ")); |
| logger.display("Setting work dir:\n" |
| + " " + workDir); |
| logger.display("Launching debuggee:\n" |
| + " " + ArgumentHandler.joinArguments(cmd, "\"")); |
| |
| process = Runtime.getRuntime().exec(cmd, envp, new File(workDir)); |
| logger.display(" debuggee launched successfully"); |
| |
| launchStreamRedirectors(process); |
| } catch (Exception e) { |
| if (!(e instanceof Failure)) { |
| e.printStackTrace(log.getOutStream()); |
| } |
| logger.complain("Caught exception while launching debuggee:\n\t" + e); |
| sendReply(new CaughtException(e)); |
| return; |
| } |
| |
| sendReply(new OK()); |
| } |
| |
| /** |
| * Execute request for waiting for debuggee exited. |
| * |
| * @param request request to execute |
| */ |
| private void waitForDebugee(WaitForDebugee request) throws IOException { |
| logger.trace(TRACE_LEVEL_ACTIONS, "WaitForDebugee: handle request: " + request); |
| |
| if (process == null) { |
| String reply = "No debuggee process to wait for"; |
| logger.complain(reply); |
| sendReply(new RequestFailed(reply)); |
| return; |
| } |
| |
| logger.display("Waiting for debuggee to exit"); |
| /* |
| // because timeout is not supported now |
| // we do not use separate thread for waiting for process |
| // and so following lines are commented out |
| |
| waitingThread = new ProcessWaitingThread(); |
| logger.trace(TRACE_LEVEL_ACTIONS, "LaunchingThread: starting thread for waiting for debugee process"); |
| waitingThread.start(); |
| try { |
| waitingThread.join(request.timeout); |
| if (waitingThread.isAlive()) { |
| String reply = "Timeout exceeded while waiting for debuggee to exit"; |
| logger.complain(reply); |
| waitingThread.interrupt(); |
| sendReply(socOut, new RequestFailed(reply)); |
| return; |
| } |
| } catch (InterruptedException e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while waiting for debuggee:\n\t" + e); |
| sendReply(new CaughtException(e)); |
| return; |
| } |
| int exitStatus = waitingThread.exitStatus; |
| waitingThread = null; |
| */ |
| int exitStatus; |
| try { |
| exitStatus = process.waitFor(); |
| waitForRedirectors(THREAD_TIMEOUT); |
| process.destroy(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while waiting for debuggee process to exit:\n\t" |
| + e); |
| sendReply(new CaughtException(e)); |
| return; |
| } |
| logger.display(" debuggee exited with exit status: " + exitStatus); |
| sendReply(new OK(exitStatus)); |
| } |
| |
| /** |
| * Execute request for returning debuggee exit code. |
| * |
| * @param request request to execute |
| */ |
| private void debugeeExitCode(DebugeeExitCode request) throws IOException { |
| logger.trace(TRACE_LEVEL_ACTIONS, "DebugeeExitCode: handle request: " + request); |
| |
| if (process == null) { |
| String reply = "No debuggee process to get exit code for"; |
| logger.complain(reply); |
| sendReply(new RequestFailed(reply)); |
| return; |
| } |
| |
| int exitStatus = 0; |
| try { |
| exitStatus = process.exitValue(); |
| } catch (IllegalThreadStateException e) { |
| logger.display("# WARNING: Caught exception while getting exit status of debuggee:\n\t" |
| + e); |
| sendReply(new CaughtException(e)); |
| return; |
| } |
| logger.trace(TRACE_LEVEL_ACTIONS, "DebugeeExitCode: return debuggee exit status: " + exitStatus); |
| sendReply(new OK(exitStatus)); |
| } |
| |
| /** |
| * Execute request for unconditional terminating debuggee process. |
| * |
| * @param request request to execute |
| */ |
| private void killDebugee(KillDebugee request) throws IOException { |
| logger.trace(TRACE_LEVEL_ACTIONS, "killDebugee: handle request: " + request); |
| |
| if (process == null) { |
| String reply = "No debuggee process to kill"; |
| logger.complain(reply); |
| sendReply(new RequestFailed(reply)); |
| return; |
| } |
| |
| logger.trace(TRACE_LEVEL_ACTIONS, "killDebugee: killing debuggee process"); |
| process.destroy(); |
| |
| logger.trace(TRACE_LEVEL_ACTIONS, "killDebugee: debuggee process killed"); |
| sendReply(new OK()); |
| } |
| |
| /** |
| * Terminate debigee VM process if still alive. |
| */ |
| private void terminateDebugeeAtExit() { |
| if (process != null) { |
| logger.trace(TRACE_LEVEL_ACTIONS, "Checking that debuggee process has exited correctly"); |
| try { |
| int value = process.exitValue(); |
| } catch (IllegalThreadStateException e) { |
| logger.complain("Debuggee process has not exited correctly: trying to kill it"); |
| process.destroy(); |
| try { |
| int value = process.exitValue(); |
| } catch (IllegalThreadStateException ie) { |
| logger.complain("Debuggee process is alive after killing it"); |
| } |
| process = null; |
| return; |
| } |
| logger.trace(TRACE_LEVEL_ACTIONS, "Debuggee process has exited correctly"); |
| } |
| } |
| |
| /** |
| * Wait for stream redirecting threads finished |
| * for specified timeout. |
| * |
| * @param millis timeout in milliseconds |
| */ |
| private void waitForRedirectors(long millis) { |
| try { |
| if (stdoutRedirectingThread != null) { |
| stdoutRedirectingThread.join(millis); |
| } |
| if (stderrRedirectingThread != null) { |
| stderrRedirectingThread.join(millis); |
| } |
| } catch (InterruptedException e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while waiting for debuggee process exited:\n\t" |
| + e); |
| } |
| } |
| |
| /** |
| * Wait for this thread finished |
| * for specified timeout or interrupt it. |
| * |
| * @param millis timeout in milliseconds |
| */ |
| public void waitForThread(long millis) { |
| shouldStop = true; |
| handleRequest(null); |
| waitInterruptThread(this, millis); |
| } |
| |
| /** |
| * Close connection with debuggee. |
| */ |
| public void closeConnection() { |
| // no connections to close |
| } |
| |
| /** |
| * Close thread by closing all connections with debuggee, |
| * finishing all redirectors and wait for thread finished. |
| */ |
| public synchronized void close() { |
| if (closed) { |
| return; |
| } |
| closeConnection(); |
| terminateDebugeeAtExit(); |
| if (stdoutRedirectingThread != null) { |
| stdoutRedirectingThread.close(); |
| stdoutRedirectingThread = null; |
| } |
| if (stderrRedirectingThread != null) { |
| stderrRedirectingThread.close(); |
| stderrRedirectingThread = null; |
| } |
| waitForThread(THREAD_TIMEOUT); |
| closed = true; |
| logger.trace(TRACE_LEVEL_THREADS, "LaunchingThread closed"); |
| } |
| |
| /** |
| * An inner thread for waiting for debuggee process exited |
| * and saving its exit status. (currently not used) |
| */ |
| /* |
| private class ProcessWaitingThread extends Thread { |
| int exitStatus = 0; |
| |
| ProcessWaitingThread() { |
| super("ProcessWaitingThread"); |
| } |
| |
| public void run() { |
| logger.trace(TRACE_LEVEL_THREADS, "ProcessWaitingThread: starting waiting for process"); |
| try { |
| exitStatus = process.waitFor(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while waiting for debuggee process:\n\t" |
| + e); |
| } |
| logger.trace(TRACE_LEVEL_ACTIONS, "ProcessWaitingThread: process finished with status: " + exitStatus); |
| logger.trace(TRACE_LEVEL_THREADS, "ProcessWaitingThread: exiting"); |
| } |
| |
| public synchronized void close() { |
| logger.trace(TRACE_LEVEL_THREADS, "ProcessWaitingThread closed"); |
| } |
| |
| } // ProcessWaitingThread |
| */ |
| } // LaunchingThread |
| |
| ///////// Redirecting threads ///////// |
| |
| /** |
| * An abstract base class for internal threads which redirects stderr/stdout |
| * output from debuggee process via <code>BindServer</code> connection. |
| * <p> |
| * Two derived classes will redirect <i>stderr</i> or </i>stdout</i> stream |
| * by enwrapping stream line by <code>DebugeeStderr</code> or |
| * <code>DebugeeStderr</code> objects. They should implement only one |
| * abstract method <code>enwrapLine(String)</code> to make the difference. |
| */ |
| public static abstract class StreamRedirectingThread extends Thread { |
| private volatile boolean shouldStop = false; |
| private volatile boolean closed = false; |
| |
| private LaunchingThread owner = null; |
| |
| private BufferedReader bin = null; |
| private String prefix = null; |
| |
| /** |
| * Make a thread to enwrap and redirect lines from specified |
| * input stream with given prefix. |
| * |
| * @param owner owner of this thread |
| * @param is input stream to redirect lines from |
| * @param prefix prefix to add to each line |
| */ |
| public StreamRedirectingThread(LaunchingThread owner, InputStream is, String prefix) { |
| super("StreamRedirectingThread"); |
| this.prefix = prefix; |
| this.owner = owner; |
| bin = new BufferedReader(new InputStreamReader(is)); |
| } |
| |
| /** |
| * Read lines from an input stream, enwrap them, and send to remote |
| * test via <code>BindServer</code> connection. |
| */ |
| public void run() { |
| logger.trace(TRACE_LEVEL_THREADS, "StreamRedirectingThread: starting redirect output stream"); |
| try { |
| String line; |
| logger.trace(TRACE_LEVEL_IO, "StreamRedirectingThread: waiting for line from debuggee output"); |
| while(!shouldStop) { |
| line = bin.readLine(); |
| if (line == null) |
| break; |
| owner.sendStreamMessage(enwrapLine(prefix + line)); |
| } |
| } catch (EOFException e) { |
| logger.display("Debuggee output stream closed by process"); |
| } catch (IOException e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.display("# WARNING: Connection to debuggee output stream aborted:\n\t" + e); |
| } catch (Exception e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while redirecting debuggee output stream:\n\t" |
| + e); |
| } |
| logger.trace(TRACE_LEVEL_THREADS, "StreamRedirectingThread: exiting"); |
| closeConnection(); |
| } |
| |
| /** |
| * Envrap output line by the appropriate wrapper. |
| * @param line line to enwrap |
| */ |
| protected abstract RedirectedStream enwrapLine(String line); |
| |
| /** |
| * Wait for this thread finished or interrupt it. |
| * |
| * @param millis timeout in milliseconds |
| */ |
| public void waitForThread(long millis) { |
| shouldStop = true; |
| waitInterruptThread(this, millis); |
| } |
| |
| /** |
| * Close redirected process output stream. |
| */ |
| public void closeConnection() { |
| if (closed) { |
| return; |
| } |
| if (bin != null) { |
| try { |
| bin.close(); |
| } catch (IOException e) { |
| e.printStackTrace(log.getOutStream()); |
| logger.complain("Caught exception while closing debuggee output stream:\n\t" |
| + e); |
| } |
| bin = null; |
| } |
| closed = true; |
| logger.trace(TRACE_LEVEL_THREADS, "StreamRedirectingThread closed"); |
| } |
| |
| /** |
| * Close thread by waiting redirected stream closed |
| * and finish the thread. |
| */ |
| public synchronized void close() { |
| if (closed) { |
| return; |
| } |
| waitForThread(THREAD_TIMEOUT); |
| closeConnection(); |
| closed = true; |
| logger.trace(TRACE_LEVEL_THREADS, "StreamRedirectingThread closed"); |
| } |
| |
| } // StreamRedirectingThread |
| |
| /** |
| * Particalar case of <code>StreamRedirectingThread</code> to redirect |
| * <i>stderr</i> stream by enwrapping lines into <code>DebugeeStderr</code> |
| * objects. |
| */ |
| private static class StderrRedirectingThread extends StreamRedirectingThread { |
| |
| /** |
| * Make a thread to redirect <i>stderr</i> output stream. |
| */ |
| StderrRedirectingThread(LaunchingThread owner, InputStream is, String prefix) { |
| super(owner, is, prefix); |
| setName("StderrRedirectingThread"); |
| } |
| |
| /** |
| * Enwrap given line into <code>DebugeeStderr</code> object. |
| */ |
| protected RedirectedStream enwrapLine(String line) { |
| return new DebugeeStderr(line); |
| } |
| |
| } |
| |
| /** |
| * Particalar case of <code>StreamRedirectingThread</code> to redirect |
| * <i>stdout</i> stream by enwrapping lines into <code>DebugeeStdout</code> |
| * objects. |
| */ |
| private static class StdoutRedirectingThread extends StreamRedirectingThread { |
| |
| /** |
| * Make a thread to redirect <i>stdout</i> output stream. |
| */ |
| StdoutRedirectingThread(LaunchingThread owner, InputStream is, String prefix) { |
| super(owner, is, prefix); |
| setName("StdoutRedirectingThread"); |
| } |
| |
| /** |
| * Enwrap given line into <code>DebugeeStdout</code> object. |
| */ |
| protected RedirectedStream enwrapLine(String line) { |
| return new DebugeeStdout(line); |
| } |
| |
| } |
| |
| ///////// BinderServer's packets ////////// |
| |
| /** |
| * Base serializable object to transmit request or reply |
| * via <code>BindServer</code> connection. |
| */ |
| public static class Packet implements Serializable {} |
| |
| ///////// Binder's requests ////////// |
| |
| /** |
| * Base class to represent request to <code>BindServer</code>. |
| */ |
| public static abstract class Request extends Packet {} |
| |
| /** |
| * This class implements task identification command. |
| */ |
| public static class TaskID extends Request { |
| public String id; |
| |
| public TaskID(String id) { |
| this.id = id; |
| } |
| |
| public String toString() { |
| return "TaskID: id=" + id; |
| } |
| } |
| |
| /** |
| * This class implements a request for launching a debugee. |
| */ |
| public static class LaunchDebugee extends Request { |
| public String slash; // slash symbol used on debugger host |
| public String[] cmd; // command line arguments as seen on debugger host |
| public String workDir; // path to working directory as seen on debugger host |
| public String[] classPathes; // list of class pathes as seen on debugger host |
| |
| public LaunchDebugee(String[] cmd, String slash, String workDir, |
| String[] pathes, String[] classPathes, |
| String[] libPathes) { |
| this.cmd = cmd; |
| this.slash = slash; |
| this.workDir = workDir; |
| this.classPathes = classPathes; |
| } |
| |
| public String toString() { |
| return "LaunchDebugee:" |
| + "\n\tcommand=" + ArgumentParser.joinArguments(cmd, "\"") |
| + "\n\tWORKDIR=" + workDir |
| + "\n\tCLASSPATH=" + ArgumentParser.joinArguments(classPathes, "", ":") |
| + "\n\tslash=" + slash; |
| } |
| } |
| |
| /** |
| * This class implements a request for waiting for debugee |
| * termination. |
| */ |
| public static class WaitForDebugee extends Request { |
| public long timeout = 0; // timeout in minutes for waiting |
| |
| public WaitForDebugee(long value) { |
| timeout = value; |
| } |
| |
| public String toString() { |
| return "WaitForDebugee: timeout=" + timeout; |
| } |
| } |
| |
| /** |
| * This class implements a request for exit code of |
| * debugee process. |
| */ |
| public static class DebugeeExitCode extends Request { |
| public String toString() { |
| return "SebugeeExitCode"; |
| } |
| } |
| |
| /** |
| * This class implements a request for killing debugee process. |
| */ |
| public static class KillDebugee extends Request { |
| public String toString() { |
| return "KillDebugee"; |
| } |
| } |
| |
| /** |
| * This class implements a request to disconnect connection with test. |
| */ |
| public static class Disconnect extends Request { |
| public String toString() { |
| return "Disconnect"; |
| } |
| } |
| |
| ///////// BindServer's responses ////////// |
| |
| /** |
| * Base class to represent response from <code>BindServer</code>. |
| */ |
| public static abstract class Response extends Packet {} |
| |
| /** |
| * This class implements a response that a previoulsy received |
| * request has been successfully performed. |
| */ |
| public static class OK extends Response { |
| public long info = BindServer.VERSION; // optional additional info |
| |
| public OK() { |
| } |
| |
| public OK(long value) { |
| info = value; |
| } |
| |
| public String toString() { |
| return "OK(" + info + ")"; |
| } |
| } |
| |
| /** |
| * This class implements a response that the BindServer is |
| * unable to serve a previoulsy received request. |
| */ |
| public static class RequestFailed extends Response { |
| public String reason; // the short explanation of failure |
| |
| public RequestFailed(String reason) { |
| this.reason = reason; |
| } |
| |
| public String toString() { |
| return "RequestFailed(" + reason + ")"; |
| } |
| } |
| |
| /** |
| * This class implements a response that the BindServer is |
| * unable to serve a previoulsy received request because of |
| * caught exception. |
| */ |
| public static class CaughtException extends RequestFailed { |
| public CaughtException(Exception cause) { |
| super("Caught exception: " + cause); |
| } |
| } |
| |
| ///////// Wrappers for redirected messages ////////// |
| |
| /** |
| * Base class to represent wrappers for redirected streams. |
| */ |
| public static class RedirectedStream extends Packet { |
| public String line; // line containing line from redirected stream |
| |
| public RedirectedStream(String str) { |
| line = str; |
| } |
| |
| public String toString() { |
| return "RedirectedStream(" + line + ")"; |
| } |
| } |
| |
| /** |
| * This class enwraps redirected line of <i>stdout</i> stream. |
| */ |
| public static class DebugeeStdout extends RedirectedStream { |
| |
| public DebugeeStdout(String str) { |
| super(str); |
| } |
| |
| public String toString() { |
| return "DebugeeStdout(" + line + ")"; |
| } |
| } |
| |
| /** |
| * This class enwraps redirected line of <i>stderr</i> stream. |
| */ |
| public static class DebugeeStderr extends RedirectedStream { |
| public DebugeeStderr(String str) { |
| super(str); |
| } |
| |
| public String toString() { |
| return "DebugeeStderr(" + line + ")"; |
| } |
| } |
| |
| /////// ArgumentHandler for BindServer command line ///////// |
| |
| /** |
| * This class is used to parse arguments from command line |
| * and specified <i>bind-file</i>, |
| */ |
| private static class ArgumentHandler extends ArgumentParser { |
| |
| protected Properties fileOptions; |
| |
| /** |
| * Make parser object for command line arguments. |
| * |
| * @param args list of command line arguments |
| */ |
| public ArgumentHandler(String[] args) { |
| super(args); |
| } |
| |
| /** |
| * Check if given command line option is aloowed. |
| * |
| * @param option option name |
| * @param value option value |
| */ |
| protected boolean checkOption(String option, String value) { |
| if (option.equals("bind.file")) { |
| // accept any file name |
| return true; |
| } |
| return super.checkOption(option, value); |
| } |
| |
| /** |
| * Check if all recignized options are compatible. |
| */ |
| protected void checkOptions() { |
| if (getBindFileName() == null) { |
| throw new BadOption("Option -bind.file is requred "); |
| } |
| super.checkOptions(); |
| } |
| |
| /** |
| * Check if value of this option points to a existing directory. |
| * |
| * @param option option name |
| * @param dir option value |
| */ |
| private void checkDir(String option, String dir) { |
| File file = new File(dir); |
| if (!file.exists()) { |
| throw new BadOption(option + " does not exist: " + dir); |
| } |
| if (!file.isAbsolute()) { |
| throw new BadOption(option + " is not absolute pathname: " + dir); |
| } |
| if (!file.isDirectory()) { |
| throw new BadOption(option + " is not directory: " + dir); |
| } |
| } |
| |
| /** |
| * Check if option from <i>bind-file</i> is allowed. |
| * |
| * @param option option name |
| * @param value option value |
| */ |
| protected boolean checkAdditionalOption(String option, String value) { |
| |
| if (option.equals("DEBUGGER_HOST")) { |
| // accept any hostname |
| return true; |
| } |
| |
| if (option.equals("BINDSERVER_PORT")) { |
| // accept only integer value |
| try { |
| int port = Integer.parseInt(value); |
| } catch (NumberFormatException e) { |
| throw new Failure("Not integer value of bind-file option " + option |
| + ": " + value); |
| } |
| return true; |
| } |
| |
| if (option.equals("DEBUGGER_TESTED_JAVA_HOME") |
| || option.equals("DEBUGGER_WORKDIR") |
| || option.equals("DEBUGGER_TESTBASE")) { |
| if (value == null || value.equals("")) { |
| throw new BadOption("Empty value of bind-file option " + option); |
| } |
| return true; |
| } |
| |
| if (option.equals("DEBUGGEE_TESTED_JAVA_HOME") |
| || option.equals("DEBUGGEE_WORKDIR") |
| || option.equals("DEBUGGEE_TESTBASE")) { |
| if (value == null || value.equals("")) { |
| throw new BadOption("Empty value of bind-file option " + option); |
| } |
| checkDir(option, value); |
| return true; |
| } |
| |
| if (option.equals("DEBUGGEE_WINDIR")) { |
| if (!(value == null || value.equals(""))) { |
| checkDir(option, value); |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Check if all recignized options form <i>bind-file</i> are compatible. |
| */ |
| protected void checkAdditionalOptions() { |
| |
| if (getDebuggerJavaHome() == null) { |
| throw new BadOption("Option DEBUGGER_JAVA_HOME missed from bind-file"); |
| } |
| if (getDebuggerWorkDir() == null) { |
| throw new BadOption("Option DEBUGGER_WORKDIR missed from bind-file"); |
| } |
| if (getDebuggerTestbase() == null) { |
| throw new BadOption("Option DEBUGGER_TESTBASE missed from bind-file"); |
| } |
| |
| if (getDebugeeJavaHome() == null) { |
| throw new BadOption("Option DEBUGGEE_JAVA_HOME missed from bind-file"); |
| } |
| if (getDebugeeWorkDir() == null) { |
| throw new BadOption("Option DEBUGGEE_WORKDIR missed from bind-file"); |
| } |
| if (getDebugeeTestbase() == null) { |
| throw new BadOption("Option DEBUGGEE_TESTBASE missed from bind-file"); |
| } |
| } |
| |
| /** |
| * Parse options form specified <i>bind-file</i>. |
| */ |
| protected void parseAdditionalOptions() { |
| Enumeration keys = fileOptions.keys(); |
| while (keys.hasMoreElements()) { |
| String option = (String)keys.nextElement(); |
| String value = fileOptions.getProperty(option); |
| if (! checkAdditionalOption(option, value)) { |
| throw new BadOption("Unrecognized bind-file option: " + option); |
| } |
| } |
| checkAdditionalOptions(); |
| } |
| |
| /** |
| * Parse all options from command line and specified <i>bind-file</i>. |
| */ |
| protected void parseArguments() { |
| super.parseArguments(); |
| String fileName = getBindFileName(); |
| try { |
| FileInputStream bindFile = new FileInputStream(fileName); |
| fileOptions = new Properties(); |
| fileOptions.load(bindFile); |
| bindFile.close(); |
| } catch(FileNotFoundException e) { |
| throw new BadOption("Unable to open bind-file " + fileName + ": " + e); |
| } catch(IOException e) { |
| e.printStackTrace(log.getOutStream()); |
| throw new Failure("Caught exception while reading bind-file:\n" + e); |
| } |
| parseAdditionalOptions(); |
| } |
| |
| /** Return name of specified <i>bind-file<i>. */ |
| public String getBindFileName() { |
| return options.getProperty("bind.file"); |
| } |
| |
| /** Return specified debuggee host name . */ |
| public String getDebuggerHost() { |
| return fileOptions.getProperty("DEBUGGER_HOST", "localhost"); |
| } |
| |
| /** Return string representation of port number for <code>BindServer<code> connection. */ |
| public String getBindPort() { |
| return fileOptions.getProperty("BINDSERVER_PORT", "9000"); |
| } |
| |
| /** Return specified port number for <code>BindServer<code> connection. */ |
| public int getBindPortNumber() { |
| try { |
| return Integer.parseInt(getBindPort()); |
| } catch (NumberFormatException e) { |
| throw new Failure("Not integer value of BindServer port"); |
| } |
| } |
| |
| /** Return specified path to tested JDK used for debuggee VM. */ |
| public String getDebugeeJavaHome() { |
| return fileOptions.getProperty("DEBUGGEE_TESTED_JAVA_HOME"); |
| } |
| |
| /** Return specified path to tested JDK used for debugger. */ |
| public String getDebuggerJavaHome() { |
| return fileOptions.getProperty("DEBUGGER_TESTED_JAVA_HOME"); |
| } |
| |
| /** Return specified path to working dir from debuggee host. */ |
| public String getDebugeeWorkDir() { |
| return fileOptions.getProperty("DEBUGGEE_WORKDIR"); |
| } |
| |
| /** Return specified path to working dir from debugger host. */ |
| public String getDebuggerWorkDir() { |
| return fileOptions.getProperty("DEBUGGER_WORKDIR"); |
| } |
| |
| /** Return specified path to testbase dir from debuggee host. */ |
| public String getDebugeeTestbase() { |
| return fileOptions.getProperty("DEBUGGEE_TESTBASE"); |
| } |
| |
| /** Return specified path to testbase dir from debugger host. */ |
| public String getDebuggerTestbase() { |
| return fileOptions.getProperty("DEBUGGER_TESTBASE"); |
| } |
| |
| /** Return specified path to system directory on Wimdows platform. */ |
| public String getDebugeeWinDir() { |
| return fileOptions.getProperty("DEBUGGEE_WINDIR"); |
| } |
| |
| } // ArgumentHandler |
| |
| } // BindServer |