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