UnixFileSystem.java revision 51a43d9402a355b24c0445df615d8f4975f04fc3
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
26package java.io;
27
28import java.security.AccessController;
29import sun.security.action.GetPropertyAction;
30
31
32class UnixFileSystem extends FileSystem {
33
34    private final char slash;
35    private final char colon;
36    private final String javaHome;
37
38    public UnixFileSystem() {
39        slash = AccessController.doPrivileged(
40            new GetPropertyAction("file.separator")).charAt(0);
41        colon = AccessController.doPrivileged(
42            new GetPropertyAction("path.separator")).charAt(0);
43        javaHome = AccessController.doPrivileged(
44            new GetPropertyAction("java.home"));
45    }
46
47
48    /* -- Normalization and construction -- */
49
50    public char getSeparator() {
51        return slash;
52    }
53
54    public char getPathSeparator() {
55        return colon;
56    }
57
58    /* A normal Unix pathname contains no duplicate slashes and does not end
59       with a slash.  It may be the empty string. */
60
61    /* Normalize the given pathname, whose length is len, starting at the given
62       offset; everything before this offset is already normal. */
63    private String normalize(String pathname, int len, int off) {
64        if (len == 0) return pathname;
65        int n = len;
66        while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--;
67        if (n == 0) return "/";
68        StringBuffer sb = new StringBuffer(pathname.length());
69        if (off > 0) sb.append(pathname.substring(0, off));
70        char prevChar = 0;
71        for (int i = off; i < n; i++) {
72            char c = pathname.charAt(i);
73            if ((prevChar == '/') && (c == '/')) continue;
74            sb.append(c);
75            prevChar = c;
76        }
77        return sb.toString();
78    }
79
80    /* Check that the given pathname is normal.  If not, invoke the real
81       normalizer on the part of the pathname that requires normalization.
82       This way we iterate through the whole pathname string only once. */
83    public String normalize(String pathname) {
84        int n = pathname.length();
85        char prevChar = 0;
86        for (int i = 0; i < n; i++) {
87            char c = pathname.charAt(i);
88            if ((prevChar == '/') && (c == '/'))
89                return normalize(pathname, n, i - 1);
90            prevChar = c;
91        }
92        if (prevChar == '/') return normalize(pathname, n, n - 1);
93        return pathname;
94    }
95
96    public int prefixLength(String pathname) {
97        if (pathname.length() == 0) return 0;
98        return (pathname.charAt(0) == '/') ? 1 : 0;
99    }
100
101    public String resolve(String parent, String child) {
102        if (child.equals("")) return parent;
103        if (child.charAt(0) == '/') {
104            if (parent.equals("/")) return child;
105            return parent + child;
106        }
107        if (parent.equals("/")) return parent + child;
108        return parent + '/' + child;
109    }
110
111    public String getDefaultParent() {
112        return "/";
113    }
114
115    public String fromURIPath(String path) {
116        String p = path;
117        if (p.endsWith("/") && (p.length() > 1)) {
118            // "/foo/" --> "/foo", but "/" --> "/"
119            p = p.substring(0, p.length() - 1);
120        }
121        return p;
122    }
123
124
125    /* -- Path operations -- */
126
127    public boolean isAbsolute(File f) {
128        return (f.getPrefixLength() != 0);
129    }
130
131    public String resolve(File f) {
132        if (isAbsolute(f)) return f.getPath();
133        return resolve(System.getProperty("user.dir"), f.getPath());
134    }
135
136    // Caches for canonicalization results to improve startup performance.
137    // The first cache handles repeated canonicalizations of the same path
138    // name. The prefix cache handles repeated canonicalizations within the
139    // same directory, and must not create results differing from the true
140    // canonicalization algorithm in canonicalize_md.c. For this reason the
141    // prefix cache is conservative and is not used for complex path names.
142    private ExpiringCache cache = new ExpiringCache();
143    // On Unix symlinks can jump anywhere in the file system, so we only
144    // treat prefixes in java.home as trusted and cacheable in the
145    // canonicalization algorithm
146    private ExpiringCache javaHomePrefixCache = new ExpiringCache();
147
148    public String canonicalize(String path) throws IOException {
149        if (!useCanonCaches) {
150            return canonicalize0(path);
151        } else {
152            String res = cache.get(path);
153            if (res == null) {
154                String dir = null;
155                String resDir = null;
156                if (useCanonPrefixCache) {
157                    // Note that this can cause symlinks that should
158                    // be resolved to a destination directory to be
159                    // resolved to the directory they're contained in
160                    dir = parentOrNull(path);
161                    if (dir != null) {
162                        resDir = javaHomePrefixCache.get(dir);
163                        if (resDir != null) {
164                            // Hit only in prefix cache; full path is canonical
165                            String filename = path.substring(1 + dir.length());
166                            res = resDir + slash + filename;
167                            cache.put(dir + slash + filename, res);
168                        }
169                    }
170                }
171                if (res == null) {
172                    res = canonicalize0(path);
173                    cache.put(path, res);
174                    if (useCanonPrefixCache &&
175                        dir != null && dir.startsWith(javaHome)) {
176                        resDir = parentOrNull(res);
177                        // Note that we don't allow a resolved symlink
178                        // to elsewhere in java.home to pollute the
179                        // prefix cache (java.home prefix cache could
180                        // just as easily be a set at this point)
181                        if (resDir != null && resDir.equals(dir)) {
182                            File f = new File(res);
183                            if (f.exists() && !f.isDirectory()) {
184                                javaHomePrefixCache.put(dir, resDir);
185                            }
186                        }
187                    }
188                }
189            }
190            return res;
191        }
192    }
193    private native String canonicalize0(String path) throws IOException;
194    // Best-effort attempt to get parent of this path; used for
195    // optimization of filename canonicalization. This must return null for
196    // any cases where the code in canonicalize_md.c would throw an
197    // exception or otherwise deal with non-simple pathnames like handling
198    // of "." and "..". It may conservatively return null in other
199    // situations as well. Returning null will cause the underlying
200    // (expensive) canonicalization routine to be called.
201    static String parentOrNull(String path) {
202        if (path == null) return null;
203        char sep = File.separatorChar;
204        int last = path.length() - 1;
205        int idx = last;
206        int adjacentDots = 0;
207        int nonDotCount = 0;
208        while (idx > 0) {
209            char c = path.charAt(idx);
210            if (c == '.') {
211                if (++adjacentDots >= 2) {
212                    // Punt on pathnames containing . and ..
213                    return null;
214                }
215            } else if (c == sep) {
216                if (adjacentDots == 1 && nonDotCount == 0) {
217                    // Punt on pathnames containing . and ..
218                    return null;
219                }
220                if (idx == 0 ||
221                    idx >= last - 1 ||
222                    path.charAt(idx - 1) == sep) {
223                    // Punt on pathnames containing adjacent slashes
224                    // toward the end
225                    return null;
226                }
227                return path.substring(0, idx);
228            } else {
229                ++nonDotCount;
230                adjacentDots = 0;
231            }
232            --idx;
233        }
234        return null;
235    }
236
237    /* -- Attribute accessors -- */
238
239    /* ----- BEGIN android -----
240    public native int getBooleanAttributes0(File f);*/
241    public native int getBooleanAttributes0(String abspath);
242
243    public int getBooleanAttributes(File f) {
244        /* ----- BEGIN android -----
245        int rv = getBooleanAttributes0(f);*/
246        int rv = getBooleanAttributes0(f.getPath());
247        // ----- END android -----
248        String name = f.getName();
249        boolean hidden = (name.length() > 0) && (name.charAt(0) == '.');
250        return rv | (hidden ? BA_HIDDEN : 0);
251    }
252
253    public native boolean checkAccess(File f, int access);
254    public native long getLastModifiedTime(File f);
255    public native long getLength(File f);
256    public native boolean setPermission(File f, int access, boolean enable, boolean owneronly);
257
258    /* -- File operations -- */
259
260    public native boolean createFileExclusively(String path)
261        throws IOException;
262    public boolean delete(File f) {
263        // Keep canonicalization caches in sync after file deletion
264        // and renaming operations. Could be more clever than this
265        // (i.e., only remove/update affected entries) but probably
266        // not worth it since these entries expire after 30 seconds
267        // anyway.
268        cache.clear();
269        javaHomePrefixCache.clear();
270        return delete0(f);
271    }
272    private native boolean delete0(File f);
273    public native String[] list(File f);
274    public native boolean createDirectory(File f);
275    public boolean rename(File f1, File f2) {
276        // Keep canonicalization caches in sync after file deletion
277        // and renaming operations. Could be more clever than this
278        // (i.e., only remove/update affected entries) but probably
279        // not worth it since these entries expire after 30 seconds
280        // anyway.
281        cache.clear();
282        javaHomePrefixCache.clear();
283        return rename0(f1, f2);
284    }
285    private native boolean rename0(File f1, File f2);
286    public native boolean setLastModifiedTime(File f, long time);
287    public native boolean setReadOnly(File f);
288
289
290    /* -- Filesystem interface -- */
291
292    public File[] listRoots() {
293        try {
294            SecurityManager security = System.getSecurityManager();
295            if (security != null) {
296                security.checkRead("/");
297            }
298            return new File[] { new File("/") };
299        } catch (SecurityException x) {
300            return new File[0];
301        }
302    }
303
304    /* -- Disk usage -- */
305    public native long getSpace(File f, int t);
306
307    /* -- Basic infrastructure -- */
308
309    public int compare(File f1, File f2) {
310        return f1.getPath().compareTo(f2.getPath());
311    }
312
313    public int hashCode(File f) {
314        return f.getPath().hashCode() ^ 1234321;
315    }
316
317
318    private static native void initIDs();
319
320    static {
321        initIDs();
322    }
323
324}
325