1 /*
   2  * Copyright (c) 2004, 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 sun.jvmstat.perfdata.monitor.protocol.local;
  27 
  28 import java.io.File;
  29 import java.io.FilenameFilter;
  30 
  31 /**
  32  * Class to provide translations from the local Vm Identifier
  33  * name space into the file system name space and vice-versa.
  34  * <p>
  35  * Provides a factory for creating a File object to the backing
  36  * store file for instrumentation shared memory region for a JVM
  37  * identified by its Local Java Virtual Machine Identifier, or
  38  * <em>lvmid</em>.
  39  *
  40  * @author Brian Doherty
  41  * @since 1.5
  42  * @see java.io.File
  43  */
  44 public class PerfDataFile {
  45     private PerfDataFile() { };
  46 
  47     /**
  48      * The name of the of the system dependent temporary directory
  49      */
  50     public static final String tmpDirName;
  51 
  52     /**
  53      * The file name prefix for PerfData shared memory files.
  54      * <p>
  55      * This prefix must be kept in sync with the prefix used by the JVM.
  56      */
  57     public static final String dirNamePrefix = "hsperfdata_";
  58 
  59     /**
  60      * The directory name pattern for the user directories.
  61      */
  62     public static final String userDirNamePattern = "hsperfdata_\\S*";
  63 
  64     /**
  65      * The file name pattern for PerfData shared memory files.
  66      * <p>
  67      * This pattern must be kept in synch with the file name pattern
  68      * used by the 1.4.2 and later HotSpot JVM.
  69      */
  70     public static final String fileNamePattern = "^[0-9]+$";
  71 
  72     /**
  73      * The file name pattern for 1.4.1 PerfData shared memory files.
  74      * <p>
  75      * This pattern must be kept in synch with the file name pattern
  76      * used by the 1.4.1 HotSpot JVM.
  77      */
  78     public static final String tmpFileNamePattern =
  79             "^hsperfdata_[0-9]+(_[1-2]+)?$";
  80 
  81 
  82     /**
  83      * Get a File object for the instrumentation backing store file
  84      * for the JVM identified by the given local Vm Identifier.
  85      * <p>
  86      * This method looks for the most up to date backing store file for
  87      * the given <tt>lvmid</tt>. It will search all the user specific
  88      * directories in the temporary directory for the host operating
  89      * system, which may be influenced by platform specific environment
  90      * variables.
  91      *
  92      * @param lvmid  the local Java Virtual Machine Identifier for the target
  93      * @return File - a File object to the backing store file for the named
  94      *                shared memory region of the target JVM.
  95      * @see java.io.File
  96      * @see #getTempDirectory()
  97      */
  98     public static File getFile(int lvmid) {
  99         if (lvmid == 0) {
 100             /*
 101              * lvmid == 0 is used to indicate the current Java Virtual Machine.
 102              * If the SDK provided an API to get a unique Java Virtual Machine
 103              * identifier, then a filename could be constructed with that
 104              * identifier. In absence of such an api, return null.
 105              */
 106             return null;
 107         }
 108 
 109         /*
 110          * iterate over all files in all directories in tmpDirName that
 111          * match the file name patterns.
 112          */
 113         File tmpDir = new File(tmpDirName);
 114         String[] files = tmpDir.list(new FilenameFilter() {
 115             public boolean accept(File dir, String name) {
 116                 if (!name.startsWith(dirNamePrefix)) {
 117                     return false;
 118                 }
 119                 File candidate = new File(dir, name);
 120                 return ((candidate.isDirectory() || candidate.isFile())
 121                         && candidate.canRead());
 122             }
 123         });
 124 
 125         long newestTime = 0;
 126         File newest = null;
 127 
 128         for (int i = 0; i < files.length; i++) {
 129             File f = new File(tmpDirName + files[i]);
 130             File candidate = null;
 131 
 132             if (f.exists() && f.isDirectory()) {
 133                 /*
 134                  * found a directory matching the name patterns. This
 135                  * is a 1.4.2 hsperfdata_<user> directory. Check for
 136                  * file named <lvmid> in that directory
 137                  */
 138                 String name = Integer.toString(lvmid);
 139                 candidate = new File(f.getName(), name);
 140 
 141             } else if (f.exists() && f.isFile()) {
 142                 /*
 143                  * found a file matching the name patterns. This
 144                  * is a 1.4.1 hsperfdata_<lvmid> file.
 145                  */
 146                 candidate = f;
 147 
 148             } else {
 149                 // unexpected - let conditional below filter this one out
 150                 candidate = f;
 151             }
 152 
 153             if (candidate.exists() && candidate.isFile()
 154                     && candidate.canRead()) {
 155                 long modTime = candidate.lastModified();
 156                 if (modTime >= newestTime) {
 157                     newestTime = modTime;
 158                     newest = candidate;
 159                 }
 160             }
 161         }
 162         return newest;
 163     }
 164 
 165     /**
 166      * Return the File object for the backing store file for the specified Java
 167      * Virtual Machine.
 168      * <p>
 169      * This method looks for the most up to date backing store file for
 170      * the JVM identified by the given user name and lvmid. The directory
 171      * searched is the temporary directory for the host operating system,
 172      * which may be influenced by environment variables.
 173      *
 174      * @param user   the user name
 175      * @param lvmid  the local Java Virtual Machine Identifier for the target
 176      * @return File - a File object to the backing store file for the named
 177      *                shared memory region of the target JVM.
 178      * @see java.io.File
 179      * @see #getTempDirectory()
 180      */
 181     public static File getFile(String user, int lvmid) {
 182         if (lvmid == 0) {
 183             /*
 184              * lvmid == 0 is used to indicate the current Java Virtual Machine.
 185              * If the SDK provided an API to get a unique Java Virtual Machine
 186              * identifier, then a filename could be constructed with that
 187              * identifier. In absence of such an api, return null.
 188              */
 189             return null;
 190         }
 191 
 192         // first try for 1.4.2 and later JVMs
 193         String basename = getTempDirectory(user) + Integer.toString(lvmid);
 194         File f = new File(basename);
 195 
 196         if (f.exists() && f.isFile() && f.canRead()) {
 197             return f;
 198         }
 199 
 200         // No hit on 1.4.2 JVMs, try 1.4.1 files
 201         long newestTime = 0;
 202         File newest = null;
 203         for (int i = 0; i < 2; i++) {
 204             if (i == 0) {
 205                 basename = getTempDirectory() + Integer.toString(lvmid);
 206             } else {
 207                 basename = getTempDirectory() + Integer.toString(lvmid)
 208                            + Integer.toString(i);
 209             }
 210 
 211             f = new File(basename);
 212 
 213             if (f.exists() && f.isFile() && f.canRead()) {
 214                 long modTime = f.lastModified();
 215                 if (modTime >= newestTime) {
 216                     newestTime = modTime;
 217                     newest = f;
 218                 }
 219             }
 220         }
 221         return newest;
 222     }
 223 
 224     /**
 225      * Method to extract a local Java Virtual Machine Identifier from the
 226      * file name of the given File object.
 227      *
 228      * @param file A File object representing the name of a
 229      *             shared memory region for a target JVM
 230      * @return int - the local Java Virtual Machine Identifier for the target
 231      *               associated with the file
 232      * @throws java.lang.IllegalArgumentException Thrown if the file name
 233      *               does not conform to the expected pattern
 234      */
 235     public static int getLocalVmId(File file) {
 236         int lvmid = 0;
 237 
 238         try {
 239             // try 1.4.2 and later format first
 240             return Integer.parseInt(file.getName());
 241         } catch (NumberFormatException e) { }
 242 
 243         // now try the 1.4.1 format
 244         String name = file.getName();
 245         if (name.startsWith(dirNamePrefix)) {
 246             int first = name.indexOf('_');
 247             int last = name.lastIndexOf('_');
 248             try {
 249                 if (first == last) {
 250                     return Integer.parseInt(name.substring(first + 1));
 251                 } else {
 252                     return Integer.parseInt(name.substring(first + 1, last));
 253                 }
 254             } catch (NumberFormatException e) { }
 255         }
 256         throw new IllegalArgumentException("file name does not match pattern");
 257     }
 258 
 259     /**
 260      * Return the name of the temporary directory being searched for
 261      * HotSpot PerfData backing store files.
 262      * <p>
 263      * This method generally returns the value of the java.io.tmpdir
 264      * property. However, on some platforms it may return a different
 265      * directory, as the JVM implementation may store the PerfData backing
 266      * store files in a different directory for performance reasons.
 267      *
 268      * @return String - the name of the temporary directory.
 269      */
 270     public static String getTempDirectory() {
 271         return tmpDirName;
 272     }
 273 
 274     /**
 275      * Return the name of the temporary directory to be searched
 276      * for HotSpot PerfData backing store files for a given user.
 277      * <p>
 278      * This method generally returns the name of a subdirectory of
 279      * the directory indicated in the java.io.tmpdir property. However,
 280      * on some platforms it may return a different directory, as the
 281      * JVM implementation may store the PerfData backing store files
 282      * in a different directory for performance reasons.
 283      *
 284      * @return String - the name of the temporary directory.
 285      */
 286     public static String getTempDirectory(String user) {
 287         return tmpDirName + dirNamePrefix + user + File.separator;
 288     }
 289 
 290     /*
 291      * this static initializer would not be necessary if the
 292      * Solaris java.io.tmpdir property were set to /tmp by default
 293      */
 294     static {
 295         /*
 296          * Why is java.io.tmpdir on Solaris set to "/var/tmp/" when the
 297          * HotSpot JVM os:get_temp_path() method returns "/tmp/"
 298          *
 299          * Why do Solaris and Windows return a string with a trailing
 300          * file separator character where as Linix does not? (this change
 301          * seems to have occurred sometime during hopper beta)
 302          */
 303         String tmpdir = System.getProperty("java.io.tmpdir");
 304 
 305         if (tmpdir.compareTo("/var/tmp/") == 0) {
 306              /*
 307               * shared memory files are created in /tmp. Interestingly,
 308               * java.io.tmpdir is set to "/var/tmp/" on Solaris and Linux,
 309               * but os::get_temp_directory() is set to "/tmp/" on these
 310               * platforms. the java.io.logging packages also makes reference
 311               * to java.io.tmpdir.
 312               */
 313              tmpdir = "/tmp/";
 314         }
 315 
 316         /*
 317          * Assure that the string returned has a trailing File.separator
 318          * character. This check was added because the Linux implementation
 319          * changed such that the java.io.tmpdir string no longer terminates
 320          * with a File.separator character.
 321          */
 322         if (tmpdir.lastIndexOf(File.separator) != (tmpdir.length()-1)) {
 323             tmpdir = tmpdir + File.separator;
 324         }
 325         tmpDirName = tmpdir;
 326     }
 327 }