diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java b/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java index f1616ba0eea..0b9913f7f06 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Spawner.java @@ -21,6 +21,7 @@ package org.elasticsearch.bootstrap; import org.apache.lucene.util.Constants; import org.apache.lucene.util.IOUtils; +import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.Platforms; import org.elasticsearch.plugins.PluginInfo; @@ -73,6 +74,9 @@ final class Spawner implements Closeable { */ try (DirectoryStream stream = Files.newDirectoryStream(pluginsFile)) { for (final Path plugin : stream) { + if (FileSystemUtils.isDesktopServicesStore(plugin)) { + continue; + } final PluginInfo info = PluginInfo.readFromProperties(plugin); final Path spawnPath = Platforms.nativeControllerPath(plugin); if (!Files.isRegularFile(spawnPath)) { diff --git a/core/src/main/java/org/elasticsearch/common/io/FileSystemUtils.java b/core/src/main/java/org/elasticsearch/common/io/FileSystemUtils.java index b2c6340ebe4..a976fe779db 100644 --- a/core/src/main/java/org/elasticsearch/common/io/FileSystemUtils.java +++ b/core/src/main/java/org/elasticsearch/common/io/FileSystemUtils.java @@ -20,6 +20,7 @@ package org.elasticsearch.common.io; import org.apache.logging.log4j.Logger; +import org.apache.lucene.util.Constants; import org.apache.lucene.util.IOUtils; import org.elasticsearch.common.Strings; import org.elasticsearch.common.SuppressForbidden; @@ -65,6 +66,16 @@ public final class FileSystemUtils { return fileName.toString().startsWith("."); } + /** + * Check whether the file denoted by the given path is a desktop services store created by Finder on macOS. + * + * @param path the path + * @return true if the current system is macOS and the specified file appears to be a desktop services store file + */ + public static boolean isDesktopServicesStore(final Path path) { + return Constants.MAC_OS_X && Files.isRegularFile(path) && ".DS_Store".equals(path.getFileName().toString()); + } + /** * Appends the path to the given base and strips N elements off the path if strip is > 0. */ diff --git a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java index a41f3bb4d3e..1a50bcc7213 100644 --- a/core/src/main/java/org/elasticsearch/plugins/PluginsService.java +++ b/core/src/main/java/org/elasticsearch/plugins/PluginsService.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.LifecycleComponent; import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; @@ -326,6 +327,9 @@ public class PluginsService extends AbstractComponent { try (DirectoryStream stream = Files.newDirectoryStream(pluginsDirectory)) { for (Path plugin : stream) { + if (FileSystemUtils.isDesktopServicesStore(plugin)) { + continue; + } logger.trace("--- adding plugin [{}]", plugin.toAbsolutePath()); final PluginInfo info; try { diff --git a/core/src/test/java/org/elasticsearch/common/io/FileSystemUtilsTests.java b/core/src/test/java/org/elasticsearch/common/io/FileSystemUtilsTests.java index e0a8a1c1e1c..7d4fc0ae0ed 100644 --- a/core/src/test/java/org/elasticsearch/common/io/FileSystemUtilsTests.java +++ b/core/src/test/java/org/elasticsearch/common/io/FileSystemUtilsTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.io; +import org.apache.lucene.util.Constants; import org.apache.lucene.util.LuceneTestCase.SuppressFileSystems; import org.elasticsearch.test.ESTestCase; import org.junit.Before; @@ -34,6 +35,8 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Arrays; +import static org.hamcrest.Matchers.equalTo; + /** * Unit tests for {@link org.elasticsearch.common.io.FileSystemUtils}. */ @@ -137,4 +140,16 @@ public class FileSystemUtilsTests extends ESTestCase { assertArrayEquals(expectedBytes, actualBytes); } } + + public void testIsDesktopServicesStoreFile() throws IOException { + final Path path = createTempDir(); + final Path desktopServicesStore = path.resolve(".DS_Store"); + Files.createFile(desktopServicesStore); + assertThat(FileSystemUtils.isDesktopServicesStore(desktopServicesStore), equalTo(Constants.MAC_OS_X)); + + Files.delete(desktopServicesStore); + Files.createDirectory(desktopServicesStore); + assertFalse(FileSystemUtils.isDesktopServicesStore(desktopServicesStore)); + } + } diff --git a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java index c3fd0b19f73..e00f1773c2b 100644 --- a/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java +++ b/core/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.plugins; +import org.apache.lucene.util.Constants; import org.apache.lucene.util.LuceneTestCase; import org.elasticsearch.Version; import org.elasticsearch.common.settings.Settings; @@ -27,6 +28,7 @@ import org.elasticsearch.index.IndexModule; import org.elasticsearch.test.ESTestCase; import java.io.IOException; +import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -36,6 +38,7 @@ import java.util.Locale; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasToString; +import static org.hamcrest.Matchers.instanceOf; @LuceneTestCase.SuppressFileSystems(value = "ExtrasFS") public class PluginsServiceTests extends ESTestCase { @@ -124,6 +127,28 @@ public class PluginsServiceTests extends ESTestCase { assertThat(e, hasToString(containsString(expected))); } + public void testDesktopServicesStoreFiles() throws IOException { + final Path home = createTempDir(); + final Settings settings = + Settings.builder() + .put(Environment.PATH_HOME_SETTING.getKey(), home) + .build(); + final Path plugins = home.resolve("plugins"); + Files.createDirectories(plugins); + final Path desktopServicesStore = plugins.resolve(".DS_Store"); + Files.createFile(desktopServicesStore); + if (Constants.MAC_OS_X) { + @SuppressWarnings("unchecked") final PluginsService pluginsService = newPluginsService(settings); + assertNotNull(pluginsService); + } else { + final IllegalStateException e = expectThrows(IllegalStateException.class, () -> newPluginsService(settings)); + assertThat(e, hasToString(containsString("Could not load plugin descriptor for existing plugin [.DS_Store]"))); + assertNotNull(e.getCause()); + assertThat(e.getCause(), instanceOf(FileSystemException.class)); + assertThat(e.getCause(), hasToString(containsString("Not a directory"))); + } + } + public void testStartupWithRemovingMarker() throws IOException { final Path home = createTempDir(); final Settings settings = diff --git a/qa/no-bootstrap-tests/src/test/java/org/elasticsearch/bootstrap/SpawnerNoBootstrapTests.java b/qa/no-bootstrap-tests/src/test/java/org/elasticsearch/bootstrap/SpawnerNoBootstrapTests.java index 8da6d8d3afe..2435c17f4c3 100644 --- a/qa/no-bootstrap-tests/src/test/java/org/elasticsearch/bootstrap/SpawnerNoBootstrapTests.java +++ b/qa/no-bootstrap-tests/src/test/java/org/elasticsearch/bootstrap/SpawnerNoBootstrapTests.java @@ -31,6 +31,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFileAttributeView; @@ -40,8 +41,10 @@ import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.hasToString; /** * Create a simple "daemon controller", put it in the right place and check that it runs. @@ -189,6 +192,29 @@ public class SpawnerNoBootstrapTests extends LuceneTestCase { equalTo("plugin [test_plugin] does not have permission to fork native controller")); } + public void testSpawnerHandlingOfDesktopServicesStoreFiles() throws IOException { + final Path esHome = createTempDir().resolve("home"); + final Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), esHome.toString()).build(); + + final Environment environment = new Environment(settings); + + Files.createDirectories(environment.pluginsFile()); + + final Path desktopServicesStore = environment.pluginsFile().resolve(".DS_Store"); + Files.createFile(desktopServicesStore); + + final Spawner spawner = new Spawner(); + if (Constants.MAC_OS_X) { + // if the spawner were not skipping the Desktop Services Store files on macOS this would explode + spawner.spawnNativePluginControllers(environment); + } else { + // we do not ignore these files on non-macOS systems + final FileSystemException e = + expectThrows(FileSystemException.class, () -> spawner.spawnNativePluginControllers(environment)); + assertThat(e, hasToString(containsString("Not a directory"))); + } + } + private void createControllerProgram(final Path outputFile) throws IOException { final Path outputDir = outputFile.getParent(); Files.createDirectories(outputDir);