diff --git a/java/src/main/java/org/rocksdb/util/Environment.java b/java/src/main/java/org/rocksdb/util/Environment.java index 5da471f18..9ad51c7c7 100644 --- a/java/src/main/java/org/rocksdb/util/Environment.java +++ b/java/src/main/java/org/rocksdb/util/Environment.java @@ -1,21 +1,20 @@ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. package org.rocksdb.util; +import java.io.File; import java.io.IOException; public class Environment { private static String OS = System.getProperty("os.name").toLowerCase(); private static String ARCH = System.getProperty("os.arch").toLowerCase(); - private static boolean MUSL_LIBC; + private static String MUSL_ENVIRONMENT = System.getenv("ROCKSDB_MUSL_LIBC"); - static { - try { - final Process p = new ProcessBuilder("/usr/bin/env", "sh", "-c", "ldd /usr/bin/env | grep -q musl").start(); - MUSL_LIBC = p.waitFor() == 0; - } catch (final IOException | InterruptedException e) { - MUSL_LIBC = false; - } - } + /** + * Will be lazily initialised by {@link #isMuslLibc()} instead of the previous static + * initialisation. The lazy initialisation prevents Windows from reporting suspicious behaviour of + * the JVM attempting IO on Unix paths. + */ + private static Boolean MUSL_LIBC = null; public static boolean isAarch64() { return ARCH.contains("aarch64"); @@ -50,10 +49,80 @@ public class Environment { OS.contains("nux"); } + /** + * Determine if the environment has a musl libc. + * + * @return true if the environment has a musl libc, false otherwise. + */ public static boolean isMuslLibc() { + if (MUSL_LIBC == null) { + MUSL_LIBC = initIsMuslLibc(); + } return MUSL_LIBC; } + /** + * Determine if the environment has a musl libc. + * + * The initialisation counterpart of {@link #isMuslLibc()}. + * + * Intentionally package-private for testing. + * + * @return true if the environment has a musl libc, false otherwise. + */ + static boolean initIsMuslLibc() { + // consider explicit user setting from environment first + if ("true".equalsIgnoreCase(MUSL_ENVIRONMENT)) { + return true; + } + if ("false".equalsIgnoreCase(MUSL_ENVIRONMENT)) { + return false; + } + + // check if ldd indicates a muslc lib + try { + final Process p = + new ProcessBuilder("/usr/bin/env", "sh", "-c", "ldd /usr/bin/env | grep -q musl").start(); + if (p.waitFor() == 0) { + return true; + } + } catch (final IOException | InterruptedException e) { + // do nothing, and move on to the next check + } + + final File lib = new File("/lib"); + if (lib.exists() && lib.isDirectory() && lib.canRead()) { + // attempt the most likely musl libc name first + final String possibleMuslcLibName; + if (isPowerPC()) { + possibleMuslcLibName = "libc.musl-ppc64le.so.1"; + } else if (isAarch64()) { + possibleMuslcLibName = "libc.musl-aarch64.so.1"; + } else if (isS390x()) { + possibleMuslcLibName = "libc.musl-s390x.so.1"; + } else { + possibleMuslcLibName = "libc.musl-x86_64.so.1"; + } + final File possibleMuslcLib = new File(lib, possibleMuslcLibName); + if (possibleMuslcLib.exists() && possibleMuslcLib.canRead()) { + return true; + } + + // fallback to scanning for a musl libc + final File[] libFiles = lib.listFiles(); + if (libFiles == null) { + return false; + } + for (final File f : libFiles) { + if (f.getName().startsWith("libc.musl")) { + return true; + } + } + } + + return false; + } + public static boolean isSolaris() { return OS.contains("sunos"); } diff --git a/java/src/test/java/org/rocksdb/util/EnvironmentTest.java b/java/src/test/java/org/rocksdb/util/EnvironmentTest.java index 301dec22f..ae340e06d 100644 --- a/java/src/test/java/org/rocksdb/util/EnvironmentTest.java +++ b/java/src/test/java/org/rocksdb/util/EnvironmentTest.java @@ -4,28 +4,32 @@ // (found in the LICENSE.Apache file in the root directory). package org.rocksdb.util; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; + +import java.lang.reflect.Field; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import java.lang.reflect.Field; - -import static org.assertj.core.api.Assertions.assertThat; - public class EnvironmentTest { private final static String ARCH_FIELD_NAME = "ARCH"; private final static String OS_FIELD_NAME = "OS"; + + private final static String MUSL_ENVIRONMENT_FIELD_NAME = "MUSL_ENVIRONMENT"; private final static String MUSL_LIBC_FIELD_NAME = "MUSL_LIBC"; private static String INITIAL_OS; private static String INITIAL_ARCH; - private static boolean INITIAL_MUSL_LIBC; + private static String INITIAL_MUSL_ENVIRONMENT; + private static Boolean INITIAL_MUSL_LIBC; @BeforeClass public static void saveState() { INITIAL_ARCH = getEnvironmentClassField(ARCH_FIELD_NAME); INITIAL_OS = getEnvironmentClassField(OS_FIELD_NAME); INITIAL_MUSL_LIBC = getEnvironmentClassField(MUSL_LIBC_FIELD_NAME); + INITIAL_MUSL_ENVIRONMENT = getEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME); } @Test @@ -236,6 +240,21 @@ public class EnvironmentTest { setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); } + @Test + public void resolveIsMuslLibc() { + setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, null); + setEnvironmentClassFields("win", "anyarch"); + assertThat(Environment.isUnix()).isFalse(); + + // with user input, will resolve to true if set as true. Even on OSs that appear absurd for + // musl. Users choice + assertThat(Environment.initIsMuslLibc()).isFalse(); + setEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME, "true"); + assertThat(Environment.initIsMuslLibc()).isTrue(); + setEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME, "false"); + assertThat(Environment.initIsMuslLibc()).isFalse(); + } + private void setEnvironmentClassFields(String osName, String osArch) { setEnvironmentClassField(OS_FIELD_NAME, osName); @@ -246,6 +265,7 @@ public class EnvironmentTest { public static void restoreState() { setEnvironmentClassField(OS_FIELD_NAME, INITIAL_OS); setEnvironmentClassField(ARCH_FIELD_NAME, INITIAL_ARCH); + setEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME, INITIAL_MUSL_ENVIRONMENT); setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, INITIAL_MUSL_LIBC); }