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