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 static java.io.File.CANONICALIZE_PARENT_OF_ROOT_DIR; 29 30 import android.compat.Compatibility; 31 import android.compat.annotation.ChangeId; 32 import android.compat.annotation.EnabledSince; 33 import android.system.ErrnoException; 34 import android.system.OsConstants; 35 36 import dalvik.annotation.compat.VersionCodes; 37 import dalvik.system.BlockGuard; 38 import dalvik.system.VMRuntime; 39 40 import libcore.io.Libcore; 41 42 import java.util.Properties; 43 44 import jdk.internal.util.StaticProperty; 45 import sun.security.action.GetPropertyAction; 46 47 48 class UnixFileSystem extends FileSystem { 49 50 private final char slash; 51 private final char colon; 52 private final String javaHome; 53 private final String userDir; 54 UnixFileSystem()55 public UnixFileSystem() { 56 Properties props = GetPropertyAction.privilegedGetProperties(); 57 slash = props.getProperty("file.separator").charAt(0); 58 colon = props.getProperty("path.separator").charAt(0); 59 javaHome = StaticProperty.javaHome(); 60 userDir = StaticProperty.userDir(); 61 } 62 63 64 /* -- Normalization and construction -- */ 65 getSeparator()66 public char getSeparator() { 67 return slash; 68 } 69 getPathSeparator()70 public char getPathSeparator() { 71 return colon; 72 } 73 74 /* 75 * A normal Unix pathname does not contain consecutive slashes and does not end 76 * with a slash. The empty string and "/" are special cases that are also 77 * considered normal. 78 */ 79 80 // BEGIN Android-removed: Dead code. 81 /* 82 /* Normalize the given pathname, whose length is len, starting at the given 83 offset; everything before this offset is already normal. * 84 private String normalize(String pathname, int len, int off) { 85 if (len == 0) return pathname; 86 int n = len; 87 while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--; 88 if (n == 0) return "/"; 89 StringBuilder sb = new StringBuilder(pathname.length()); 90 if (off > 0) sb.append(pathname, 0, off); 91 char prevChar = 0; 92 for (int i = off; i < n; i++) { 93 char c = pathname.charAt(i); 94 if ((prevChar == '/') && (c == '/')) continue; 95 sb.append(c); 96 prevChar = c; 97 } 98 return sb.toString(); 99 } 100 */ 101 // END Android-removed: Dead code. 102 103 /* Check that the given pathname is normal. If not, invoke the real 104 normalizer on the part of the pathname that requires normalization. 105 This way we iterate through the whole pathname string only once. */ normalize(String pathname)106 public String normalize(String pathname) { 107 int n = pathname.length(); 108 char[] normalized = pathname.toCharArray(); 109 int index = 0; 110 char prevChar = 0; 111 for (int i = 0; i < n; i++) { 112 char current = normalized[i]; 113 // Remove duplicate slashes. 114 if (!(current == '/' && prevChar == '/')) { 115 normalized[index++] = current; 116 } 117 118 prevChar = current; 119 } 120 121 // Omit the trailing slash, except when pathname == "/". 122 if (prevChar == '/' && n > 1) { 123 index--; 124 } 125 126 return (index != n) ? new String(normalized, 0, index) : pathname; 127 } 128 prefixLength(String pathname)129 public int prefixLength(String pathname) { 130 if (pathname.isEmpty()) return 0; 131 return (pathname.charAt(0) == '/') ? 1 : 0; 132 } 133 134 // Invariant: Both |parent| and |child| are normalized paths. resolve(String parent, String child)135 public String resolve(String parent, String child) { 136 if (child.isEmpty() || child.equals("/")) { 137 return parent; 138 } 139 140 if (child.charAt(0) == '/') { 141 if (parent.equals("/")) return child; 142 return parent + child; 143 } 144 145 if (parent.equals("/")) return parent + child; 146 return parent + '/' + child; 147 } 148 getDefaultParent()149 public String getDefaultParent() { 150 return "/"; 151 } 152 fromURIPath(String path)153 public String fromURIPath(String path) { 154 String p = path; 155 if (p.endsWith("/") && (p.length() > 1)) { 156 // "/foo/" --> "/foo", but "/" --> "/" 157 p = p.substring(0, p.length() - 1); 158 } 159 return p; 160 } 161 162 163 /* -- Path operations -- */ 164 isAbsolute(File f)165 public boolean isAbsolute(File f) { 166 return (f.getPrefixLength() != 0); 167 } 168 resolve(File f)169 public String resolve(File f) { 170 if (isAbsolute(f)) return f.getPath(); 171 SecurityManager sm = System.getSecurityManager(); 172 if (sm != null) { 173 sm.checkPropertyAccess("user.dir"); 174 } 175 return resolve(userDir, f.getPath()); 176 } 177 178 // Caches for canonicalization results to improve startup performance. 179 // The first cache handles repeated canonicalizations of the same path 180 // name. The prefix cache handles repeated canonicalizations within the 181 // same directory, and must not create results differing from the true 182 // canonicalization algorithm in canonicalize_md.c. For this reason the 183 // prefix cache is conservative and is not used for complex path names. 184 private ExpiringCache cache = new ExpiringCache(); 185 // On Unix symlinks can jump anywhere in the file system, so we only 186 // treat prefixes in java.home as trusted and cacheable in the 187 // canonicalization algorithm 188 private ExpiringCache javaHomePrefixCache = new ExpiringCache(); 189 canonicalize(String path)190 public String canonicalize(String path) throws IOException { 191 if (!useCanonCaches) { 192 return canonicalize0(path); 193 } else { 194 String res = cache.get(path); 195 if (res == null) { 196 String dir = null; 197 String resDir = null; 198 if (useCanonPrefixCache) { 199 // Note that this can cause symlinks that should 200 // be resolved to a destination directory to be 201 // resolved to the directory they're contained in 202 dir = parentOrNull(path); 203 if (dir != null) { 204 resDir = javaHomePrefixCache.get(dir); 205 if (resDir != null) { 206 // Hit only in prefix cache; full path is canonical 207 String filename = path.substring(1 + dir.length()); 208 res = resDir + slash + filename; 209 cache.put(dir + slash + filename, res); 210 } 211 } 212 } 213 if (res == null) { 214 // BEGIN Android-added: BlockGuard support. 215 BlockGuard.getThreadPolicy().onReadFromDisk(); 216 BlockGuard.getVmPolicy().onPathAccess(path); 217 // END Android-added: BlockGuard support. 218 res = canonicalize0(path); 219 cache.put(path, res); 220 if (useCanonPrefixCache && 221 dir != null && dir.startsWith(javaHome)) { 222 resDir = parentOrNull(res); 223 // Note that we don't allow a resolved symlink 224 // to elsewhere in java.home to pollute the 225 // prefix cache (java.home prefix cache could 226 // just as easily be a set at this point) 227 if (resDir != null && resDir.equals(dir)) { 228 File f = new File(res); 229 if (f.exists() && !f.isDirectory()) { 230 javaHomePrefixCache.put(dir, resDir); 231 } 232 } 233 } 234 } 235 } 236 return res; 237 } 238 } 239 240 // BEGIN Android-changed: Remove parent directory /.. at the rootfs. http://b/312399441 canonicalize0(String path)241 private String canonicalize0(String path) throws IOException { 242 boolean isAtLeastTargetSdk35 = 243 VMRuntime.getSdkVersion() >= VersionCodes.VANILLA_ICE_CREAM && 244 Compatibility.isChangeEnabled(CANONICALIZE_PARENT_OF_ROOT_DIR); 245 return canonicalize0(path, isAtLeastTargetSdk35); 246 } 247 canonicalize0(String path, boolean isAtLeastTargetSdk35)248 private native String canonicalize0(String path, boolean isAtLeastTargetSdk35) 249 throws IOException; 250 // END Android-changed: Remove parent directory /.. at the rootfs. http://b/312399441 251 252 // Best-effort attempt to get parent of this path; used for 253 // optimization of filename canonicalization. This must return null for 254 // any cases where the code in canonicalize_md.c would throw an 255 // exception or otherwise deal with non-simple pathnames like handling 256 // of "." and "..". It may conservatively return null in other 257 // situations as well. Returning null will cause the underlying 258 // (expensive) canonicalization routine to be called. parentOrNull(String path)259 static String parentOrNull(String path) { 260 if (path == null) return null; 261 char sep = File.separatorChar; 262 int last = path.length() - 1; 263 int idx = last; 264 int adjacentDots = 0; 265 int nonDotCount = 0; 266 while (idx > 0) { 267 char c = path.charAt(idx); 268 if (c == '.') { 269 if (++adjacentDots >= 2) { 270 // Punt on pathnames containing . and .. 271 return null; 272 } 273 } else if (c == sep) { 274 if (adjacentDots == 1 && nonDotCount == 0) { 275 // Punt on pathnames containing . and .. 276 return null; 277 } 278 if (idx == 0 || 279 idx >= last - 1 || 280 path.charAt(idx - 1) == sep) { 281 // Punt on pathnames containing adjacent slashes 282 // toward the end 283 return null; 284 } 285 return path.substring(0, idx); 286 } else { 287 ++nonDotCount; 288 adjacentDots = 0; 289 } 290 --idx; 291 } 292 return null; 293 } 294 295 /* -- Attribute accessors -- */ 296 getBooleanAttributes0(String abspath)297 private native int getBooleanAttributes0(String abspath); 298 getBooleanAttributes(File f)299 public int getBooleanAttributes(File f) { 300 // BEGIN Android-added: BlockGuard support. 301 BlockGuard.getThreadPolicy().onReadFromDisk(); 302 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 303 // END Android-added: BlockGuard support. 304 305 int rv = getBooleanAttributes0(f.getPath()); 306 String name = f.getName(); 307 boolean hidden = !name.isEmpty() && name.charAt(0) == '.'; 308 return rv | (hidden ? BA_HIDDEN : 0); 309 } 310 311 // Android-changed: Access files through common interface. checkAccess(File f, int access)312 public boolean checkAccess(File f, int access) { 313 final int mode; 314 switch (access) { 315 case FileSystem.ACCESS_OK: 316 mode = OsConstants.F_OK; 317 break; 318 case FileSystem.ACCESS_READ: 319 mode = OsConstants.R_OK; 320 break; 321 case FileSystem.ACCESS_WRITE: 322 mode = OsConstants.W_OK; 323 break; 324 case FileSystem.ACCESS_EXECUTE: 325 mode = OsConstants.X_OK; 326 break; 327 default: 328 throw new IllegalArgumentException("Bad access mode: " + access); 329 } 330 331 try { 332 return Libcore.os.access(f.getPath(), mode); 333 } catch (ErrnoException e) { 334 return false; 335 } 336 } 337 338 // Android-changed: Add method to intercept native method call; BlockGuard support. getLastModifiedTime(File f)339 public long getLastModifiedTime(File f) { 340 BlockGuard.getThreadPolicy().onReadFromDisk(); 341 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 342 return getLastModifiedTime0(f); 343 } getLastModifiedTime0(File f)344 private native long getLastModifiedTime0(File f); 345 346 // Android-changed: Access files through common interface. getLength(File f)347 public long getLength(File f) { 348 try { 349 return Libcore.os.stat(f.getPath()).st_size; 350 } catch (ErrnoException e) { 351 return 0; 352 } 353 } 354 355 // Android-changed: Add method to intercept native method call; BlockGuard support. setPermission(File f, int access, boolean enable, boolean owneronly)356 public boolean setPermission(File f, int access, boolean enable, boolean owneronly) { 357 BlockGuard.getThreadPolicy().onWriteToDisk(); 358 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 359 return setPermission0(f, access, enable, owneronly); 360 } setPermission0(File f, int access, boolean enable, boolean owneronly)361 private native boolean setPermission0(File f, int access, boolean enable, boolean owneronly); 362 363 /* -- File operations -- */ 364 // Android-changed: Add method to intercept native method call; BlockGuard support. createFileExclusively(String path)365 public boolean createFileExclusively(String path) throws IOException { 366 BlockGuard.getThreadPolicy().onWriteToDisk(); 367 BlockGuard.getVmPolicy().onPathAccess(path); 368 return createFileExclusively0(path); 369 } createFileExclusively0(String path)370 private native boolean createFileExclusively0(String path) throws IOException; 371 delete(File f)372 public boolean delete(File f) { 373 // Keep canonicalization caches in sync after file deletion 374 // and renaming operations. Could be more clever than this 375 // (i.e., only remove/update affected entries) but probably 376 // not worth it since these entries expire after 30 seconds 377 // anyway. 378 cache.clear(); 379 javaHomePrefixCache.clear(); 380 // BEGIN Android-changed: Access files through common interface. 381 try { 382 Libcore.os.remove(f.getPath()); 383 return true; 384 } catch (ErrnoException e) { 385 return false; 386 } 387 // END Android-changed: Access files through common interface. 388 } 389 390 // Android-removed: Access files through common interface. 391 // private native boolean delete0(File f); 392 393 // Android-changed: Add method to intercept native method call; BlockGuard support. list(File f)394 public String[] list(File f) { 395 BlockGuard.getThreadPolicy().onReadFromDisk(); 396 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 397 return list0(f); 398 } list0(File f)399 private native String[] list0(File f); 400 401 // Android-changed: Add method to intercept native method call; BlockGuard support. createDirectory(File f)402 public boolean createDirectory(File f) { 403 BlockGuard.getThreadPolicy().onWriteToDisk(); 404 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 405 return createDirectory0(f); 406 } createDirectory0(File f)407 private native boolean createDirectory0(File f); 408 rename(File f1, File f2)409 public boolean rename(File f1, File f2) { 410 // Keep canonicalization caches in sync after file deletion 411 // and renaming operations. Could be more clever than this 412 // (i.e., only remove/update affected entries) but probably 413 // not worth it since these entries expire after 30 seconds 414 // anyway. 415 cache.clear(); 416 javaHomePrefixCache.clear(); 417 // BEGIN Android-changed: Access files through common interface. 418 try { 419 Libcore.os.rename(f1.getPath(), f2.getPath()); 420 return true; 421 } catch (ErrnoException e) { 422 return false; 423 } 424 // END Android-changed: Access files through common interface. 425 } 426 427 // Android-removed: Access files through common interface. 428 // private native boolean rename0(File f1, File f2); 429 430 // Android-changed: Add method to intercept native method call; BlockGuard support. setLastModifiedTime(File f, long time)431 public boolean setLastModifiedTime(File f, long time) { 432 BlockGuard.getThreadPolicy().onWriteToDisk(); 433 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 434 return setLastModifiedTime0(f, time); 435 } setLastModifiedTime0(File f, long time)436 private native boolean setLastModifiedTime0(File f, long time); 437 438 // Android-changed: Add method to intercept native method call; BlockGuard support. setReadOnly(File f)439 public boolean setReadOnly(File f) { 440 BlockGuard.getThreadPolicy().onWriteToDisk(); 441 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 442 return setReadOnly0(f); 443 } setReadOnly0(File f)444 private native boolean setReadOnly0(File f); 445 446 447 /* -- Filesystem interface -- */ 448 listRoots()449 public File[] listRoots() { 450 try { 451 SecurityManager security = System.getSecurityManager(); 452 if (security != null) { 453 security.checkRead("/"); 454 } 455 return new File[] { new File("/") }; 456 } catch (SecurityException x) { 457 return new File[0]; 458 } 459 } 460 461 /* -- Disk usage -- */ 462 // Android-changed: Add method to intercept native method call; BlockGuard support. getSpace(File f, int t)463 public long getSpace(File f, int t) { 464 BlockGuard.getThreadPolicy().onReadFromDisk(); 465 BlockGuard.getVmPolicy().onPathAccess(f.getPath()); 466 467 return getSpace0(f, t); 468 } getSpace0(File f, int t)469 private native long getSpace0(File f, int t); 470 471 /* -- Basic infrastructure -- */ 472 getNameMax0(String path)473 private native long getNameMax0(String path); 474 getNameMax(String path)475 public int getNameMax(String path) { 476 long nameMax = getNameMax0(path); 477 if (nameMax > Integer.MAX_VALUE) { 478 nameMax = Integer.MAX_VALUE; 479 } 480 return (int)nameMax; 481 } 482 compare(File f1, File f2)483 public int compare(File f1, File f2) { 484 return f1.getPath().compareTo(f2.getPath()); 485 } 486 hashCode(File f)487 public int hashCode(File f) { 488 return f.getPath().hashCode() ^ 1234321; 489 } 490 491 initIDs()492 private static native void initIDs(); 493 494 static { initIDs()495 initIDs(); 496 } 497 498 } 499