libnativeloader: clean-up hard-coded public libs

art, nn, i18n apexes provide some of libs listed in
public.libraries.txt. The list of these apexes are now read from
/linkerconfig/apex.libraries.config.txt generated by
/system/bin/linkerconfig.

Bug: 150767721
Test: cuttlefish boots
Test: atest libnativeloader_test
Change-Id: Ic603b42669dff89d5d3da2f6822312e827eddd86
diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp
index 3ef50e3..47050ca 100644
--- a/libnativeloader/library_namespaces.cpp
+++ b/libnativeloader/library_namespaces.cpp
@@ -50,9 +50,6 @@
 constexpr const char* kVendorNamespaceName = "sphal";
 constexpr const char* kVndkNamespaceName = "vndk";
 constexpr const char* kVndkProductNamespaceName = "vndk_product";
-constexpr const char* kArtNamespaceName = "com_android_art";
-constexpr const char* kI18nNamespaceName = "com_android_i18n";
-constexpr const char* kNeuralNetworksNamespaceName = "com_android_neuralnetworks";
 constexpr const char* kStatsdNamespaceName = "com_android_os_statsd";
 
 // classloader-namespace is a linker namespace that is created for the loaded
@@ -314,31 +311,14 @@
     return linked.error();
   }
 
-  auto art_ns = NativeLoaderNamespace::GetExportedNamespace(kArtNamespaceName, is_bridged);
-  // ART APEX does not exist on host, and under certain build conditions.
-  if (art_ns.ok()) {
-    linked = app_ns->Link(*art_ns, art_public_libraries());
-    if (!linked.ok()) {
-      return linked.error();
-    }
-  }
-
-  auto i18n_ns = NativeLoaderNamespace::GetExportedNamespace(kI18nNamespaceName, is_bridged);
-  // i18n APEX does not exist on host, and under certain build conditions.
-  if (i18n_ns.ok()) {
-    linked = app_ns->Link(*i18n_ns, i18n_public_libraries());
-    if (!linked.ok()) {
-      return linked.error();
-    }
-  }
-
-  // Give access to NNAPI libraries (apex-updated LLNDK library).
-  auto nnapi_ns =
-      NativeLoaderNamespace::GetExportedNamespace(kNeuralNetworksNamespaceName, is_bridged);
-  if (nnapi_ns.ok()) {
-    linked = app_ns->Link(*nnapi_ns, neuralnetworks_public_libraries());
-    if (!linked.ok()) {
-      return linked.error();
+  for (const auto&[apex_ns_name, public_libs] : apex_public_libraries()) {
+    auto ns = NativeLoaderNamespace::GetExportedNamespace(apex_ns_name, is_bridged);
+    // Even if APEX namespace is visible, it may not be available to bridged.
+    if (ns.ok()) {
+      linked = app_ns->Link(*ns, public_libs);
+      if (!linked.ok()) {
+        return linked.error();
+      }
     }
   }
 
@@ -370,8 +350,8 @@
     if (jni_libs != "") {
       auto apex_ns = NativeLoaderNamespace::GetExportedNamespace(*apex_ns_name, is_bridged);
       if (apex_ns.ok()) {
-        auto link = app_ns->Link(*apex_ns, jni_libs);
-        if (!link.ok()) {
+        linked = app_ns->Link(*apex_ns, jni_libs);
+        if (!linked.ok()) {
           return linked.error();
         }
       }
diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp
index 81109b6..146ab5a 100644
--- a/libnativeloader/native_loader_test.cpp
+++ b/libnativeloader/native_loader_test.cpp
@@ -41,7 +41,7 @@
 using ::testing::_;
 using internal::ConfigEntry;
 using internal::ParseConfig;
-using internal::ParseJniConfig;
+using internal::ParseApexLibrariesConfig;
 
 #if defined(__LP64__)
 #define LIB_DIR "lib64"
@@ -369,13 +369,13 @@
   bool expected_link_with_neuralnetworks_ns = true;
   bool expected_link_with_statsd_ns = true;
   std::string expected_shared_libs_to_platform_ns = default_public_libraries();
-  std::string expected_shared_libs_to_art_ns = art_public_libraries();
-  std::string expected_shared_libs_to_i18n_ns = i18n_public_libraries();
+  std::string expected_shared_libs_to_art_ns = apex_public_libraries().at("com_android_art");
+  std::string expected_shared_libs_to_i18n_ns = apex_public_libraries().at("com_android_i18n");
   std::string expected_shared_libs_to_sphal_ns = vendor_public_libraries();
   std::string expected_shared_libs_to_vndk_ns = vndksp_libraries_vendor();
   std::string expected_shared_libs_to_vndk_product_ns = vndksp_libraries_product();
   std::string expected_shared_libs_to_default_ns = default_public_libraries();
-  std::string expected_shared_libs_to_neuralnetworks_ns = neuralnetworks_public_libraries();
+  std::string expected_shared_libs_to_neuralnetworks_ns = apex_public_libraries().at("com_android_neuralnetworks");
   std::string expected_shared_libs_to_statsd_ns = statsd_public_libraries();
 
   void SetExpectations() {
@@ -690,29 +690,77 @@
   ASSERT_FALSE(ParseConfig("libA.so nopreload # comment", always_true).ok());
 }
 
-TEST(NativeLoaderJniConfigParser, BasicLoading) {
+TEST(NativeLoaderApexLibrariesConfigParser, BasicLoading) {
   const char file_content[] = R"(
 # comment
-com_android_foo libfoo.so
+jni com_android_foo libfoo.so
 # Empty line is ignored
 
-com_android_bar libbar.so:libbar2.so
+jni com_android_bar libbar.so:libbar2.so
+
+  public com_android_bar libpublic.so
 )";
 
-  std::map<std::string, std::string> expected_result{
+  auto jni_libs = ParseApexLibrariesConfig(file_content, "jni");
+  ASSERT_RESULT_OK(jni_libs);
+  std::map<std::string, std::string> expected_jni_libs {
     {"com_android_foo", "libfoo.so"},
     {"com_android_bar", "libbar.so:libbar2.so"},
   };
+  ASSERT_EQ(expected_jni_libs, *jni_libs);
 
-  Result<std::map<std::string, std::string>> result = ParseJniConfig(file_content);
-  ASSERT_RESULT_OK(result);
-  ASSERT_EQ(expected_result, *result);
+  auto public_libs = ParseApexLibrariesConfig(file_content, "public");
+  ASSERT_RESULT_OK(public_libs);
+  std::map<std::string, std::string> expected_public_libs {
+    {"com_android_bar", "libpublic.so"},
+  };
+  ASSERT_EQ(expected_public_libs, *public_libs);
 }
 
-TEST(NativeLoaderJniConfigParser, RejectMalformed) {
-  ASSERT_FALSE(ParseJniConfig("com_android_foo").ok());
+TEST(NativeLoaderApexLibrariesConfigParser, RejectMalformedLine) {
+  const char file_content[] = R"(
+jni com_android_foo libfoo
+# missing <library list>
+jni com_android_bar
+)";
+  auto result = ParseApexLibrariesConfig(file_content, "jni");
+  ASSERT_FALSE(result.ok());
+  ASSERT_EQ("Malformed line \"jni com_android_bar\"", result.error().message());
 }
 
+TEST(NativeLoaderApexLibrariesConfigParser, RejectInvalidTag) {
+  const char file_content[] = R"(
+jni apex1 lib
+public apex2 lib
+# unknown tag
+unknown com_android_foo libfoo
+)";
+  auto result = ParseApexLibrariesConfig(file_content, "jni");
+  ASSERT_FALSE(result.ok());
+  ASSERT_EQ("Invalid tag \"unknown com_android_foo libfoo\"", result.error().message());
+}
+
+TEST(NativeLoaderApexLibrariesConfigParser, RejectInvalidApexNamespace) {
+  const char file_content[] = R"(
+# apex linker namespace should be mangled ('.' -> '_')
+jni com.android.foo lib
+)";
+  auto result = ParseApexLibrariesConfig(file_content, "jni");
+  ASSERT_FALSE(result.ok());
+  ASSERT_EQ("Invalid apex_namespace \"jni com.android.foo lib\"", result.error().message());
+}
+
+TEST(NativeLoaderApexLibrariesConfigParser, RejectInvalidLibraryList) {
+  const char file_content[] = R"(
+# library list is ":" separated list of filenames
+jni com_android_foo lib64/libfoo.so
+)";
+  auto result = ParseApexLibrariesConfig(file_content, "jni");
+  ASSERT_FALSE(result.ok());
+  ASSERT_EQ("Invalid library_list \"jni com_android_foo lib64/libfoo.so\"", result.error().message());
+}
+
+
 }  // namespace nativeloader
 }  // namespace android
 
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index 575ce2d..c3f38f6 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -23,6 +23,8 @@
 #include <algorithm>
 #include <map>
 #include <memory>
