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