/*
* Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program 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 program 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 at /legal/license.txt).
*
* 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 Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
import java.io.*;
import java.util.Vector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketException;
import java.net.InetAddress;
import sun.misc.VMInspector;
/*
* cvmsh provides a shell for developers to use. It is provide basic features
* like launching applications either synchronously or in background threads,
* as well as inspection tools to look query the state of the VM.
*/
public class cvmsh
{
final static int DEFAULT_PORT = 4321;
protected int port = 0;
protected Class serverSocketClass = null;
protected Object serverSocket = null;
protected CVMSHServer server = null;
protected boolean isVerbose = false;
protected boolean isServerMode = false;
protected boolean hasQuitRequest = false;
protected boolean shellIsRunning = false;
// JVMPI data dump service:
protected boolean jvmpiSupported;
protected Class cvmJvmpiClass;
static final String[] helpMsg = {
"Commands:",
" help - prints this list",
" quit - exits this shell nicely",
" quit! - exits this shell by calling OS exit()",
" detach - detaches the client (if applicable) but",
" leaves the VM and server running",
"",
" sysinfo - dumps misc system information",
" gc - request a full GC cycle",
" memstat - prints VM heap memory usage",
" enableGC - enables the GC",
" disableGC - disables the GC",
" keepObjectsAlive true|false - forces the GC to keep all objs alive (or not)",
"",
" verbose - enables verbose output",
" quiet - disables verbose output",
" port=<port number> - sets the server port number to listen on",
" startServer - starts server thread",
" stopServer - stops server thread",
"",
" Dumpers:",
" ========",
" print <objAddr> - print object as string",
" dumpObject <objAddr> - dump the specified object",
" dumpClassBlock <cbAddr> - dump the specified class",
" dumpObjectReferences <objAddr> - dump all references to the obj",
" dumpClassReferences <classname> - dump all references to instances of the class",
" dumpClassBlocks <classname> - dump all CBs of specified name",
" dumpHeap [simple|verbose|stats] - dump heap contents or info",
" - does a simple(default), verbose, or stats dump.",
"",
" Capturing and Comparing Heap states:",
" ====================================",
" captureHeapState [<comment>] - capture the current heap state",
" releaseHeapState <id> - release the specified heap state",
" releaseAllHeapStates - release all heap states",
" listHeapStates - list captured heap states",
" dumpHeapState <id> [obj|class] - dump the specified heap state",
" - sort by obj or obj's class or none if unspecified.",
" compareHeapState <id1> <id2> - compares the objs in the heap states",
"",
" Thread Queries:",
" ===============",
" listAllThreads - list all the currently live threads",
" dumpAllThreads - dump the stack trace of all the currently",
" live threads",
" dumpStack <eeAddr> - dump the stack trace of the specified thread",
"",
" Misc utilities:",
" ===============",
" time <command> - computes time to execute specified command",
" run <class> [args ...] - runs the specified app synchronously",
" bg <class> [args ...] - runs the specified app in a new thread",
};
static final String[] jvmpiHelpMsg = {
"",
" JVMPI utilities:",
" ================",
" jvmpiDumpData - requests a JVMPI data dump",
};
public cvmsh() {
try {
serverSocketClass = Class.forName("java.net.ServerSocket");
} catch (ClassNotFoundException e) {
}
// Get the CVMJVMPI class if present:
try {
cvmJvmpiClass = Class.forName("sun.misc.CVMJVMPI");
jvmpiSupported = true;
} catch (ClassNotFoundException e) {
cvmJvmpiClass = null;
jvmpiSupported = false;
}
}
void doHelp() {
printHelp(helpMsg);
printJVMPIHelp();
}
void printHelp(String[] msg) {
for (int i = 0; i < msg.length; i++) {
System.out.println(msg[i]);
}
}
protected void printJVMPIHelp() {
if (jvmpiSupported) {
printHelp(jvmpiHelpMsg);
}
}
public boolean quitRequested() {
return hasQuitRequest;
}
void doGC() {
if (VMInspector.gcIsDisabled()) {
System.err.println("ERROR: GC is currently disabled. " +
"Re-enable GC before invoking gc.");
return;
}
java.lang.Runtime.getRuntime().gc();
doMemstat();
}
void doMemstat() {
Runtime r = java.lang.Runtime.getRuntime();
System.out.println("free memory = " + r.freeMemory());
System.out.println("total memory = " + r.totalMemory());
}
void keepObjectsAlive(CmdStream cmd) {
String token = getToken(cmd);
try {
boolean keepAlive = getBoolean(token);
VMInspector.keepAllObjectsAlive(keepAlive);
} catch (IllegalArgumentException e) {
System.err.println("ERROR: Expecting true or false instead of:" +
token);
}
}
Object getObject(String token) {
Object obj = null;
try {
long addr = getLong(token);
try {
obj = VMInspector.addrToObject(addr);
} catch (IllegalStateException e) {
System.err.println("ERROR: Need to disable GC before " +
"calling print");
} catch (IllegalArgumentException e) {
System.err.println("ERROR: address " + token +
" is not a valid object");
}
} catch (NumberFormatException e) {
System.err.println("ERROR: Invalid address format: " + token);
}
return obj;
}
long getAddr(String token) {
long addr = 0L;
try {
addr = getLong(token);
} catch (NumberFormatException e) {
System.err.println("ERROR: Invalid address format: " + token);
}
return addr;
}
void print(CmdStream cmd) {
String token = getToken(cmd);
Object obj = getObject(token);
if (obj != null) {
if (obj instanceof String) {
String s = (String)obj;
System.out.println("String " + token + ": length= " +
s.length());
System.out.println(" value= \"" + s + "\"");
} else {
System.out.println("Object " + token + ": " + obj);
}
}
}
void dumpObject(CmdStream cmd) {
String token = getToken(cmd);
long addr = getAddr(token);
if (addr != 0L) {
VMInspector.dumpObject(addr);
}
}
void dumpClassBlock(CmdStream cmd) {
String token = getToken(cmd);
long addr = getAddr(token);
if (addr != 0L) {
VMInspector.dumpClassBlock(addr);
}
}
void dumpObjectReferences(CmdStream cmd) {
String token = getToken(cmd);
long addr = getAddr(token);
if (addr != 0L) {
VMInspector.dumpObjectReferences(addr);
}
}
void dumpClassReferences(CmdStream cmd) {
String token = getToken(cmd);
if (token != "") {
VMInspector.dumpClassReferences(token);
} else {
System.err.println("ERROR: Classname not specified");
}
}
void dumpClassBlocks(CmdStream cmd) {
String token = getToken(cmd);
if (token != "") {
VMInspector.dumpClassBlocks(token);
} else {
System.err.println("ERROR: Classname not specified");
}
}
void dumpHeap(CmdStream cmd) {
String token = getToken(cmd);
if (token.equals("")) {
VMInspector.dumpHeapSimple();
} else if (token.equals("simple")) {
VMInspector.dumpHeapSimple();
} else if (token.equals("verbose")) {
VMInspector.dumpHeapVerbose();
} else if (token.equals("stats")) {
VMInspector.dumpHeapStats();
} else {
System.err.println("ERROR: Unsupported heap dump type: " + token);
}
}
void captureHeapState(CmdStream cmd) {
String name = cmd.input;
VMInspector.captureHeapState(name);
}
void releaseHeapState(CmdStream cmd) {
String token = getToken(cmd);
try {
int id = getInt(token);
VMInspector.releaseHeapState(id);
} catch (NumberFormatException e) {
System.err.println("ERROR: Invalid ID format: " + token);
}
}
void dumpHeapState(CmdStream cmd) {
String idToken = getToken(cmd);
String sortKeyToken = getToken(cmd);
try {
int id = getInt(idToken);
int sortKey = VMInspector.SORT_NONE;
if (sortKeyToken.equals("obj")) {
sortKey = VMInspector.SORT_BY_OBJ;
} else if (sortKeyToken.equals("class")) {
sortKey = VMInspector.SORT_BY_OBJCLASS;
}
VMInspector.dumpHeapState(id, sortKey);
} catch (NumberFormatException e) {
System.err.println("ERROR: Invalid ID format: " + idToken);
}
}
void compareHeapState(CmdStream cmd) {
String token1 = getToken(cmd);
String token2 = getToken(cmd);
String currentToken = token1;
try {
int id1 = getInt(token1);
currentToken = token2;
int id2 = getInt(token2);
VMInspector.compareHeapState(id1, id2);
} catch (NumberFormatException e) {
System.err.println("ERROR: Invalid ID format: " + currentToken);
}
}
// Thread Queries:
void dumpStack(CmdStream cmd) {
String token = getToken(cmd);
try {
int ee = getInt(token);
VMInspector.dumpStack(ee);
} catch (NumberFormatException e) {
System.err.println("ERROR: Invalid EE format: " + token);
}
}
class BackgroundThread extends Thread {
String className;
String[] args;
cvmsh shell;
BackgroundThread(cvmsh shell, String className, String[] args) {
this.className = className;
this.args = args;
this.shell = shell;
}
public void run() {
shell.run(className, args, false);
}
}
void run(String className, String[] args, boolean background) {
if (!background) {
try {
Class cls = Class.forName(className);
Class[] argClses = {String[].class};
Method mainMethod = cls.getMethod("main", argClses);
Object[] argObjs = {args};
mainMethod.invoke(null, argObjs);
} catch (InvocationTargetException ite) {
ite.getTargetException().printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
BackgroundThread t = new BackgroundThread(this, className, args);
t.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// JVMPI utilties:
protected void jvmpiDumpData() {
if (!jvmpiSupported) {
return;
}
try {
// Use reflection to call:
// sun.misc.CVMJVMPI.postDataDumpRequestEvent();
Method dataDumpMtd;
Class[] argsTypes = new Class[0];
dataDumpMtd =
cvmJvmpiClass.getMethod("postDataDumpRequestEvent", argsTypes);
Object[] voidArgs = new Object[0];
dataDumpMtd.invoke(null, voidArgs);
} catch (Exception e) {
System.err.println("ERROR: Unexpected exception while " +
"requesting JVMPI data dump: " + e);
}
}
class CmdStream {
// NOTE: The tokenizing code assumes that the delimiter is a single
// character as is the case now. If this assumption changes, then
// the code will need to be changed accordingly.
private static final char DELIMITER = ';';
private static final String DELIMITER_STR = ";";
CmdStream(String inputString) {
input = inputString;
//System.err.println("new CmdStream(): \"" + input + "\"");
}
private String input;
public boolean isDelimiter(String token) {
// NOTE: token should never be null. CmdStream.getToken() will
// always return a valid String token. If the CmdStream is
// empty, then getToken() returns "". Hence, we don't need a
// null check here.
if (token.equals(DELIMITER_STR) || token.equals("")) {
return true;
}
return false;
}
public boolean isEmpty() {
return ((input == null) || (input.equals("")));
}
public void clear() {
input = null;
}
public String getToken() {
String token;
int wsIdx;
int delimIdx;
int index;
int length;
//System.err.println("getToken(): from \"" + input + "\"");
// If we have no more commands to parse, return a NULL token:
if (input == null) {
return "";
}
// Count and remove of leading whitespaces:
length = input.length();
for (wsIdx = 0;
(wsIdx < length) && (input.charAt(wsIdx) == ' ');
wsIdx++);
length -= wsIdx;
input = input.substring(wsIdx);
// Find next white space:
wsIdx = input.indexOf(' ');
// Find the next delimiter:
delimIdx = input.indexOf(DELIMITER);
if (delimIdx == 0) {
// We have a delimiter token:
input = input.substring(DELIMITER_STR.length());
return DELIMITER_STR;
}
// If we get here, then we know that the token is not a delimiter:
length = input.length();
if (delimIdx == -1) delimIdx = length;
if (wsIdx == -1) wsIdx = length;
// The token ends at the next white space or delimiter, whichever
// comes first:
index = Math.min(delimIdx, wsIdx);
token = input.substring(0, index);
input = input.substring(index);
//System.err.println("TOKEN: \"" + token + "\" : \"" + input + "\"");
return token;
}
public String[] getArgs() {
String[] args = new String[0];
Vector argsVec = new Vector();
String token;
token = getToken();
while (!isDelimiter(token)) {
argsVec.add(token);
token = getToken();
}
args = (String[])argsVec.toArray(args);
return args;
}
}
public String getToken(CmdStream cmd) {
return cmd.getToken();
}
String[] getArgs(CmdStream cmd) {
return cmd.getArgs();
}
static int getInt(String token) throws NumberFormatException {
int result;
int radix = 10;
if (token.startsWith("0x")) {
int index;
index = token.indexOf('x');
token = token.substring(index + 1);
radix = 16;
}
result = Integer.parseInt(token, radix);
return result;
}
static long getLong(String token) throws NumberFormatException {
long result;
int radix = 10;
if (token.startsWith("0x")) {
int index;
index = token.indexOf('x');
token = token.substring(index + 1);
radix = 16;
}
result = Long.parseLong(token, radix);
return result;
}
static boolean getBoolean(String token)
throws IllegalArgumentException {
boolean result = false;
if (token.toLowerCase().equals("true")) {
result = true;
} else if (token.toLowerCase().equals("false")) {
result = false;
} else {
throw new IllegalArgumentException();
}
return result;
}
boolean processCmd(CmdStream cmd, CVMSHRunnable server) {
boolean done;
do {
String token = getToken(cmd);
done = processToken(token, cmd, server);
if (server != null) {
done = done || server.isStopped();
}
} while (!done && !cmd.isEmpty());
return done;
}
boolean processToken(String token, CmdStream cmd,
CVMSHRunnable server) {
if (isVerbose && !cmd.isDelimiter(token)){
System.out.println("command: " + token);
}
// Note: the difference between "quit" and "detach" is that "quit"
// will cause the cvmsh to exit. "detach" will simply close the
// socket connection and wait for another one.
if (token.equals("quit")) {
hasQuitRequest = true;
// If the server is null, then we're not in the server
// thread. Since this is a "quit", go ahead and wait for
// the server thread to end.
//
// If the server is NOT null, then we must not stop here
// to wait for the server thread (i.e. ourself) to end.
// If we're stop here, obviously we won't be able to end.
if (isServerMode) {
stopServer(/* calledFromClient */ (server == null));
}
return true;
} else if (token.equals("quit!")) {
VMInspector.exit(0);
} else if (token.equals("detach")) {
return true;
} else if (cmd.isDelimiter(token)) {
// It's a command delimiter. Go handle the next command.
// NOTE: delimiter includes ";" and "".
return false;
} else if (token.equals("help")) {
doHelp();
} else if (token.equals("sysinfo")) {
VMInspector.dumpSysInfo();
} else if (token.equals("gc")) {
// Release everything we can before GCing:
token = null;
// cmd.input = null;
doGC();
} else if (token.equals("memstat")) {
doMemstat();
} else if (token.equals("enableGC")) {
VMInspector.enableGC();
} else if (token.equals("disableGC")) {
VMInspector.disableGC();
} else if (token.equals("keepObjectsAlive")) {
keepObjectsAlive(cmd);
} else if (token.equals("verbose")) {
if (!isVerbose) {
System.out.println("mode: verbose ON");
}
isVerbose = true;
} else if (token.equals("quiet")) {
if (isVerbose) {
System.out.println("mode: verbose OFF");
}
isVerbose = false;
} else if (token.startsWith("port=")) {
synchronized(this) {
if (isServerMode) {
System.err.println("ERROR: Cannot change port number " +
"while the server mode has been started.");
return false;
}
String portStr = token.substring(token.indexOf('=') + 1);
try {
int value = getInt(portStr);
if (isVerbose) {
System.out.println("set port: " + value);
}
port = value;
} catch (NumberFormatException e) {
System.err.println("ERROR: Invalid port number: " + portStr);
}
}
} else if (token.equals("startServer")) {
startServer();
} else if (token.equals("stopServer")) {
stopServer(/* calledFromClient */ (server == null));
// If the stopServer requests comes from the server, then we're
// done. Let's return indicating so.
if (server != null) {
return true;
}
} else if (token.equals("print")) {
print(cmd);
} else if (token.equals("dumpObject")) {
dumpObject(cmd);
} else if (token.equals("dumpClassBlock")) {
dumpClassBlock(cmd);
} else if (token.equals("dumpObjectReferences")) {
dumpObjectReferences(cmd);
} else if (token.equals("dumpClassReferences")) {
dumpClassReferences(cmd);
} else if (token.equals("dumpClassBlocks")) {
dumpClassBlocks(cmd);
} else if (token.equals("dumpHeap")) {
dumpHeap(cmd);
} else if (token.equals("captureHeapState")) {
captureHeapState(cmd);
} else if (token.equals("releaseHeapState")) {
releaseHeapState(cmd);
} else if (token.equals("releaseAllHeapStates")) {
VMInspector.releaseAllHeapState();
} else if (token.equals("listHeapStates")) {
VMInspector.listHeapStates();
} else if (token.equals("dumpHeapState")) {
dumpHeapState(cmd);
} else if (token.equals("compareHeapState")) {
compareHeapState(cmd);
// Thread Queries:
} else if (token.equals("listAllThreads")) {
VMInspector.listAllThreads();
} else if (token.equals("dumpAllThreads")) {
VMInspector.dumpAllThreads();
} else if (token.equals("dumpStack")) {
dumpStack(cmd);
// Misc Utilities:
} else if (token.equals("run")) {
String classname = getToken(cmd);
String[] args = getArgs(cmd);
run(classname, args, false);
} else if (token.equals("bg")) {
String classname = getToken(cmd);
String[] args = getArgs(cmd);
run(classname, args, true);
} else if (token.equals("time")) {
long startTime = System.currentTimeMillis();
processCmd(cmd, server);
long endTime = System.currentTimeMillis();
System.out.println("Time elapsed: " + (endTime - startTime) + " ms");
// JVMPI utilities:
} else if (jvmpiSupported && token.equals("jvmpiDumpData")) {
jvmpiDumpData();
} else {
System.err.print("Unknown command: \"" + token);
if (!cmd.isEmpty()) {
System.err.print(cmd.input);
cmd.clear();
}
System.err.println("\"");
System.err.println("type \"help\" for available commands");
}
return false;
}
public void runShell() {
shellIsRunning = true;
serviceInput(System.in, System.out, null);
}
public void serviceInput(InputStream inStream, OutputStream outStream,
CVMSHRunnable server) {
InputStreamReader in = new InputStreamReader(inStream);
int numberOfChars;
char[] buf = new char[1000];
boolean promptDisturbed = false;
// If we're called with server != null, then we know that we're
// called from the server thread. That in turn means that the client
// console has just attached, and we're now ready to accept commands.
if (isVerbose && (server != null)) {
System.err.println("STATUS: client has attached");
promptDisturbed = true;
}
try {
boolean done = false;
final char CR = (char)0xD;
final char LF = (char)0xA;
while (!done && !quitRequested()) {
String input;
// If the server is null, then we are currently servicing
// the console for cvmsh (as opposed to a remote console).
// Hence, we need to print the console prompt first:
if ((server == null) ||
(shellIsRunning && promptDisturbed)) {
System.out.print("cvmsh> ");
}
// Note: we'll be blocking here until some input comes in.
// If the source of the input happens to be from a cvmclient
// across a socket connection, then we'll be waiting until
// client sends us some info.
// Note: This means that if we also have a local console
// running at the same time, and a quit is requested, then
// cvmsh will not quit until after it read some input from
// the client via the socket.
numberOfChars = in.read(buf, 0, buf.length);
// First strip off the LF character if present:
if ((numberOfChars > 0) && (buf[numberOfChars - 1] == LF)) {
numberOfChars--;
}
// Next, strip off the CR character if present:
if ((numberOfChars > 0) && (buf[numberOfChars - 1] == CR)) {
numberOfChars--;
}
// Don't include the '\n' at the end of the buffer in the
// string:
input = new String(buf, 0, numberOfChars);
//System.out.println("READ: [" + input.length() +
// "] \"" + input + "\"");
// If the server is NOT null, then we are currently
// servicing a remote console for cvmsh (as opposed to the
// local cvmsh console). Hence, we need to print the
// console prompt and the received command for context:
if (server != null) {
if (!shellIsRunning) {
System.out.print("cvmsh> ");
}
System.out.println(input);
}
CmdStream cmd = new CmdStream(input);
input = null; // Release it.
done = processCmd(cmd, server);
if (server != null) {
promptDisturbed = true;
done = done || server.isStopped();
}
}
} catch (SocketException e) {
if (isVerbose && isServerMode) {
System.err.println("STATUS: client has detached");
promptDisturbed = true;
}
} catch (IOException e) {
if (isVerbose && isServerMode) {
System.err.println("STATUS: no client attached");
promptDisturbed = true;
}
} catch (Throwable e) {
if (isVerbose && isServerMode) {
System.err.println("STATUS: lost connection with client");
promptDisturbed = true;
} else {
System.err.println("ERROR: " + e);
promptDisturbed = true;
}
}
// If the shell prompt
if (promptDisturbed && shellIsRunning) {
System.out.print("cvmsh> ");
}
}
/**
* Starts a socket server to service incoming requests to cvmsh.
*/
public synchronized void startServer() {
if (serverSocketClass == null) {
System.err.println("Server mode not supported: need Foundation Profile or higher");
return;
}
if (isServerMode) {
if (isVerbose) {
System.err.println("Server already started on port "+ port);
}
return;
}
isServerMode = true;
if (port == 0) {
port = DEFAULT_PORT;
if (isVerbose) {
System.out.println("port not set. Using default port: " +
port);
}
}
try {
// Use reflection to do this:
// serverSocket = new ServerSocket(port);
// Get the contructor:
Class[] argsTypes = new Class[1];
argsTypes[0] = Integer.TYPE;
Constructor ct = serverSocketClass.getConstructor(argsTypes);
Object[] argsList = new Object[1];
argsList[0] = new Integer(port);
serverSocket = ct.newInstance(argsList);
// Create the server and run it in its own thread:
server = new CVMSHServer(this, serverSocketClass, serverSocket, port);
Thread serverThread = new Thread(server);
serverThread.start();
} catch (InvocationTargetException e) {
System.err.println("ERROR: Cannot start server on port "+ port);
return;
} catch (Exception e) {
System.err.println("ERROR: Unexpected exception while " +
"creating server: " + e);
return;
}
}
/**
* Stop the previously started socket server.
*/
public synchronized void stopServer(boolean calledFromClient) {
if (!isServerMode) {
if (isVerbose) {
System.err.println("Server not started yet. No need to stop");
}
return;
}
server.stop(calledFromClient);
}
/**
* Report that the server is stopping.
*/
public synchronized void serverStopped() {
if (isServerMode) {
try {
// Use reflection to call:
// serverSocket.close();
Class[] argsTypes = new Class[0];
Method closeMtd;
closeMtd = serverSocketClass.getMethod("close", argsTypes);
Object[] voidArgs = new Object[0];
closeMtd.invoke(serverSocket, voidArgs);
} catch (InvocationTargetException ioe) {
System.err.println("ERROR: IO error while closing "+
"server socket");
} catch (Exception e) {
System.err.println("ERROR: Unexpected exception while " +
"closing server socket: " + e);
return;
}
serverSocket = null;
server = null;
isServerMode = false;
}
}
/**
* Starts a socket server to service incoming requests to cvmsh.
* @returns true if we're done with cvmsh. Else, returns false.
*/
public boolean parseOptions(String[] args) {
boolean done = false;
boolean foundMainClass = false;
for (int i = 0; !done && i < args.length; i++) {
String arg = args[i];
if (arg.startsWith("--X")) {
if (++i < args.length) {
CmdStream cmd = new CmdStream(args[i]);
done = processCmd(cmd, null);
}
} else {
// The rest of the args are for the main class and it's args:
String className = arg;
int numberOfArgs = args.length - i - 1;
String[] newArgs = new String[numberOfArgs];
//Build the new args array:
for (int j = 0; j < numberOfArgs; j++) {
newArgs[j] = args[i + j + 1];
}
i += numberOfArgs;
foundMainClass = true;
// Invoke the main of the mainClass with its args:
run(className, newArgs, false);
}
}
// We need to run the shell in ineractive mode if we do NOT have
// one of the following conditions:
// 1. We encountered a "quit" from parsing the --X args (i.e. the --X
// args asked us to quit and not continue.
// 2. We found a main class to execute.
// 3. We have been told to wait for input in the server thread.
if (!done && !foundMainClass && !isServerMode) {
runShell();
}
return done;
}
public static void main(String[] args) {
cvmsh sh = new cvmsh();
sh.parseOptions(args);
}
}
interface CVMSHRunnable {
boolean isStopped();
}
class CVMSHServer implements Runnable, CVMSHRunnable {
boolean stopRequested = false;
boolean isStopped = false;
cvmsh shell;
int port;
Object[] voidArgs;
Class serverSocketClass = null;
Method acceptMtd;
Object serverSocket;
Class socketClass;
Method closeMtd;
Method getOutputStreamMtd;
Method getInputStreamMtd;
protected Object client;
protected OutputStream outStream;
protected InputStream inStream;
CVMSHServer(cvmsh shell, Class socketClass, Object socket, int port)
throws Exception {
this.shell = shell;
this.serverSocketClass = socketClass;
this.serverSocket = socket;
this.port = port;
// Prepare data for reflection calls later to
// ServerSocket.accept(), Socket.close(),
// Socket.getOutputStream() and Socket.getInputStream() below:
Class[] voidArgsTypes = new Class[0];
voidArgs = new Object[0];
acceptMtd = serverSocketClass.getMethod("accept", voidArgsTypes);
socketClass = Class.forName("java.net.Socket");
closeMtd = socketClass.getMethod("close", voidArgsTypes);
getOutputStreamMtd =
socketClass.getMethod("getOutputStream", voidArgsTypes);
getInputStreamMtd =
socketClass.getMethod("getInputStream", voidArgsTypes);
}
// Shuts down the server. This should only be called by the server thread.
synchronized void shutDown() {
// Clean up the streams and sockets:
if (outStream != null) {
try {
outStream.close();
} catch(IOException e) {
System.err.println("ERROR closing server output stream: " + e);
}
}
if (inStream != null) {
try {
inStream.close();
} catch(IOException e) {
System.err.println("ERROR closing server input stream: " + e);
}
}
if (client != null) {
try {
// Use reflection to call this:
// client.close():
closeMtd.invoke(client, voidArgs);
} catch(InvocationTargetException e) {
System.err.println("ERROR closing socket: " +
e.getTargetException());
} catch (Exception e) {
System.err.println("ERROR: Unexpected exception while " +
"closing socket: " + e);
}
client = null;
}
// Change server state to indicate that it has stopped:
isStopped = true;
// Tell the shell that we're done shutting down:
notifyAll();
}
public void run(){
// Now run the service loop:
while(!shell.quitRequested() && !stopRequested) {
// Wait for a client to connect to us:
try{
// Use reflection to call this:
// client = serverSocket.accept();
client = acceptMtd.invoke(serverSocket, voidArgs);
} catch (InvocationTargetException e) {
System.err.println("ERROR: Failed to accept on port: " + port);
shutDown(); // Clean up and stop the server.
return;
} catch (Exception e) {
System.err.println("ERROR: Unexpected exception while " +
"accepting server connection: " + e);
return;
}
// Get the input and output streams of the client socket:
try {
// Use reflection to call these:
// outStream = client.getOutputStream();
// inStream = client.getInputStream();
outStream = (OutputStream)
getOutputStreamMtd.invoke(client, voidArgs);
inStream = (InputStream)
getInputStreamMtd.invoke(client, voidArgs);
} catch (InvocationTargetException e) {
System.err.println("ERROR: Failed to get client IO streams");
shutDown(); // Clean up and stop the server.
return;
} catch (Exception e) {
System.err.println("ERROR: Unexpected exception while " +
"getting client IO streams: " + e);
return;
}
// Service the input from the client:
shell.serviceInput(inStream, outStream, this);
}
shutDown(); // Clean up and stop the server.
}
public synchronized void stop(boolean calledFromClient) {
stopRequested = true;
// If it's a local console that is requesting that the server stop,
// then make it wait while the server shuts down:
if (calledFromClient) {
try {
wait();
} catch (InterruptedException e) {
}
}
// The server is now done. Tell the shell:
shell.serverStopped();
}
public boolean isStopped() {
return isStopped;
};
}