/*
* Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.servicetag;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import static com.sun.servicetag.Util.*;
import static com.sun.servicetag.RegistrationDocument.*;
/**
* A service tag registry is a XML-based registry containing
* the list of {@link ServiceTag service tags} installed in the system.
* The {@code Registry} class provides interfaces
* to add, remove, update, and get a service tag from a service tag
* registry.
* This {@code Registry} class may not be supported
* on all systems. The {@link #isSupported} method
* can be called to determine if it is supported.
*
* A registry may implement restrictions to only allow certain users
* to {@link #updateServiceTag update} and
* to {@link #removeServiceTag remove} a service tag record. Typically,
* only the owner of the service tag, the owner of the registry
* and superuser are authorized to update or remove a service tag in
* the registry.
*
* @see
* Service Tag User Guide
*/
public class Registry {
private static final String STCLIENT_SOLARIS = "/usr/bin/stclient";
private static final String STCLIENT_LINUX = "/opt/sun/servicetag/bin/stclient";
// stclient exit value (see sthelper.h)
private static final int ST_ERR_NOT_AUTH = 245;
private static final int ST_ERR_REC_NOT_FOUND = 225;
// The stclient output has to be an exported interface
private static final String INSTANCE_URN_DESC = "Product instance URN=";
private static boolean initialized = false;
private static File stclient = null;
private static String stclientPath = null;
private static Registry registry = new Registry();
// System properties for testing
private static String SVCTAG_STCLIENT_CMD = "servicetag.stclient.cmd";
private static String SVCTAG_STHELPER_SUPPORTED = "servicetag.sthelper.supported";
private Registry() {
}
private synchronized static String getSTclient() {
if (!initialized) {
// Initialization to determine the platform's stclient pathname
String os = System.getProperty("os.name");
if (os.equals("SunOS")) {
stclient = new File(STCLIENT_SOLARIS);
} else if (os.equals("Linux")) {
stclient = new File(STCLIENT_LINUX);
} else if (os.startsWith("Windows")) {
stclient = getWindowsStClientFile();
} else {
if (isVerbose()) {
System.out.println("Running on unsupported platform");
}
}
initialized = true;
}
boolean supportsHelperClass = true; // default
if (System.getProperty(SVCTAG_STHELPER_SUPPORTED) != null) {
// the system property always overrides the default setting
supportsHelperClass = Boolean.getBoolean(SVCTAG_STHELPER_SUPPORTED);
}
if (!supportsHelperClass) {
// disable system registry
return null;
}
// This is only used for testing
String path = System.getProperty(SVCTAG_STCLIENT_CMD);
if (path != null) {
return path;
}
// com.sun.servicetag package has to be compiled with JDK 5 as well
// JDK 5 doesn't support the File.canExecute() method.
// Risk not checking isExecute() for the stclient command is very low.
if (stclientPath == null && stclient != null && stclient.exists()) {
stclientPath = stclient.getAbsolutePath();
}
return stclientPath;
}
/**
* Returns the system service tag registry. The {@code Registry} class
* may not be supported on some platforms; use the {@link #isSupported}
* method to determine if it is supported.
*
* @return the {@code Registry} object for the system service tag registry.
*
* @throws UnsupportedOperationException if the {@code Registry} class is
* not supported.
*/
public static Registry getSystemRegistry() {
if (isSupported()) {
return registry;
} else {
throw new UnsupportedOperationException("Registry class is not supported");
}
}
/**
* Returns {@code true} if the {@code Registry} class is supported on this system.
*
* @return {@code true} if the {@code Registry} class is supported;
* otherwise, return {@code false}.
*/
public static synchronized boolean isSupported() {
return getSTclient() != null;
}
private static List getCommandList() {
// Set up the arguments to call stclient
List command = new ArrayList();
if (System.getProperty(SVCTAG_STCLIENT_CMD) != null) {
// This is for jtreg testing use. This will be set to something
// like:
// $JAVA_HOME/bin/java -cp $TEST_DIR \
// -Dstclient.registry.path=$TEST_DIR/registry.xml \
// SvcTagClient
//
// On Windows, the JAVA_HOME and TEST_DIR path could contain
// space e.g. c:\Program Files\Java\jdk1.6.0_05\bin\java.
// The SVCTAG_STCLIENT_CMD must be set with a list of
// space-separated parameters. If a parameter contains spaces,
// it must be quoted with '"'.
String cmd = getSTclient();
int len = cmd.length();
int i = 0;
while (i < len) {
char separator = ' ';
if (cmd.charAt(i) == '"') {
separator = '"';
i++;
}
// look for the separator or matched the closing '"'
int j;
for (j = i+1; j < len; j++) {
if (cmd.charAt(j) == separator) {
break;
}
}
if (i == j-1) {
// add an empty parameter
command.add("\"\"");
} else {
// double quotes and space are not included
command.add(cmd.substring(i,j));
}
// skip spaces
for (i = j+1; i < len; i++) {
if (!Character.isSpaceChar(cmd.charAt(i))) {
break;
}
}
}
if (isVerbose()) {
System.out.println("Command list:");
for (String s : command) {
System.out.println(s);
}
}
} else {
command.add(getSTclient());
}
return command;
}
// Returns null if the service tag record not found;
// or throw UnauthorizedAccessException or IOException
// based on the exitValue.
private static ServiceTag checkReturnError(int exitValue,
String output,
ServiceTag st) throws IOException {
switch (exitValue) {
case ST_ERR_REC_NOT_FOUND:
return null;
case ST_ERR_NOT_AUTH:
if (st != null) {
throw new UnauthorizedAccessException(
"Not authorized to access " + st.getInstanceURN() +
" installer_uid=" + st.getInstallerUID());
} else {
throw new UnauthorizedAccessException(
"Not authorized:" + output);
}
default:
throw new IOException("stclient exits with error" +
" (" + exitValue + ")\n" + output);
}
}
/**
* Adds a service tag to this registry.
* If the given service tag has an empty instance_urn,
* this helper class will generate a URN and place it in the
* copy of the service tag in this registry.
* This method will return the {@code ServiceTag} representing
* the service tag entry to this registry.
*
* @param st {@code ServiceTag} object
* @return a {@code ServiceTag} object representing the service tag
* entry to this registry.
*
* @throws IllegalArgumentException if a service tag of the same
* instance_urn already exists in this registry.
*
* @throws java.io.IOException if an I/O error occurs in this operation.
*/
public ServiceTag addServiceTag(ServiceTag st) throws IOException {
List command = getCommandList();
command.add("-a");
if (st.getInstanceURN().length() > 0) {
ServiceTag sysSvcTag = getServiceTag(st.getInstanceURN());
if (sysSvcTag != null) {
throw new IllegalArgumentException("Instance_urn = " +
st.getInstanceURN() + " already exists");
}
command.add("-i");
command.add(st.getInstanceURN());
}
command.add("-p");
command.add(st.getProductName());
command.add("-e");
command.add(st.getProductVersion());
command.add("-t");
command.add(st.getProductURN());
if (st.getProductParentURN().length() > 0) {
command.add("-F");
command.add(st.getProductParentURN());
}
command.add("-P");
command.add(st.getProductParent());
if (st.getProductDefinedInstanceID().length() > 0) {
command.add("-I");
command.add(st.getProductDefinedInstanceID());
}
command.add("-m");
command.add(st.getProductVendor());
command.add("-A");
command.add(st.getPlatformArch());
command.add("-z");
command.add(st.getContainer());
command.add("-S");
command.add(st.getSource());
BufferedReader in = null;
try {
ProcessBuilder pb = new ProcessBuilder(command);
Process p = pb.start();
String output = commandOutput(p);
if (isVerbose()) {
System.out.println("Output from stclient -a command:");
System.out.println(output);
}
String urn = "";
if (p.exitValue() == 0) {
// Obtain the instance urn from the stclient output
in = new BufferedReader(new StringReader(output));
String line = null;
while ((line = in.readLine()) != null) {
line = line.trim();
if (line.startsWith(INSTANCE_URN_DESC)) {
urn = line.substring(INSTANCE_URN_DESC.length());
break;
}
}
if (urn.length() == 0) {
throw new IOException("Error in creating service tag:\n" +
output);
}
return getServiceTag(urn);
} else {
return checkReturnError(p.exitValue(), output, st);
}
} finally {
if (in != null) {
in.close();
}
}
}
/**
* Removes a service tag of the given instance_urn from this
* registry.
*
* @param instanceURN the instance_urn of the service tag
* to be removed.
*
* @return the {@code ServiceTag} object removed from this registry;
* or {@code null} if the service tag does not exist in this registry.
*
* @throws UnauthorizedAccessException if the user is not authorized to
* remove the service tag of the given instance_urn
* from this registry.
*
* @throws java.io.IOException if an I/O error occurs in this operation.
*/
public ServiceTag removeServiceTag(String instanceURN) throws IOException {
ServiceTag st = getServiceTag(instanceURN);
if (st == null) {
return null;
}
List command = getCommandList();
command.add("-d");
command.add("-i");
command.add(instanceURN);
ProcessBuilder pb = new ProcessBuilder(command);
Process p = pb.start();
String output = commandOutput(p);
if (isVerbose()) {
System.out.println("Output from stclient -d command:");
System.out.println(output);
}
if (p.exitValue() == 0) {
return st;
} else {
return checkReturnError(p.exitValue(), output, st);
}
}
/**
* Updates the product_defined_instance_id in the service tag
* of the specified instance_urn in this registry.
*
* @param instanceURN the instance_urn of the service tag to be updated.
* @param productDefinedInstanceID the value of the
* product_defined_instance_id to be set.
*
* @return the updated {@code ServiceTag} object;
* or {@code null} if the service tag does not exist in this
* registry.
*
* @throws UnauthorizedAccessException if the user is not authorized to
* update the service tag from this registry.
*
* @throws IOException if an I/O error occurs in this operation.
*/
public ServiceTag updateServiceTag(String instanceURN,
String productDefinedInstanceID)
throws IOException {
ServiceTag svcTag = getServiceTag(instanceURN);
if (svcTag == null) {
return null;
}
List command = getCommandList();
command.add("-u");
command.add("-i");
command.add(instanceURN);
command.add("-I");
if (productDefinedInstanceID.length() > 0) {
command.add(productDefinedInstanceID);
} else {
command.add("\"\"");
}
ProcessBuilder pb = new ProcessBuilder(command);
Process p = pb.start();
String output = commandOutput(p);
if (isVerbose()) {
System.out.println("Output from stclient -u command:");
System.out.println(output);
}
if (p.exitValue() == 0) {
return getServiceTag(instanceURN);
} else {
return checkReturnError(p.exitValue(), output, svcTag);
}
}
/**
* Returns a {@code ServiceTag} object of the given instance_urn
* in this registry.
*
* @param instanceURN the instance_urn of the service tag
* @return a {@code ServiceTag} object of the given instance_urn
* in this registry; or {@code null} if not found.
*
* @throws java.io.IOException if an I/O error occurs in this operation.
*/
public ServiceTag getServiceTag(String instanceURN) throws IOException {
if (instanceURN == null) {
throw new NullPointerException("instanceURN is null");
}
List command = getCommandList();
command.add("-g");
command.add("-i");
command.add(instanceURN);
ProcessBuilder pb = new ProcessBuilder(command);
Process p = pb.start();
String output = commandOutput(p);
if (isVerbose()) {
System.out.println("Output from stclient -g command:");
System.out.println(output);
}
if (p.exitValue() == 0) {
return parseServiceTag(output);
} else {
return checkReturnError(p.exitValue(), output, null);
}
}
private ServiceTag parseServiceTag(String output) throws IOException {
BufferedReader in = null;
try {
Properties props = new Properties();
// parse the service tag output from stclient
in = new BufferedReader(new StringReader(output));
String line = null;
while ((line = in.readLine()) != null) {
if ((line = line.trim()).length() > 0) {
String[] ss = line.trim().split("=", 2);
if (ss.length == 2) {
props.setProperty(ss[0].trim(), ss[1].trim());
} else {
props.setProperty(ss[0].trim(), "");
}
}
}
String urn = props.getProperty(ST_NODE_INSTANCE_URN);
String productName = props.getProperty(ST_NODE_PRODUCT_NAME);
String productVersion = props.getProperty(ST_NODE_PRODUCT_VERSION);
String productURN = props.getProperty(ST_NODE_PRODUCT_URN);
String productParent = props.getProperty(ST_NODE_PRODUCT_PARENT);
String productParentURN = props.getProperty(ST_NODE_PRODUCT_PARENT_URN);
String productDefinedInstanceID =
props.getProperty(ST_NODE_PRODUCT_DEFINED_INST_ID);
String productVendor = props.getProperty(ST_NODE_PRODUCT_VENDOR);
String platformArch = props.getProperty(ST_NODE_PLATFORM_ARCH);
String container = props.getProperty(ST_NODE_CONTAINER);
String source = props.getProperty(ST_NODE_SOURCE);
int installerUID =
Util.getIntValue(props.getProperty(ST_NODE_INSTALLER_UID));
Date timestamp =
Util.parseTimestamp(props.getProperty(ST_NODE_TIMESTAMP));
return new ServiceTag(urn,
productName,
productVersion,
productURN,
productParent,
productParentURN,
productDefinedInstanceID,
productVendor,
platformArch,
container,
source,
installerUID,
timestamp);
} finally {
if (in != null) {
in.close();
}
}
}
/**
* Returns the service tags of the specified
* product_urn in this registry.
*
* @param productURN the product_urn to look up
* @return a {@code Set} of {@code ServiceTag} objects
* of the specified product_urn in this registry.
*
* @throws java.io.IOException if an I/O error occurs in this operation.
*/
public Set findServiceTags(String productURN) throws IOException {
if (productURN == null) {
throw new NullPointerException("productURN is null");
}
List command = getCommandList();
command.add("-f");
command.add("-t");
command.add(productURN);
BufferedReader in = null;
try {
ProcessBuilder pb = new ProcessBuilder(command);
Process p = pb.start();
String output = commandOutput(p);
Set instances = new HashSet();
if (p.exitValue() == 0) {
// parse the service tag output from stclient
in = new BufferedReader(new StringReader(output));
String line = null;
while ((line = in.readLine()) != null) {
String s = line.trim();
if (s.startsWith("urn:st:")) {
instances.add(getServiceTag(s));
}
}
} else {
checkReturnError(p.exitValue(), output, null);
}
return instances;
} finally {
if (in != null) {
in.close();
}
}
}
}