blob: 9b2e3ae7c08bf7c845152365fb5bf7b62dc49312 [file] [log] [blame]
/*
* 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.
*/
#include "derive_classpath.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <android-modules-utils/sdk_level.h>
#include <glob.h>
#include <regex>
#include <sstream>
#include "packages/modules/common/proto/classpaths.pb.h"
namespace android {
namespace derive_classpath {
using Filepaths = std::vector<std::string>;
using Classpaths = std::unordered_map<Classpath, Filepaths>;
static const std::regex kBindMountedApex("^/apex/[^/]+@[0-9]+/");
static const std::regex kApexPathRegex("(/apex/[^/]+)/");
// 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".
static const std::vector<std::string> kBootclasspathFragmentGlobPatterns = {
// ART module is a special case and must come first before any other classpath entries.
"/apex/com.android.art/etc/classpaths/bootclasspath.pb",
"/system/etc/classpaths/bootclasspath.pb",
"/apex/*/etc/classpaths/bootclasspath.pb",
};
// 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 kBootclasspathFragmentGlobPatterns apply here.
static const std::vector<std::string> kSystemserverclasspathFragmentGlobPatterns = {
"/system/etc/classpaths/systemserverclasspath.pb",
"/apex/*/etc/classpaths/systemserverclasspath.pb",
};
// 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().
bool GlobClasspathFragments(Filepaths* fragments, const std::string& pattern) {
glob_t glob_result;
const int ret = glob(pattern.c_str(), GLOB_MARK, nullptr, &glob_result);
if (ret != 0 && ret != GLOB_NOMATCH) {
globfree(&glob_result);
LOG(ERROR) << "Failed to glob " << 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>
if (std::regex_search(path, 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';
const std::string& content = out.str();
LOG(INFO) << "WriteClasspathExports content\n" << content;
const std::string path_str(output_path);
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;
}
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 std::string& globPatternPrefix, Classpaths& classpaths, bool boot_jars) {
LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath");
auto glob_patterns =
boot_jars ? kBootclasspathFragmentGlobPatterns : kSystemserverclasspathFragmentGlobPatterns;
Filepaths fragments;
for (const auto& pattern : glob_patterns) {
if (!GlobClasspathFragments(&fragments, globPatternPrefix + 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()) {
// TODO(b/180105615): check for SdkVersion ranges;
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))
<< fragment_path << " must not export a jar for " << Classpath_Name(classpath);
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(std::string_view output_path) {
// Outside of tests use actual config fragments.
return GenerateClasspathExports("", output_path);
}
// Internal implementation of GenerateClasspathExports that allows putting config fragments in
// temporary directories. `globPatternPrefix` is appended to each glob pattern from
// kBootclasspathFragmentGlobPatterns and kSystemserverclasspathFragmentGlobPatterns, which allows
// adding mock configs in /data/local/tmp for example.
bool GenerateClasspathExports(const std::string& globPatternPrefix, std::string_view output_path) {
// Parse all known classpath fragments
CHECK(android::modules::sdklevel::IsAtLeastS())
<< "derive_classpath must only be run on Android 12 or above";
Classpaths classpaths;
if (!ParseFragments(globPatternPrefix, classpaths, /*boot_jars=*/true)) {
LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments";
return false;
}
if (!ParseFragments(globPatternPrefix, classpaths, /*boot_jars=*/false)) {
LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments";
return false;
}
// Write export actions for init.rc
if (!WriteClasspathExports(classpaths, output_path)) {
PLOG(ERROR) << "Failed to write " << output_path;
return false;
}
return true;
}
} // namespace derive_classpath
} // namespace android