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 == null) return 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