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 java.util.Properties;
29
30 import jdk.internal.util.StaticProperty;
31 import sun.security.action.GetPropertyAction;
32
33
34 class UnixFileSystem extends FileSystem {
35
36     private final char slash;
37     private final char colon;
38     private final String javaHome;
39     private final String userDir;
40
41     public UnixFileSystem() {
42         Properties props = GetPropertyAction.privilegedGetProperties();
43         slash = props.getProperty("file.separator").charAt(0);
44         colon = props.getProperty("path.separator").charAt(0);
45         javaHome = StaticProperty.javaHome();
46         userDir = StaticProperty.userDir();
47     }
48
49
50     /* -- Normalization and construction -- */
51
52     public char getSeparator() {
53         return slash;
54     }
55
56     public char getPathSeparator() {
57         return colon;
58     }
59
60     /* A normal Unix pathname contains no duplicate slashes and does not end
61        with a slash.  It may be the empty string. */

62
63     /* Normalize the given pathname, whose length is len, starting at the given
64        offset; everything before this offset is already normal. */

65     private String normalize(String pathname, int len, int off) {
66         if (len == 0) return pathname;
67         int n = len;
68         while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--;
69         if (n == 0) return "/";
70         StringBuilder sb = new StringBuilder(pathname.length());
71         if (off > 0) sb.append(pathname, 0, off);
72         char prevChar = 0;
73         for (int i = off; i < n; i++) {
74             char c = pathname.charAt(i);
75             if ((prevChar == '/') && (c == '/')) continue;
76             sb.append(c);
77             prevChar = c;
78         }
79         return sb.toString();
80     }
81
82     /* Check that the given pathname is normal.  If not, invoke the real
83        normalizer on the part of the pathname that requires normalization.
84        This way we iterate through the whole pathname string only once. */

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