1 /*
2  * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved.
3  * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
4  *
5  *
6  *
7  *
8  *
9  *
10  *
11  *
12  *
13  *
14  *
15  *
16  *
17  *
18  *
19  *
20  *
21  *
22  *
23  *
24  */

25
26 package java.io;
27
28 import java.security.AccessController;
29 import java.util.Locale;
30 import sun.security.action.GetPropertyAction;
31
32 /**
33  * Unicode-aware FileSystem for Windows NT/2000.
34  *
35  * @author Konstantin Kladko
36  * @since 1.4
37  */

38 class WinNTFileSystem extends FileSystem {
39
40     private final char slash;
41     private final char altSlash;
42     private final char semicolon;
43
44     public WinNTFileSystem() {
45         slash = AccessController.doPrivileged(
46             new GetPropertyAction("file.separator")).charAt(0);
47         semicolon = AccessController.doPrivileged(
48             new GetPropertyAction("path.separator")).charAt(0);
49         altSlash = (this.slash == '\\') ? '/' : '\\';
50     }
51
52     private boolean isSlash(char c) {
53         return (c == '\\') || (c == '/');
54     }
55
56     private boolean isLetter(char c) {
57         return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
58     }
59
60     private String slashify(String p) {
61         if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p;
62         else return p;
63     }
64
65     /* -- Normalization and construction -- */
66
67     @Override
68     public char getSeparator() {
69         return slash;
70     }
71
72     @Override
73     public char getPathSeparator() {
74         return semicolon;
75     }
76
77     /* Check that the given pathname is normal.  If not, invoke the real
78        normalizer on the part of the pathname that requires normalization.
79        This way we iterate through the whole pathname string only once. */

80     @Override
81     public String normalize(String path) {
82         int n = path.length();
83         char slash = this.slash;
84         char altSlash = this.altSlash;
85         char prev = 0;
86         for (int i = 0; i < n; i++) {
87             char c = path.charAt(i);
88             if (c == altSlash)
89                 return normalize(path, n, (prev == slash) ? i - 1 : i);
90             if ((c == slash) && (prev == slash) && (i > 1))
91                 return normalize(path, n, i - 1);
92             if ((c == ':') && (i > 1))
93                 return normalize(path, n, 0);
94             prev = c;
95         }
96         if (prev == slash) return normalize(path, n, n - 1);
97         return path;
98     }
99
100     /* Normalize the given pathname, whose length is len, starting at the given
101        offset; everything before this offset is already normal. */

102     private String normalize(String path, int len, int off) {
103         if (len == 0) return path;
104         if (off < 3) off = 0;   /* Avoid fencepost cases with UNC pathnames */
105         int src;
106         char slash = this.slash;
107         StringBuffer sb = new StringBuffer(len);
108
109         if (off == 0) {
110             /* Complete normalization, including prefix */
111             src = normalizePrefix(path, len, sb);
112         } else {
113             /* Partial normalization */
114             src = off;
115             sb.append(path.substring(0, off));
116         }
117
118         /* Remove redundant slashes from the remainder of the path, forcing all
119            slashes into the preferred slash */

120         while (src < len) {
121             char c = path.charAt(src++);
122             if (isSlash(c)) {
123                 while ((src < len) && isSlash(path.charAt(src))) src++;
124                 if (src == len) {
125                     /* Check for trailing separator */
126                     int sn = sb.length();
127                     if ((sn == 2) && (sb.charAt(1) == ':')) {
128                         /* "z:\\" */
129                         sb.append(slash);
130                         break;
131                     }
132                     if (sn == 0) {
133                         /* "\\" */
134                         sb.append(slash);
135                         break;
136                     }
137                     if ((sn == 1) && (isSlash(sb.charAt(0)))) {
138                         /* "\\\\" is not collapsed to "\\" because "\\\\" marks
139                            the beginning of a UNC pathname.  Even though it is
140                            not, by itself, a valid UNC pathname, we leave it as
141                            is in order to be consistent with the win32 APIs,
142                            which treat this case as an invalid UNC pathname
143                            rather than as an alias for the root directory of
144                            the current drive. */

145                         sb.append(slash);
146                         break;
147                     }
148                     /* Path does not denote a root directory, so do not append
149                        trailing slash */

150                     break;
151                 } else {
152                     sb.append(slash);
153                 }
154             } else {
155                 sb.append(c);
156             }
157         }
158
159         String rv = sb.toString();
160         return rv;
161     }
162
163     /* A normal Win32 pathname contains no duplicate slashes, except possibly
164        for a UNC prefix, and does not end with a slash.  It may be the empty
165        string.  Normalized Win32 pathnames have the convenient property that
166        the length of the prefix almost uniquely identifies the type of the path
167        and whether it is absolute or relative:
168
169            0  relative to both drive and directory
170            1  drive-relative (begins with '\\')
171            2  absolute UNC (if first char is '\\'),
172                 else directory-relative (has form "z:foo")
173            3  absolute local pathname (begins with "z:\\")
174      */

175     private int normalizePrefix(String path, int len, StringBuffer sb) {
176         int src = 0;
177         while ((src < len) && isSlash(path.charAt(src))) src++;
178         char c;
179         if ((len - src >= 2)
180             && isLetter(c = path.charAt(src))
181             && path.charAt(src + 1) == ':') {
182             /* Remove leading slashes if followed by drive specifier.
183                This hack is necessary to support file URLs containing drive
184                specifiers (e.g., "file://c:/path").  As a side effect,
185                "/c:/path" can be used as an alternative to "c:/path". */

186             sb.append(c);
187             sb.append(':');
188             src += 2;
189         } else {
190             src = 0;
191             if ((len >= 2)
192                 && isSlash(path.charAt(0))
193                 && isSlash(path.charAt(1))) {
194                 /* UNC pathname: Retain first slash; leave src pointed at
195                    second slash so that further slashes will be collapsed
196                    into the second slash.  The result will be a pathname
197                    beginning with "\\\\" followed (most likely) by a host
198                    name. */

199                 src = 1;
200                 sb.append(slash);
201             }
202         }
203         return src;
204     }
205
206     @Override
207     public int prefixLength(String path) {
208         char slash = this.slash;
209         int n = path.length();
210         if (n == 0) return 0;
211         char c0 = path.charAt(0);
212         char c1 = (n > 1) ? path.charAt(1) : 0;
213         if (c0 == slash) {
214             if (c1 == slash) return 2;  /* Absolute UNC pathname "\\\\foo" */
215             return 1;                   /* Drive-relative "\\foo" */
216         }
217         if (isLetter(c0) && (c1 == ':')) {
218             if ((n > 2) && (path.charAt(2) == slash))
219                 return 3;               /* Absolute local pathname "z:\\foo" */
220             return 2;                   /* Directory-relative "z:foo" */
221         }
222         return 0;                       /* Completely relative */
223     }
224
225     @Override
226     public String resolve(String parent, String child) {
227         int pn = parent.length();
228         if (pn == 0) return child;
229         int cn = child.length();
230         if (cn == 0) return parent;
231
232         String c = child;
233         int childStart = 0;
234         int parentEnd = pn;
235
236         if ((cn > 1) && (c.charAt(0) == slash)) {
237             if (c.charAt(1) == slash) {
238                 /* Drop prefix when child is a UNC pathname */
239                 childStart = 2;
240             } else {
241                 /* Drop prefix when child is drive-relative */
242                 childStart = 1;
243
244             }
245             if (cn == childStart) { // Child is double slash
246                 if (parent.charAt(pn - 1) == slash)
247                     return parent.substring(0, pn - 1);
248                 return parent;
249             }
250         }
251
252         if (parent.charAt(pn - 1) == slash)
253             parentEnd--;
254
255         int strlen = parentEnd + cn - childStart;
256         char[] theChars = null;
257         if (child.charAt(childStart) == slash) {
258             theChars = new char[strlen];
259             parent.getChars(0, parentEnd, theChars, 0);
260             child.getChars(childStart, cn, theChars, parentEnd);
261         } else {
262             theChars = new char[strlen + 1];
263             parent.getChars(0, parentEnd, theChars, 0);
264             theChars[parentEnd] = slash;
265             child.getChars(childStart, cn, theChars, parentEnd + 1);
266         }
267         return new String(theChars);
268     }
269
270     @Override
271     public String getDefaultParent() {
272         return ("" + slash);
273     }
274
275     @Override
276     public String fromURIPath(String path) {
277         String p = path;
278         if ((p.length() > 2) && (p.charAt(2) == ':')) {
279             // "/c:/foo" --> "c:/foo"
280             p = p.substring(1);
281             // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
282             if ((p.length() > 3) && p.endsWith("/"))
283                 p = p.substring(0, p.length() - 1);
284         } else if ((p.length() > 1) && p.endsWith("/")) {
285             // "/foo/" --> "/foo"
286             p = p.substring(0, p.length() - 1);
287         }
288         return p;
289     }
290
291     /* -- Path operations -- */
292
293     @Override
294     public boolean isAbsolute(File f) {
295         int pl = f.getPrefixLength();
296         return (((pl == 2) && (f.getPath().charAt(0) == slash))
297                 || (pl == 3));
298     }
299
300     @Override
301     public String resolve(File f) {
302         String path = f.getPath();
303         int pl = f.getPrefixLength();
304         if ((pl == 2) && (path.charAt(0) == slash))
305             return path;                        /* UNC */
306         if (pl == 3)
307             return path;                        /* Absolute local */
308         if (pl == 0)
309             return getUserPath() + slashify(path); /* Completely relative */
310         if (pl == 1) {                          /* Drive-relative */
311             String up = getUserPath();
312             String ud = getDrive(up);
313             if (ud != nullreturn ud + path;
314             return up + path;                   /* User dir is a UNC path */
315         }
316         if (pl == 2) {                          /* Directory-relative */
317             String up = getUserPath();
318             String ud = getDrive(up);
319             if ((ud != null) && path.startsWith(ud))
320                 return up + slashify(path.substring(2));
321             char drive = path.charAt(0);
322             String dir = getDriveDirectory(drive);
323             String np;
324             if (dir != null) {
325                 /* When resolving a directory-relative path that refers to a
326                    drive other than the current drive, insist that the caller
327                    have read permission on the result */

328                 String p = drive + (':' + dir + slashify(path.substring(2)));
329                 SecurityManager security = System.getSecurityManager();
330                 try {
331                     if (security != null) security.checkRead(p);
332                 } catch (SecurityException x) {
333                     /* Don't disclose the drive's directory in the exception */
334                     throw new SecurityException("Cannot resolve path " + path);
335                 }
336                 return p;
337             }
338             return drive + ":" + slashify(path.substring(2)); /* fake it */
339         }
340         throw new InternalError("Unresolvable path: " + path);
341     }
342
343     private String getUserPath() {
344         /* For both compatibility and security,
345            we must look this up every time */

346         return normalize(System.getProperty("user.dir"));
347     }
348
349     private String getDrive(String path) {
350         int pl = prefixLength(path);
351         return (pl == 3) ? path.substring(0, 2) : null;
352     }
353
354     private static String[] driveDirCache = new String[26];
355
356     private static int driveIndex(char d) {
357         if ((d >= 'a') && (d <= 'z')) return d - 'a';
358         if ((d >= 'A') && (d <= 'Z')) return d - 'A';
359         return -1;
360     }
361
362     private native String getDriveDirectory(int drive);
363
364     private String getDriveDirectory(char drive) {
365         int i = driveIndex(drive);
366         if (i < 0) return null;
367         String s = driveDirCache[i];
368         if (s != nullreturn s;
369         s = getDriveDirectory(i + 1);
370         driveDirCache[i] = s;
371         return s;
372     }
373
374     // Caches for canonicalization results to improve startup performance.
375     // The first cache handles repeated canonicalizations of the same path
376     // name. The prefix cache handles repeated canonicalizations within the
377     // same directory, and must not create results differing from the true
378     // canonicalization algorithm in canonicalize_md.c. For this reason the
379     // prefix cache is conservative and is not used for complex path names.
380     private ExpiringCache cache       = new ExpiringCache();
381     private ExpiringCache prefixCache = new ExpiringCache();
382
383     @Override
384     public String canonicalize(String path) throws IOException {
385         // If path is a drive letter only then skip canonicalization
386         int len = path.length();
387         if ((len == 2) &&
388             (isLetter(path.charAt(0))) &&
389             (path.charAt(1) == ':')) {
390             char c = path.charAt(0);
391             if ((c >= 'A') && (c <= 'Z'))
392                 return path;
393             return "" + ((char) (c-32)) + ':';
394         } else if ((len == 3) &&
395                    (isLetter(path.charAt(0))) &&
396                    (path.charAt(1) == ':') &&
397                    (path.charAt(2) == '\\')) {
398             char c = path.charAt(0);
399             if ((c >= 'A') && (c <= 'Z'))
400                 return path;
401             return "" + ((char) (c-32)) + ':' + '\\';
402         }
403         if (!useCanonCaches) {
404             return canonicalize0(path);
405         } else {
406             String res = cache.get(path);
407             if (res == null) {
408                 String dir = null;
409                 String resDir = null;
410                 if (useCanonPrefixCache) {
411                     dir = parentOrNull(path);
412                     if (dir != null) {
413                         resDir = prefixCache.get(dir);
414                         if (resDir != null) {
415                             /*
416                              * Hit only in prefix cache; full path is canonical,
417                              * but we need to get the canonical name of the file
418                              * in this directory to get the appropriate
419                              * capitalization
420                              */

421                             String filename = path.substring(1 + dir.length());
422                             res = canonicalizeWithPrefix(resDir, filename);
423                             cache.put(dir + File.separatorChar + filename, res);
424                         }
425                     }
426                 }
427                 if (res == null) {
428                     res = canonicalize0(path);
429                     cache.put(path, res);
430                     if (useCanonPrefixCache && dir != null) {
431                         resDir = parentOrNull(res);
432                         if (resDir != null) {
433                             File f = new File(res);
434                             if (f.exists() && !f.isDirectory()) {
435                                 prefixCache.put(dir, resDir);
436                             }
437                         }
438                     }
439                 }
440             }
441             return res;
442         }
443     }
444
445     private native String canonicalize0(String path)
446             throws IOException;
447
448     private String canonicalizeWithPrefix(String canonicalPrefix,
449             String filename) throws IOException
450     {
451         return canonicalizeWithPrefix0(canonicalPrefix,
452                 canonicalPrefix + File.separatorChar + filename);
453     }
454
455     // Run the canonicalization operation assuming that the prefix
456     // (everything up to the last filename) is canonical; just gets
457     // the canonical name of the last element of the path
458     private native String canonicalizeWithPrefix0(String canonicalPrefix,
459             String pathWithCanonicalPrefix)
460             throws IOException;
461
462     // Best-effort attempt to get parent of this path; used for
463     // optimization of filename canonicalization. This must return null for
464     // any cases where the code in canonicalize_md.c would throw an
465     // exception or otherwise deal with non-simple pathnames like handling
466     // of "." and "..". It may conservatively return null in other
467     // situations as well. Returning null will cause the underlying
468     // (expensive) canonicalization routine to be called.
469     private static String parentOrNull(String path) {
470         if (path == nullreturn null;
471         char sep = File.separatorChar;
472         char altSep = '/';
473         int last = path.length() - 1;
474         int idx = last;
475         int adjacentDots = 0;
476         int nonDotCount = 0;
477         while (idx > 0) {
478             char c = path.charAt(idx);
479             if (c == '.') {
480                 if (++adjacentDots >= 2) {
481                     // Punt on pathnames containing . and ..
482                     return null;
483                 }
484                 if (nonDotCount == 0) {
485                     // Punt on pathnames ending in a .
486                     return null;
487                 }
488             } else if (c == sep) {
489                 if (adjacentDots == 1 && nonDotCount == 0) {
490                     // Punt on pathnames containing . and ..
491                     return null;
492                 }
493                 if (idx == 0 ||
494                     idx >= last - 1 ||
495                     path.charAt(idx - 1) == sep ||
496                     path.charAt(idx - 1) == altSep) {
497                     // Punt on pathnames containing adjacent slashes
498                     // toward the end
499                     return null;
500                 }
501                 return path.substring(0, idx);
502             } else if (c == altSep) {
503                 // Punt on pathnames containing both backward and
504                 // forward slashes
505                 return null;
506             } else if (c == '*' || c == '?') {
507                 // Punt on pathnames containing wildcards
508                 return null;
509             } else {
510                 ++nonDotCount;
511                 adjacentDots = 0;
512             }
513             --idx;
514         }
515         return null;
516     }
517
518     /* -- Attribute accessors -- */
519
520     @Override
521     public native int getBooleanAttributes(File f);
522
523     @Override
524     public native boolean checkAccess(File f, int access);
525
526     @Override
527     public native long getLastModifiedTime(File f);
528
529     @Override
530     public native long getLength(File f);
531
532     @Override
533     public native boolean setPermission(File f, int access, boolean enable,
534             boolean owneronly);
535
536     /* -- File operations -- */
537
538     @Override
539     public native boolean createFileExclusively(String path)
540             throws IOException;
541
542     @Override
543     public native String[] list(File f);
544
545     @Override
546     public native boolean createDirectory(File f);
547
548     @Override
549     public native boolean setLastModifiedTime(File f, long time);
550
551     @Override
552     public native boolean setReadOnly(File f);
553
554     @Override
555     public boolean delete(File f) {
556         // Keep canonicalization caches in sync after file deletion
557         // and renaming operations. Could be more clever than this
558         // (i.e., only remove/update affected entries) but probably
559         // not worth it since these entries expire after 30 seconds
560         // anyway.
561         cache.clear();
562         prefixCache.clear();
563         return delete0(f);
564     }
565
566     private native boolean delete0(File f);
567
568     @Override
569     public boolean rename(File f1, File f2) {
570         // Keep canonicalization caches in sync after file deletion
571         // and renaming operations. Could be more clever than this
572         // (i.e., only remove/update affected entries) but probably
573         // not worth it since these entries expire after 30 seconds
574         // anyway.
575         cache.clear();
576         prefixCache.clear();
577         return rename0(f1, f2);
578     }
579
580     private native boolean rename0(File f1, File f2);
581
582     /* -- Filesystem interface -- */
583
584     @Override
585     public File[] listRoots() {
586         int ds = listRoots0();
587         int n = 0;
588         for (int i = 0; i < 26; i++) {
589             if (((ds >> i) & 1) != 0) {
590                 if (!access((char)('A' + i) + ":" + slash))
591                     ds &= ~(1 << i);
592                 else
593                     n++;
594             }
595         }
596         File[] fs = new File[n];
597         int j = 0;
598         char slash = this.slash;
599         for (int i = 0; i < 26; i++) {
600             if (((ds >> i) & 1) != 0)
601                 fs[j++] = new File((char)('A' + i) + ":" + slash);
602         }
603         return fs;
604     }
605
606     private static native int listRoots0();
607
608     private boolean access(String path) {
609         try {
610             SecurityManager security = System.getSecurityManager();
611             if (security != null) security.checkRead(path);
612             return true;
613         } catch (SecurityException x) {
614             return false;
615         }
616     }
617
618     /* -- Disk usage -- */
619
620     @Override
621     public long getSpace(File f, int t) {
622         if (f.exists()) {
623             return getSpace0(f, t);
624         }
625         return 0;
626     }
627
628     private native long getSpace0(File f, int t);
629
630     /* -- Basic infrastructure -- */
631
632     @Override
633     public int compare(File f1, File f2) {
634         return f1.getPath().compareToIgnoreCase(f2.getPath());
635     }
636
637     @Override
638     public int hashCode(File f) {
639         /* Could make this more efficient: String.hashCodeIgnoreCase */
640         return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321;
641     }
642
643     private static native void initIDs();
644
645     static {
646         initIDs();
647     }
648 }
649
Powered by JavaMelody