+#include <regex>
+#include <string>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -43,7 +45,7 @@
 using android::base::Result;
 using internal::ConfigEntry;
 using internal::ParseConfig;
-using internal::ParseJniConfig;
+using internal::ParseApexLibrariesConfig;
 using std::literals::string_literals::operator""s;
 
 namespace {
@@ -51,23 +53,11 @@
 constexpr const char* kDefaultPublicLibrariesFile = "/etc/public.libraries.txt";
 constexpr const char* kExtendedPublicLibrariesFilePrefix = "public.libraries-";
 constexpr const char* kExtendedPublicLibrariesFileSuffix = ".txt";
-constexpr const char* kJniConfigFile = "/linkerconfig/jni.config.txt";
+constexpr const char* kApexLibrariesConfigFile = "/linkerconfig/apex.libraries.config.txt";
 constexpr const char* kVendorPublicLibrariesFile = "/vendor/etc/public.libraries.txt";
 constexpr const char* kLlndkLibrariesFile = "/apex/com.android.vndk.v{}/etc/llndk.libraries.{}.txt";
 constexpr const char* kVndkLibrariesFile = "/apex/com.android.vndk.v{}/etc/vndksp.libraries.{}.txt";
 
-const std::vector<const std::string> kArtApexPublicLibraries = {
-    "libnativehelper.so",
-};
-
-const std::vector<const std::string> ki18nApexPublicLibraries = {
-    "libicuuc.so",
-    "libicui18n.so",
-};
-
-constexpr const char* kI18nApexLibPath = "/apex/com.android.i18n/" LIB;
-
-constexpr const char* kNeuralNetworksApexPublicLibrary = "libneuralnetworks.so";
 
 constexpr const char* kStatsdApexPublicLibrary = "libstats_jni.so";
 
@@ -198,54 +188,22 @@
     return android::base::Join(*sonames, ':');
   }
 
