/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.utils.incubator; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.utils.common.StringUtils; /** * Provides reusable file system operations. * * @author Robert Mischke */ public final class FileSystemOperations { /** * The {@link FileVisitor} of the {@link FileSystemOperations#deleteSandboxDirectory(File)} method. Most of this method's functionality * is provided by this visitor's behavior. * * @author Robert Mischke */ private static final class FilesAndDirsDeletionFileVisitor extends SimpleFileVisitor<Path> { private final Log log; private final DirectoryDeletionStats stats; private FilesAndDirsDeletionFileVisitor(Log log, DirectoryDeletionStats stats) { this.log = log; this.stats = stats; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (attrs.isRegularFile()) { try { Files.delete(file); stats.filesDeleted++; } catch (IOException e) { log.warn(StringUtils.format("Failed to delete %s: %s", file.toString(), e.toString())); stats.errors++; } } else if (attrs.isSymbolicLink()) { try { log.debug(StringUtils.format("Deleting symbolic link %s", file.toString())); Files.delete(file); // note: this deletes the symlink, NOT the target it points to stats.symlinksDeleted++; } catch (IOException e) { log.warn(StringUtils.format("Failed to delete symbolic link %s: %s", file.toString(), e.toString())); stats.errors++; } } else { log.warn("Not deleting a file as it is neither a normal file nor a symbolic link: " + file.toString()); stats.errors++; } return super.visitFile(file, attrs); } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { // conservative check; may never be triggered - misc_ro if (!attrs.isDirectory()) { log.warn("Unexpected type of directory (please examine manually): " + dir.toString()); return FileVisitResult.SKIP_SUBTREE; } // If the directory is a junction, the junction itself is deleted, but the contents are not. if (attrs.isOther()) { try { log.debug(StringUtils.format("Deleting junction point %s", dir.toString())); Files.delete(dir); stats.junctionsDeleted++; } catch (IOException e) { log.warn(StringUtils.format("Failed to delete junction point %s: %s", dir.toString(), e.toString())); stats.errors++; } return FileVisitResult.SKIP_SUBTREE; } return super.preVisitDirectory(dir, attrs); } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { try { Files.delete(dir); stats.directoriesDeleted++; } catch (DirectoryNotEmptyException e) { log.warn(StringUtils.format("Cannot delete directory %s as it is not empty", dir.toString())); // do not count this as an error, as it is usually the follow-up of a previous error } return super.postVisitDirectory(dir, exc); } @Override public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException { try { /* One possibility why the visit could have failed is that the file is a junction to a directory that does not exist anymore. Therefore, the file is deleted now.*/ log.debug(StringUtils.format( "Deleting unknown type of file/directory %s (possibly a link/junction to a non-existing target)", file.toString())); Files.delete(file); stats.unknownDeleted++; } catch (IOException e1) { log.warn(StringUtils.format("Failed to delete unknown type of file/directory %s: %s", file.toString(), e1.toString())); stats.errors++; } return FileVisitResult.CONTINUE; } } /** * Simple holder to share some counters between the deletion {@link FileVisitor} and the calling method. * * @author Robert Mischke */ private static final class DirectoryDeletionStats { private int filesDeleted = 0; // assuming that 2^31 files are sufficient for now ;-) private int symlinksDeleted = 0; private int directoriesDeleted = 0; private int junctionsDeleted = 0; private int unknownDeleted = 0; private int errors = 0; } private FileSystemOperations() { } /** * Recursively deletes the given directory, with additional protection against symbolic links or junction points in that directory that * point to locations outside of it. This kind of symbolic links is dangerous as it might delete files outside of the given directory * with the permissions of the current system user. * * If a symbolic link is encountered, a debug message is logged and an attempt is made to delete the symbolic link (instead of the * target it points to). * * @param directory the directory to delete (with all its contents) */ public static void deleteSandboxDirectory(File directory) { // make absolute for proper log output, if necessary final File absoluteDirectory = directory.getAbsoluteFile(); final Log log = LogFactory.getLog(FileSystemOperations.class); final DirectoryDeletionStats stats = new DirectoryDeletionStats(); try { // note: this call does not follow symlinks when traversing Files.walkFileTree(absoluteDirectory.toPath(), new FilesAndDirsDeletionFileVisitor(log, stats)); } catch (IOException e) { log.error(StringUtils.format("Uncaught exception while trying to delete directory %s", absoluteDirectory.toString()), e); } if (!directory.exists()) { log.debug(StringUtils .format( "Successfully deleted %s (which consisted of %d files, %d symbolic links, %d directories, %d junctions and" + " %d files/directories of unknown type.)", absoluteDirectory.toString(), stats.filesDeleted, stats.symlinksDeleted, stats.directoriesDeleted, stats.junctionsDeleted, stats.unknownDeleted)); } else { log.warn(StringUtils .format( "Failed to fully delete directory %s (deleted %d files, %d symbolic links, %d directories %d junctions and" + " %d files/directories of unknown type; encountered %d errors)", absoluteDirectory.toString(), stats.filesDeleted, stats.symlinksDeleted, stats.directoriesDeleted, stats.junctionsDeleted, stats.unknownDeleted, stats.errors)); } } }