001: /*
002: * @(#)UnixFileSystem.java 1.15 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package java.io;
029:
030: import java.security.AccessController;
031: import sun.security.action.GetPropertyAction;
032:
033: class UnixFileSystem extends FileSystem {
034:
035: private final char slash;
036: private final char colon;
037: private final String javaHome;
038:
039: public UnixFileSystem() {
040: slash = ((String) AccessController
041: .doPrivileged(new GetPropertyAction("file.separator")))
042: .charAt(0);
043: colon = ((String) AccessController
044: .doPrivileged(new GetPropertyAction("path.separator")))
045: .charAt(0);
046: javaHome = (String) AccessController
047: .doPrivileged(new GetPropertyAction("java.home"));
048: }
049:
050: /* -- Normalization and construction -- */
051:
052: public char getSeparator() {
053: return slash;
054: }
055:
056: public char getPathSeparator() {
057: return colon;
058: }
059:
060: /* A normal Unix pathname contains no duplicate slashes and does not end
061: with a slash. It may be the empty string. */
062:
063: /* Normalize the given pathname, whose length is len, starting at the given
064: offset; everything before this offset is already normal. */
065: private String normalize(String pathname, int len, int off) {
066: if (len == 0)
067: return pathname;
068: int n = len;
069: while ((n > 0) && (pathname.charAt(n - 1) == '/'))
070: n--;
071: if (n == 0)
072: return "/";
073: StringBuffer sb = new StringBuffer(pathname.length());
074: if (off > 0)
075: sb.append(pathname.substring(0, off));
076: char prevChar = 0;
077: for (int i = off; i < n; i++) {
078: char c = pathname.charAt(i);
079: if ((prevChar == '/') && (c == '/'))
080: continue;
081: sb.append(c);
082: prevChar = c;
083: }
084: return sb.toString();
085: }
086:
087: /* Check that the given pathname is normal. If not, invoke the real
088: normalizer on the part of the pathname that requires normalization.
089: This way we iterate through the whole pathname string only once. */
090: public String normalize(String pathname) {
091: int n = pathname.length();
092: char prevChar = 0;
093: for (int i = 0; i < n; i++) {
094: char c = pathname.charAt(i);
095: if ((prevChar == '/') && (c == '/'))
096: return normalize(pathname, n, i - 1);
097: prevChar = c;
098: }
099: if (prevChar == '/')
100: return normalize(pathname, n, n - 1);
101: return pathname;
102: }
103:
104: public int prefixLength(String pathname) {
105: if (pathname.length() == 0)
106: return 0;
107: return (pathname.charAt(0) == '/') ? 1 : 0;
108: }
109:
110: public String resolve(String parent, String child) {
111: if (child.equals(""))
112: return parent;
113: if (child.charAt(0) == '/') {
114: if (parent.equals("/"))
115: return child;
116: return parent + child;
117: }
118: if (parent.equals("/"))
119: return parent + child;
120: return parent + '/' + child;
121: }
122:
123: public String getDefaultParent() {
124: return "/";
125: }
126:
127: public String fromURIPath(String path) {
128: String p = path;
129: if (p.endsWith("/") && (p.length() > 1)) {
130: // "/foo/" --> "/foo", but "/" --> "/"
131: p = p.substring(0, p.length() - 1);
132: }
133: return p;
134: }
135:
136: /* -- Path operations -- */
137:
138: public boolean isAbsolute(File f) {
139: return (f.getPrefixLength() != 0);
140: }
141:
142: public String resolve(File f) {
143: if (isAbsolute(f))
144: return f.getPath();
145: return resolve(System.getProperty("user.dir"), f.getPath());
146: }
147:
148: // Caches for canonicalization results to improve startup performance.
149: // The first cache handles repeated canonicalizations of the same path
150: // name. The prefix cache handles repeated canonicalizations within the
151: // same directory, and must not create results differing from the true
152: // canonicalization algorithm in canonicalize_md.c. For this reason the
153: // prefix cache is conservative and is not used for complex path names.
154:
155: private ExpiringCache cache = new ExpiringCache();
156:
157: // On Unix symlinks can jump anywhere in the file system, so we only
158: // treat prefixes in java.home as trusted and cacheable in the
159: // canonicalization algorithm
160:
161: private ExpiringCache javaHomePrefixCache = new ExpiringCache();
162:
163: public String canonicalize(String path) throws IOException {
164: if (!useCanonCaches) {
165: return canonicalize0(path);
166: } else {
167: String res = cache.get(path);
168: if (res == null) {
169: String dir = null;
170: String resDir = null;
171: if (useCanonPrefixCache) {
172: dir = parentOrNull(path);
173: if (dir != null) {
174: resDir = javaHomePrefixCache.get(dir);
175: if (resDir != null) {
176: // Hit only in prefix cache; full path is canonical
177: String filename = path.substring(1 + dir
178: .length());
179: res = resDir + slash + filename;
180: cache.put(dir + slash + filename, res);
181: }
182: }
183: }
184: if (res == null) {
185: res = canonicalize0(path);
186: cache.put(path, res);
187: if (useCanonPrefixCache && dir != null
188: && dir.startsWith(javaHome)) {
189: resDir = parentOrNull(res);
190: if (resDir != null
191: && resDir.startsWith(javaHome)) {
192: File f = new File(res);
193: if (f.exists() && !f.isDirectory()) {
194: javaHomePrefixCache.put(dir, resDir);
195: }
196: }
197: }
198: }
199: }
200: if (sun.misc.BuildFlags.qAssertsEnabled) {
201: assert canonicalize0(path).equals(res);
202: }
203: return res;
204: }
205: }
206:
207: private native String canonicalize0(String path) throws IOException;
208:
209: // Best-effort attempt to get parent of this path; used for
210: // optimization of filename canonicalization. This must return null for
211: // any cases where the code in canonicalize_md.c would throw an
212: // exception or otherwise deal with non-simple pathnames like handling
213: // of "." and "..". It may conservatively return null in other
214: // situations as well. Returning null will cause the underlying
215: // (expensive) canonicalization routine to be called.
216: static String parentOrNull(String path) {
217: if (path == null)
218: return null;
219: char sep = File.separatorChar;
220: int last = path.length() - 1;
221: int idx = last;
222: int adjacentDots = 0;
223: int nonDotCount = 0;
224: while (idx > 0) {
225: char c = path.charAt(idx);
226: if (c == '.') {
227: if (++adjacentDots >= 2) {
228: // Punt on pathnames containing . and ..
229: return null;
230: }
231: } else if (c == sep) {
232: if (adjacentDots == 1 && nonDotCount == 0) {
233: // Punt on pathnames containing . and ..
234: return null;
235: }
236: if (idx == 0 || idx >= last - 1
237: || path.charAt(idx - 1) == sep) {
238: // Punt on pathnames containing adjacent slashes
239: // toward the end
240: return null;
241: }
242: return path.substring(0, idx);
243: } else {
244: ++nonDotCount;
245: adjacentDots = 0;
246: }
247: --idx;
248: }
249: return null;
250: }
251:
252: /* -- Attribute accessors -- */
253:
254: public native int getBooleanAttributes0(File f);
255:
256: public int getBooleanAttributes(File f) {
257: int rv = getBooleanAttributes0(f);
258: String name = f.getName();
259: boolean hidden = (name.length() > 0) && (name.charAt(0) == '.');
260: return rv | (hidden ? BA_HIDDEN : 0);
261: }
262:
263: public native boolean checkAccess(File f, boolean write);
264:
265: public native long getLastModifiedTime(File f);
266:
267: public native long getLength(File f);
268:
269: /* -- File operations -- */
270:
271: public native boolean createFileExclusively(String path)
272: throws IOException;
273:
274: public boolean delete(File f) {
275: // Keep canonicalization caches in sync after file deletion
276: // and renaming operations. Could be more clever than this
277: // (i.e., only remove/update affected entries) but probably
278: // not worth it since these entries expire after 30 seconds
279: // anyway.
280: cache.clear();
281: javaHomePrefixCache.clear();
282: return delete0(f);
283: }
284:
285: private native boolean delete0(File f);
286:
287: public synchronized native boolean deleteOnExit(File f);
288:
289: public native String[] list(File f);
290:
291: public native boolean createDirectory(File f);
292:
293: public boolean rename(File f1, File f2) {
294: // Keep canonicalization caches in sync after file deletion
295: // and renaming operations. Could be more clever than this
296: // (i.e., only remove/update affected entries) but probably
297: // not worth it since these entries expire after 30 seconds
298: // anyway.
299: cache.clear();
300: javaHomePrefixCache.clear();
301: return rename0(f1, f2);
302: }
303:
304: private native boolean rename0(File f1, File f2);
305:
306: public native boolean setLastModifiedTime(File f, long time);
307:
308: public native boolean setReadOnly(File f);
309:
310: /* -- Filesystem interface -- */
311:
312: public File[] listRoots() {
313: try {
314: SecurityManager security = System.getSecurityManager();
315: if (security != null) {
316: security.checkRead("/");
317: }
318: return new File[] { new File("/") };
319: } catch (SecurityException x) {
320: return new File[0];
321: }
322: }
323:
324: /* -- Basic infrastructure -- */
325:
326: public int compare(File f1, File f2) {
327: return f1.getPath().compareTo(f2.getPath());
328: }
329:
330: public int hashCode(File f) {
331: return f.getPath().hashCode() ^ 1234321;
332: }
333:
334: private static native void initIDs();
335:
336: static {
337: initIDs();
338: }
339:
340: }
|