-  // Remove the public libs in the i18n namespace.
-  // These libs are listed in public.android.txt, but we don't want the rest of android
-  // in default namespace to dlopen the libs.
-  // For example, libicuuc.so is exposed to classloader namespace from art namespace.
-  // Unfortunately, it does not have stable C symbols, and default namespace should only use
-  // stable symbols in libandroidicu.so. http://b/120786417
-  for (const std::string& lib_name : ki18nApexPublicLibraries) {
-    std::string path(kI18nApexLibPath);
-    path.append("/").append(lib_name);
-
-    struct stat s;
-    // Do nothing if the path in /apex does not exist.
-    // Runtime APEX must be mounted since libnativeloader is in the same APEX
-    if (stat(path.c_str(), &s) != 0) {
+  // Remove the public libs provided by apexes because these libs are available
+  // from apex namespaces.
+  for (const auto& p : apex_public_libraries()) {
+    // TODO(b/167578583) remove this `if` block after fixing the bug
+    // Skip ART APEX to keep behaviors
+    if (p.first == "com_android_art") {
       continue;
     }
-
-    auto it = std::find(sonames->begin(), sonames->end(), lib_name);
-    if (it != sonames->end()) {
-      sonames->erase(it);
-    }
-  }
-
-  // Remove the public libs in the nnapi namespace.
-  auto it = std::find(sonames->begin(), sonames->end(), kNeuralNetworksApexPublicLibrary);
-  if (it != sonames->end()) {
-    sonames->erase(it);
+    auto public_libs = base::Split(p.second, ":");
+    sonames->erase(std::remove_if(sonames->begin(), sonames->end(), [&public_libs](const std::string& v) {
+      return std::find(public_libs.begin(), public_libs.end(), v) != public_libs.end();
+    }), sonames->end());
   }
   return android::base::Join(*sonames, ':');
 }
 
-static std::string InitArtPublicLibraries() {
-  CHECK_GT((int)kArtApexPublicLibraries.size(), 0);
-  std::string list = android::base::Join(kArtApexPublicLibraries, ":");
-
-  std::string additional_libs = additional_public_libraries();
-  if (!additional_libs.empty()) {
-    list = list + ':' + additional_libs;
-  }
-  return list;
-}
-
-static std::string InitI18nPublicLibraries() {
-  static_assert(sizeof(ki18nApexPublicLibraries) > 0, "ki18nApexPublicLibraries is empty");
-  std::string list = android::base::Join(ki18nApexPublicLibraries, ":");
-  return list;
-}
-
 static std::string InitVendorPublicLibraries() {
   // This file is optional, quietly ignore if the file does not exist.
   auto sonames = ReadConfig(kVendorPublicLibrariesFile, always_true);
@@ -318,29 +276,50 @@
   return android::base::Join(*sonames, ':');
 }
 
-static std::string InitNeuralNetworksPublicLibraries() {
-  return kNeuralNetworksApexPublicLibrary;
-}
-
 static std::string InitStatsdPublicLibraries() {
   return kStatsdApexPublicLibrary;
 }
 
-static std::map<std::string, std::string> InitApexJniLibraries() {
+static std::map<std::string, std::string> InitApexLibraries(const std::string& tag) {
   std::string file_content;
-  if (!base::ReadFileToString(kJniConfigFile, &file_content)) {
-    // jni config is optional
+  if (!base::ReadFileToString(kApexLibrariesConfigFile, &file_content)) {
+    // config is optional
     return {};
   }
-  auto config = ParseJniConfig(file_content);
+  auto config = ParseApexLibrariesConfig(file_content, tag);
   if (!config.ok()) {
-    LOG_ALWAYS_FATAL("%s: %s", kJniConfigFile, config.error().message().c_str());
-    // not reach here
+    LOG_ALWAYS_FATAL("%s: %s", kApexLibrariesConfigFile, config.error().message().c_str());
     return {};
   }
   return *config;
 }
 
+struct ApexLibrariesConfigLine {
+  std::string tag;
+  std::string apex_namespace;
+  std::string library_list;
+};
+
+const std::regex kApexNamespaceRegex("[0-9a-zA-Z_]+");
+const std::regex kLibraryListRegex("[0-9a-zA-Z.:@+_-]+");
+
+Result<ApexLibrariesConfigLine> ParseApexLibrariesConfigLine(const std::string& line) {
+  std::vector<std::string> tokens = base::Split(line, " ");
+  if (tokens.size() != 3) {
+    return Errorf("Malformed line \"{}\"", line);
+  }
+  if (tokens[0] != "jni" && tokens[0] != "public") {
+    return Errorf("Invalid tag \"{}\"", line);
+  }
+  if (!std::regex_match(tokens[1], kApexNamespaceRegex)) {
+    return Errorf("Invalid apex_namespace \"{}\"", line);
+  }
+  if (!std::regex_match(tokens[2], kLibraryListRegex)) {
+    return Errorf("Invalid library_list \"{}\"", line);
+  }
+  return ApexLibrariesConfigLine{std::move(tokens[0]), std::move(tokens[1]), std::move(tokens[2])};
+}
+
 }  // namespace
 
 const std::string& preloadable_public_libraries() {
@@ -353,16 +332,6 @@
   return list;
 }
 
-const std::string& art_public_libraries() {
-  static std::string list = InitArtPublicLibraries();
-  return list;
-}
-
-const std::string& i18n_public_libraries() {
-  static std::string list = InitI18nPublicLibraries();
-  return list;
-}
-
 const std::string& vendor_public_libraries() {
   static std::string list = InitVendorPublicLibraries();
   return list;
@@ -373,11 +342,6 @@
   return list;
 }
 
-const std::string& neuralnetworks_public_libraries() {
-  static std::string list = InitNeuralNetworksPublicLibraries();
-  return list;
-}
-
 const std::string& statsd_public_libraries() {
   static std::string list = InitStatsdPublicLibraries();
   return list;
@@ -404,10 +368,15 @@
 }
 
 const std::string& apex_jni_libraries(const std::string& apex_ns_name) {
-  static std::map<std::string, std::string> jni_libraries = InitApexJniLibraries();
+  static std::map<std::string, std::string> jni_libraries = InitApexLibraries("jni");
   return jni_libraries[apex_ns_name];
 }
 
+const std::map<std::string, std::string>& apex_public_libraries() {
+  static std::map<std::string, std::string> public_libraries = InitApexLibraries("public");
+  return public_libraries;
+}
+
 bool is_product_vndk_version_defined() {
 #if defined(ART_TARGET_ANDROID)
   return android::sysprop::VndkProperties::product_vndk_version().has_value();
@@ -485,7 +454,19 @@
   return sonames;
 }
 
-Result<std::map<std::string, std::string>> ParseJniConfig(const std::string& file_content) {
+// Parses apex.libraries.config.txt file generated by linkerconfig which looks like
+//   system/linkerconfig/testdata/golden_output/stages/apex.libraries.config.txt
+// and returns mapping of <apex namespace> to <library list> which matches <tag>.
+//
+// The file is line-based and each line consists of "<tag> <apex namespace> <library list>".
+//
+// <tag> explains what <library list> is. (e.g "jni", "public")
+// <library list> is colon-separated list of library names. (e.g "libfoo.so:libbar.so")
+//
+// If <tag> is "jni", <library list> is the list of JNI libraries exposed by <apex namespace>.
+// If <tag> is "public", <library list> is the list of public libraries exposed by <apex namespace>.
+// Public libraries are the libs listed in /system/etc/public.libraries.txt.
+Result<std::map<std::string, std::string>> ParseApexLibrariesConfig(const std::string& file_content, const std::string& tag) {
   std::map<std::string, std::string> entries;
   std::vector<std::string> lines = base::Split(file_content, "\n");
   for (auto& line : lines) {
@@ -493,12 +474,22 @@
     if (trimmed_line[0] == '#' || trimmed_line.empty()) {
       continue;
     }
-
-    std::vector<std::string> tokens = base::Split(trimmed_line, " ");
-    if (tokens.size() < 2) {
-      return Errorf( "Malformed line \"{}\"", line);
+    auto config_line = ParseApexLibrariesConfigLine(trimmed_line);
+    if (!config_line.ok()) {
+      return config_line.error();
     }
-    entries[tokens[0]] = tokens[1];
+    if (config_line->tag != tag) {
+      continue;
+    }
+    entries[config_line->apex_namespace] = config_line->library_list;
+  }
+
+  // TODO(b/167578583) remove this `if` block after fixing the bug
+  if (tag == "public") {
+    std::string additional_libs = additional_public_libraries();
+    if (!additional_libs.empty()) {
+      entries["com_android_art"] += ':' + additional_libs;
+    }
   }
   return entries;
 }
diff --git a/libnativeloader/public_libraries.h b/libnativeloader/public_libraries.h
index b60a2ef..c1460db 100644
--- a/libnativeloader/public_libraries.h
+++ b/libnativeloader/public_libraries.h
@@ -32,18 +32,21 @@
 // e.g., if it is a vendor app or not, different set of libraries are made available.
 const std::string& preloadable_public_libraries();
 const std::string& default_public_libraries();
-const std::string& art_public_libraries();
 const std::string& statsd_public_libraries();
 const std::string& vendor_public_libraries();
 const std::string& extended_public_libraries();
-const std::string& i18n_public_libraries();
-const std::string& neuralnetworks_public_libraries();
 const std::string& llndk_libraries_product();
 const std::string& llndk_libraries_vendor();
 const std::string& vndksp_libraries_product();
 const std::string& vndksp_libraries_vendor();
 const std::string& apex_jni_libraries(const std::string& apex_name);
 
+// Returns the table of apexes and public libraries provided by the apexes.
+// For example, com_android_foo -> libfoo.so:libbar.so
+// Note that libfoo.so and libbar.so are listed in /system/etc/public.libraries.txt
+// but provided by com.android.foo APEX.
+const std::map<std::string, std::string>& apex_public_libraries();
+
 // Returns true if libnativeloader is running on devices and the device has
 // ro.product.vndk.version property. It returns false for host.
 bool is_product_vndk_version_defined();
@@ -65,8 +68,10 @@
     const std::string& file_content,
     const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn);
 
-Result<std::map<std::string, std::string>> ParseJniConfig(
-    const std::string& file_content);
+// Parses apex.libraries.config.txt file generated by linkerconfig
+// and returns mapping of <apex namespace> to <library list> which matches <tag>.
+Result<std::map<std::string, std::string>> ParseApexLibrariesConfig(
+    const std::string& file_content, const std::string& tag);
 
 }  // namespace internal