Skip to content

Commit

Permalink
PluginManager: Dont leave leftover files on unsuccessful installs
Browse files Browse the repository at this point in the history
If the plugin manager cannot successfully install a plugin, ensure
that every directory is cleaned up again. This includes

plugins/foo
config/foo
bin/foo

Closes elastic#12749
  • Loading branch information
spinscale committed Oct 8, 2015
1 parent 9688e86 commit 70b2d90
Show file tree
Hide file tree
Showing 2 changed files with 363 additions and 38 deletions.
105 changes: 67 additions & 38 deletions core/src/main/java/org/elasticsearch/plugins/PluginManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@
package org.elasticsearch.plugins;

import org.apache.lucene.util.IOUtils;
import org.elasticsearch.Build;
import org.elasticsearch.ElasticsearchCorruptionException;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.Version;
import org.elasticsearch.*;
import org.elasticsearch.bootstrap.JarHell;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.cli.Terminal;
Expand All @@ -40,21 +36,11 @@
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
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.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
import java.util.*;
import java.util.stream.StreamSupport;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
Expand Down Expand Up @@ -225,7 +211,6 @@ private Path download(PluginHandle pluginHandle, Terminal terminal) throws IOExc
}

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);
Expand Down Expand Up @@ -255,22 +240,74 @@ private void extract(PluginHandle pluginHandle, Terminal terminal, Path pluginFi
terminal.println("Installed %s into %s", pluginHandle.name, extractLocation.toAbsolutePath());

// cleanup
IOUtils.rm(tmp, pluginFile);
tryToDeletePath(terminal, tmp, pluginFile);

// take care of bin/ by moving and applying permissions if needed
Path binFile = extractLocation.resolve("bin");
if (Files.isDirectory(binFile)) {
Path toLocation = pluginHandle.binDir(environment);
terminal.println(VERBOSE, "Found bin, moving to %s", toLocation.toAbsolutePath());
if (Files.exists(toLocation)) {
IOUtils.rm(toLocation);
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, pluginHandle.binDir(environment), e.getMessage());
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, pluginHandle.binDir(environment));
throw new IOException("plugin config directory " + destConfigDirectory + " is not a directory");
}

try {
FileSystemUtils.move(binFile, toLocation);
terminal.println(VERBOSE, "Found config, moving to %s", destConfigDirectory.toAbsolutePath());
moveFilesWithoutOverwriting(sourceConfigDirectory, destConfigDirectory, ".new");
terminal.println(VERBOSE, "Installed %s into %s", pluginHandle.name, destConfigDirectory.toAbsolutePath());
} catch (IOException e) {
throw new IOException("Could not move [" + binFile + "] to [" + toLocation + "]", e);
terminal.printError("Error copying config directory [%s] to [%s], cleaning up, reason: %s", sourceConfigDirectory, pluginHandle.binDir(environment), e.getMessage());
tryToDeletePath(terminal, extractLocation, pluginHandle.binDir(environment), pluginHandle.configDir(environment));
throw e;
}
if (Environment.getFileStore(toLocation).supportsFileAttributeView(PosixFileAttributeView.class)) {
}
}

private void tryToDeletePath(Terminal terminal, Path ... paths) {
for (Path path : paths) {
try {
IOUtils.rm(path);
} catch (IOException e) {
terminal.printError(e);
}
}
}

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)) {
// add read and execute permissions to existing perms, so execution will work.
// read should generally be set already, but set it anyway: don't rely on umask...
final Set<PosixFilePermission> executePerms = new HashSet<>();
Expand All @@ -280,7 +317,7 @@ private void extract(PluginHandle pluginHandle, Terminal terminal, Path pluginFi
executePerms.add(PosixFilePermission.OWNER_EXECUTE);
executePerms.add(PosixFilePermission.GROUP_EXECUTE);
executePerms.add(PosixFilePermission.OTHERS_EXECUTE);
Files.walkFileTree(toLocation, new SimpleFileVisitor<Path>() {
Files.walkFileTree(destPluginBinDirectory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (attrs.isRegularFile()) {
Expand All @@ -294,15 +331,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
} else {
terminal.println(VERBOSE, "Skipping posix permissions - filestore doesn't support posix permission");
}
terminal.println(VERBOSE, "Installed %s into %s", pluginHandle.name, toLocation.toAbsolutePath());
}

Path configFile = extractLocation.resolve("config");
if (Files.isDirectory(configFile)) {
Path configDestLocation = pluginHandle.configDir(environment);
terminal.println(VERBOSE, "Found config, moving to %s", configDestLocation.toAbsolutePath());
moveFilesWithoutOverwriting(configFile, configDestLocation, ".new");
terminal.println(VERBOSE, "Installed %s into %s", pluginHandle.name, configDestLocation.toAbsolutePath());
terminal.println(VERBOSE, "Installed %s into %s", pluginName, destPluginBinDirectory.toAbsolutePath());
}
}

Expand Down
Loading

0 comments on commit 70b2d90

Please sign in to comment.