Details
-
Bug
-
Status: Resolved
-
Major
-
Resolution: Fixed
-
2.8.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._LINKS in other calls to Files. if (Files.isDirectory(file, LinkOption._LINKS)) { throw new NoSuchFileException(file.toString()); } final PathCounters pathCounts = Counters.longPathCounters(); final boolean exists = Files.exists(file, LinkOption._LINKS); final long size = exists ? Files.size(file) : 0; if (overrideReadOnly(options) && exists) { setReadOnly(file, false, LinkOption._LINKS); } if (Files.deleteIfExists(file)) { pathCounts.getFileCounter().increment(); pathCounts.getByteCounter().add(size); } return pathCounts; }
This manifests because
Files.exists(file, LinkOption._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