e1e758a7b4
Reviewed-by: coleenp, dholmes
1886 lines
68 KiB
Java
1886 lines
68 KiB
Java
/*
|
|
* Copyright (c) 2001, 2023, 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, "");
|
|
|
|
registerCleanup();
|
|
|
|
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 {
|
|
cleanup();
|
|
} 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()
|
|
*/
|
|
@Override
|
|
public void cleanup() {
|
|
close();
|
|
}
|
|
|
|
/**
|
|
* Make finalization of <code>BindServer</code> object at program exit
|
|
* by invoking method <code>cleanup()</code>.
|
|
*
|
|
*/
|
|
public void finalizeAtExit() throws Throwable {
|
|
cleanup();
|
|
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
|