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