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