private void unzipPlugin(Path zip, Path target) throws IOException { Files.createDirectories(target); try (ZipInputStream zipInput = new ZipInputStream(Files.newInputStream(zip))) { ZipEntry entry; byte[] buffer = new byte[8192]; while ((entry = zipInput.getNextEntry()) != null) { Path targetFile = target.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() == false) { try (OutputStream out = Files.newOutputStream(targetFile)) { int len; while ((len = zipInput.read(buffer)) >= 0) { out.write(buffer, 0, len); } } } zipInput.closeEntry(); } } }
/** check a candidate plugin for jar hell before installing it */ private void jarHellCheck(Path candidate, boolean isolated) throws IOException { // create list of current jars in classpath final List<URL> jars = new ArrayList<>(Arrays.asList(JarHell.parseClassPath())); // read existing bundles. this does some checks on the installation too. List<Bundle> bundles = PluginsService.getPluginBundles(environment.pluginsFile()); // if we aren't isolated, we need to jarhellcheck against any other non-isolated plugins // thats always the first bundle if (isolated == false) { jars.addAll(bundles.get(0).urls); } // add plugin jars to the list Path pluginJars[] = FileSystemUtils.files(candidate, "*.jar"); for (Path jar : pluginJars) { jars.add(jar.toUri().toURL()); } // check combined (current classpath + new jars to-be-added) try { JarHell.checkJarHell(jars.toArray(new URL[jars.size()])); } catch (Exception ex) { throw new RuntimeException(ex); } }
public void listInstalledPlugins(Terminal terminal) throws IOException { Path[] plugins = getListInstalledPlugins(); terminal.println("Installed plugins in %s:", environment.pluginsFile().toAbsolutePath()); if (plugins == null || plugins.length == 0) { terminal.println(" - No plugin detected"); } else { for (Path plugin : plugins) { terminal.println(" - " + plugin.getFileName()); } } }
/** * we check whether we need to remove the top-level folder while extracting sometimes (e.g. * github) the downloaded archive contains a top-level folder which needs to be removed */ private Path findPluginRoot(Path dir) throws IOException { if (Files.exists(dir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES))) { return dir; } else { final Path[] topLevelFiles = FileSystemUtils.files(dir); if (topLevelFiles.length == 1 && Files.isDirectory(topLevelFiles[0])) { Path subdir = topLevelFiles[0]; if (Files.exists(subdir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES))) { return subdir; } } } throw new RuntimeException( "Could not find plugin descriptor '" + PluginInfo.ES_PLUGIN_PROPERTIES + "' in plugin zip"); }
public void removePlugin(String name, Terminal terminal) throws IOException { if (name == null) { throw new IllegalArgumentException("plugin name must be supplied with remove [name]."); } PluginHandle pluginHandle = PluginHandle.parse(name); boolean removed = false; checkForForbiddenName(pluginHandle.name); Path pluginToDelete = pluginHandle.extractedDir(environment); if (Files.exists(pluginToDelete)) { terminal.println(VERBOSE, "Removing: %s", pluginToDelete); try { IOUtils.rm(pluginToDelete); } catch (IOException ex) { throw new IOException( "Unable to remove " + pluginHandle.name + ". Check file permissions on " + pluginToDelete.toString(), ex); } removed = true; } Path binLocation = pluginHandle.binDir(environment); if (Files.exists(binLocation)) { terminal.println(VERBOSE, "Removing: %s", binLocation); try { IOUtils.rm(binLocation); } catch (IOException ex) { throw new IOException( "Unable to remove " + pluginHandle.name + ". Check file permissions on " + binLocation.toString(), ex); } removed = true; } if (removed) { terminal.println("Removed %s", name); } else { terminal.println( "Plugin %s not found. Run \"plugin list\" to get list of installed plugins.", name); } }
private void copyBinDirectory( Path sourcePluginBinDirectory, Path destPluginBinDirectory, String pluginName, Terminal terminal) throws IOException { boolean canCopyFromSource = Files.exists(sourcePluginBinDirectory) && Files.isReadable(sourcePluginBinDirectory) && Files.isDirectory(sourcePluginBinDirectory); if (canCopyFromSource) { terminal.println(VERBOSE, "Found bin, moving to %s", destPluginBinDirectory.toAbsolutePath()); if (Files.exists(destPluginBinDirectory)) { IOUtils.rm(destPluginBinDirectory); } try { Files.createDirectories(destPluginBinDirectory.getParent()); FileSystemUtils.move(sourcePluginBinDirectory, destPluginBinDirectory); } catch (IOException e) { throw new IOException( "Could not move [" + sourcePluginBinDirectory + "] to [" + destPluginBinDirectory + "]", e); } if (Environment.getFileStore(destPluginBinDirectory) .supportsFileAttributeView(PosixFileAttributeView.class)) { final PosixFileAttributes parentDirAttributes = Files.getFileAttributeView( destPluginBinDirectory.getParent(), PosixFileAttributeView.class) .readAttributes(); // copy permissions from parent bin directory final Set<PosixFilePermission> filePermissions = new HashSet<>(); for (PosixFilePermission posixFilePermission : parentDirAttributes.permissions()) { switch (posixFilePermission) { case OWNER_EXECUTE: case GROUP_EXECUTE: case OTHERS_EXECUTE: break; default: filePermissions.add(posixFilePermission); } } // add file execute permissions to existing perms, so execution will work. filePermissions.add(PosixFilePermission.OWNER_EXECUTE); filePermissions.add(PosixFilePermission.GROUP_EXECUTE); filePermissions.add(PosixFilePermission.OTHERS_EXECUTE); Files.walkFileTree( destPluginBinDirectory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (attrs.isRegularFile()) { setPosixFileAttributes( file, parentDirAttributes.owner(), parentDirAttributes.group(), filePermissions); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { setPosixFileAttributes( dir, parentDirAttributes.owner(), parentDirAttributes.group(), parentDirAttributes.permissions()); return FileVisitResult.CONTINUE; } }); } else { terminal.println( VERBOSE, "Skipping posix permissions - filestore doesn't support posix permission"); } terminal.println( VERBOSE, "Installed %s into %s", pluginName, destPluginBinDirectory.toAbsolutePath()); } }
private void extract(PluginHandle pluginHandle, Terminal terminal, Path pluginFile) throws IOException { // unzip plugin to a staging temp dir, named for the plugin Path tmp = Files.createTempDirectory(environment.tmpFile(), null); Path root = tmp.resolve(pluginHandle.name); unzipPlugin(pluginFile, root); // find the actual root (in case its unzipped with extra directory wrapping) root = findPluginRoot(root); // read and validate the plugin descriptor PluginInfo info = PluginInfo.readFromProperties(root); terminal.println(VERBOSE, "%s", info); // update name in handle based on 'name' property found in descriptor file pluginHandle = new PluginHandle(info.getName(), pluginHandle.version, pluginHandle.user); final Path extractLocation = pluginHandle.extractedDir(environment); if (Files.exists(extractLocation)) { throw new IOException( "plugin directory " + extractLocation.toAbsolutePath() + " already exists. To update the plugin, uninstall it first using 'remove " + pluginHandle.name + "' command"); } // check for jar hell before any copying if (info.isJvm()) { jarHellCheck(root, info.isIsolated()); } // install plugin FileSystemUtils.copyDirectoryRecursively(root, extractLocation); terminal.println("Installed %s into %s", pluginHandle.name, extractLocation.toAbsolutePath()); // cleanup tryToDeletePath(terminal, tmp, pluginFile); // take care of bin/ by moving and applying permissions if needed Path sourcePluginBinDirectory = extractLocation.resolve("bin"); Path destPluginBinDirectory = pluginHandle.binDir(environment); boolean needToCopyBinDirectory = Files.exists(sourcePluginBinDirectory); if (needToCopyBinDirectory) { if (Files.exists(destPluginBinDirectory) && !Files.isDirectory(destPluginBinDirectory)) { tryToDeletePath(terminal, extractLocation); throw new IOException( "plugin bin directory " + destPluginBinDirectory + " is not a directory"); } try { copyBinDirectory( sourcePluginBinDirectory, destPluginBinDirectory, pluginHandle.name, terminal); } catch (IOException e) { // rollback and remove potentially before installed leftovers terminal.printError( "Error copying bin directory [%s] to [%s], cleaning up, reason: %s", sourcePluginBinDirectory, destPluginBinDirectory, ExceptionsHelper.detailedMessage(e)); tryToDeletePath(terminal, extractLocation, pluginHandle.binDir(environment)); throw e; } } Path sourceConfigDirectory = extractLocation.resolve("config"); Path destConfigDirectory = pluginHandle.configDir(environment); boolean needToCopyConfigDirectory = Files.exists(sourceConfigDirectory); if (needToCopyConfigDirectory) { if (Files.exists(destConfigDirectory) && !Files.isDirectory(destConfigDirectory)) { tryToDeletePath(terminal, extractLocation, destPluginBinDirectory); throw new IOException( "plugin config directory " + destConfigDirectory + " is not a directory"); } try { terminal.println( VERBOSE, "Found config, moving to %s", destConfigDirectory.toAbsolutePath()); moveFilesWithoutOverwriting(sourceConfigDirectory, destConfigDirectory, ".new"); if (Environment.getFileStore(destConfigDirectory) .supportsFileAttributeView(PosixFileAttributeView.class)) { // We copy owner, group and permissions from the parent ES_CONFIG directory, assuming they // were properly set depending // on how es was installed in the first place: can be root:elasticsearch (750) if es was // installed from rpm/deb packages // or most likely elasticsearch:elasticsearch if installed from tar/zip. As for // permissions we don't rely on umask. final PosixFileAttributes parentDirAttributes = Files.getFileAttributeView( destConfigDirectory.getParent(), PosixFileAttributeView.class) .readAttributes(); // for files though, we make sure not to copy execute permissions from the parent dir and // leave them untouched final Set<PosixFilePermission> baseFilePermissions = new HashSet<>(); for (PosixFilePermission posixFilePermission : parentDirAttributes.permissions()) { switch (posixFilePermission) { case OWNER_EXECUTE: case GROUP_EXECUTE: case OTHERS_EXECUTE: break; default: baseFilePermissions.add(posixFilePermission); } } Files.walkFileTree( destConfigDirectory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (attrs.isRegularFile()) { Set<PosixFilePermission> newFilePermissions = new HashSet<>(baseFilePermissions); Set<PosixFilePermission> currentFilePermissions = Files.getPosixFilePermissions(file); for (PosixFilePermission posixFilePermission : currentFilePermissions) { switch (posixFilePermission) { case OWNER_EXECUTE: case GROUP_EXECUTE: case OTHERS_EXECUTE: newFilePermissions.add(posixFilePermission); } } setPosixFileAttributes( file, parentDirAttributes.owner(), parentDirAttributes.group(), newFilePermissions); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { setPosixFileAttributes( dir, parentDirAttributes.owner(), parentDirAttributes.group(), parentDirAttributes.permissions()); return FileVisitResult.CONTINUE; } }); } else { terminal.println( VERBOSE, "Skipping posix permissions - filestore doesn't support posix permission"); } terminal.println( VERBOSE, "Installed %s into %s", pluginHandle.name, destConfigDirectory.toAbsolutePath()); } catch (IOException e) { terminal.printError( "Error copying config directory [%s] to [%s], cleaning up, reason: %s", sourceConfigDirectory, destConfigDirectory, ExceptionsHelper.detailedMessage(e)); tryToDeletePath(terminal, extractLocation, destPluginBinDirectory, destConfigDirectory); throw e; } } }