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