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 }