/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.util; import java.io.File; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Execute; /** * Contains methods related to symbolic links - or what Ant thinks is * a symbolic link based on the absent support for them in Java. * * @since Ant 1.8.0 */ public class SymbolicLinkUtils { private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); /** * Shared instance. */ private static final SymbolicLinkUtils PRIMARY_INSTANCE = new SymbolicLinkUtils(); /** * Method to retrieve The SymbolicLinkUtils, which is shared by * all users of this method. * @return an instance of SymbolicLinkUtils. */ public static SymbolicLinkUtils getSymbolicLinkUtils() { // keep the door open for Java X.Y specific subclass if symbolic // links ever become supported in the classlib return PRIMARY_INSTANCE; } /** * Empty constructor. */ protected SymbolicLinkUtils() { } /** * Checks whether a given file is a symbolic link. * *
It doesn't really test for symbolic links but whether the * canonical and absolute paths of the file are identical--this * may lead to false positives on some platforms.
* * @param file the file to test. Must not be null. * * @return true if the file is a symbolic link. * @throws IOException on error. */ public boolean isSymbolicLink(final File file) throws IOException { return isSymbolicLink(file.getParentFile(), file.getName()); } /** * Checks whether a given file is a symbolic link. * *It doesn't really test for symbolic links but whether the * canonical and absolute paths of the file are identical--this * may lead to false positives on some platforms.
* * @param name the name of the file to test. * * @return true if the file is a symbolic link. * @throws IOException on error. */ public boolean isSymbolicLink(final String name) throws IOException { return isSymbolicLink(new File(name)); } /** * Checks whether a given file is a symbolic link. * *It doesn't really test for symbolic links but whether the * canonical and absolute paths of the file are identical--this * may lead to false positives on some platforms.
* * @param parent the parent directory of the file to test * @param name the name of the file to test. * * @return true if the file is a symbolic link. * @throws IOException on error. */ public boolean isSymbolicLink(final File parent, final String name) throws IOException { final File toTest = parent != null ? new File(parent.getCanonicalPath(), name) : new File(name); return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath()); } /** * Checks whether a given file is a broken symbolic link. * *It doesn't really test for symbolic links but whether Java * reports that the File doesn't exist but its parent's child list * contains it--this may lead to false positives on some * platforms.
* *Note that #isSymbolicLink returns false if this method * returns true since Java won't produce a canonical name * different from the abolute one if the link is broken.
* * @param name the name of the file to test. * * @return true if the file is a broken symbolic link. * @throws IOException on error. */ public boolean isDanglingSymbolicLink(final String name) throws IOException { return isDanglingSymbolicLink(new File(name)); } /** * Checks whether a given file is a broken symbolic link. * *It doesn't really test for symbolic links but whether Java * reports that the File doesn't exist but its parent's child list * contains it--this may lead to false positives on some * platforms.
* *Note that #isSymbolicLink returns false if this method * returns true since Java won't produce a canonical name * different from the abolute one if the link is broken.
* * @param file the file to test. * * @return true if the file is a broken symbolic link. * @throws IOException on error. */ public boolean isDanglingSymbolicLink(final File file) throws IOException { return isDanglingSymbolicLink(file.getParentFile(), file.getName()); } /** * Checks whether a given file is a broken symbolic link. * *It doesn't really test for symbolic links but whether Java * reports that the File doesn't exist but its parent's child list * contains it--this may lead to false positives on some * platforms.
* *Note that #isSymbolicLink returns false if this method * returns true since Java won't produce a canonical name * different from the abolute one if the link is broken.
* * @param parent the parent directory of the file to test * @param name the name of the file to test. * * @return true if the file is a broken symbolic link. * @throws IOException on error. */ public boolean isDanglingSymbolicLink(final File parent, final String name) throws IOException { final File f = new File(parent, name); if (!f.exists()) { final String localName = f.getName(); final String[] c = parent.list(new FilenameFilter() { public boolean accept(final File d, final String n) { return localName.equals(n); } }); return c != null && c.length > 0; } return false; } /** * Delete a symlink (without deleting the associated resource). * *This is a utility method that removes a unix symlink without * removing the resource that the symlink points to. If it is * accidentally invoked on a real file, the real file will not be * harmed, but silently ignored.
* *Normally this method works by * getting the canonical path of the link, using the canonical path to * rename the resource (breaking the link) and then deleting the link. * The resource is then returned to its original name inside a finally * block to ensure that the resource is unharmed even in the event of * an exception.
* *There may be cases where the algorithm described above doesn't work, * in that case the method tries to use the native "rm" command on * the symlink instead.
* * @param link AFile
object of the symlink to delete.
* @param task An Ant Task required if "rm" needs to be invoked.
*
* @throws IOException If calls to File.rename
,
* File.delete
or File.getCanonicalPath
* fail.
* @throws BuildException if the execution of "rm" failed.
*/
public void deleteSymbolicLink(File link, final Task task)
throws IOException {
if (isDanglingSymbolicLink(link)) {
if (!link.delete()) {
throw new IOException("failed to remove dangling symbolic link "
+ link);
}
return;
}
if (!isSymbolicLink(link)) {
// plain file, not a link
return;
}
if (!link.exists()) {
throw new FileNotFoundException("No such symbolic link: " + link);
}
// find the resource of the existing link:
final File target = link.getCanonicalFile();
// no reason to try the renaming algorithm if we aren't allowed to
// write to the target's parent directory. Let's hope that
// File.canWrite works on all platforms.
if (task == null || target.getParentFile().canWrite()) {
// rename the resource, thus breaking the link:
final File temp = FILE_UTILS.createTempFile("symlink", ".tmp",
target.getParentFile(), false,
false);
if (FILE_UTILS.isLeadingPath(target, link)) {
// link points to a parent directory, renaming the parent
// will rename the file
link = new File(temp,
FILE_UTILS.removeLeadingPath(target, link));
}
boolean renamedTarget = false;
try {
try {
FILE_UTILS.rename(target, temp);
renamedTarget = true;
} catch (final IOException e) {
throw new IOException("Couldn't rename resource when "
+ "attempting to delete '" + link
+ "'. Reason: " + e.getMessage());
}
// delete the (now) broken link:
if (!link.delete()) {
throw new IOException("Couldn't delete symlink: "
+ link
+ " (was it a real file? is this "
+ "not a UNIX system?)");
}
} finally {
if (renamedTarget) {
// return the resource to its original name:
try {
FILE_UTILS.rename(temp, target);
} catch (final IOException e) {
throw new IOException("Couldn't return resource "
+ temp
+ " to its original name: "
+ target.getAbsolutePath()
+ ". Reason: " + e.getMessage()
+ "\n THE RESOURCE'S NAME ON DISK"
+ " HAS BEEN CHANGED BY THIS"
+ " ERROR!\n");
}
}
}
} else {
Execute.runCommand(task,
new String[] {"rm", link.getAbsolutePath()});
}
}
}