1 /*
   2  * Copyright (c) 2008, 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 
  26 package com.sun.servicetag;
  27 
  28 import java.io.*;
  29 import java.util.ArrayList;
  30 import java.util.Date;
  31 import java.util.HashSet;
  32 import java.util.List;
  33 import java.util.Properties;
  34 import java.util.Set;
  35 
  36 import static com.sun.servicetag.Util.*;
  37 import static com.sun.servicetag.RegistrationDocument.*;
  38 
  39 /**
  40  * A service tag registry is a XML-based registry containing
  41  * the list of {@link ServiceTag service tags} installed in the system.
  42  * The {@code Registry} class provides interfaces
  43  * to add, remove, update, and get a service tag from a service tag
  44  * registry.
  45  * This {@code Registry} class may not be supported
  46  * on all systems. The {@link #isSupported} method
  47  * can be called to determine if it is supported.
  48  * <p>
  49  * A registry may implement restrictions to only allow certain users
  50  * to {@link #updateServiceTag update} and
  51  * to {@link #removeServiceTag remove} a service tag record. Typically,
  52  * only the owner of the service tag, the owner of the registry
  53  * and superuser are authorized to update or remove a service tag in
  54  * the registry.
  55  *
  56  * @see <a href="https://sn-tools.central.sun.com/twiki/bin/view/ServiceTags/ServiceTagDevGuideHelper">
  57  * Service Tag User Guide</a>
  58  */
  59 public class Registry {
  60 
  61     private static final String STCLIENT_SOLARIS = "/usr/bin/stclient";
  62     private static final String STCLIENT_LINUX = "/opt/sun/servicetag/bin/stclient";
  63     // stclient exit value (see sthelper.h)
  64     private static final int ST_ERR_NOT_AUTH = 245;
  65     private static final int ST_ERR_REC_NOT_FOUND = 225;
  66 
  67     // The stclient output has to be an exported interface
  68     private static final String INSTANCE_URN_DESC = "Product instance URN=";
  69     private static boolean initialized = false;
  70     private static File stclient = null;
  71     private static String stclientPath = null;
  72     private static Registry registry = new Registry();
  73 
  74     // System properties for testing
  75     private static String SVCTAG_STCLIENT_CMD = "servicetag.stclient.cmd";
  76     private static String SVCTAG_STHELPER_SUPPORTED = "servicetag.sthelper.supported";
  77 
  78     private Registry() {
  79     }
  80 
  81     private synchronized static String getSTclient() {
  82         if (!initialized) {
  83             // Initialization to determine the platform's stclient pathname
  84             String os = System.getProperty("os.name");
  85             if (os.equals("SunOS")) {
  86                 stclient = new File(STCLIENT_SOLARIS);
  87             } else if (os.equals("Linux")) {
  88                 stclient = new File(STCLIENT_LINUX);
  89             } else if (os.startsWith("Windows")) {
  90                 stclient = getWindowsStClientFile();
  91             } else {
  92                 if (isVerbose()) {
  93                     System.out.println("Running on unsupported platform");
  94                 }
  95             }
  96             initialized = true;
  97         }
  98 
  99         boolean supportsHelperClass = true; // default
 100         if (System.getProperty(SVCTAG_STHELPER_SUPPORTED) != null) {
 101             // the system property always overrides the default setting
 102             supportsHelperClass = Boolean.getBoolean(SVCTAG_STHELPER_SUPPORTED);
 103         }
 104 
 105         if (!supportsHelperClass) {
 106             // disable system registry
 107             return null;
 108         }
 109 
 110         // This is only used for testing
 111         String path = System.getProperty(SVCTAG_STCLIENT_CMD);
 112         if (path != null) {
 113             return path;
 114         }
 115 
 116         // com.sun.servicetag package has to be compiled with JDK 5 as well
 117         // JDK 5 doesn't support the File.canExecute() method.
 118         // Risk not checking isExecute() for the stclient command is very low.
 119         if (stclientPath == null && stclient != null && stclient.exists()) {
 120             stclientPath = stclient.getAbsolutePath();
 121         }
 122         return stclientPath;
 123     }
 124 
 125     /**
 126      * Returns the system service tag registry. The {@code Registry} class
 127      * may not be supported on some platforms; use the {@link #isSupported}
 128      * method to determine if it is supported.
 129      *
 130      * @return the {@code Registry} object for the system service tag registry.
 131      *
 132      * @throws UnsupportedOperationException if the {@code Registry} class is
 133      * not supported.
 134      */
 135     public static Registry getSystemRegistry() {
 136         if (isSupported()) {
 137             return registry;
 138         } else {
 139             throw new UnsupportedOperationException("Registry class is not supported");
 140         }
 141     }
 142 
 143     /**
 144      * Returns {@code true} if the {@code Registry} class is supported on this system.
 145      *
 146      * @return {@code true} if the {@code Registry} class is supported;
 147      * otherwise, return {@code false}.
 148      */
 149     public static synchronized boolean isSupported() {
 150         return getSTclient() != null;
 151     }
 152 
 153     private static List<String> getCommandList() {
 154         // Set up the arguments to call stclient
 155         List<String> command = new ArrayList<String>();
 156         if (System.getProperty(SVCTAG_STCLIENT_CMD) != null) {
 157             // This is for jtreg testing use. This will be set to something
 158             // like:
 159             // $JAVA_HOME/bin/java -cp $TEST_DIR \
 160             //    -Dstclient.registry.path=$TEST_DIR/registry.xml \
 161             //    SvcTagClient
 162             //
 163             // On Windows, the JAVA_HOME and TEST_DIR path could contain
 164             // space e.g. c:\Program Files\Java\jdk1.6.0_05\bin\java.
 165             // The SVCTAG_STCLIENT_CMD must be set with a list of
 166             // space-separated parameters.  If a parameter contains spaces,
 167             // it must be quoted with '"'.
 168 
 169             String cmd = getSTclient();
 170             int len = cmd.length();
 171             int i = 0;
 172             while (i < len) {
 173                 char separator = ' ';
 174                 if (cmd.charAt(i) == '"') {
 175                     separator = '"';
 176                     i++;
 177                 }
 178                 // look for the separator or matched the closing '"'
 179                 int j;
 180                 for (j = i+1; j < len; j++) {
 181                     if (cmd.charAt(j) == separator) {
 182                         break;
 183                     }
 184                 }
 185 
 186                 if (i == j-1) {
 187                     // add an empty parameter
 188                     command.add("\"\"");
 189                 } else {
 190                     // double quotes and space are not included
 191                     command.add(cmd.substring(i,j));
 192                 }
 193 
 194                 // skip spaces
 195                 for (i = j+1; i < len; i++) {
 196                     if (!Character.isSpaceChar(cmd.charAt(i))) {
 197                         break;
 198                     }
 199                 }
 200             }
 201             if (isVerbose()) {
 202                 System.out.println("Command list:");
 203                 for (String s : command) {
 204                     System.out.println(s);
 205                 }
 206             }
 207         } else {
 208             command.add(getSTclient());
 209         }
 210         return command;
 211     }
 212 
 213     // Returns null if the service tag record not found;
 214     // or throw UnauthorizedAccessException or IOException
 215     // based on the exitValue.
 216     private static ServiceTag checkReturnError(int exitValue,
 217                                                String output,
 218                                                ServiceTag st) throws IOException {
 219         switch (exitValue) {
 220             case ST_ERR_REC_NOT_FOUND:
 221                 return null;
 222             case ST_ERR_NOT_AUTH:
 223                 if (st != null) {
 224                     throw new UnauthorizedAccessException(
 225                         "Not authorized to access " + st.getInstanceURN() +
 226                         " installer_uid=" + st.getInstallerUID());
 227                 } else  {
 228                     throw new UnauthorizedAccessException(
 229                         "Not authorized:" + output);
 230                 }
 231             default:
 232                 throw new IOException("stclient exits with error" +
 233                      " (" + exitValue + ")\n" + output);
 234         }
 235     }
 236 
 237     /**
 238      * Adds a service tag to this registry.
 239      * If the given service tag has an empty <tt>instance_urn</tt>,
 240      * this helper class will generate a URN and place it in the
 241      * copy of the service tag in this registry.
 242      * This method will return the {@code ServiceTag} representing
 243      * the service tag entry to this registry.
 244      *
 245      * @param st {@code ServiceTag} object
 246      * @return a {@code ServiceTag} object representing the service tag
 247      *         entry to this registry.
 248      *
 249      * @throws IllegalArgumentException if a service tag of the same
 250      * <tt>instance_urn</tt> already exists in this registry.
 251      *
 252      * @throws java.io.IOException if an I/O error occurs in this operation.
 253      */
 254     public ServiceTag addServiceTag(ServiceTag st) throws IOException {
 255         List<String> command = getCommandList();
 256         command.add("-a");
 257         if (st.getInstanceURN().length() > 0) {
 258             ServiceTag sysSvcTag = getServiceTag(st.getInstanceURN());
 259             if (sysSvcTag != null) {
 260                 throw new IllegalArgumentException("Instance_urn = " +
 261                     st.getInstanceURN() + " already exists");
 262             }
 263             command.add("-i");
 264             command.add(st.getInstanceURN());
 265         }
 266         command.add("-p");
 267         command.add(st.getProductName());
 268         command.add("-e");
 269         command.add(st.getProductVersion());
 270         command.add("-t");
 271         command.add(st.getProductURN());
 272         if (st.getProductParentURN().length() > 0) {
 273             command.add("-F");
 274             command.add(st.getProductParentURN());
 275         }
 276         command.add("-P");
 277         command.add(st.getProductParent());
 278         if (st.getProductDefinedInstanceID().length() > 0) {
 279             command.add("-I");
 280             command.add(st.getProductDefinedInstanceID());
 281         }
 282         command.add("-m");
 283         command.add(st.getProductVendor());
 284         command.add("-A");
 285         command.add(st.getPlatformArch());
 286         command.add("-z");
 287         command.add(st.getContainer());
 288         command.add("-S");
 289         command.add(st.getSource());
 290 
 291         BufferedReader in = null;
 292         try {
 293             ProcessBuilder pb = new ProcessBuilder(command);
 294             Process p = pb.start();
 295             String output = commandOutput(p);
 296             if (isVerbose()) {
 297                 System.out.println("Output from stclient -a command:");
 298                 System.out.println(output);
 299             }
 300             String urn = "";
 301             if (p.exitValue() == 0) {
 302                 // Obtain the instance urn from the stclient output
 303                 in = new BufferedReader(new StringReader(output));
 304                 String line = null;
 305                 while ((line = in.readLine()) != null) {
 306                     line = line.trim();
 307                     if (line.startsWith(INSTANCE_URN_DESC)) {
 308                         urn = line.substring(INSTANCE_URN_DESC.length());
 309                         break;
 310                     }
 311                 }
 312                 if (urn.length() == 0) {
 313                     throw new IOException("Error in creating service tag:\n" +
 314                         output);
 315                 }
 316                 return getServiceTag(urn);
 317             } else {
 318                 return checkReturnError(p.exitValue(), output, st);
 319             }
 320         } finally {
 321             if (in != null) {
 322                 in.close();
 323             }
 324         }
 325     }
 326 
 327     /**
 328      * Removes a service tag of the given <tt>instance_urn</tt> from this
 329      * registry.
 330      *
 331      * @param instanceURN the <tt>instance_urn</tt> of the service tag
 332      *        to be removed.
 333      *
 334      * @return the {@code ServiceTag} object removed from this registry;
 335      * or {@code null} if the service tag does not exist in this registry.
 336      *
 337      * @throws UnauthorizedAccessException if the user is not authorized to
 338      * remove the service tag of the given <tt>instance_urn</tt>
 339      * from this registry.
 340      *
 341      * @throws java.io.IOException if an I/O error occurs in this operation.
 342      */
 343     public ServiceTag removeServiceTag(String instanceURN) throws IOException {
 344         ServiceTag st = getServiceTag(instanceURN);
 345         if (st == null) {
 346             return null;
 347         }
 348 
 349         List<String> command = getCommandList();
 350         command.add("-d");
 351         command.add("-i");
 352         command.add(instanceURN);
 353 
 354         ProcessBuilder pb = new ProcessBuilder(command);
 355         Process p = pb.start();
 356         String output = commandOutput(p);
 357         if (isVerbose()) {
 358             System.out.println("Output from stclient -d command:");
 359             System.out.println(output);
 360         }
 361         if (p.exitValue() == 0) {
 362             return st;
 363         } else {
 364             return checkReturnError(p.exitValue(), output, st);
 365         }
 366     }
 367 
 368     /**
 369      * Updates the <tt>product_defined_instance_id</tt> in the service tag
 370      * of the specified <tt>instance_urn</tt> in this registry.
 371      *
 372      * @param instanceURN the <tt>instance_urn</tt> of the service tag to be updated.
 373      * @param productDefinedInstanceID the value of the
 374      * <tt>product_defined_instance_id</tt> to be set.
 375      *
 376      * @return the updated {@code ServiceTag} object;
 377      * or {@code null} if the service tag does not exist in this
 378      * registry.
 379      *
 380      * @throws UnauthorizedAccessException if the user is not authorized to
 381      * update the service tag from this registry.
 382      *
 383      * @throws IOException if an I/O error occurs in this operation.
 384      */
 385     public ServiceTag updateServiceTag(String instanceURN,
 386                                        String productDefinedInstanceID)
 387             throws IOException {
 388         ServiceTag svcTag = getServiceTag(instanceURN);
 389         if (svcTag == null) {
 390             return null;
 391         }
 392 
 393         List<String> command = getCommandList();
 394         command.add("-u");
 395         command.add("-i");
 396         command.add(instanceURN);
 397         command.add("-I");
 398         if (productDefinedInstanceID.length() > 0) {
 399             command.add(productDefinedInstanceID);
 400         } else {
 401             command.add("\"\"");
 402         }
 403 
 404         ProcessBuilder pb = new ProcessBuilder(command);
 405         Process p = pb.start();
 406         String output = commandOutput(p);
 407         if (isVerbose()) {
 408             System.out.println("Output from stclient -u command:");
 409             System.out.println(output);
 410         }
 411 
 412         if (p.exitValue() == 0) {
 413             return getServiceTag(instanceURN);
 414         } else {
 415             return checkReturnError(p.exitValue(), output, svcTag);
 416         }
 417     }
 418 
 419     /**
 420      * Returns a {@code ServiceTag} object of the given  <tt>instance_urn</tt>
 421      * in this registry.
 422      *
 423      * @param instanceURN the  <tt>instance_urn</tt> of the service tag
 424      * @return a {@code ServiceTag} object of the given <tt>instance_urn</tt>
 425      * in this registry; or {@code null} if not found.
 426      *
 427      * @throws java.io.IOException if an I/O error occurs in this operation.
 428      */
 429     public ServiceTag getServiceTag(String instanceURN) throws IOException {
 430         if (instanceURN == null) {
 431             throw new NullPointerException("instanceURN is null");
 432         }
 433 
 434         List<String> command = getCommandList();
 435         command.add("-g");
 436         command.add("-i");
 437         command.add(instanceURN);
 438 
 439         ProcessBuilder pb = new ProcessBuilder(command);
 440         Process p = pb.start();
 441         String output = commandOutput(p);
 442         if (isVerbose()) {
 443             System.out.println("Output from stclient -g command:");
 444             System.out.println(output);
 445         }
 446         if (p.exitValue() == 0) {
 447             return parseServiceTag(output);
 448         } else {
 449             return checkReturnError(p.exitValue(), output, null);
 450         }
 451     }
 452 
 453     private ServiceTag parseServiceTag(String output) throws IOException {
 454         BufferedReader in = null;
 455         try {
 456             Properties props = new Properties();
 457             // parse the service tag output from stclient
 458             in = new BufferedReader(new StringReader(output));
 459             String line = null;
 460             while ((line = in.readLine()) != null) {
 461                 if ((line = line.trim()).length() > 0) {
 462                     String[] ss = line.trim().split("=", 2);
 463                     if (ss.length == 2) {
 464                         props.setProperty(ss[0].trim(), ss[1].trim());
 465                     } else {
 466                         props.setProperty(ss[0].trim(), "");
 467                     }
 468                 }
 469             }
 470 
 471             String urn = props.getProperty(ST_NODE_INSTANCE_URN);
 472             String productName = props.getProperty(ST_NODE_PRODUCT_NAME);
 473             String productVersion = props.getProperty(ST_NODE_PRODUCT_VERSION);
 474             String productURN = props.getProperty(ST_NODE_PRODUCT_URN);
 475             String productParent = props.getProperty(ST_NODE_PRODUCT_PARENT);
 476             String productParentURN = props.getProperty(ST_NODE_PRODUCT_PARENT_URN);
 477             String productDefinedInstanceID =
 478                 props.getProperty(ST_NODE_PRODUCT_DEFINED_INST_ID);
 479             String productVendor = props.getProperty(ST_NODE_PRODUCT_VENDOR);
 480             String platformArch = props.getProperty(ST_NODE_PLATFORM_ARCH);
 481             String container = props.getProperty(ST_NODE_CONTAINER);
 482             String source = props.getProperty(ST_NODE_SOURCE);
 483             int installerUID =
 484                 Util.getIntValue(props.getProperty(ST_NODE_INSTALLER_UID));
 485             Date timestamp =
 486                 Util.parseTimestamp(props.getProperty(ST_NODE_TIMESTAMP));
 487 
 488             return new ServiceTag(urn,
 489                                   productName,
 490                                   productVersion,
 491                                   productURN,
 492                                   productParent,
 493                                   productParentURN,
 494                                   productDefinedInstanceID,
 495                                   productVendor,
 496                                   platformArch,
 497                                   container,
 498                                   source,
 499                                   installerUID,
 500                                   timestamp);
 501         } finally {
 502             if (in != null) {
 503                 in.close();
 504             }
 505         }
 506 
 507     }
 508 
 509     /**
 510      * Returns the service tags of the specified
 511      * <tt>product_urn</tt> in this registry.
 512      *
 513      * @param productURN the  <tt>product_urn</tt> to look up
 514      * @return a {@code Set} of {@code ServiceTag} objects
 515      * of the specified <tt>product_urn</tt> in this registry.
 516      *
 517      * @throws java.io.IOException if an I/O error occurs in this operation.
 518      */
 519     public Set<ServiceTag> findServiceTags(String productURN) throws IOException {
 520         if (productURN == null) {
 521             throw new NullPointerException("productURN is null");
 522         }
 523 
 524         List<String> command = getCommandList();
 525         command.add("-f");
 526         command.add("-t");
 527         command.add(productURN);
 528 
 529         BufferedReader in = null;
 530         try {
 531             ProcessBuilder pb = new ProcessBuilder(command);
 532             Process p = pb.start();
 533             String output = commandOutput(p);
 534 
 535             Set<ServiceTag> instances = new HashSet<ServiceTag>();
 536             if (p.exitValue() == 0) {
 537                 // parse the service tag output from stclient
 538                 in = new BufferedReader(new StringReader(output));
 539                 String line = null;
 540                 while ((line = in.readLine()) != null) {
 541                     String s = line.trim();
 542                     if (s.startsWith("urn:st:")) {
 543                         instances.add(getServiceTag(s));
 544                     }
 545                 }
 546             } else {
 547                 checkReturnError(p.exitValue(), output, null);
 548             }
 549             return instances;
 550         } finally {
 551             if (in != null) {
 552                 in.close();
 553             }
 554         }
 555     }
 556 }