Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8154364

Files.isSameFile() throws NoSuchFileException with broken symbolic links

    Details

    • Type: Bug
    • Status: Open
    • Priority: P4
    • Resolution: Unresolved
    • Affects Version/s: 9
    • Fix Version/s: tbd
    • Component/s: core-libs
    • Labels:
      None
    • Subcomponent:
    • Introduced In Version:
      7
    • CPU:
      generic
    • OS:
      linux, os_x, solaris

      Description

      The current posix provider implementation of Files.isSameFile() throws NoSuchFileException too aggressively. The current behaviour is as follows:

      Path A and path B equals():
      Neither A nor B exist: return "true"
      Both A and B exist: return "true"

      Path A and path B not equals():
      Neither A nor B exist: throw NoSuchFileException(A)
      Path A exists and B does not exist: throw NoSuchFileException(B)
      Path A does not exist and B exists: throw NoSuchFileException(A)
      Both A and B exist and point to same file: return "true"
      Both A and B exist and point to different files: return "false

      The thrown NSFE exceptions are not useful. A more useful response would be (*** indicates changed behaviour):

      Path A and path B equals():
      Neither A nor B exist: return "true"
      Both A and B exist: return "true"

      Path A and path B not equals():
      Neither A nor B exist: ***return false*** [1]
      Path A exists and B does not exist: ***return "false"*** [2]
      Path A does not exist and B exists: ***return "false"*** [2]
      Both A and B exist and point to same file: return "true"
      Both A and B exist and point to different files: return "false

      The two changed behaviours are rationalized as follows:

      [1] If the paths were equals() then we would return "true" without checking for existense. It reasonably follows then that non-equal paths which both point to nothing are not the same path.

      [2] If one path resolves and the other does not then they do not refer to the same file.

      The common alternative which must be employed in most cases is to check existence independently and only if both exist then call Files.isSameFile. If neither exist then equals is used:

         public static boolean safeIsSameFile(Path a, Path b) throws IOException {
             boolean isA = Files.exists(a) ;
             boolean isB = Files.exists(b);
             return isA && isB
                  ? Files.isSameFile(a, b)
                  : isA || isB
                     ? false
                     : a.equals(b);
         }

      public static void main(String... args) throws IOException {
             Path home = Files.createTempDirectory("test");

             Path a = home.resolve("a");
             Path aa = home.resolve("a");
             Path b = home.resolve("b");
             Path c = home.resolve("c");

             // no files exist.
             assert safeIsSameFile(a, a) : "same non-existant paths";
             assert safeIsSameFile(a, aa) : "equal non-existant paths";
             assert !safeIsSameFile(a, b) : "non-equal non-existant paths";
             assert safeIsSameFile(b, b) : "same non-existant paths";

             // create a.
             Files.write(a, "hello".getBytes(StandardCharsets.UTF_8));

             assert safeIsSameFile(a, a) : "same existing path";
             assert safeIsSameFile(a, aa) : "equal existing paths";
             assert !safeIsSameFile(a, b) : "non-equal one exists paths";

             // create b.
             Files.write(b, "world".getBytes(StandardCharsets.UTF_8));
             assert !safeIsSameFile(a, b) : "non-equal existing paths";
             assert safeIsSameFile(b, b) : "same existing paths";

             // create c.
             assert !safeIsSameFile(a, c) : "non-equal paths only one exists";
             Files.createSymbolicLink(c, a);
             assert safeIsSameFile(a, c) : "non-equal paths same existing file";
             assert !safeIsSameFile(a, b) : "non-equal paths different existing files";

             // remove a.
             Files.delete(a);
              assert safeIsSameFile(a, a) : "same non-existant paths";
             assert safeIsSameFile(a, aa) : "equal non-existant paths";
             assert !safeIsSameFile(a, b) : "non-equal non-existant paths";
             assert safeIsSameFile(b, b) : "same non-existant paths";
             assert !safeIsSameFile(a, c) : "non-equal paths only one exists";
        }


      Exceptions might still be thrown if bad paths are passed or a file system or system error occurs.

      From my reading of the specification of Files.isSameFile this proposed interpretation as provided by safeIsSameFile would be permitted and would generally be compatible with existing user code and indeed would allow simplification of user code by eliminating need of pre-checking existence.

        Attachments

          Activity

            People

            • Assignee:
              bpb Brian Burkhalter
              Reporter:
              mduigou Michael Duigou
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated: