From a839644826be4de6bf1ad1ac042eb68fdc1e9db9 Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Tue, 30 Jun 2015 16:42:01 -0400 Subject: [PATCH] detect jar hell before installing a plugin --- .../org/elasticsearch/bootstrap/JarHell.java | 16 +++-- .../elasticsearch/plugins/PluginManager.java | 68 +++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/bootstrap/JarHell.java b/core/src/main/java/org/elasticsearch/bootstrap/JarHell.java index 2884da4fe1c..40edc695689 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/JarHell.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/JarHell.java @@ -41,21 +41,29 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; /** Simple check for duplicate class files across the classpath */ -class JarHell { +public class JarHell { /** * Checks the current classloader for duplicate classes * @throws IllegalStateException if jar hell was found */ - @SuppressForbidden(reason = "needs JarFile for speed, just reading entries") - static void checkJarHell() throws Exception { + public static void checkJarHell() throws Exception { ClassLoader loader = JarHell.class.getClassLoader(); if (loader instanceof URLClassLoader == false) { return; } + checkJarHell(((URLClassLoader)loader).getURLs()); + } + + /** + * Checks the set of URLs for duplicate classes + * @throws IllegalStateException if jar hell was found + */ + @SuppressForbidden(reason = "needs JarFile for speed, just reading entries") + public static void checkJarHell(URL urls[]) throws Exception { final Map clazzes = new HashMap<>(32768); Set seenJars = new HashSet<>(); - for (final URL url : ((URLClassLoader)loader).getURLs()) { + for (final URL url : urls) { String path = URLDecoder.decode(url.getPath(), "UTF-8"); if (path.endsWith(".jar")) { if (!seenJars.add(path)) { diff --git a/core/src/main/java/org/elasticsearch/plugins/PluginManager.java b/core/src/main/java/org/elasticsearch/plugins/PluginManager.java index de16aeb5691..9413e05df16 100644 --- a/core/src/main/java/org/elasticsearch/plugins/PluginManager.java +++ b/core/src/main/java/org/elasticsearch/plugins/PluginManager.java @@ -22,8 +22,10 @@ package org.elasticsearch.plugins; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; + import org.apache.lucene.util.IOUtils; import org.elasticsearch.*; +import org.elasticsearch.bootstrap.JarHell; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.cli.Terminal; import org.elasticsearch.common.collect.Tuple; @@ -35,14 +37,18 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.node.internal.InternalSettingsPreparer; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLClassLoader; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFilePermission; import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import static org.elasticsearch.common.Strings.hasLength; import static org.elasticsearch.common.io.FileSystemUtils.moveFilesWithoutOverwriting; @@ -147,6 +153,41 @@ public class PluginManager { if (!downloaded) { throw new IOException("failed to download out of all possible locations..., use --verbose to get detailed information"); } + + // unzip plugin to a temp dir + Path tmp = unzipToTemporary(pluginFile); + + // create list of current jars in classpath + final List jars = new ArrayList<>(); + ClassLoader loader = PluginManager.class.getClassLoader(); + if (loader instanceof URLClassLoader) { + for (URL url : ((URLClassLoader) loader).getURLs()) { + jars.add(url); + } + } + + // add any jars we find in the plugin to the list + Files.walkFileTree(tmp, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".jar")) { + jars.add(file.toUri().toURL()); + } + return FileVisitResult.CONTINUE; + } + }); + + // check combined (current classpath + new jars to-be-added) + try { + JarHell.checkJarHell(jars.toArray(new URL[0])); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + // cleanup + IOUtils.rm(tmp); + + // TODO: we have a tmpdir made above, so avoid zipfilesystem try (FileSystem zipFile = FileSystems.newFileSystem(pluginFile, null)) { for (final Path root : zipFile.getRootDirectories() ) { final Path[] topLevelFiles = FileSystemUtils.files(root); @@ -260,6 +301,33 @@ public class PluginManager { } } + private Path unzipToTemporary(Path zip) throws IOException { + Path tmp = Files.createTempDirectory(environment.tmpFile(), null); + + try (ZipInputStream zipInput = new ZipInputStream(Files.newInputStream(zip))) { + ZipEntry entry; + byte[] buffer = new byte[8192]; + while ((entry = zipInput.getNextEntry()) != null) { + Path targetFile = tmp.resolve(entry.getName()); + + // be on the safe side: do not rely on that directories are always extracted + // before their children (although this makes sense, but is it guaranteed?) + Files.createDirectories(targetFile.getParent()); + if (!entry.isDirectory()) { + OutputStream out = Files.newOutputStream(targetFile); + int len; + while((len = zipInput.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + out.close(); + } + zipInput.closeEntry(); + } + } + + return tmp; + } + public void removePlugin(String name) throws IOException { if (name == null) { throw new IllegalArgumentException("plugin name must be supplied with --remove [name].");