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