Odd result with canonical paths with intermixed usage of java.io.File and java.nio.file.Files APIs
Jaikiran Pai
jai.forums2013 at gmail.com
Mon Dec 11 14:06:37 UTC 2017
I have been debugging an issue with canonical file path and it seems
that I either have run into a bug or am incorrectly intermixing the use
of java.io.File and java.nio.file.Files APIs. I'm on MacOS and this
issue is reproducible with both Java 8 and Java 9:
Java 8:
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)
Java 9:
java version "9.0.1"
Java(TM) SE Runtime Environment (build 9.0.1+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)
The code that reproduces this issue is as simple as this:
public static void main(final String[] args) throws Exception {
final Path symlinkPath = Paths.get("/tmp/foosymlink");
final Path targetPath = Paths.get("/tmp/existing-text-file.txt");
// create a symbolic link
java.nio.file.Files.createSymbolicLink(symlinkPath, targetPath);
System.out.println("Canonical path on first symlink creation " +
symlinkPath.toFile().getCanonicalPath());
// delete the link (but not the target)
Files.delete(symlinkPath);
// recreate the link to point to some other target file
final Path someOtherTargetPath =
Paths.get("/tmp/other-existing-text-file.txt");
java.nio.file.Files.createSymbolicLink(symlinkPath,
someOtherTargetPath);
System.out.println("Canonical path on symlink recreation " +
symlinkPath.toFile().getCanonicalPath());
}
So what that code does is:
1. Create a symlink with the link pointing to some existing file
2. Invoke getCanonicalPath() on the symlink File object. This is
important to reproduce the issue (for reasons explained later)
3. Delete the symlink (not the target of the symlink) using
java.nio.file.Files.delete(Path) API.
4. Recreate the symlink (at the same path) but pointing it to a
different (existent) target file
5. Invoke the getCanonicalPath() on the symlink File object and
evaluate the returned value.
Step#5 incorrectly returns the canonical path which points to the
outdated target path (the link to which was broken in step#3). However,
if step#5 is invoked after a delay of around 30 seconds from step#3,
then it returns the correct canonical path which points to the target
file of the link which was recreated in step#4.
Looking into the implementation of getCanonicalPath and the underlying
java.io.FileSystem, on my setup the java.io.UnixFileSystem gets picked
up which internally maintains a canonical path cache (which expires its
entries with a 30 second duration). So it's pretty clear why this
behaves the way it does. There's a system property which controls
disabling this cache, so this in itself isn't an issue.
However, if I switch the call (in step#3 above) from:
Files.delete(symlinkPath)
to
symlinkPath.toFile().delete()
then the step#5 (immediately) returns the right canonical path which
points to the target file of the recreated link. Looking at the
implementation of java.io.File.delete() it triggers the
java.io.UnixFileSystem.delete() which clears off the canonical path
cache[1] and thus doesn't end up returning stale path values on calls to
getCanonicalPath().
So a few related questions that I have are:
1. Is this inconsistency an expected behaviour or is this a bug?
2. If this is an expected behaviour, then would it be a better idea
(as an application developer) to use Path.toRealPath[2] instead of using
the File.getCanonicalPath()? Are these 2 APIs semantically equivalent?
The File.getCanonicalPath() talks about the canonical path being
"unique" paths but the Path.toRealPath has no such mentions.
[1]
http://hg.openjdk.java.net/jdk/jdk/file/31febb3f66f7/src/java.base/unix/classes/java/io/UnixFileSystem.java#l256
[2]
https://docs.oracle.com/javase/9/docs/api/java/nio/file/Path.html#toRealPath-java.nio.file.LinkOption...-
-Jaikiran
More information about the discuss
mailing list