blob: e6c37bc4ac97dcd7c6ba9b58d9daaeb7b910e7f5 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package java.io;
27
28import java.security.AccessController;
29import java.util.Locale;
30import sun.security.action.GetPropertyAction;
31
32
33class Win32FileSystem extends FileSystem {
34
35 private final char slash;
36 private final char altSlash;
37 private final char semicolon;
38
39 public Win32FileSystem() {
40 slash = AccessController.doPrivileged(
41 new GetPropertyAction("file.separator")).charAt(0);
42 semicolon = AccessController.doPrivileged(
43 new GetPropertyAction("path.separator")).charAt(0);
44 altSlash = (this.slash == '\\') ? '/' : '\\';
45 }
46
47 private boolean isSlash(char c) {
48 return (c == '\\') || (c == '/');
49 }
50
51 private boolean isLetter(char c) {
52 return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
53 }
54
55 private String slashify(String p) {
56 if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p;
57 else return p;
58 }
59
60
61 /* -- Normalization and construction -- */
62
63 public char getSeparator() {
64 return slash;
65 }
66
67 public char getPathSeparator() {
68 return semicolon;
69 }
70
71 /* A normal Win32 pathname contains no duplicate slashes, except possibly
72 for a UNC prefix, and does not end with a slash. It may be the empty
73 string. Normalized Win32 pathnames have the convenient property that
74 the length of the prefix almost uniquely identifies the type of the path
75 and whether it is absolute or relative:
76
77 0 relative to both drive and directory
78 1 drive-relative (begins with '\\')
79 2 absolute UNC (if first char is '\\'),
80 else directory-relative (has form "z:foo")
81 3 absolute local pathname (begins with "z:\\")
82 */
83
84 private int normalizePrefix(String path, int len, StringBuffer sb) {
85 int src = 0;
86 while ((src < len) && isSlash(path.charAt(src))) src++;
87 char c;
88 if ((len - src >= 2)
89 && isLetter(c = path.charAt(src))
90 && path.charAt(src + 1) == ':') {
91 /* Remove leading slashes if followed by drive specifier.
92 This hack is necessary to support file URLs containing drive
93 specifiers (e.g., "file://c:/path"). As a side effect,
94 "/c:/path" can be used as an alternative to "c:/path". */
95 sb.append(c);
96 sb.append(':');
97 src += 2;
98 } else {
99 src = 0;
100 if ((len >= 2)
101 && isSlash(path.charAt(0))
102 && isSlash(path.charAt(1))) {
103 /* UNC pathname: Retain first slash; leave src pointed at
104 second slash so that further slashes will be collapsed
105 into the second slash. The result will be a pathname
106 beginning with "\\\\" followed (most likely) by a host
107 name. */
108 src = 1;
109 sb.append(slash);
110 }
111 }
112 return src;
113 }
114
115 /* Normalize the given pathname, whose length is len, starting at the given
116 offset; everything before this offset is already normal. */
117 private String normalize(String path, int len, int off) {
118 if (len == 0) return path;
119 if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */
120 int src;
121 char slash = this.slash;
122 StringBuffer sb = new StringBuffer(len);
123
124 if (off == 0) {
125 /* Complete normalization, including prefix */
126 src = normalizePrefix(path, len, sb);
127 } else {
128 /* Partial normalization */
129 src = off;
130 sb.append(path.substring(0, off));
131 }
132
133 /* Remove redundant slashes from the remainder of the path, forcing all
134 slashes into the preferred slash */
135 while (src < len) {
136 char c = path.charAt(src++);
137 if (isSlash(c)) {
138 while ((src < len) && isSlash(path.charAt(src))) src++;
139 if (src == len) {
140 /* Check for trailing separator */
141 int sn = sb.length();
142 if ((sn == 2) && (sb.charAt(1) == ':')) {
143 /* "z:\\" */
144 sb.append(slash);
145 break;
146 }
147 if (sn == 0) {
148 /* "\\" */
149 sb.append(slash);
150 break;
151 }
152 if ((sn == 1) && (isSlash(sb.charAt(0)))) {
153 /* "\\\\" is not collapsed to "\\" because "\\\\" marks
154 the beginning of a UNC pathname. Even though it is
155 not, by itself, a valid UNC pathname, we leave it as
156 is in order to be consistent with the win32 APIs,
157 which treat this case as an invalid UNC pathname
158 rather than as an alias for the root directory of
159 the current drive. */
160 sb.append(slash);
161 break;
162 }
163 /* Path does not denote a root directory, so do not append
164 trailing slash */
165 break;
166 } else {
167 sb.append(slash);
168 }
169 } else {
170 sb.append(c);
171 }
172 }
173
174 String rv = sb.toString();
175 return rv;
176 }
177
178 /* Check that the given pathname is normal. If not, invoke the real
179 normalizer on the part of the pathname that requires normalization.
180 This way we iterate through the whole pathname string only once. */
181 public String normalize(String path) {
182 int n = path.length();
183 char slash = this.slash;
184 char altSlash = this.altSlash;
185 char prev = 0;
186 for (int i = 0; i < n; i++) {
187 char c = path.charAt(i);
188 if (c == altSlash)
189 return normalize(path, n, (prev == slash) ? i - 1 : i);
190 if ((c == slash) && (prev == slash) && (i > 1))
191 return normalize(path, n, i - 1);
192 if ((c == ':') && (i > 1))
193 return normalize(path, n, 0);
194 prev = c;
195 }
196 if (prev == slash) return normalize(path, n, n - 1);
197 return path;
198 }
199
200 public int prefixLength(String path) {
201 char slash = this.slash;
202 int n = path.length();
203 if (n == 0) return 0;
204 char c0 = path.charAt(0);
205 char c1 = (n > 1) ? path.charAt(1) : 0;
206 if (c0 == slash) {
207 if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */
208 return 1; /* Drive-relative "\\foo" */
209 }
210 if (isLetter(c0) && (c1 == ':')) {
211 if ((n > 2) && (path.charAt(2) == slash))
212 return 3; /* Absolute local pathname "z:\\foo" */
213 return 2; /* Directory-relative "z:foo" */
214 }
215 return 0; /* Completely relative */
216 }
217
218 public String resolve(String parent, String child) {
219 int pn = parent.length();
220 if (pn == 0) return child;
221 int cn = child.length();
222 if (cn == 0) return parent;
223
224 String c = child;
225 int childStart = 0;
226 int parentEnd = pn;
227
228 if ((cn > 1) && (c.charAt(0) == slash)) {
229 if (c.charAt(1) == slash) {
230 /* Drop prefix when child is a UNC pathname */
231 childStart = 2;
232 } else {
233 /* Drop prefix when child is drive-relative */
234 childStart = 1;
235
236 }
237 if (cn == childStart) { // Child is double slash
238 if (parent.charAt(pn - 1) == slash)
239 return parent.substring(0, pn - 1);
240 return parent;
241 }
242 }
243
244 if (parent.charAt(pn - 1) == slash)
245 parentEnd--;
246
247 int strlen = parentEnd + cn - childStart;
248 char[] theChars = null;
249 if (child.charAt(childStart) == slash) {
250 theChars = new char[strlen];
251 parent.getChars(0, parentEnd, theChars, 0);
252 child.getChars(childStart, cn, theChars, parentEnd);
253 } else {
254 theChars = new char[strlen + 1];
255 parent.getChars(0, parentEnd, theChars, 0);
256 theChars[parentEnd] = slash;
257 child.getChars(childStart, cn, theChars, parentEnd + 1);
258 }
259 return new String(theChars);
260 }
261
262 public String getDefaultParent() {
263 return ("" + slash);
264 }
265
266 public String fromURIPath(String path) {
267 String p = path;
268 if ((p.length() > 2) && (p.charAt(2) == ':')) {
269 // "/c:/foo" --> "c:/foo"
270 p = p.substring(1);
271 // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
272 if ((p.length() > 3) && p.endsWith("/"))
273 p = p.substring(0, p.length() - 1);
274 } else if ((p.length() > 1) && p.endsWith("/")) {
275 // "/foo/" --> "/foo"
276 p = p.substring(0, p.length() - 1);
277 }
278 return p;
279 }
280
281
282
283 /* -- Path operations -- */
284
285 public boolean isAbsolute(File f) {
286 int pl = f.getPrefixLength();
287 return (((pl == 2) && (f.getPath().charAt(0) == slash))
288 || (pl == 3));
289 }
290
291 protected native String getDriveDirectory(int drive);
292
293 private static String[] driveDirCache = new String[26];
294
295 private static int driveIndex(char d) {
296 if ((d >= 'a') && (d <= 'z')) return d - 'a';
297 if ((d >= 'A') && (d <= 'Z')) return d - 'A';
298 return -1;
299 }
300
301 private String getDriveDirectory(char drive) {
302 int i = driveIndex(drive);
303 if (i < 0) return null;
304 String s = driveDirCache[i];
305 if (s != null) return s;
306 s = getDriveDirectory(i + 1);
307 driveDirCache[i] = s;
308 return s;
309 }
310
311 private String getUserPath() {
312 /* For both compatibility and security,
313 we must look this up every time */
314 return normalize(System.getProperty("user.dir"));
315 }
316
317 private String getDrive(String path) {
318 int pl = prefixLength(path);
319 return (pl == 3) ? path.substring(0, 2) : null;
320 }
321
322 public String resolve(File f) {
323 String path = f.getPath();
324 int pl = f.getPrefixLength();
325 if ((pl == 2) && (path.charAt(0) == slash))
326 return path; /* UNC */
327 if (pl == 3)
328 return path; /* Absolute local */
329 if (pl == 0)
330 return getUserPath() + slashify(path); /* Completely relative */
331 if (pl == 1) { /* Drive-relative */
332 String up = getUserPath();
333 String ud = getDrive(up);
334 if (ud != null) return ud + path;
335 return up + path; /* User dir is a UNC path */
336 }
337 if (pl == 2) { /* Directory-relative */
338 String up = getUserPath();
339 String ud = getDrive(up);
340 if ((ud != null) && path.startsWith(ud))
341 return up + slashify(path.substring(2));
342 char drive = path.charAt(0);
343 String dir = getDriveDirectory(drive);
344 String np;
345 if (dir != null) {
346 /* When resolving a directory-relative path that refers to a
347 drive other than the current drive, insist that the caller
348 have read permission on the result */
349 String p = drive + (':' + dir + slashify(path.substring(2)));
350 SecurityManager security = System.getSecurityManager();
351 try {
352 if (security != null) security.checkRead(p);
353 } catch (SecurityException x) {
354 /* Don't disclose the drive's directory in the exception */
355 throw new SecurityException("Cannot resolve path " + path);
356 }
357 return p;
358 }
359 return drive + ":" + slashify(path.substring(2)); /* fake it */
360 }
361 throw new InternalError("Unresolvable path: " + path);
362 }
363
364 // Caches for canonicalization results to improve startup performance.
365 // The first cache handles repeated canonicalizations of the same path
366 // name. The prefix cache handles repeated canonicalizations within the
367 // same directory, and must not create results differing from the true
368 // canonicalization algorithm in canonicalize_md.c. For this reason the
369 // prefix cache is conservative and is not used for complex path names.
370 private ExpiringCache cache = new ExpiringCache();
371 private ExpiringCache prefixCache = new ExpiringCache();
372
373 public String canonicalize(String path) throws IOException {
374 // If path is a drive letter only then skip canonicalization
375 int len = path.length();
376 if ((len == 2) &&
377 (isLetter(path.charAt(0))) &&
378 (path.charAt(1) == ':')) {
379 char c = path.charAt(0);
380 if ((c >= 'A') && (c <= 'Z'))
381 return path;
382 return "" + ((char) (c-32)) + ':';
383 } else if ((len == 3) &&
384 (isLetter(path.charAt(0))) &&
385 (path.charAt(1) == ':') &&
386 (path.charAt(2) == '\\')) {
387 char c = path.charAt(0);
388 if ((c >= 'A') && (c <= 'Z'))
389 return path;
390 return "" + ((char) (c-32)) + ':' + '\\';
391 }
392 if (!useCanonCaches) {
393 return canonicalize0(path);
394 } else {
395 String res = cache.get(path);
396 if (res == null) {
397 String dir = null;
398 String resDir = null;
399 if (useCanonPrefixCache) {
400 dir = parentOrNull(path);
401 if (dir != null) {
402 resDir = prefixCache.get(dir);
403 if (resDir != null) {
404 // Hit only in prefix cache; full path is canonical,
405 // but we need to get the canonical name of the file
406 // in this directory to get the appropriate capitalization
407 String filename = path.substring(1 + dir.length());
408 res = canonicalizeWithPrefix(resDir, filename);
409 cache.put(dir + File.separatorChar + filename, res);
410 }
411 }
412 }
413 if (res == null) {
414 res = canonicalize0(path);
415 cache.put(path, res);
416 if (useCanonPrefixCache && dir != null) {
417 resDir = parentOrNull(res);
418 if (resDir != null) {
419 File f = new File(res);
420 if (f.exists() && !f.isDirectory()) {
421 prefixCache.put(dir, resDir);
422 }
423 }
424 }
425 }
426 }
427 assert canonicalize0(path).equalsIgnoreCase(res);
428 return res;
429 }
430 }
431
432 protected native String canonicalize0(String path)
433 throws IOException;
434 protected String canonicalizeWithPrefix(String canonicalPrefix,
435 String filename) throws IOException
436 {
437 return canonicalizeWithPrefix0(canonicalPrefix,
438 canonicalPrefix + File.separatorChar + filename);
439 }
440 // Run the canonicalization operation assuming that the prefix
441 // (everything up to the last filename) is canonical; just gets
442 // the canonical name of the last element of the path
443 protected native String canonicalizeWithPrefix0(String canonicalPrefix,
444 String pathWithCanonicalPrefix)
445 throws IOException;
446 // Best-effort attempt to get parent of this path; used for
447 // optimization of filename canonicalization. This must return null for
448 // any cases where the code in canonicalize_md.c would throw an
449 // exception or otherwise deal with non-simple pathnames like handling
450 // of "." and "..". It may conservatively return null in other
451 // situations as well. Returning null will cause the underlying
452 // (expensive) canonicalization routine to be called.
453 static String parentOrNull(String path) {
454 if (path == null) return null;
455 char sep = File.separatorChar;
456 char altSep = '/';
457 int last = path.length() - 1;
458 int idx = last;
459 int adjacentDots = 0;
460 int nonDotCount = 0;
461 while (idx > 0) {
462 char c = path.charAt(idx);
463 if (c == '.') {
464 if (++adjacentDots >= 2) {
465 // Punt on pathnames containing . and ..
466 return null;
467 }
468 if (nonDotCount == 0) {
469 // Punt on pathnames ending in a .
470 return null;
471 }
472 } else if (c == sep) {
473 if (adjacentDots == 1 && nonDotCount == 0) {
474 // Punt on pathnames containing . and ..
475 return null;
476 }
477 if (idx == 0 ||
478 idx >= last - 1 ||
479 path.charAt(idx - 1) == sep ||
480 path.charAt(idx - 1) == altSep) {
481 // Punt on pathnames containing adjacent slashes
482 // toward the end
483 return null;
484 }
485 return path.substring(0, idx);
486 } else if (c == altSep) {
487 // Punt on pathnames containing both backward and
488 // forward slashes
489 return null;
490 } else if (c == '*' || c == '?') {
491 // Punt on pathnames containing wildcards
492 return null;
493 } else {
494 ++nonDotCount;
495 adjacentDots = 0;
496 }
497 --idx;
498 }
499 return null;
500 }
501
502
503 /* -- Attribute accessors -- */
504
505 public native int getBooleanAttributes(File f);
506 public native boolean checkAccess(File f, int access);
507 public native long getLastModifiedTime(File f);
508 public native long getLength(File f);
509 public native boolean setPermission(File f, int access, boolean enable, boolean owneronly);
510
511 /* -- File operations -- */
512
513 public native boolean createFileExclusively(String path)
514 throws IOException;
515 public boolean delete(File f) {
516 // Keep canonicalization caches in sync after file deletion
517 // and renaming operations. Could be more clever than this
518 // (i.e., only remove/update affected entries) but probably
519 // not worth it since these entries expire after 30 seconds
520 // anyway.
521 cache.clear();
522 prefixCache.clear();
523 return delete0(f);
524 }
525 protected native boolean delete0(File f);
526 public native String[] list(File f);
527 public native boolean createDirectory(File f);
528 public boolean rename(File f1, File f2) {
529 // Keep canonicalization caches in sync after file deletion
530 // and renaming operations. Could be more clever than this
531 // (i.e., only remove/update affected entries) but probably
532 // not worth it since these entries expire after 30 seconds
533 // anyway.
534 cache.clear();
535 prefixCache.clear();
536 return rename0(f1, f2);
537 }
538 protected native boolean rename0(File f1, File f2);
539 public native boolean setLastModifiedTime(File f, long time);
540 public native boolean setReadOnly(File f);
541
542
543 /* -- Filesystem interface -- */
544
545 private boolean access(String path) {
546 try {
547 SecurityManager security = System.getSecurityManager();
548 if (security != null) security.checkRead(path);
549 return true;
550 } catch (SecurityException x) {
551 return false;
552 }
553 }
554
555 private static native int listRoots0();
556
557 public File[] listRoots() {
558 int ds = listRoots0();
559 int n = 0;
560 for (int i = 0; i < 26; i++) {
561 if (((ds >> i) & 1) != 0) {
562 if (!access((char)('A' + i) + ":" + slash))
563 ds &= ~(1 << i);
564 else
565 n++;
566 }
567 }
568 File[] fs = new File[n];
569 int j = 0;
570 char slash = this.slash;
571 for (int i = 0; i < 26; i++) {
572 if (((ds >> i) & 1) != 0)
573 fs[j++] = new File((char)('A' + i) + ":" + slash);
574 }
575 return fs;
576 }
577
578
579 /* -- Disk usage -- */
580 public long getSpace(File f, int t) {
581 if (f.exists()) {
582 File file = (f.isDirectory() ? f : f.getParentFile());
583 return getSpace0(file, t);
584 }
585 return 0;
586 }
587
588 private native long getSpace0(File f, int t);
589
590
591 /* -- Basic infrastructure -- */
592
593 public int compare(File f1, File f2) {
594 return f1.getPath().compareToIgnoreCase(f2.getPath());
595 }
596
597 public int hashCode(File f) {
598 /* Could make this more efficient: String.hashCodeIgnoreCase */
599 return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321;
600 }
601
602
603 private static native void initIDs();
604
605 static {
606 initIDs();
607 }
608
609}