1 /*
   2  * Copyright (c) 1998, 2010, 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 java.io;
  27 
  28 import java.security.AccessController;
  29 import sun.security.action.GetPropertyAction;
  30 
  31 
  32 class UnixFileSystem extends FileSystem {
  33 
  34     private final char slash;
  35     private final char colon;
  36     private final String javaHome;
  37 
  38     public UnixFileSystem() {
  39         slash = AccessController.doPrivileged(
  40             new GetPropertyAction("file.separator")).charAt(0);
  41         colon = AccessController.doPrivileged(
  42             new GetPropertyAction("path.separator")).charAt(0);
  43         javaHome = AccessController.doPrivileged(
  44             new GetPropertyAction("java.home"));
  45     }
  46 
  47 
  48     /* -- Normalization and construction -- */
  49 
  50     public char getSeparator() {
  51         return slash;
  52     }
  53 
  54     public char getPathSeparator() {
  55         return colon;
  56     }
  57 
  58     /* A normal Unix pathname contains no duplicate slashes and does not end
  59        with a slash.  It may be the empty string. */
  60 
  61     /* Normalize the given pathname, whose length is len, starting at the given
  62        offset; everything before this offset is already normal. */
  63     private String normalize(String pathname, int len, int off) {
  64         if (len == 0) return pathname;
  65         int n = len;
  66         while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--;
  67         if (n == 0) return "/";
  68         StringBuffer sb = new StringBuffer(pathname.length());
  69         if (off > 0) sb.append(pathname.substring(0, off));
  70         char prevChar = 0;
  71         for (int i = off; i < n; i++) {
  72             char c = pathname.charAt(i);
  73             if ((prevChar == '/') && (c == '/')) continue;
  74             sb.append(c);
  75             prevChar = c;
  76         }
  77         return sb.toString();
  78     }
  79 
  80     /* Check that the given pathname is normal.  If not, invoke the real
  81        normalizer on the part of the pathname that requires normalization.
  82        This way we iterate through the whole pathname string only once. */
  83     public String normalize(String pathname) {
  84         int n = pathname.length();
  85         char prevChar = 0;
  86         for (int i = 0; i < n; i++) {
  87             char c = pathname.charAt(i);
  88             if ((prevChar == '/') && (c == '/'))
  89                 return normalize(pathname, n, i - 1);
  90             prevChar = c;
  91         }
  92         if (prevChar == '/') return normalize(pathname, n, n - 1);
  93         return pathname;
  94     }
  95 
  96     public int prefixLength(String pathname) {
  97         if (pathname.length() == 0) return 0;
  98         return (pathname.charAt(0) == '/') ? 1 : 0;
  99     }
 100 
 101     public String resolve(String parent, String child) {
 102         if (child.equals("")) return parent;
 103         if (child.charAt(0) == '/') {
 104             if (parent.equals("/")) return child;
 105             return parent + child;
 106         }
 107         if (parent.equals("/")) return parent + child;
 108         return parent + '/' + child;
 109     }
 110 
 111     public String getDefaultParent() {
 112         return "/";
 113     }
 114 
 115     public String fromURIPath(String path) {
 116         String p = path;
 117         if (p.endsWith("/") && (p.length() > 1)) {
 118             // "/foo/" --> "/foo", but "/" --> "/"
 119             p = p.substring(0, p.length() - 1);
 120         }
 121         return p;
 122     }
 123 
 124 
 125     /* -- Path operations -- */
 126 
 127     public boolean isAbsolute(File f) {
 128         return (f.getPrefixLength() != 0);
 129     }
 130 
 131     public String resolve(File f) {
 132         if (isAbsolute(f)) return f.getPath();
 133         return resolve(System.getProperty("user.dir"), f.getPath());
 134     }
 135 
 136     // Caches for canonicalization results to improve startup performance.
 137     // The first cache handles repeated canonicalizations of the same path
 138     // name. The prefix cache handles repeated canonicalizations within the
 139     // same directory, and must not create results differing from the true
 140     // canonicalization algorithm in canonicalize_md.c. For this reason the
 141     // prefix cache is conservative and is not used for complex path names.
 142     private ExpiringCache cache = new ExpiringCache();
 143     // On Unix symlinks can jump anywhere in the file system, so we only
 144     // treat prefixes in java.home as trusted and cacheable in the
 145     // canonicalization algorithm
 146     private ExpiringCache javaHomePrefixCache = new ExpiringCache();
 147 
 148     public String canonicalize(String path) throws IOException {
 149         if (!useCanonCaches) {
 150             return canonicalize0(path);
 151         } else {
 152             String res = cache.get(path);
 153             if (res == null) {
 154                 String dir = null;
 155                 String resDir = null;
 156                 if (useCanonPrefixCache) {
 157                     // Note that this can cause symlinks that should
 158                     // be resolved to a destination directory to be
 159                     // resolved to the directory they're contained in
 160                     dir = parentOrNull(path);
 161                     if (dir != null) {
 162                         resDir = javaHomePrefixCache.get(dir);
 163                         if (resDir != null) {
 164                             // Hit only in prefix cache; full path is canonical
 165                             String filename = path.substring(1 + dir.length());
 166                             res = resDir + slash + filename;
 167                             cache.put(dir + slash + filename, res);
 168                         }
 169                     }
 170                 }
 171                 if (res == null) {
 172                     res = canonicalize0(path);
 173                     cache.put(path, res);
 174                     if (useCanonPrefixCache &&
 175                         dir != null && dir.startsWith(javaHome)) {
 176                         resDir = parentOrNull(res);
 177                         // Note that we don't allow a resolved symlink
 178                         // to elsewhere in java.home to pollute the
 179                         // prefix cache (java.home prefix cache could
 180                         // just as easily be a set at this point)
 181                         if (resDir != null && resDir.equals(dir)) {
 182                             File f = new File(res);
 183                             if (f.exists() && !f.isDirectory()) {
 184                                 javaHomePrefixCache.put(dir, resDir);
 185                             }
 186                         }
 187                     }
 188                 }
 189             }
 190             return res;
 191         }
 192     }
 193     private native String canonicalize0(String path) throws IOException;
 194     // Best-effort attempt to get parent of this path; used for
 195     // optimization of filename canonicalization. This must return null for
 196     // any cases where the code in canonicalize_md.c would throw an
 197     // exception or otherwise deal with non-simple pathnames like handling
 198     // of "." and "..". It may conservatively return null in other
 199     // situations as well. Returning null will cause the underlying
 200     // (expensive) canonicalization routine to be called.
 201     static String parentOrNull(String path) {
 202         if (path == null) return null;
 203         char sep = File.separatorChar;
 204         int last = path.length() - 1;
 205         int idx = last;
 206         int adjacentDots = 0;
 207         int nonDotCount = 0;
 208         while (idx > 0) {
 209             char c = path.charAt(idx);
 210             if (c == '.') {
 211                 if (++adjacentDots >= 2) {
 212                     // Punt on pathnames containing . and ..
 213                     return null;
 214                 }
 215             } else if (c == sep) {
 216                 if (adjacentDots == 1 && nonDotCount == 0) {
 217                     // Punt on pathnames containing . and ..
 218                     return null;
 219                 }
 220                 if (idx == 0 ||
 221                     idx >= last - 1 ||
 222                     path.charAt(idx - 1) == sep) {
 223                     // Punt on pathnames containing adjacent slashes
 224                     // toward the end
 225                     return null;
 226                 }
 227                 return path.substring(0, idx);
 228             } else {
 229                 ++nonDotCount;
 230                 adjacentDots = 0;
 231             }
 232             --idx;
 233         }
 234         return null;
 235     }
 236 
 237     /* -- Attribute accessors -- */
 238 
 239     public native int getBooleanAttributes0(File f);
 240 
 241     public int getBooleanAttributes(File f) {
 242         int rv = getBooleanAttributes0(f);
 243         String name = f.getName();
 244         boolean hidden = (name.length() > 0) && (name.charAt(0) == '.');
 245         return rv | (hidden ? BA_HIDDEN : 0);
 246     }
 247 
 248     public native boolean checkAccess(File f, int access);
 249     public native long getLastModifiedTime(File f);
 250     public native long getLength(File f);
 251     public native boolean setPermission(File f, int access, boolean enable, boolean owneronly);
 252 
 253     /* -- File operations -- */
 254 
 255     public native boolean createFileExclusively(String path)
 256         throws IOException;
 257     public boolean delete(File f) {
 258         // Keep canonicalization caches in sync after file deletion
 259         // and renaming operations. Could be more clever than this
 260         // (i.e., only remove/update affected entries) but probably
 261         // not worth it since these entries expire after 30 seconds
 262         // anyway.
 263         cache.clear();
 264         javaHomePrefixCache.clear();
 265         return delete0(f);
 266     }
 267     private native boolean delete0(File f);
 268     public native String[] list(File f);
 269     public native boolean createDirectory(File f);
 270     public boolean rename(File f1, File f2) {
 271         // Keep canonicalization caches in sync after file deletion
 272         // and renaming operations. Could be more clever than this
 273         // (i.e., only remove/update affected entries) but probably
 274         // not worth it since these entries expire after 30 seconds
 275         // anyway.
 276         cache.clear();
 277         javaHomePrefixCache.clear();
 278         return rename0(f1, f2);
 279     }
 280     private native boolean rename0(File f1, File f2);
 281     public native boolean setLastModifiedTime(File f, long time);
 282     public native boolean setReadOnly(File f);
 283 
 284 
 285     /* -- Filesystem interface -- */
 286 
 287     public File[] listRoots() {
 288         try {
 289             SecurityManager security = System.getSecurityManager();
 290             if (security != null) {
 291                 security.checkRead("/");
 292             }
 293             return new File[] { new File("/") };
 294         } catch (SecurityException x) {
 295             return new File[0];
 296         }
 297     }
 298 
 299     /* -- Disk usage -- */
 300     public native long getSpace(File f, int t);
 301 
 302     /* -- Basic infrastructure -- */
 303 
 304     public int compare(File f1, File f2) {
 305         return f1.getPath().compareTo(f2.getPath());
 306     }
 307 
 308     public int hashCode(File f) {
 309         return f.getPath().hashCode() ^ 1234321;
 310     }
 311 
 312 
 313     private static native void initIDs();
 314 
 315     static {
 316         initIDs();
 317     }
 318 
 319 }