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