From 6722b9c4a2f5917d73272ff86e3ff80a30305511 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 25 Oct 2017 11:25:29 -0400 Subject: [PATCH] Ignore .DS_Store files on macOS Finder creates these files if you browse a directory there. These files are really annoying, but it's an incredible pain for users that these files are created unbeknownst to them, and then they get in the way of Elasticsearch starting. This commit adds leniency on macOS only to skip these files. Relates #27108 --- .../org/elasticsearch/bootstrap/Spawner.java | 4 +++ .../common/io/FileSystemUtils.java | 11 ++++++++ .../elasticsearch/plugins/PluginsService.java | 4 +++ .../common/io/FileSystemUtilsTests.java | 15 +++++++++++ .../plugins/PluginsServiceTests.java | 25 ++++++++++++++++++ .../bootstrap/SpawnerNoBootstrapTests.java | 26 +++++++++++++++++++ 6 files changed, 85 insertions(+) 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);