diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixPath.java b/src/java.base/unix/classes/sun/nio/fs/UnixPath.java index 6b0cd1bbf63..b722c30db42 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixPath.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixPath.java @@ -948,28 +948,51 @@ class UnixPath implements Path { // Obtain the stream of entries in the directory corresponding // to the path constructed thus far, and extract the entry whose - // key is equal to the key of the current element + // internal path bytes equal the internal path bytes of the current + // element, or whose key is equal to the key of the current element + boolean found = false; DirectoryStream.Filter filter = (p) -> { return true; }; + // compare path bytes until a match is found or no more entries try (DirectoryStream entries = new UnixDirectoryStream(path, dp, filter)) { - boolean found = false; for (Path entry : entries) { - UnixPath p = path.resolve(entry.getFileName()); - UnixFileAttributes attributes = null; - try { - attributes = UnixFileAttributes.get(p, false); - UnixFileKey key = attributes.fileKey(); - if (key.equals(elementKey)) { - path = path.resolve(entry); - found = true; - break; + Path name = entry.getFileName(); + if (name.compareTo(element) == 0) { + found = true; + path = path.resolve(entry); + break; + } + } + } + + // if no path match found, compare file keys + if (!found) { + try { + dp = opendir(path); + } catch (UnixException x) { + x.rethrowAsIOException(path); + } + + try (DirectoryStream entries = new UnixDirectoryStream(path, dp, filter)) { + for (Path entry : entries) { + Path name = entry.getFileName(); + UnixPath p = path.resolve(name); + UnixFileAttributes attributes = null; + try { + attributes = UnixFileAttributes.get(p, false); + UnixFileKey key = attributes.fileKey(); + if (key.equals(elementKey)) { + found = true; + path = path.resolve(entry); + break; + } + } catch (UnixException ignore) { + continue; } - } catch (UnixException ignore) { - continue; } } - // Fallback which should in theory never happen if (!found) { + // Fallback which should in theory never happen path = path.resolve(element); } } diff --git a/test/jdk/java/nio/file/Path/ToRealPath.java b/test/jdk/java/nio/file/Path/ToRealPath.java index 474b1417197..b9a5633c6de 100644 --- a/test/jdk/java/nio/file/Path/ToRealPath.java +++ b/test/jdk/java/nio/file/Path/ToRealPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -151,18 +151,18 @@ public class ToRealPath { public void noCollapseDots1() throws IOException { Path subPath = DIR.resolve(Path.of("dir", "subdir")); Path sub = Files.createDirectories(subPath); - System.out.println("sub: " + sub); + System.err.println("sub: " + sub); Files.createSymbolicLink(LINK, sub); - System.out.println("LINK: " + LINK + " -> " + sub); + System.err.println("LINK: " + LINK + " -> " + sub); Path p = Path.of("..", "..", FILE.getFileName().toString()); - System.out.println("p: " + p); + System.err.println("p: " + p); Path path = LINK.resolve(p); - System.out.println("path: " + path); + System.err.println("path: " + path); if (Platform.isWindows() && Files.notExists(path)) { Files.createFile(path); extraDeletions.add(path); } - System.out.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS)); + System.err.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS)); if (Platform.isWindows()) assertTrue(Files.isSameFile(path.toRealPath(NOFOLLOW_LINKS), path)); else @@ -181,23 +181,23 @@ public class ToRealPath { Path out = Files.createFile(DIR.resolve(Path.of("out.txt"))); Path aaa = DIR.resolve(Path.of("aaa")); Files.createSymbolicLink(aaa, sub); - System.out.println("aaa: " + aaa + " -> " + sub); + System.err.println("aaa: " + aaa + " -> " + sub); Path bbb = DIR.resolve(Path.of("bbb")); Files.createSymbolicLink(bbb, sub); - System.out.println("bbb: " + bbb + " -> " + sub); + System.err.println("bbb: " + bbb + " -> " + sub); Path p = Path.of("aaa", "..", "..", "bbb", "..", "..", "out.txt"); Path path = DIR.resolve(p); - System.out.println("path: " + path); + System.err.println("path: " + path); if (Platform.isWindows() && Files.notExists(path)) { Files.createFile(path); extraDeletions.add(path); } - System.out.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS)); + System.err.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS)); if (Platform.isWindows()) assertTrue(Files.isSameFile(path.toRealPath(NOFOLLOW_LINKS), path)); else assertEquals(path.toRealPath(NOFOLLOW_LINKS), path); - System.out.println(path.toRealPath()); + System.err.println(path.toRealPath()); Files.delete(sub); Files.delete(sub.getParent()); diff --git a/test/micro/org/openjdk/bench/java/nio/file/ToRealPath.java b/test/micro/org/openjdk/bench/java/nio/file/ToRealPath.java new file mode 100644 index 00000000000..23dba3858ba --- /dev/null +++ b/test/micro/org/openjdk/bench/java/nio/file/ToRealPath.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.nio.file; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.FileVisitResult; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Random; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; + +@State(Scope.Benchmark) +public class ToRealPath { + + static Random RND = new Random(17_000_126); + + static final String NAME = "RealPath"; + static final int LEN = NAME.length(); + + Path root; + Path[] files; + + @Setup + public void init() throws IOException { + // root the test files at CWD/NAME + root = Path.of(System.getProperty("user.dir")).resolve(NAME); + + // populate files array + StringBuilder sb = new StringBuilder(); + files = new Path[100]; + for (int i = 0; i < files.length; i++) { + // create directories up to a depth of 9, inclusive + sb.setLength(0); + int depth = RND.nextInt(10); + for (int j = 0; j < depth; j++) { + sb.append("dir"); + sb.append(j); + sb.append(File.separatorChar); + } + Path dir = root.resolve(sb.toString()); + Files.createDirectories(dir); + + // set the file prefix with random case conversion + String prefix; + if (RND.nextBoolean()) { + sb.setLength(0); + for (int k = 0; k < LEN; k++) { + char c = NAME.charAt(k); + sb.append(RND.nextBoolean() + ? Character.toLowerCase(c) + : Character.toUpperCase(c)); + } + prefix = sb.append(i).toString(); + } else { + prefix = NAME + i; + } + + // create the file + Path tmpFile = Files.createTempFile(dir, prefix, ".tmp"); + + // set the array path to a version with a lower case name + String tmpName = tmpFile.getFileName().toString().toLowerCase(); + files[i] = tmpFile.getParent().resolve(tmpName); + } + } + + @TearDown + public void cleanup() throws IOException { + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attrs) + throws IOException + { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, + IOException e) + throws IOException + { + if (e == null) { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } else { + // directory iteration failed + throw e; + } + } + }); + } + + @Benchmark + public Path noFollowLinks() throws IOException { + int i = RND.nextInt(0, files.length); + return files[i].toRealPath(LinkOption.NOFOLLOW_LINKS); + } +}