• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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