| /* | |
| * Copyright (C) 2021 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #define LOG_TAG "derive_classpath" | |
| #include "derive_classpath.h" | |
| #include <ctype.h> | |
| #include <glob.h> | |
| #include <regex> | |
| #include <sstream> | |
| #include <unordered_map> | |
| #include <android-base/file.h> | |
| #include <android-base/logging.h> | |
| #include <android-base/parseint.h> | |
| #include <android-base/strings.h> | |
| #include "packages/modules/common/proto/classpaths.pb.h" | |
| #ifdef SDKEXT_ANDROID | |
| #include <android-modules-utils/sdk_level.h> | |
| #include <android-modules-utils/unbounded_sdk_level.h> | |
| #endif | |
| namespace android { | |
| namespace derive_classpath { | |
| using Filepaths = std::vector<std::string>; | |
| using Classpaths = std::unordered_map<Classpath, Filepaths>; | |
| // Matches path of format: /apex/<module-name>@<version-digits-only>/* | |
| static const std::regex kBindMountedApex("/apex/[^/]+@[0-9]+/"); | |
| // Capture module name in following formats: | |
| // - /apex/<module-name>/* | |
| // - /apex/<module-name>@*/* | |
| static const std::regex kApexPathRegex("(/apex/[^@/]+)(?:@[^@/]+)?/"); | |
| static const std::string kBootclasspathFragmentLocation = "/etc/classpaths/bootclasspath.pb"; | |
| static const std::string kSystemserverclasspathFragmentLocation = | |
| "/etc/classpaths/systemserverclasspath.pb"; | |
| static int GetVersionInt(const std::string& version) { | |
| int version_int = 0; | |
| if (!android::base::ParseInt(version, &version_int, /*min=*/1, /*max=*/INT_MAX)) { | |
| PLOG(FATAL) << "Failed to convert version \"" << version << "\" to int"; | |
| } | |
| return version_int; | |
| } | |
| static bool IsCodename(const std::string& version) { | |
| LOG_IF(FATAL, version.empty()) << "Empty version"; | |
| return isupper(version[0]); | |
| } | |
| static bool SdkLevelIsAtLeast(const Args& args, const std::string& version) { | |
| #ifdef SDKEXT_ANDROID | |
| if (args.override_device_sdk_version == 0) { | |
| // Most common case: no override. | |
| return android::modules::sdklevel::unbounded::IsAtLeast(version.c_str()); | |
| } | |
| #endif | |
| // Mirrors the logic in unbounded_sdk_level.h. | |
| if (args.override_device_codename == "REL") { | |
| if (IsCodename(version)) { | |
| return false; | |
| } | |
| return args.override_device_sdk_version >= GetVersionInt(version); | |
| } | |
| if (IsCodename(version)) { | |
| return args.override_device_known_codenames.contains(version); | |
| } | |
| return args.override_device_sdk_version >= GetVersionInt(version); | |
| } | |
| static bool SdkLevelIsAtMost(const Args& args, const std::string& version) { | |
| #ifdef SDKEXT_ANDROID | |
| if (args.override_device_sdk_version == 0) { | |
| // Most common case: no override. | |
| return android::modules::sdklevel::unbounded::IsAtMost(version.c_str()); | |
| } | |
| #endif | |
| // Mirrors the logic in unbounded_sdk_level.h. | |
| if (args.override_device_codename == "REL") { | |
| if (IsCodename(version)) { | |
| return true; | |
| } | |
| return args.override_device_sdk_version <= GetVersionInt(version); | |
| } | |
| if (IsCodename(version)) { | |
| return !args.override_device_known_codenames.contains(version) || | |
| args.override_device_codename == version; | |
| } | |
| return args.override_device_sdk_version < GetVersionInt(version); | |
| } | |
| std::vector<std::string> getBootclasspathFragmentGlobPatterns(const Args& args) { | |
| // Scan only specific directory for fragments if scan_dir is specified | |
| if (!args.scan_dirs.empty()) { | |
| std::vector<std::string> patterns; | |
| for (const auto& scan_dir : args.scan_dirs) { | |
| patterns.push_back(scan_dir + kBootclasspathFragmentLocation); | |
| } | |
| return patterns; | |
| } | |
| // Defines the order of individual fragments to be merged for BOOTCLASSPATH: | |
| // 1. Jars in ART module always come first; | |
| // 2. Jars defined as part of /system/etc/classpaths; | |
| // 3. Jars defined in all non-ART apexes that expose /apex/*/etc/classpaths fragments. | |
| // | |
| // Notes: | |
| // - Relative order in the individual fragment files is not changed when merging. | |
| // - If a fragment file is matched by multiple globs, the first one is used; i.e. ART module | |
| // fragment is only parsed once, even if there is a "/apex/*/" pattern later. | |
| // - If there are multiple files matched for a glob pattern with wildcards, the results are sorted | |
| // by pathname (default glob behaviour); i.e. all fragment files are sorted within a single | |
| // "pattern block". | |
| std::vector<std::string> patterns = { | |
| // ART module is a special case and must come first before any other classpath entries. | |
| "/apex/com.android.art" + kBootclasspathFragmentLocation, | |
| }; | |
| if (args.system_bootclasspath_fragment.empty()) { | |
| patterns.emplace_back("/system" + kBootclasspathFragmentLocation); | |
| } else { | |
| // TODO: Avoid applying glob(3) expansion later to this path. Although the caller should not | |
| // provide a path that contains '*', it can technically happen. Instead of checking the string | |
| // format, we should just avoid the glob(3) for this string. | |
| patterns.emplace_back(args.system_bootclasspath_fragment); | |
| } | |
| patterns.emplace_back("/apex/*" + kBootclasspathFragmentLocation); | |
| return patterns; | |
| } | |
| std::vector<std::string> getSystemserverclasspathFragmentGlobPatterns(const Args& args) { | |
| // Scan only specific directory for fragments if scan_dir is specified | |
| if (!args.scan_dirs.empty()) { | |
| std::vector<std::string> patterns; | |
| for (const auto& scan_dir : args.scan_dirs) { | |
| patterns.push_back(scan_dir + kSystemserverclasspathFragmentLocation); | |
| } | |
| return patterns; | |
| } | |
| // Defines the order of individual fragments to be merged for SYSTEMSERVERCLASSPATH. | |
| // | |
| // ART system server jars are not special in this case, and are considered to be part of all the | |
| // other apexes that may expose system server jars. | |
| // | |
| // All notes from getBootclasspathFragmentGlobPatterns apply here. | |
| std::vector<std::string> patterns; | |
| if (args.system_systemserverclasspath_fragment.empty()) { | |
| patterns.emplace_back("/system" + kSystemserverclasspathFragmentLocation); | |
| } else { | |
| // TODO: Avoid applying glob(3) expansion later to this path. See above. | |
| patterns.emplace_back(args.system_systemserverclasspath_fragment); | |
| } | |
| patterns.emplace_back("/apex/*" + kSystemserverclasspathFragmentLocation); | |
| return patterns; | |
| }; | |
| // Finds all classpath fragment files that match the glob pattern and appends them to `fragments`. | |
| // | |
| // If a newly found fragment is already present in `fragments`, it is skipped to avoid duplicates. | |
| // Note that appended fragment files are sorted by pathnames, which is a default behaviour for | |
| // glob(). | |
| // | |
| // glob_pattern_prefix is only populated for unit tests so that we can search for pattern in a test | |
| // directory instead of from root. | |
| bool GlobClasspathFragments(Filepaths* fragments, const std::string& glob_pattern_prefix, | |
| const std::string& pattern) { | |
| glob_t glob_result; | |
| const int ret = glob((glob_pattern_prefix + pattern).c_str(), GLOB_MARK, nullptr, &glob_result); | |
| if (ret != 0 && ret != GLOB_NOMATCH) { | |
| globfree(&glob_result); | |
| LOG(ERROR) << "Failed to glob " << glob_pattern_prefix + pattern; | |
| return false; | |
| } | |
| for (size_t i = 0; i < glob_result.gl_pathc; i++) { | |
| std::string path = glob_result.gl_pathv[i]; | |
| // Skip <name>@<ver> dirs, as they are bind-mounted to <name> | |
| // Remove glob_pattern_prefix first since kBindMountedAPex has prefix requirement | |
| if (std::regex_search(path.substr(glob_pattern_prefix.size()), kBindMountedApex)) { | |
| continue; | |
| } | |
| // Make sure we don't push duplicate fragments from previously processed patterns | |
| if (std::find(fragments->begin(), fragments->end(), path) == fragments->end()) { | |
| fragments->push_back(path); | |
| } | |
| } | |
| globfree(&glob_result); | |
| return true; | |
| } | |
| // Writes the contents of *CLASSPATH variables to /data in the format expected by `load_exports` | |
| // action from init.rc. See platform/system/core/init/README.md. | |
| bool WriteClasspathExports(Classpaths classpaths, std::string_view output_path) { | |
| LOG(INFO) << "WriteClasspathExports " << output_path; | |
| std::stringstream out; | |
| out << "export BOOTCLASSPATH " << android::base::Join(classpaths[BOOTCLASSPATH], ':') << '\n'; | |
| out << "export DEX2OATBOOTCLASSPATH " | |
| << android::base::Join(classpaths[DEX2OATBOOTCLASSPATH], ':') << '\n'; | |
| out << "export SYSTEMSERVERCLASSPATH " | |
| << android::base::Join(classpaths[SYSTEMSERVERCLASSPATH], ':') << '\n'; | |
| out << "export STANDALONE_SYSTEMSERVER_JARS " | |
| << android::base::Join(classpaths[STANDALONE_SYSTEMSERVER_JARS], ':') << '\n'; | |
| const std::string& content = out.str(); | |
| LOG(INFO) << "WriteClasspathExports content\n" << content; | |
| const std::string path_str(output_path); | |
| if (android::base::StartsWith(path_str, "/data/")) { | |
| // When writing to /data, write to a temp file first to make sure the partition is not full. | |
| const std::string temp_str(path_str + ".tmp"); | |
| if (!android::base::WriteStringToFile(content, temp_str, /*follow_symlinks=*/true)) { | |
| return false; | |
| } | |
| return rename(temp_str.c_str(), path_str.c_str()) == 0; | |
| } else { | |
| return android::base::WriteStringToFile(content, path_str, /*follow_symlinks=*/true); | |
| } | |
| } | |
| bool ReadClasspathFragment(ExportedClasspathsJars* fragment, const std::string& filepath) { | |
| LOG(INFO) << "ReadClasspathFragment " << filepath; | |
| std::string contents; | |
| if (!android::base::ReadFileToString(filepath, &contents)) { | |
| PLOG(ERROR) << "Failed to read " << filepath; | |
| return false; | |
| } | |
| if (!fragment->ParseFromString(contents)) { | |
| LOG(ERROR) << "Failed to parse " << filepath; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Returns an allowed prefix for a jar filepaths declared in a given fragment. | |
| // For a given apex fragment, it returns the apex path - "/apex/com.android.foo" - as an allowed | |
| // prefix for jars. This can be used to enforce that an apex fragment only exports jars located in | |
| // that apex. For system fragment, it returns an empty string to allow any jars to be exported by | |
| // the platform. | |
| std::string GetAllowedJarPathPrefix(const std::string& fragment_path) { | |
| std::smatch match; | |
| if (std::regex_search(fragment_path, match, kApexPathRegex)) { | |
| return match[1]; | |
| } | |
| return ""; | |
| } | |
| // Finds and parses all classpath fragments on device matching given glob patterns. | |
| bool ParseFragments(const Args& args, Classpaths& classpaths, bool boot_jars) { | |
| LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath"); | |
| auto glob_patterns = boot_jars ? getBootclasspathFragmentGlobPatterns(args) | |
| : getSystemserverclasspathFragmentGlobPatterns(args); | |
| Filepaths fragments; | |
| for (const auto& pattern : glob_patterns) { | |
| if (!GlobClasspathFragments(&fragments, args.glob_pattern_prefix, pattern)) { | |
| return false; | |
| } | |
| } | |
| for (const auto& fragment_path : fragments) { | |
| ExportedClasspathsJars exportedJars; | |
| if (!ReadClasspathFragment(&exportedJars, fragment_path)) { | |
| return false; | |
| } | |
| // Either a path to the apex, or an empty string | |
| const std::string& allowed_jar_prefix = GetAllowedJarPathPrefix(fragment_path); | |
| for (const Jar& jar : exportedJars.jars()) { | |
| const std::string& jar_path = jar.path(); | |
| CHECK(android::base::StartsWith(jar_path, allowed_jar_prefix)) | |
| << fragment_path << " must not export a jar from outside of the apex: " << jar_path; | |
| const Classpath classpath = jar.classpath(); | |
| CHECK(boot_jars ^ | |
| (classpath == SYSTEMSERVERCLASSPATH || classpath == STANDALONE_SYSTEMSERVER_JARS)) | |
| << fragment_path << " must not export a jar for " << Classpath_Name(classpath); | |
| if (!jar.min_sdk_version().empty()) { | |
| const auto& min_sdk_version = jar.min_sdk_version(); | |
| if (!SdkLevelIsAtLeast(args, min_sdk_version)) { | |
| LOG(INFO) << "not installing " << jar_path << " with min_sdk_version " << min_sdk_version; | |
| continue; | |
| } | |
| } | |
| if (!jar.max_sdk_version().empty()) { | |
| const auto& max_sdk_version = jar.max_sdk_version(); | |
| if (!SdkLevelIsAtMost(args, max_sdk_version)) { | |
| LOG(INFO) << "not installing " << jar_path << " with max_sdk_version " << max_sdk_version; | |
| continue; | |
| } | |
| } | |
| classpaths[classpath].push_back(jar_path); | |
| } | |
| } | |
| return true; | |
| } | |
| // Generates /data/system/environ/classpath exports file by globing and merging individual | |
| // classpaths.proto config fragments. The exports file is read by init.rc to setenv *CLASSPATH | |
| // environ variables at runtime. | |
| bool GenerateClasspathExports(const Args& args) { | |
| #ifdef SDKEXT_ANDROID | |
| // Parse all known classpath fragments | |
| CHECK(android::modules::sdklevel::IsAtLeastS()) | |
| << "derive_classpath must only be run on Android 12 or above"; | |
| #endif | |
| Classpaths classpaths; | |
| if (!ParseFragments(args, classpaths, /*boot_jars=*/true)) { | |
| LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments"; | |
| return false; | |
| } | |
| if (!ParseFragments(args, classpaths, /*boot_jars=*/false)) { | |
| LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments"; | |
| return false; | |
| } | |
| // Write export actions for init.rc | |
| if (!WriteClasspathExports(classpaths, args.output_path)) { | |
| PLOG(ERROR) << "Failed to write " << args.output_path; | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace derive_classpath | |
| } // namespace android |