org.jboss.byteman.agent.submit.Submit Maven / Gradle / Ivy
/*
* 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 null
, 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 null
, 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 null
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 getAllScripts() throws Exception {
// we use this to retain the order in which script file names occur
List scriptFileNames = new ArrayList();
Map rulesByScript = new HashMap();
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 scriptTexts = new ArrayList(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 getAllRules() throws Exception {
Map rulesByScript = new HashMap();
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 splitAllRulesFromScript(String scriptContent) throws Exception {
List rules = new ArrayList();
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 null
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 boot
* 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 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 system
* 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 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 getLoadedBootClassloaderJars() throws Exception {
String results = submitRequest("LISTBOOT\n");
List jars = new ArrayList();
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 getLoadedSystemClassloaderJars() throws Exception {
String results = submitRequest("LISTSYS\n");
List jars = new ArrayList();
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 filePaths) throws Exception {
List 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 resourceStreams) throws Exception {
List 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 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 rules) throws Exception {
if (rules == null || rules.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("LOAD\n");
for (Map.Entry 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 filePaths) throws Exception {
List 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 resourceStreams) throws Exception {
List 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 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 rules) throws Exception {
if (rules == null || rules.size() == 0) {
return "";
}
StringBuilder str = new StringBuilder("DELETE\n");
for (Map.Entry 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy