1 /*
   2  * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.management;
  26 
  27 import java.io.BufferedInputStream;
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.lang.management.ManagementFactory;
  34 import java.lang.reflect.InvocationTargetException;
  35 import java.lang.reflect.Method;
  36 import java.net.InetAddress;
  37 import java.net.UnknownHostException;
  38 import java.text.MessageFormat;
  39 import java.util.MissingResourceException;
  40 import java.util.Properties;
  41 import java.util.ResourceBundle;
  42 
  43 import javax.management.remote.JMXConnectorServer;
  44 import javax.management.remote.JMXServiceURL;
  45 
  46 import static sun.management.AgentConfigurationError.*;
  47 import sun.management.jmxremote.ConnectorBootstrap;
  48 import sun.management.jdp.JdpController;
  49 import sun.management.jdp.JdpException;
  50 import sun.misc.VMSupport;
  51 
  52 /**
  53  * This Agent is started by the VM when -Dcom.sun.management.snmp or
  54  * -Dcom.sun.management.jmxremote is set. This class will be loaded by the
  55  * system class loader. Also jmx framework could be started by jcmd
  56  */
  57 public class Agent {
  58     // management properties
  59 
  60     private static Properties mgmtProps;
  61     private static ResourceBundle messageRB;
  62     private static final String CONFIG_FILE =
  63             "com.sun.management.config.file";
  64     private static final String SNMP_PORT =
  65             "com.sun.management.snmp.port";
  66     private static final String JMXREMOTE =
  67             "com.sun.management.jmxremote";
  68     private static final String JMXREMOTE_PORT =
  69             "com.sun.management.jmxremote.port";
  70     private static final String RMI_PORT =
  71             "com.sun.management.jmxremote.rmi.port";
  72     private static final String ENABLE_THREAD_CONTENTION_MONITORING =
  73             "com.sun.management.enableThreadContentionMonitoring";
  74     private static final String LOCAL_CONNECTOR_ADDRESS_PROP =
  75             "com.sun.management.jmxremote.localConnectorAddress";
  76     private static final String SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME =
  77             "sun.management.snmp.AdaptorBootstrap";
  78 
  79     private static final String JDP_DEFAULT_ADDRESS = "224.0.23.178";
  80     private static final int JDP_DEFAULT_PORT = 7095;
  81 
  82     // The only active agent allowed
  83     private static JMXConnectorServer jmxServer = null;
  84 
  85     // Parse string com.sun.management.prop=xxx,com.sun.management.prop=yyyy
  86     // and return property set if args is null or empty
  87     // return empty property set
  88     private static Properties parseString(String args) {
  89         Properties argProps = new Properties();
  90         if (args != null && !args.trim().equals("")) {
  91             for (String option : args.split(",")) {
  92                 String s[] = option.split("=", 2);
  93                 String name = s[0].trim();
  94                 String value = (s.length > 1) ? s[1].trim() : "";
  95 
  96                 if (!name.startsWith("com.sun.management.")) {
  97                     error(INVALID_OPTION, name);
  98                 }
  99 
 100                 argProps.setProperty(name, value);
 101             }
 102         }
 103 
 104         return argProps;
 105     }
 106 
 107     // invoked by -javaagent or -Dcom.sun.management.agent.class
 108     public static void premain(String args) throws Exception {
 109         agentmain(args);
 110     }
 111 
 112     // invoked by attach mechanism
 113     public static void agentmain(String args) throws Exception {
 114         if (args == null || args.length() == 0) {
 115             args = JMXREMOTE;           // default to local management
 116         }
 117 
 118         Properties arg_props = parseString(args);
 119 
 120         // Read properties from the config file
 121         Properties config_props = new Properties();
 122         String fname = arg_props.getProperty(CONFIG_FILE);
 123         readConfiguration(fname, config_props);
 124 
 125         // Arguments override config file
 126         config_props.putAll(arg_props);
 127         startAgent(config_props);
 128     }
 129 
 130     // jcmd ManagementAgent.start_local entry point
 131     // Also called due to command-line via startAgent()
 132     private static synchronized void startLocalManagementAgent() {
 133         Properties agentProps = VMSupport.getAgentProperties();
 134 
 135         // start local connector if not started
 136         if (agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP) == null) {
 137             JMXConnectorServer cs = ConnectorBootstrap.startLocalConnectorServer();
 138             String address = cs.getAddress().toString();
 139             // Add the local connector address to the agent properties
 140             agentProps.put(LOCAL_CONNECTOR_ADDRESS_PROP, address);
 141 
 142             try {
 143                 // export the address to the instrumentation buffer
 144                 ConnectorAddressLink.export(address);
 145             } catch (Exception x) {
 146                 // Connector server started but unable to export address
 147                 // to instrumentation buffer - non-fatal error.
 148                 warning(EXPORT_ADDRESS_FAILED, x.getMessage());
 149             }
 150         }
 151     }
 152 
 153     // jcmd ManagementAgent.start entry point
 154     // This method starts the remote JMX agent and starts neither
 155     // the local JMX agent nor the SNMP agent
 156     // @see #startLocalManagementAgent and also @see #startAgent.
 157     private static synchronized void startRemoteManagementAgent(String args) throws Exception {
 158         if (jmxServer != null) {
 159             throw new RuntimeException(getText(INVALID_STATE, "Agent already started"));
 160         }
 161 
 162         try {
 163             Properties argProps = parseString(args);
 164             Properties configProps = new Properties();
 165 
 166             // Load the management properties from the config file
 167             // if config file is not specified readConfiguration implicitly
 168             // reads <java.home>/lib/management/management.properties
 169 
 170             String fname = System.getProperty(CONFIG_FILE);
 171             readConfiguration(fname, configProps);
 172 
 173             // management properties can be overridden by system properties
 174             // which take precedence
 175             Properties sysProps = System.getProperties();
 176             synchronized (sysProps) {
 177                 configProps.putAll(sysProps);
 178             }
 179 
 180             // if user specifies config file into command line for either
 181             // jcmd utilities or attach command it overrides properties set in
 182             // command line at the time of VM start
 183             String fnameUser = argProps.getProperty(CONFIG_FILE);
 184             if (fnameUser != null) {
 185                 readConfiguration(fnameUser, configProps);
 186             }
 187 
 188             // arguments specified in command line of jcmd utilities
 189             // override both system properties and one set by config file
 190             // specified in jcmd command line
 191             configProps.putAll(argProps);
 192 
 193             // jcmd doesn't allow to change ThreadContentionMonitoring, but user
 194             // can specify this property inside config file, so enable optional
 195             // monitoring functionality if this property is set
 196             final String enableThreadContentionMonitoring =
 197                     configProps.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
 198 
 199             if (enableThreadContentionMonitoring != null) {
 200                 ManagementFactory.getThreadMXBean().
 201                         setThreadContentionMonitoringEnabled(true);
 202             }
 203 
 204             String jmxremotePort = configProps.getProperty(JMXREMOTE_PORT);
 205             if (jmxremotePort != null) {
 206                 jmxServer = ConnectorBootstrap.
 207                         startRemoteConnectorServer(jmxremotePort, configProps);
 208 
 209                 startDiscoveryService(configProps);
 210             } else {
 211                 throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, "No port specified");
 212             }
 213         } catch (AgentConfigurationError err) {
 214             error(err);
 215         }
 216     }
 217 
 218     private static synchronized void stopRemoteManagementAgent() throws Exception {
 219 
 220         JdpController.stopDiscoveryService();
 221 
 222         if (jmxServer != null) {
 223             ConnectorBootstrap.unexportRegistry();
 224 
 225             // Attempt to stop already stopped agent
 226             // Don't cause any errors.
 227             jmxServer.stop();
 228             jmxServer = null;
 229         }
 230     }
 231 
 232     private static void startAgent(Properties props) throws Exception {
 233         String snmpPort = props.getProperty(SNMP_PORT);
 234         String jmxremote = props.getProperty(JMXREMOTE);
 235         String jmxremotePort = props.getProperty(JMXREMOTE_PORT);
 236 
 237         // Enable optional monitoring functionality if requested
 238         final String enableThreadContentionMonitoring =
 239                 props.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
 240         if (enableThreadContentionMonitoring != null) {
 241             ManagementFactory.getThreadMXBean().
 242                     setThreadContentionMonitoringEnabled(true);
 243         }
 244 
 245         try {
 246             if (snmpPort != null) {
 247                 loadSnmpAgent(snmpPort, props);
 248             }
 249 
 250             /*
 251              * If the jmxremote.port property is set then we start the
 252              * RMIConnectorServer for remote M&M.
 253              *
 254              * If the jmxremote or jmxremote.port properties are set then
 255              * we start a RMIConnectorServer for local M&M. The address
 256              * of this "local" server is exported as a counter to the jstat
 257              * instrumentation buffer.
 258              */
 259             if (jmxremote != null || jmxremotePort != null) {
 260                 if (jmxremotePort != null) {
 261                     jmxServer = ConnectorBootstrap.
 262                             startRemoteConnectorServer(jmxremotePort, props);
 263                     startDiscoveryService(props);
 264                 }
 265                 startLocalManagementAgent();
 266             }
 267 
 268         } catch (AgentConfigurationError e) {
 269             error(e);
 270         } catch (Exception e) {
 271             error(e);
 272         }
 273     }
 274 
 275     private static void startDiscoveryService(Properties props)
 276             throws IOException {
 277         // Start discovery service if requested
 278         String discoveryPort = props.getProperty("com.sun.management.jdp.port");
 279         String discoveryAddress = props.getProperty("com.sun.management.jdp.address");
 280         String discoveryShouldStart = props.getProperty("com.sun.management.jmxremote.autodiscovery");
 281 
 282         // Decide whether we should start autodicovery service.
 283         // To start autodiscovery following conditions should be met:
 284         // autodiscovery==true OR (autodicovery==null AND jdp.port != NULL)
 285 
 286         boolean shouldStart = false;
 287         if (discoveryShouldStart == null){
 288             shouldStart = (discoveryPort != null);
 289         }
 290         else{
 291             try{
 292                shouldStart = Boolean.parseBoolean(discoveryShouldStart);
 293             } catch (NumberFormatException e) {
 294                 throw new AgentConfigurationError("Couldn't parse autodiscovery argument");
 295             }
 296         }
 297 
 298         if (shouldStart) {
 299             // port and address are required arguments and have no default values
 300             InetAddress address;
 301             try {
 302                 address = (discoveryAddress == null) ?
 303                         InetAddress.getByName(JDP_DEFAULT_ADDRESS) : InetAddress.getByName(discoveryAddress);
 304             } catch (UnknownHostException e) {
 305                 throw new AgentConfigurationError("Unable to broadcast to requested address", e);
 306             }
 307 
 308             int port = JDP_DEFAULT_PORT;
 309             if (discoveryPort != null) {
 310                try {
 311                   port = Integer.parseInt(discoveryPort);
 312                } catch (NumberFormatException e) {
 313                  throw new AgentConfigurationError("Couldn't parse JDP port argument");
 314                }
 315             }
 316 
 317             // Rebuilding service URL to broadcast it
 318             String jmxremotePort = props.getProperty(JMXREMOTE_PORT);
 319             String rmiPort = props.getProperty(RMI_PORT);
 320 
 321             JMXServiceURL url = jmxServer.getAddress();
 322             String hostname = url.getHost();
 323 
 324             String jmxUrlStr = (rmiPort != null)
 325                     ? String.format(
 326                     "service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/jmxrmi",
 327                     hostname, rmiPort, hostname, jmxremotePort)
 328                     : String.format(
 329                     "service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", hostname, jmxremotePort);
 330 
 331             String instanceName = props.getProperty("com.sun.management.jdp.name");
 332 
 333             try{
 334                JdpController.startDiscoveryService(address, port, instanceName, jmxUrlStr);
 335             }
 336             catch(JdpException e){
 337                 throw new AgentConfigurationError("Couldn't start JDP service", e);
 338             }
 339         }
 340     }
 341 
 342     public static Properties loadManagementProperties() {
 343         Properties props = new Properties();
 344 
 345         // Load the management properties from the config file
 346 
 347         String fname = System.getProperty(CONFIG_FILE);
 348         readConfiguration(fname, props);
 349 
 350         // management properties can be overridden by system properties
 351         // which take precedence
 352         Properties sysProps = System.getProperties();
 353         synchronized (sysProps) {
 354             props.putAll(sysProps);
 355         }
 356 
 357         return props;
 358     }
 359 
 360     public static synchronized Properties getManagementProperties() {
 361         if (mgmtProps == null) {
 362             String configFile = System.getProperty(CONFIG_FILE);
 363             String snmpPort = System.getProperty(SNMP_PORT);
 364             String jmxremote = System.getProperty(JMXREMOTE);
 365             String jmxremotePort = System.getProperty(JMXREMOTE_PORT);
 366 
 367             if (configFile == null && snmpPort == null
 368                     && jmxremote == null && jmxremotePort == null) {
 369                 // return if out-of-the-management option is not specified
 370                 return null;
 371             }
 372             mgmtProps = loadManagementProperties();
 373         }
 374         return mgmtProps;
 375     }
 376 
 377     private static void loadSnmpAgent(String snmpPort, Properties props) {
 378         try {
 379             // invoke the following through reflection:
 380             //     AdaptorBootstrap.initialize(snmpPort, props);
 381             final Class<?> adaptorClass =
 382                     Class.forName(SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME, true, null);
 383             final Method initializeMethod =
 384                     adaptorClass.getMethod("initialize",
 385                     String.class, Properties.class);
 386             initializeMethod.invoke(null, snmpPort, props);
 387         } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException x) {
 388             // snmp runtime doesn't exist - initialization fails
 389             throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, x);
 390         } catch (InvocationTargetException x) {
 391             final Throwable cause = x.getCause();
 392             if (cause instanceof RuntimeException) {
 393                 throw (RuntimeException) cause;
 394             } else if (cause instanceof Error) {
 395                 throw (Error) cause;
 396             }
 397             // should not happen...
 398             throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, cause);
 399         }
 400     }
 401 
 402     // read config file and initialize the properties
 403     private static void readConfiguration(String fname, Properties p) {
 404         if (fname == null) {
 405             String home = System.getProperty("java.home");
 406             if (home == null) {
 407                 throw new Error("Can't find java.home ??");
 408             }
 409             StringBuffer defaultFileName = new StringBuffer(home);
 410             defaultFileName.append(File.separator).append("lib");
 411             defaultFileName.append(File.separator).append("management");
 412             defaultFileName.append(File.separator).append("management.properties");
 413             // Set file name
 414             fname = defaultFileName.toString();
 415         }
 416         final File configFile = new File(fname);
 417         if (!configFile.exists()) {
 418             error(CONFIG_FILE_NOT_FOUND, fname);
 419         }
 420 
 421         InputStream in = null;
 422         try {
 423             in = new FileInputStream(configFile);
 424             BufferedInputStream bin = new BufferedInputStream(in);
 425             p.load(bin);
 426         } catch (FileNotFoundException e) {
 427             error(CONFIG_FILE_OPEN_FAILED, e.getMessage());
 428         } catch (IOException e) {
 429             error(CONFIG_FILE_OPEN_FAILED, e.getMessage());
 430         } catch (SecurityException e) {
 431             error(CONFIG_FILE_ACCESS_DENIED, fname);
 432         } finally {
 433             if (in != null) {
 434                 try {
 435                     in.close();
 436                 } catch (IOException e) {
 437                     error(CONFIG_FILE_CLOSE_FAILED, fname);
 438                 }
 439             }
 440         }
 441     }
 442 
 443     public static void startAgent() throws Exception {
 444         String prop = System.getProperty("com.sun.management.agent.class");
 445 
 446         // -Dcom.sun.management.agent.class not set so read management
 447         // properties and start agent
 448         if (prop == null) {
 449             // initialize management properties
 450             Properties props = getManagementProperties();
 451             if (props != null) {
 452                 startAgent(props);
 453             }
 454             return;
 455         }
 456 
 457         // -Dcom.sun.management.agent.class=<agent classname>:<agent args>
 458         String[] values = prop.split(":");
 459         if (values.length < 1 || values.length > 2) {
 460             error(AGENT_CLASS_INVALID, "\"" + prop + "\"");
 461         }
 462         String cname = values[0];
 463         String args = (values.length == 2 ? values[1] : null);
 464 
 465         if (cname == null || cname.length() == 0) {
 466             error(AGENT_CLASS_INVALID, "\"" + prop + "\"");
 467         }
 468 
 469         if (cname != null) {
 470             try {
 471                 // Instantiate the named class.
 472                 // invoke the premain(String args) method
 473                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
 474                 Method premain = clz.getMethod("premain",
 475                         new Class<?>[]{String.class});
 476                 premain.invoke(null, /* static */
 477                         new Object[]{args});
 478             } catch (ClassNotFoundException ex) {
 479                 error(AGENT_CLASS_NOT_FOUND, "\"" + cname + "\"");
 480             } catch (NoSuchMethodException ex) {
 481                 error(AGENT_CLASS_PREMAIN_NOT_FOUND, "\"" + cname + "\"");
 482             } catch (SecurityException ex) {
 483                 error(AGENT_CLASS_ACCESS_DENIED);
 484             } catch (Exception ex) {
 485                 String msg = (ex.getCause() == null
 486                         ? ex.getMessage()
 487                         : ex.getCause().getMessage());
 488                 error(AGENT_CLASS_FAILED, msg);
 489             }
 490         }
 491     }
 492 
 493     public static void error(String key) {
 494         String keyText = getText(key);
 495         System.err.print(getText("agent.err.error") + ": " + keyText);
 496         throw new RuntimeException(keyText);
 497     }
 498 
 499     public static void error(String key, String message) {
 500         String keyText = getText(key);
 501         System.err.print(getText("agent.err.error") + ": " + keyText);
 502         System.err.println(": " + message);
 503         throw new RuntimeException(keyText + ": " + message);
 504     }
 505 
 506     public static void error(Exception e) {
 507         e.printStackTrace();
 508         System.err.println(getText(AGENT_EXCEPTION) + ": " + e.toString());
 509         throw new RuntimeException(e);
 510     }
 511 
 512     public static void error(AgentConfigurationError e) {
 513         String keyText = getText(e.getError());
 514         String[] params = e.getParams();
 515 
 516         System.err.print(getText("agent.err.error") + ": " + keyText);
 517 
 518         if (params != null && params.length != 0) {
 519            StringBuffer message = new StringBuffer(params[0]);
 520            for (int i = 1; i < params.length; i++) {
 521                message.append(" " + params[i]);
 522            }
 523            System.err.println(": " + message);
 524         }
 525         e.printStackTrace();
 526         throw new RuntimeException(e);
 527     }
 528 
 529     public static void warning(String key, String message) {
 530         System.err.print(getText("agent.err.warning") + ": " + getText(key));
 531         System.err.println(": " + message);
 532     }
 533 
 534     private static void initResource() {
 535         try {
 536             messageRB =
 537                     ResourceBundle.getBundle("sun.management.resources.agent");
 538         } catch (MissingResourceException e) {
 539             throw new Error("Fatal: Resource for management agent is missing");
 540         }
 541     }
 542 
 543     public static String getText(String key) {
 544         if (messageRB == null) {
 545             initResource();
 546         }
 547         try {
 548             return messageRB.getString(key);
 549         } catch (MissingResourceException e) {
 550             return "Missing management agent resource bundle: key = \"" + key + "\"";
 551         }
 552     }
 553 
 554     public static String getText(String key, String... args) {
 555         if (messageRB == null) {
 556             initResource();
 557         }
 558         String format = messageRB.getString(key);
 559         if (format == null) {
 560             format = "missing resource key: key = \"" + key + "\", "
 561                     + "arguments = \"{0}\", \"{1}\", \"{2}\"";
 562         }
 563         return MessageFormat.format(format, (Object[]) args);
 564     }
 565 }