diff --git a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java index ab607f0240..b6aaed358d 100644 --- a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java +++ b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java @@ -18,8 +18,8 @@ package io.netty.util.internal; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; -import java.io.Closeable; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -27,10 +27,14 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermission; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; +import java.util.EnumSet; import java.util.Locale; +import java.util.Set; /** * Helper class to load JNI resources. @@ -227,6 +231,20 @@ public final class NativeLibraryLoader { out = null; loadLibrary(loader, tmpFile.getPath(), true); + } catch (UnsatisfiedLinkError e) { + try { + if (tmpFile != null && tmpFile.isFile() && tmpFile.canRead() && + !NoexecVolumeDetector.canExecuteExecutable(tmpFile)) { + logger.info("{} exists but cannot be executed even when execute permissions set; " + + "check volume for \"noexec\" flag; use -Dio.netty.native.workdir=[path] " + + "to set native working directory separately.", + tmpFile.getPath()); + } + } catch (Throwable t) { + logger.debug("Error checking if {} is on a file store mounted with noexec", tmpFile, t); + } + // Re-throw to fail the load + throw e; } catch (Exception e) { throw (UnsatisfiedLinkError) new UnsatisfiedLinkError( "could not load a native library: " + name).initCause(e); @@ -371,4 +389,42 @@ public final class NativeLibraryLoader { private NativeLibraryLoader() { // Utility } + + private static final class NoexecVolumeDetector { + + private static boolean canExecuteExecutable(File file) throws IOException { + if (PlatformDependent.javaVersion() < 7) { + // Pre-JDK7, the Java API did not directly support POSIX permissions; instead of implementing a custom + // work-around, assume true, which disables the check. + return true; + } + + // If we can already execute, there is nothing to do. + if (file.canExecute()) { + return true; + } + + // On volumes, with noexec set, even files with the executable POSIX permissions will fail to execute. + // The File#canExecute() method honors this behavior, probaby via parsing the noexec flag when initializing + // the UnixFileStore, though the flag is not exposed via a public API. To find out if library is being + // loaded off a volume with noexec, confirm or add executalbe permissions, then check File#canExecute(). + + Set existingFilePermissions = Files.getPosixFilePermissions(file.toPath()); + Set executePermissions = EnumSet.of(PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_EXECUTE, + PosixFilePermission.OTHERS_EXECUTE); + if (existingFilePermissions.containsAll(executePermissions)) { + return false; + } + + Set newPermissions = EnumSet.copyOf(existingFilePermissions); + newPermissions.addAll(executePermissions); + Files.setPosixFilePermissions(file.toPath(), newPermissions); + return file.canExecute(); + } + + private NoexecVolumeDetector() { + // Utility + } + } } diff --git a/pom.xml b/pom.xml index 6cdbd5ba2b..354e720c21 100644 --- a/pom.xml +++ b/pom.xml @@ -709,6 +709,13 @@ java.net.InetAddress + + + java.nio.file.attribute.PosixFilePermission + java.nio.file.Files + java.nio.file.LinkOption + java.nio.file.Path + java.io.File