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