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