/* * 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); } }