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 }