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