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