Uploaded image for project: 'Commons IO'
  1. Commons IO
  2. IO-692

PathUtils delete throws an exception when deleting a symlink that points to a file that does not exist

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Resolved
    • Major
    • Resolution: Fixed
    • 2.8.0
    • 2.9.0
    • None
    • None

    Description

      PathUtils.delete throws an Exception when deleting a symlink to a file that doesn't exist, in our case this was when the files were deleted out of sequence.

      Minimal reproducing code running as a unit test (scala). This creates a symlink to a fail that does not exist at all.

      val file = Files.createSymbolicLink(
        Paths.get("target", "x.txt"),
        Paths.get("target",  "y.txt").toAbsolutePath,
      )
      PathUtils.delete(file)
      

      This throws the following exception

      [error]    java.nio.file.NoSuchFileException: target/x.txt (UnixException.java:86)
      [error] sun.nio.fs.UnixException.translateToIOException(UnixException.java:86)
      [error] sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102)
      [error] sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107)
      [error] sun.nio.fs.UnixFileAttributeViews$Basic.readAttributes(UnixFileAttributeViews.java:55)
      [error] sun.nio.fs.UnixFileSystemProvider.readAttributes(UnixFileSystemProvider.java:144)
      [error] org.apache.commons.io.file.PathUtils.deleteFile(PathUtils.java:361)
      [error] org.apache.commons.io.file.PathUtils.delete(PathUtils.java:304)
      [error] org.apache.commons.io.file.PathUtils.delete(PathUtils.java:280)

      The offending code is this in PathUtils

      public static PathCounters deleteFile(final Path file, final DeleteOption... options) throws IOException {
          // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
          if (Files.isDirectory(file, LinkOption.NOFOLLOW_LINKS)) {
              throw new NoSuchFileException(file.toString());
          }
          final PathCounters pathCounts = Counters.longPathCounters();
          final boolean exists = Files.exists(file, LinkOption.NOFOLLOW_LINKS);
          final long size = exists ? Files.size(file) : 0;
          if (overrideReadOnly(options) && exists) {
              setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS);
          }
          if (Files.deleteIfExists(file)) {
              pathCounts.getFileCounter().increment();
              pathCounts.getByteCounter().add(size);
          }
          return pathCounts;
      }
      

      This manifests because 

      Files.exists(file, LinkOption.NOFOLLOW_LINKS); // this always returns true if the symlink exists
      
      Files.size(file) // this throws an exception because there is no file to check the size of

      A guess at the solution would be to only check the size if the file exists and is not a symlink

      final long size = exists && !Files.isSymbolicLink() ? Files.size(file) : 0;

      This was discovered when using FileUtils.deleteDirectory where we have a structure like the following. We clean up these directories when the process finishes, since upgrading to 2.8.0 this fails if the parent directory is deleted before the child.

       work_dir/
         parent_dir/
           big_file.txt
         child_dir/
           symlink_to_big_file.txt

      As a work around using PathUtils.deleteDirectory seems to work regardless of the deletion order

       

      Attachments

        Activity

          People

            Unassigned Unassigned
            matthew_rooney_trimble Matthew Rooney
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: