/*
* JBoss, Home of Professional Open Source
* Copyright 2009-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* (C) 2009-10,
* @authors Andrew Dinn
*/
package org.jboss.byteman.agent.submit;
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A Java API that can be used to submit requests to a remote Byteman agent.
* This also includes a main routine for use as a command-line utility.
* This object provides a means by which you communicate with the Byteman agent at runtime allowing loading,
* reloading, unloading of rules and listing of the current rule set and any successful or failed attempts
* to inject, parse and typecheck the rules.
*
* Note that this class is completely standalone and has no dependencies on any other Byteman class.
* It can be shipped alone in a client jar to be used as a very small app.
*/
public class Submit
{
public static final String DEFAULT_ADDRESS = "localhost";
public static final int DEFAULT_PORT= 9091;
private final int port;
private final String address;
private PrintStream out;
/**
* Create a client that will connect to a Byteman agent on the default host
* and port and writing output to System.out.
*/
public Submit() {
this(DEFAULT_ADDRESS, DEFAULT_PORT, System.out);
}
/**
* Create a client that will connect to a Byteman agent on the given host
* and port and writing output to System.out.
*
* @param address
* the hostname or IP address of the machine where Byteman agent
* is located. If <code>null</code>, the default host is used.
* @param port
* the port that the Byteman agent is listening to.
* If 0 or less, the default port is used.
*/
public Submit(String address, int port) {
this(address, port, System.out);
}
/**
* Create a client that will connect to a Byteman agent on the given host
* and port and writing output to System.out.
*
* @param address
* the hostname or IP address of the machine where Byteman agent
* is located. If <code>null</code>, the default host is used.
* @param port
* the port that the Byteman agent is listening to.
* If 0 or less, the default port is used.
* @param out
* the print stream used for writing output
*/
public Submit(String address, int port, PrintStream out) {
if (address == null) {
address = DEFAULT_ADDRESS;
}
if (port <= 0) {
port = DEFAULT_PORT;
}
if (out == null) {
out = System.out;
}
this.address = address;
this.port = port;
this.out = out;
}
/**
* @return identifies the host where this client expects a Byteman agent to
* be running.
*/
public String getAddress() {
return this.address;
}
/**
* @return the port that this client expects a Byteman agent to be listening
* to on the given {@link #getAddress() host}.
*/
public int getPort() {
return this.port;
}
/**
* Returns the version of the remote Byteman agent.
*
* @return the version of the remote Byteman agent
*
* @throws Exception
* if the request failed
*/
public String getAgentVersion() throws Exception {
String version = submitRequest("VERSION\n");
return (version != null) ? version.trim() : "0";
}
/**
* Returns the version of this Byteman submit client.
*
* @return the version of the submit client, or <code>null</code> if unknown
*
* @throws Exception
* if the request failed
*/
public String getClientVersion() throws Exception {
return this.getClass().getPackage().getImplementationVersion();
}
/**
* Tells the Byteman agent to delete all rules. This will effectively revert
* the Byteman's VM to its original state; that is, no Byteman injected
* byte-code will be invoked.
*
* @return the results of the delete-all request to the Byteman agent
*
* @throws Exception
* if the request failed
*/
public String deleteAllRules() throws Exception {
return submitRequest("DELETEALL\n");
}
/**
* Tells the Byteman agent to list all deployed rules.
*
* @return all the rules deployed in the Byteman agent
*
* @throws Exception
* if the request failed
*/
public String listAllRules() throws Exception {
return submitRequest("LIST\n");
}
/**
* Gets all deployed rules from the agent just as
* {@link #listAllRules()}, but will return the rules
* organized by script (i.e. rule file). Each "script",
* or rule file, has a set of rules associated with it.
*
* @return all the scripts deployed in the Byteman agent
* the keys are the script names (typically this is
* the filenames where the rule definitions were found);
* the values are the rule definitions in the scripts
*
* @throws Exception
* if the request failed
*/
public List<ScriptText> getAllScripts() throws Exception {
// we use this to retain the order in which script file names occur
List<String> scriptFileNames = new ArrayList<String>();
Map<String, String> rulesByScript = new HashMap<String, String>();
Pattern scriptHeaderPattern = Pattern.compile("# File (.*) line \\d+\\s*");
Pattern ruleNamePattern = Pattern.compile("\\s*RULE\\s+(.+)\\s*");
Pattern endRuleNamePattern = Pattern.compile("\\s*ENDRULE\\s*");
Matcher matcher;
String currentScriptName = null;
String currentRuleName = null; // will be non-null while we are parsing actual rule code
StringBuilder currentScriptText = new StringBuilder();
String allRules = listAllRules();
BufferedReader reader = new BufferedReader(new StringReader(allRules));
String line = reader.readLine();
while (line != null) {
matcher = scriptHeaderPattern.matcher(line);
if (matcher.matches()) {
// found a new script header; squirrel away the current script text
// and get ready for this new script
if (currentScriptName != null) {
rulesByScript.put(currentScriptName, currentScriptText.toString());
}
currentScriptName = matcher.group(1);
if (rulesByScript.containsKey(currentScriptName)) {
currentScriptText = new StringBuilder(rulesByScript.get(currentScriptName));
} else {
currentScriptText = new StringBuilder();
// track the new file name
scriptFileNames.add(currentScriptName);
}
} else {
matcher = ruleNamePattern.matcher(line);
if (matcher.matches()) {
// found a new rule; definition that follows belong to this new rule
currentRuleName = matcher.group(1);
currentScriptText.append(line).append('\n');
} else {
matcher = endRuleNamePattern.matcher(line);
if (matcher.matches()) {
// reached the end of the current rule
if (currentRuleName != null) {
currentRuleName = null;
currentScriptText.append(line).append('\n');
}
} else {
// line is basic rule text, a comment or miscellaneous.
// if we are currently processing inside a rule, we'll add the line to the script
// otherwise, we ignore the line since its not part of a rule definition.
if (currentRuleName != null) {
currentScriptText.append(line).append('\n');
}
}
}
}
line = reader.readLine();
}
// finish up by adding the last script we encountered
if (currentScriptName != null && currentScriptText.length() > 0) {
rulesByScript.put(currentScriptName, currentScriptText.toString());
}
// now create a script text object for each script file we found
List<ScriptText> scriptTexts = new ArrayList<ScriptText>(scriptFileNames.size());
for (String fileName : scriptFileNames) {
String text = rulesByScript.get(fileName);
scriptTexts.add(new ScriptText(fileName, text));
}
return scriptTexts;
}
/**
* old version which returns a map rather than a list of scripts
* @return as above but as a map
* @throws Exception
* if the request failed
*/
@Deprecated
public Map<String,String> getAllRules() throws Exception {
Map<String, String> rulesByScript = new HashMap<String, String>();
Pattern scriptHeaderPattern = Pattern.compile("# File (.*) line \\d+\\s*");
Pattern ruleNamePattern = Pattern.compile("\\s*RULE\\s+(.+)\\s*");
Pattern endRuleNamePattern = Pattern.compile("\\s*ENDRULE\\s*");
Matcher matcher;
String currentScriptName = null;
String currentRuleName = null; // will be non-null while we are parsing actual rule code
StringBuilder currentScriptText = new StringBuilder();
String allRules = listAllRules();
BufferedReader reader = new BufferedReader(new StringReader(allRules));
String line = reader.readLine();
while (line != null) {
matcher = scriptHeaderPattern.matcher(line);
if (matcher.matches()) {
// found a new script header; squirrel away the current script text
// and get ready for this new script
if (currentScriptName != null) {
rulesByScript.put(currentScriptName, currentScriptText.toString());
}
currentScriptName = matcher.group(1);
if (rulesByScript.containsKey(currentScriptName)) {
currentScriptText = new StringBuilder(rulesByScript.get(currentScriptName));
} else {
currentScriptText = new StringBuilder();
}
} else {
matcher = ruleNamePattern.matcher(line);
if (matcher.matches()) {
// found a new rule; definition that follows belong to this new rule
currentRuleName = matcher.group(1);
currentScriptText.append(line).append('\n');
} else {
matcher = endRuleNamePattern.matcher(line);
if (matcher.matches()) {
// reached the end of the current rule
if (currentRuleName != null) {
currentRuleName = null;
currentScriptText.append(line).append('\n');
}
} else {
// line is basic rule text, a comment or miscellaneous.
// if we are currently processing inside a rule, we'll add the line to the script
// otherwise, we ignore the line since its not part of a rule definition.
if (currentRuleName != null) {
currentScriptText.append(line).append('\n');
}
}
}
}
line = reader.readLine();
}
// finish up by adding the last script we encountered
if (currentScriptName != null && currentScriptText.length() > 0) {
rulesByScript.put(currentScriptName, currentScriptText.toString());
}
return rulesByScript;
}
/**
* Given the content of a script (which will be one or more
* rule definitions), this will return each rule definition
* as an individual string within the returned list.
* The returned list will be ordered - that is, the first
* rule in the list is the first rule encountered in the script.
*
* One usage of this method is to pass in map values from the results
* of {@link #getAllScripts()} in case you need the scripts' individual rules.
*
* @param scriptContent
* the actual content of a script (i.e. the rule definitions)
*
* @return all the rule definitions found in the given script
* @throws Exception
* if an string processing error occurs
*/
public List<String> splitAllRulesFromScript(String scriptContent) throws Exception {
List<String> rules = new ArrayList<String>();
if (scriptContent == null || scriptContent.length() == 0) {
return rules;
}
Pattern ruleNamePattern = Pattern.compile("\\s*RULE\\s+(.+)\\s*");
Pattern endRuleNamePattern = Pattern.compile("\\s*ENDRULE\\s*");
Matcher matcher;
String currentRuleName = null; // will be non-null while we are parsing actual rule code
StringBuilder currentRuleText = new StringBuilder();
BufferedReader reader = new BufferedReader(new StringReader(scriptContent));
String line = reader.readLine();
while (line != null) {
matcher = ruleNamePattern.matcher(line);
if (matcher.matches()) {
// found a new rule; definition that follows belong to this new rule
currentRuleName = matcher.group(1);
currentRuleText = new StringBuilder();
currentRuleText.append(line).append('\n');
} else {
matcher = endRuleNamePattern.matcher(line);
if (matcher.matches()) {
// reached the end of the current rule
if (currentRuleName != null) {
currentRuleName = null;
currentRuleText.append(line).append('\n');
rules.add(currentRuleText.toString());
}
} else {
// line is basic rule text, a comment or miscellaneous.
// if we are currently processing inside a rule, we'll add the line to the script
// otherwise, we ignore the line since its not part of a rule definition.
if (currentRuleName != null) {
currentRuleText.append(line).append('\n');
}
}
}
line = reader.readLine();
}
// finish up by adding the last script we encountered.
// the only time this code would be needed is if we were missng an ENDRULE line,
// but that would not be valid rule code, so we should never really get inside this if-statement
if (currentRuleName != null && currentRuleText.length() > 0) {
rules.add(currentRuleText.toString());
}
return rules;
}
/**
* Given the content of an individual rule definition, this will
* return the name of that rule.
*
* @param ruleDefinition
* the actual content of an individual rule
*
* @return the name of the given rule, or <code>null</code> if it could not be determined
*
* @throws Exception
* if the name cannot be determined
*/
public String determineRuleName(String ruleDefinition) throws Exception {
Pattern ruleNamePattern = Pattern.compile("\\s*RULE\\s+(.+)\\s*");
Matcher matcher;
String ruleName = null;
BufferedReader reader = new BufferedReader(new StringReader(ruleDefinition));
String line = reader.readLine();
while (line != null && ruleName == null) {
matcher = ruleNamePattern.matcher(line);
if (matcher.matches()) {
ruleName = matcher.group(1);
}
line = reader.readLine();
}
return ruleName;
}
/**
* This adds the given list of files to the Byteman agent's <em>boot</em>
* classloader. Note that if the Byteman agent is running on a remote
* machine, the paths must resolve on that remote host (i.e. the file must
* exist on the remote machine at the paths given to this method).
*
* @param jarPaths
* the paths to the library .jar files that will be loaded
*
* @return the result of the load as reported by Byteman
*
* @throws Exception
* if the request failed
*/
public String addJarsToBootClassloader(List<String> jarPaths) throws Exception {
if (jarPaths == null || jarPaths.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("BOOT\n");
for (String jarPath : jarPaths) {
str.append(jarPath).append("\n");
}
str.append("ENDBOOT\n");
return submitRequest(str.toString());
}
/**
* This adds the given list of files to the Byteman agent's <em>system</em>
* classloader. Note that if the Byteman agent is running on a remote
* machine, the paths must resolve on that remote host (i.e. the file must
* exist on the remote machine at the paths given to this method).
*
* @param jarPaths
* the paths to the library .jar files that will be loaded
*
* @return the result of the load as reported by Byteman
*
* @throws Exception
* if the request failed
*/
public String addJarsToSystemClassloader(List<String> jarPaths) throws Exception {
if (jarPaths == null || jarPaths.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("SYS\n");
for (String jarPath : jarPaths) {
str.append(jarPath).append("\n");
}
str.append("ENDSYS\n");
return submitRequest(str.toString());
}
/**
* Returns a list of jars that were added to the Byteman agent's boot classloader.
*
* @return list of jars that were added to the boot classloader
*
* @throws Exception
* if the request failed
*/
public List<String> getLoadedBootClassloaderJars() throws Exception {
String results = submitRequest("LISTBOOT\n");
List<String> jars = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new StringReader(results));
String line = reader.readLine();
while (line != null) {
jars.add(line);
line = reader.readLine();
}
return jars;
}
/**
* Returns a list of jars that were added to the Byteman agent's system classloader.
*
* @return list of jars that were added to the system classloader
*
* @throws Exception
* if the request failed
*/
public List<String> getLoadedSystemClassloaderJars() throws Exception {
String results = submitRequest("LISTSYS\n");
List<String> jars = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new StringReader(results));
String line = reader.readLine();
while (line != null) {
jars.add(line);
line = reader.readLine();
}
return jars;
}
/**
* Deploys rules into Byteman, where the rule definitions are found in the
* local files found at the given paths. The rule definitions found in the
* files are actually passed down directly to Byteman, not the file paths
* themselves. Therefore, these files must exist on the machine where this
* client is running (i.e. the files are not loaded directly by the Byteman
* agent).
*
* @param filePaths
* the local files containing the rule definitions to be deployed
* to Byteman
*
* @return the results of the deployment
*
* @throws Exception
* if the request failed
*/
public String addRulesFromFiles(List<String> filePaths) throws Exception {
List<ScriptText> scripts = getRulesFromRuleFiles(filePaths);
return addScripts(scripts);
}
/**
* Deploys rules into Byteman, where the rule definitions are found in the
* given streams. Rule definitions are read from the streams and the rule
* text uploaded directly to the Byteman agent.
*
* This method is useful for using rules files from the classpath.
*
* @param resourceStreams
* input streams containing the rule definitions to be deployed
* to Byteman
*
* @return the results of the deployment
*
* @throws Exception
* if the request failed
*/
public String addRulesFromResources(List<InputStream> resourceStreams) throws Exception {
List<ScriptText> scripts = getRulesFromRuleStreams(resourceStreams);
return addScripts(scripts);
}
/**
* Deploys rule scripts into Byteman
*
* @param scripts
* scripts to be deployed to Byteman
*
* @return the results of the deployment
*
* @throws Exception
* if the request failed
*/
public String addScripts(List<ScriptText> scripts) throws Exception {
if (scripts == null || scripts.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("LOAD\n");
for (ScriptText scriptText : scripts) {
str.append("SCRIPT " + scriptText.getFileName() + '\n');
str.append(scriptText.getText()).append('\n');
str.append("ENDSCRIPT\n");
}
str.append("ENDLOAD\n");
return submitRequest(str.toString());
}
/**
* old version which uses a Map
* @param rules the rules to be added
* @return the results of the deployment
* @throws Exception
* if the request failed
*/
@Deprecated
public String addRules(Map<String,String> rules) throws Exception {
if (rules == null || rules.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("LOAD\n");
for (Map.Entry<String, String> entry : rules.entrySet()) {
str.append("SCRIPT " + entry.getKey() + '\n');
str.append(entry.getValue()).append('\n');
str.append("ENDSCRIPT\n");
}
str.append("ENDLOAD\n");
return submitRequest(str.toString());
}
/**
* Deletes rules from Byteman, where the rule definitions are found in the
* local files found at the given paths. After this method is done, the
* given rules will no longer be processed by Byteman. The rule definitions
* found in the files are actually passed down directly to Byteman, not the
* file paths themselves. Therefore, these files must exist on the machine
* where this client is running (i.e. the files are not read directly by the
* Byteman agent).
*
* @param filePaths
* the local files containing the rule definitions to be deleted
* from Byteman
*
* @return the results of the deletion
*
* @throws Exception
* if the request failed
*/
public String deleteRulesFromFiles(List<String> filePaths) throws Exception {
List<ScriptText> scripts = getRulesFromRuleFiles(filePaths);
return deleteScripts(scripts);
}
/**
* Deletes rules from Byteman, where the rule definitions are found in the
* given streams. Rule definitions are read from the streams so that details
* of which rules to unload can be uploaded directly to the Byteman agent.
*
* This method is useful for using rules files from the classpath.
*
* @param resourceStreams
* the URLS to files containing the rule definitions to be deleted
* from Byteman
*
* @return the results of the deletion
*
* @throws Exception
* if the request failed
*/
public String deleteRulesFromResources(List<InputStream> resourceStreams) throws Exception {
List<ScriptText> scripts = getRulesFromRuleStreams(resourceStreams);
return deleteScripts(scripts);
}
/**
* Deletes rules from Byteman.
*
* @param scripts
* rule scripts to be deleted from Byteman
*
* @return the results of the deletion
*
* @throws Exception
* if the request failed
*/
public String deleteScripts(List<ScriptText> scripts) throws Exception {
if (scripts == null || scripts.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("DELETE\n");
for (ScriptText scriptText : scripts) {
str.append("SCRIPT " + scriptText.getFileName() + '\n');
str.append(scriptText.getText()).append('\n');
str.append("ENDSCRIPT\n");
}
str.append("ENDDELETE\n");
return submitRequest(str.toString());
}
/**
* old version which uses a Map
* @param rules the rules to be deleted
* @return the results of the deletion
* @throws Exception
* if the request failed
*/
@Deprecated
public String deleteRules(Map<String,String> rules) throws Exception {
if (rules == null || rules.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("DELETE\n");
for (Map.Entry<String, String> entry : rules.entrySet()) {
str.append("SCRIPT " + entry.getKey() + '\n');
str.append(entry.getValue()).append('\n');
str.append("ENDSCRIPT\n");
}
str.append("ENDDELETE\n");
return submitRequest(str.toString());
}
/**
* Sets system properties in the Byteman agent VM.
* If Byteman was configured for strict mode, only Byteman related
* system properties will be allowed to be set.
*
* @param propsToSet
* system properties to set in the Byteman agent VM
*
* @return response from the Byteman agent
*
* @throws Exception
* if the request failed
*/
public String setSystemProperties(Properties propsToSet) throws Exception {
if (propsToSet == null || propsToSet.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("SETSYSPROPS\n");
for (Map.Entry<Object, Object> entry : propsToSet.entrySet()) {
str.append(entry.getKey()).append('=').append(entry.getValue()).append('\n');
}
str.append("ENDSETSYSPROPS\n");
return submitRequest(str.toString());
}
/**
* Returns the system properties set in the Byteman agent VM.
* If Byteman was configured for strict mode, only Byteman related
* system properties will be returned.
*
* @return system properties defined in the Byteman agent VM
*
* @throws Exception
* if the request failed
*/
public Properties listSystemProperties() throws Exception {
String results = submitRequest("LISTSYSPROPS\n");
Properties props = new Properties();
BufferedReader reader = new BufferedReader(new StringReader(results));
String line = reader.readLine();
while (line != null) {
String[] nameValuePair = line.split("=", 2);
if (nameValuePair.length != 2) {
throw new Exception("Invalid name/value pair in line [" + line + "]. Full response below:\n" + results);
}
props.setProperty(nameValuePair[0], nameValuePair[1].replace("\\n", "\n").replace("\\r", "\r"));
line = reader.readLine();
}
return props;
}
/**
* Submits the generic request string to the Byteman agent for processing.
*
* @param request
* the request to submit
*
* @return the response that the Byteman agent replied with
*
* @throws Exception
* if the request failed
*/
public String submitRequest(String request) throws Exception {
Comm comm = new Comm(this.address, this.port);
try {
comm.print(request);
String results = comm.readResponse();
return results;
} finally {
comm.close();
}
}
private List<ScriptText> getRulesFromRuleStreams(List<InputStream> streams) throws Exception {
if (streams == null || streams.size() == 0) {
return new ArrayList<ScriptText>(0);
}
List<ScriptText> scripts = new ArrayList<ScriptText>(streams.size());
for (InputStream is : streams) {
// read in the current rule file
try {
InputStreamReader reader = new InputStreamReader(is);
ScriptText scriptText = readScriptText(is.toString(), reader);
// put the current rule definition in our list of rules to add
scripts.add(scriptText);
} catch (IOException e) {
throw new Exception("Error reading from rule input stream: " + is, e);
}
}
return scripts;
}
private List<ScriptText> getRulesFromRuleFiles(List<String> filePaths) throws Exception {
if (filePaths == null || filePaths.size() == 0) {
return new ArrayList<ScriptText>(0);
}
List<ScriptText> scripts = new ArrayList<ScriptText>(filePaths.size());
for (String filePath : filePaths) {
// abort if a script file was invalid - we never submit the request if at least one was invalid
if (!confirmRuleFileValidity(filePath)) {
throw new Exception("Invalid rule file: " + filePath);
}
// read in the current rule file
try {
FileInputStream fis = new FileInputStream(filePath);
InputStreamReader reader = new InputStreamReader(fis);
ScriptText scriptText = readScriptText(filePath, reader);
// put the current rule definition in our list of rules to add
scripts.add(scriptText);
} catch (IOException e) {
throw new Exception("Error reading from rule file: " + filePath, e);
}
}
return scripts;
}
private ScriptText readScriptText(String filePath, InputStreamReader reader) throws Exception {
final char[] readBuffer = new char[4096];
StringBuilder scriptText = new StringBuilder();
try {
int read = reader.read(readBuffer);
while (read > 0) {
scriptText.append(readBuffer, 0, read);
read = reader.read(readBuffer);
}
reader.close();
return new ScriptText(filePath, scriptText.toString());
} catch (IOException e) {
throw new Exception("Error reading from rule file: " + filePath, e);
}
}
private boolean confirmRuleFileValidity(String path) {
// right now, we only check if its a readable file, do we want to see if
// its parsable, too?
File file = new File(path);
if (!file.isFile() || !file.canRead()) {
return false;
}
return true;
}
private class Comm {
private Socket commSocket;
private BufferedReader commInput;
private PrintWriter commOutput;
public Comm(String address, int port) throws Exception {
this.commSocket = new Socket(address, port);
InputStream is;
try {
is = this.commSocket.getInputStream();
} catch (Exception e) {
// oops. cannot handle this
try {
this.commSocket.close();
} catch (Exception e1) {
}
throw e;
}
OutputStream os;
try {
os = this.commSocket.getOutputStream();
} catch (Exception e) {
// oops. cannot handle this
try {
this.commSocket.close();
} catch (Exception e1) {
}
throw e;
}
this.commInput = new BufferedReader(new InputStreamReader(is));
this.commOutput = new PrintWriter(new OutputStreamWriter(os));
return;
}
public void close() {
try {
this.commSocket.close(); // also closes the in/out streams
} catch (Exception e) {
// TODO what should I do here? no need to abort, we are closing this object anyway
} finally {
// this object cannot be reused anymore, therefore, null everything out
// which will force NPEs if attempts to reuse this object occur later
this.commSocket = null;
this.commInput = null;
this.commOutput = null;
}
}
public void println(String line) {
this.commOutput.println(line);
this.commOutput.flush();
}
public void print(String line) {
this.commOutput.print(line);
this.commOutput.flush();
}
public String readResponse() throws Exception {
StringBuilder str = new StringBuilder();
StringBuilder errorStr = null; // will be non-null if an error was reported by the agent
String line = this.commInput.readLine();
while (line != null && !line.trim().equals("OK")) {
line = line.trim();
if (line.startsWith("ERROR") || line.startsWith("EXCEPTION")) {
if (errorStr == null) {
errorStr = new StringBuilder();
}
}
// if an error was detected, gobble up the text coming over the wire as part of the error message
if (errorStr != null) {
errorStr.append(line).append('\n');
}
str.append(line).append('\n');
line = this.commInput.readLine();
}
if (errorStr != null) {
StringBuilder msg = new StringBuilder();
msg.append("The remote byteman agent reported an error:\n").append(errorStr);
if (!errorStr.toString().equals(str.toString())) {
msg.append("\nThe full response received from the byteman agent follows:\n").append(str);
}
throw new Exception(msg.toString());
}
return str.toString();
}
}
/**
* A main routine which submits requests to the Byteman agent utilizing the Java API.
* @param args see {@link #usage(PrintStream, int)} for a description of the allowed arguments
*/
public static void main(String[] args)
{
String outfile = null;
int port = DEFAULT_PORT;
String hostname = DEFAULT_ADDRESS;
int startIdx = 0;
int maxIdx = args.length;
boolean deleteRules = false;
boolean addBoot = false;
boolean addSys = false;
boolean showVersion = false;
boolean showAddedClassloaderJars = false;
boolean sysProps = false;
int optionCount = 0;
PrintStream out = System.out;
while (startIdx < maxIdx && args[startIdx].startsWith("-")) {
if (maxIdx >= startIdx + 2 && args[startIdx].equals("-o")) {
outfile = args[startIdx+1];
File file = new File(outfile);
if (file.exists()) {
// open for append
if (file.isDirectory() || !file.canWrite()) {
out.println("Submit : invalid output file " + outfile);
System.exit(1);
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
} catch (FileNotFoundException e) {
out.println("Submit : error opening output file " + outfile);
}
out = new PrintStream(fos);
} else {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
} catch (FileNotFoundException e) {
out.println("Submit : error opening output file " + outfile);
}
out = new PrintStream(fos);
}
startIdx += 2;
} else if (maxIdx >= startIdx + 2 && args[startIdx].equals("-p")) {
try {
port = Integer.valueOf(args[startIdx+1]);
} catch (NumberFormatException e) {
out.println("Submit : invalid port " + args[startIdx+1]);
System.exit(1);
}
if (port <= 0) {
out.println("Submit : invalid port " + args[startIdx+1]);
System.exit(1);
}
startIdx += 2;
} else if (maxIdx >= startIdx + 2 && args[startIdx].equals("-h")) {
hostname = args[startIdx+1];
startIdx += 2;
} else if (args[startIdx].equals("-u")) {
deleteRules = true;
startIdx++;
optionCount++;
} else if (args[startIdx].equals("-l")) {
startIdx++;
optionCount++;
} else if (args[startIdx].equals("-b")) {
addBoot = true;
startIdx ++;
optionCount++;
} else if (args[startIdx].equals("-v")) {
showVersion = true;
startIdx ++;
optionCount++;
} else if (args[startIdx].equals("-c")) {
showAddedClassloaderJars = true;
startIdx ++;
optionCount++;
} else if (args[startIdx].equals("-s")) {
addSys = true;
startIdx ++;
optionCount++;
} else if (args[startIdx].equals("-y")) {
sysProps = true;
startIdx++;
optionCount++;
} else {
break;
}
}
if (startIdx < maxIdx && args[startIdx].startsWith("-") || optionCount > 1) {
usage(out, 1);
}
// must have some file args if adding to sys or boot classpath
if (startIdx == maxIdx && (addBoot || addSys)) {
usage(out, 1);
}
Submit client = new Submit(hostname, port, out);
String results = null;
List<String> argsList = null;
try {
if (showVersion) {
String agentVersion = client.getAgentVersion();
String clientVersion = client.getClientVersion();
results = "Agent Version: " + agentVersion + "\nClient Version: " + clientVersion;
} else if (showAddedClassloaderJars) {
List<String> bootJars = client.getLoadedBootClassloaderJars();
List<String> sysJars = client.getLoadedSystemClassloaderJars();
StringBuilder str = new StringBuilder();
str.append("Boot Classloader Jars:").append('\n');
if (bootJars.isEmpty()) {
str.append("\t<none>\n");
} else {
for (String jar : bootJars) {
str.append('\t').append(jar).append('\n');
}
}
str.append("System Classloader Jars:").append('\n');
if (sysJars.isEmpty()) {
str.append("\t<none>\n");
} else {
for (String jar : sysJars) {
str.append('\t').append(jar).append('\n');
}
}
results = str.toString();
} else {
if (startIdx == maxIdx) {
// no args means list or delete all current scripts or list sysprops
if (deleteRules) {
results = client.deleteAllRules();
} else if (sysProps) {
Properties props = client.listSystemProperties();
StringBuilder str = new StringBuilder();
for (Map.Entry<Object, Object> prop : props.entrySet()) {
str.append(prop.getKey()).append('=').append(prop.getValue()).append('\n');
}
results = str.toString();
} else {
// the default behavior (or if -l was explicitly specified) is to do this
results = client.listAllRules();
}
} else {
argsList = new ArrayList<String>();
for (int i = startIdx; i < maxIdx; i++) {
argsList.add(args[i]);
}
if (addBoot) {
results = client.addJarsToBootClassloader(argsList);
} else if (addSys) {
results = client.addJarsToSystemClassloader(argsList);
} else if (sysProps) {
Properties propsToSet = new Properties();
for (String arg : argsList) {
String[] nameValuePair = arg.split("=", 2);
if (nameValuePair.length != 2) {
throw new Exception("Invalid name/value pair: " + arg);
}
propsToSet.setProperty(nameValuePair[0], nameValuePair[1]);
}
results = client.setSystemProperties(propsToSet);
} else {
if (deleteRules) {
results = client.deleteRulesFromFiles(argsList);
} else {
// the default behavior (or if -l was explicitly specified) is to do this
results = client.addRulesFromFiles(argsList);
}
}
}
}
} catch (Exception e) {
out.println("Failed to process request: " + e);
if (argsList != null) {
out.println("-- Args were: " + argsList);
}
if (results != null) {
// rarely will results be non-null on error, but just in case, print it if we got it
out.println("-- Results were: " + results);
}
e.printStackTrace();
System.exit(1);
}
out.println(results);
if (out != System.out) {
out.close();
}
}
private static void usage(PrintStream out, int exitCode)
{
out.println("usage : Submit [-o outfile] [-p port] [-h hostname] [-l|-u] [scriptfile . . .]");
out.println(" Submit [-o outfile] [-p port] [-h hostname] [-b|-s] jarfile . . .");
out.println(" Submit [-o outfile] [-p port] [-h hostname] [-c]");
out.println(" Submit [-o outfile] [-p port] [-h hostname] [-y] [prop1[=[value1]]. . .]");
out.println(" Submit [-o outfile] [-p port] [-h hostname] [-v]");
out.println(" -o redirects output from System.out to outfile");
out.println(" -p specifies listener port");
out.println(" -h specifies listener host");
out.println(" -l (default) with scriptfile(s) means load/reload all rules in scriptfile(s)");
out.println(" with no scriptfile means list all currently loaded rules");
out.println(" -u with scriptfile(s) means unload all rules in scriptfile(s)");
out.println(" with no scriptfile means unload all currently loaded rules");
out.println(" -b with jarfile(s) means add jars to bootstrap classpath");
out.println(" -s with jarfile(s) means add jars to system classpath");
out.println(" -c prints the jars that have been added to the system and boot classloaders");
out.println(" -y with no args list all byteman config system properties");
out.println(" with args modifies specified byteman config system properties");
out.println(" prop=value sets system property 'prop' to value");
out.println(" prop= sets system property 'prop' to an empty string");
out.println(" prop unsets system property 'prop'");
out.println(" -v prints the version of the byteman agent and this client");
if (out != System.out) {
out.close();
}
System.exit(exitCode);
}
}