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