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