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