mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-10 02:26:18 +00:00
[JENKINS-39179] [JENKINS-36088] Always use NIO to create and detect symbolic links and Windows junctions (#3133)
* Always use NIO to detect symlinks * Make assertion failure message consistent * Catch NoSuchFileException to keep tests passing * Make method name more specific and simlify assumption * Remove obsolete comment and reword the main comment in isSymlink * Deprecate Kernel32Util#isJunctionOrSymlink * Use assumptions for junction creation and add messages to assumptions * Replace deprecated code with recommended alternative * Add comment explaining call to DosFileAttributes#isOther * Do not fall back to native code when creating symlinks * Log FileSystemExceptions when creating symbolic links * Catch InvalidPathException and rethrow as IOException * Deprecate Kernel32Utils#createSymbolicLink and #getWin32FileAttributes * Preserve original logging behavior on Windows and remove useless call to Util#displayIOException
This commit is contained in:
1 parent
7d29d4df37
commit
52fa4d90b9
4 files changed
+99
-125
No files matched your search
@@ -25,15 +25,12 @@ package hudson;
|
||||
|
||||
import java.nio.file.InvalidPathException;
|
||||
import jenkins.util.SystemProperties;
|
||||
import com.sun.jna.Native;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.Proc.LocalProc;
|
||||
import hudson.model.TaskListener;
|
||||
import hudson.os.PosixAPI;
|
||||
import hudson.util.QuotedStringTokenizer;
|
||||
import hudson.util.VariableResolver;
|
||||
import hudson.util.jna.WinIOException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.time.FastDateFormat;
|
||||
@@ -67,8 +64,12 @@ import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.DosFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.NumberFormat;
|
||||
@@ -81,9 +82,6 @@ import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import hudson.util.jna.Kernel32Utils;
|
||||
import static hudson.util.jna.GNUCLibrary.LIBC;
|
||||
|
||||
import java.security.DigestInputStream;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
@@ -493,7 +491,6 @@ public class Util {
|
||||
/**
|
||||
* Checks if the given file represents a symlink.
|
||||
*/
|
||||
//Taken from http://svn.apache.org/viewvc/maven/shared/trunk/file-management/src/main/java/org/apache/maven/shared/model/fileset/util/FileSetManager.java?view=markup
|
||||
public static boolean isSymlink(@Nonnull File file) throws IOException {
|
||||
/*
|
||||
* Windows Directory Junctions are effectively the same as Linux symlinks to directories.
|
||||
@@ -502,44 +499,29 @@ public class Util {
|
||||
* you have to go through BasicFileAttributes and do the following check:
|
||||
* isSymbolicLink() || isOther()
|
||||
* The isOther() call will include Windows reparse points, of which a directory junction is.
|
||||
*
|
||||
* Since we already have a function that detects Windows junctions or symlinks and treats them
|
||||
* both as symlinks, let's use that function and always call it before calling down to the
|
||||
* NIO2 API.
|
||||
*
|
||||
* It also includes includes devices, but reading the attributes of a device with NIO fails
|
||||
* or returns false for isOther(). (i.e. named pipes such as \\.\pipe\JenkinsTestPipe return
|
||||
* false for isOther(), and drives such as \\.\PhysicalDrive0 throw an exception when
|
||||
* calling readAttributes.
|
||||
*/
|
||||
if (Functions.isWindows()) {
|
||||
try {
|
||||
return Kernel32Utils.isJunctionOrSymlink(file);
|
||||
} catch (UnsupportedOperationException | LinkageError e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
Boolean r = isSymlinkJava7(file);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
String name = file.getName();
|
||||
if (name.equals(".") || name.equals(".."))
|
||||
return false;
|
||||
|
||||
File fileInCanonicalParent;
|
||||
File parentDir = file.getParentFile();
|
||||
if ( parentDir == null ) {
|
||||
fileInCanonicalParent = file;
|
||||
} else {
|
||||
fileInCanonicalParent = new File( parentDir.getCanonicalPath(), name );
|
||||
}
|
||||
return !fileInCanonicalParent.getCanonicalFile().equals( fileInCanonicalParent.getAbsoluteFile() );
|
||||
}
|
||||
|
||||
@SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
|
||||
private static Boolean isSymlinkJava7(@Nonnull File file) throws IOException {
|
||||
try {
|
||||
Path path = file.toPath();
|
||||
return Files.isSymbolicLink(path);
|
||||
} catch (Exception x) {
|
||||
throw (IOException) new IOException(x.toString()).initCause(x);
|
||||
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
||||
if (attrs.isSymbolicLink()) {
|
||||
return true;
|
||||
} else if (attrs instanceof DosFileAttributes) {
|
||||
/* Returns true for non-symbolic link reparse points and devices. We could call
|
||||
* WindowsFileAttributes#isReparsePoint with reflection instead to exclude devices,
|
||||
* but as mentioned in the above comment this does not appear to be an issue.
|
||||
*/
|
||||
return attrs.isOther();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (InvalidPathException e) {
|
||||
throw new IOException(e);
|
||||
} catch (NoSuchFileException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1341,78 +1323,6 @@ public class Util {
|
||||
*/
|
||||
public static void createSymlink(@Nonnull File baseDir, @Nonnull String targetPath,
|
||||
@Nonnull String symlinkPath, @Nonnull TaskListener listener) throws InterruptedException {
|
||||
try {
|
||||
if (createSymlinkJava7(baseDir, targetPath, symlinkPath)) {
|
||||
return;
|
||||
}
|
||||
if (NO_SYMLINK) {
|
||||
return;
|
||||
}
|
||||
|
||||
File symlinkFile = new File(baseDir, symlinkPath);
|
||||
if (Functions.isWindows()) {
|
||||
if (symlinkFile.exists()) {
|
||||
symlinkFile.delete();
|
||||
}
|
||||
File dst = new File(symlinkFile,"..\\"+targetPath);
|
||||
try {
|
||||
Kernel32Utils.createSymbolicLink(symlinkFile,targetPath,dst.isDirectory());
|
||||
} catch (WinIOException e) {
|
||||
if (e.getErrorCode()==1314) {/* ERROR_PRIVILEGE_NOT_HELD */
|
||||
warnWindowsSymlink();
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
// not available on this Windows
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String errmsg = "";
|
||||
// if a file or a directory exists here, delete it first.
|
||||
// try simple delete first (whether exists() or not, as it may be symlink pointing
|
||||
// to non-existent target), but fallback to "rm -rf" to delete non-empty dir.
|
||||
if (!symlinkFile.delete() && symlinkFile.exists())
|
||||
// ignore a failure.
|
||||
new LocalProc(new String[]{"rm","-rf", symlinkPath},new String[0],listener.getLogger(), baseDir).join();
|
||||
|
||||
Integer r=null;
|
||||
if (!SYMLINK_ESCAPEHATCH) {
|
||||
try {
|
||||
r = LIBC.symlink(targetPath,symlinkFile.getAbsolutePath());
|
||||
if (r!=0) {
|
||||
r = Native.getLastError();
|
||||
errmsg = LIBC.strerror(r);
|
||||
}
|
||||
} catch (LinkageError e) {
|
||||
// if JNA is unavailable, fall back.
|
||||
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
|
||||
POSIX posix = PosixAPI.jnr();
|
||||
if (posix.isNative()) {
|
||||
// TODO should we rethrow PosixException as IOException here?
|
||||
r = posix.symlink(targetPath,symlinkFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (r==null) {
|
||||
// if all else fail, fall back to the most expensive approach of forking a process
|
||||
// TODO is this really necessary? JavaPOSIX should do this automatically
|
||||
r = new LocalProc(new String[]{
|
||||
"ln","-s", targetPath, symlinkPath},
|
||||
new String[0],listener.getLogger(), baseDir).join();
|
||||
}
|
||||
if (r!=0)
|
||||
listener.getLogger().println(String.format("ln -s %s %s failed: %d %s",targetPath, symlinkFile, r, errmsg));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
PrintStream log = listener.getLogger();
|
||||
log.printf("ln %s %s failed%n",targetPath, new File(baseDir, symlinkPath));
|
||||
Util.displayIOException(e,listener);
|
||||
Functions.printStackTrace(e, log);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean createSymlinkJava7(@Nonnull File baseDir, @Nonnull String targetPath, @Nonnull String symlinkPath) throws IOException {
|
||||
try {
|
||||
Path path = new File(baseDir, symlinkPath).toPath();
|
||||
Path target = Paths.get(targetPath, new String[0]);
|
||||
@@ -1433,19 +1343,18 @@ public class Util {
|
||||
throw fileAlreadyExistsException;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return true; // no symlinks on this platform
|
||||
} catch (FileSystemException e) {
|
||||
if (Functions.isWindows()) {
|
||||
PrintStream log = listener.getLogger();
|
||||
log.print("Symbolic links are not supported on this platform");
|
||||
Functions.printStackTrace(e, log);
|
||||
} catch (InvalidPathException | IOException e) {
|
||||
if (Functions.isWindows() && e instanceof FileSystemException) {
|
||||
warnWindowsSymlink();
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
} catch (IOException x) {
|
||||
throw x;
|
||||
} catch (Exception x) {
|
||||
throw (IOException) new IOException(x.toString()).initCause(x);
|
||||
PrintStream log = listener.getLogger();
|
||||
log.printf("ln %s %s failed%n",targetPath, new File(baseDir, symlinkPath));
|
||||
Functions.printStackTrace(e, log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user