blob: 51a722037c50f9512a94d2b7efb4d5f35c0e2a7e [file] [log] [blame]
satayevbe3f8ea2021-03-19 11:08:49 +00001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "derive_classpath.h"
18#include <android-base/file.h>
19#include <android-base/logging.h>
Artur Satayev80516af2021-03-30 11:36:03 +010020#include <android-base/strings.h>
satayeva10318b2021-06-14 12:40:31 +010021#include <android-modules-utils/sdk_level.h>
satayev43eba8f2021-06-11 15:07:48 +010022#include <android-modules-utils/unbounded_sdk_level.h>
Artur Satayev80516af2021-03-30 11:36:03 +010023#include <glob.h>
24#include <regex>
25#include <sstream>
26
satayev6dd0d702021-05-10 12:33:24 +010027#include "packages/modules/common/proto/classpaths.pb.h"
satayevbe3f8ea2021-03-19 11:08:49 +000028
29namespace android {
30namespace derive_classpath {
31
Artur Satayev80516af2021-03-30 11:36:03 +010032using Filepaths = std::vector<std::string>;
33using Classpaths = std::unordered_map<Classpath, Filepaths>;
satayevbe3f8ea2021-03-19 11:08:49 +000034
Samiul Islam5c7cfec2021-10-09 00:23:57 +010035// Matches path of format: /apex/<module-name>@<version-digits-only>/*
36static const std::regex kBindMountedApex("/apex/[^/]+@[0-9]+/");
37// Capture module name in following formats:
38// - /apex/<module-name>/*
39// - /apex/<module-name>@*/*
40static const std::regex kApexPathRegex("(/apex/[^@/]+)(?:@[^@/]+)?/");
41
42static const std::string kBootclasspathFragmentLocation = "/etc/classpaths/bootclasspath.pb";
43static const std::string kSystemserverclasspathFragmentLocation =
44 "/etc/classpaths/systemserverclasspath.pb";
Artur Satayev80516af2021-03-30 11:36:03 +010045
Victor Hsiehe7d34bf2021-10-06 08:38:52 -070046std::vector<std::string> getBootclasspathFragmentGlobPatterns(const Args& args) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +010047 // Scan only specific directory for fragments if scan_dir is specified
48 if (!args.scan_dirs.empty()) {
49 std::vector<std::string> patterns;
50 for (const auto& scan_dir : args.scan_dirs) {
51 patterns.push_back(scan_dir + kBootclasspathFragmentLocation);
52 }
53 return patterns;
54 }
55
Victor Hsiehe7d34bf2021-10-06 08:38:52 -070056 // Defines the order of individual fragments to be merged for BOOTCLASSPATH:
57 // 1. Jars in ART module always come first;
58 // 2. Jars defined as part of /system/etc/classpaths;
59 // 3. Jars defined in all non-ART apexes that expose /apex/*/etc/classpaths fragments.
60 //
61 // Notes:
62 // - Relative order in the individual fragment files is not changed when merging.
63 // - If a fragment file is matched by multiple globs, the first one is used; i.e. ART module
64 // fragment is only parsed once, even if there is a "/apex/*/" pattern later.
65 // - If there are multiple files matched for a glob pattern with wildcards, the results are sorted
66 // by pathname (default glob behaviour); i.e. all fragment files are sorted within a single
67 // "pattern block".
68 std::vector<std::string> patterns = {
69 // ART module is a special case and must come first before any other classpath entries.
Samiul Islam5c7cfec2021-10-09 00:23:57 +010070 "/apex/com.android.art" + kBootclasspathFragmentLocation,
Victor Hsiehe7d34bf2021-10-06 08:38:52 -070071 };
72 if (args.system_bootclasspath_fragment.empty()) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +010073 patterns.emplace_back("/system" + kBootclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -070074 } else {
75 // TODO: Avoid applying glob(3) expansion later to this path. Although the caller should not
76 // provide a path that contains '*', it can technically happen. Instead of checking the string
77 // format, we should just avoid the glob(3) for this string.
78 patterns.emplace_back(args.system_bootclasspath_fragment);
79 }
Samiul Islam5c7cfec2021-10-09 00:23:57 +010080 patterns.emplace_back("/apex/*" + kBootclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -070081 return patterns;
82}
satayevf1b36412021-05-20 21:31:21 +010083
Victor Hsiehe7d34bf2021-10-06 08:38:52 -070084std::vector<std::string> getSystemserverclasspathFragmentGlobPatterns(const Args& args) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +010085 // Scan only specific directory for fragments if scan_dir is specified
86 if (!args.scan_dirs.empty()) {
87 std::vector<std::string> patterns;
88 for (const auto& scan_dir : args.scan_dirs) {
89 patterns.push_back(scan_dir + kSystemserverclasspathFragmentLocation);
90 }
91 return patterns;
92 }
93
Victor Hsiehe7d34bf2021-10-06 08:38:52 -070094 // Defines the order of individual fragments to be merged for SYSTEMSERVERCLASSPATH.
95 //
96 // ART system server jars are not special in this case, and are considered to be part of all the
97 // other apexes that may expose system server jars.
98 //
99 // All notes from getBootclasspathFragmentGlobPatterns apply here.
100 std::vector<std::string> patterns;
101 if (args.system_systemserverclasspath_fragment.empty()) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100102 patterns.emplace_back("/system" + kSystemserverclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700103 } else {
104 // TODO: Avoid applying glob(3) expansion later to this path. See above.
105 patterns.emplace_back(args.system_systemserverclasspath_fragment);
106 }
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100107 patterns.emplace_back("/apex/*" + kSystemserverclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700108 return patterns;
Artur Satayev80516af2021-03-30 11:36:03 +0100109};
110
111// Finds all classpath fragment files that match the glob pattern and appends them to `fragments`.
112//
113// If a newly found fragment is already present in `fragments`, it is skipped to avoid duplicates.
114// Note that appended fragment files are sorted by pathnames, which is a default behaviour for
115// glob().
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100116//
117// glob_pattern_prefix is only populated for unit tests so that we can search for pattern in a test
118// directory instead of from root.
119bool GlobClasspathFragments(Filepaths* fragments, const std::string& glob_pattern_prefix,
120 const std::string& pattern) {
Artur Satayev80516af2021-03-30 11:36:03 +0100121 glob_t glob_result;
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100122 const int ret = glob((glob_pattern_prefix + pattern).c_str(), GLOB_MARK, nullptr, &glob_result);
Artur Satayev80516af2021-03-30 11:36:03 +0100123 if (ret != 0 && ret != GLOB_NOMATCH) {
124 globfree(&glob_result);
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100125 LOG(ERROR) << "Failed to glob " << glob_pattern_prefix + pattern;
satayevbe3f8ea2021-03-19 11:08:49 +0000126 return false;
127 }
128
Artur Satayev80516af2021-03-30 11:36:03 +0100129 for (size_t i = 0; i < glob_result.gl_pathc; i++) {
130 std::string path = glob_result.gl_pathv[i];
131 // Skip <name>@<ver> dirs, as they are bind-mounted to <name>
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100132 // Remove glob_pattern_prefix first since kBindMountedAPex has prefix requirement
133 if (std::regex_search(path.substr(glob_pattern_prefix.size()), kBindMountedApex)) {
Artur Satayev80516af2021-03-30 11:36:03 +0100134 continue;
135 }
136 // Make sure we don't push duplicate fragments from previously processed patterns
137 if (std::find(fragments->begin(), fragments->end(), path) == fragments->end()) {
138 fragments->push_back(path);
139 }
140 }
141 globfree(&glob_result);
142 return true;
143}
144
145// Writes the contents of *CLASSPATH variables to /data in the format expected by `load_exports`
146// action from init.rc. See platform/system/core/init/README.md.
Alex Lightd7937d12021-04-26 16:49:57 -0700147bool WriteClasspathExports(Classpaths classpaths, std::string_view output_path) {
satayevacb41b92021-06-30 16:49:53 +0100148 LOG(INFO) << "WriteClasspathExports " << output_path;
Artur Satayev80516af2021-03-30 11:36:03 +0100149
satayevacb41b92021-06-30 16:49:53 +0100150 std::stringstream out;
Artur Satayev80516af2021-03-30 11:36:03 +0100151 out << "export BOOTCLASSPATH " << android::base::Join(classpaths[BOOTCLASSPATH], ':') << '\n';
152 out << "export DEX2OATBOOTCLASSPATH "
153 << android::base::Join(classpaths[DEX2OATBOOTCLASSPATH], ':') << '\n';
154 out << "export SYSTEMSERVERCLASSPATH "
155 << android::base::Join(classpaths[SYSTEMSERVERCLASSPATH], ':') << '\n';
156
satayevacb41b92021-06-30 16:49:53 +0100157 const std::string& content = out.str();
158 LOG(INFO) << "WriteClasspathExports content\n" << content;
159
Alex Lightd7937d12021-04-26 16:49:57 -0700160 const std::string path_str(output_path);
satayev43e7dff2021-08-02 16:18:21 +0100161 if (android::base::StartsWith(path_str, "/data/")) {
162 // When writing to /data, write to a temp file first to make sure the partition is not full.
163 const std::string temp_str(path_str + ".tmp");
164 if (!android::base::WriteStringToFile(content, temp_str, /*follow_symlinks=*/true)) {
165 return false;
166 }
167 return rename(temp_str.c_str(), path_str.c_str()) == 0;
168 } else {
169 return android::base::WriteStringToFile(content, path_str, /*follow_symlinks=*/true);
170 }
Artur Satayev80516af2021-03-30 11:36:03 +0100171}
172
173bool ReadClasspathFragment(ExportedClasspathsJars* fragment, const std::string& filepath) {
satayevacb41b92021-06-30 16:49:53 +0100174 LOG(INFO) << "ReadClasspathFragment " << filepath;
Artur Satayev80516af2021-03-30 11:36:03 +0100175 std::string contents;
176 if (!android::base::ReadFileToString(filepath, &contents)) {
177 PLOG(ERROR) << "Failed to read " << filepath;
178 return false;
179 }
180 if (!fragment->ParseFromString(contents)) {
181 LOG(ERROR) << "Failed to parse " << filepath;
182 return false;
183 }
184 return true;
185}
186
satayevd2687422021-05-19 19:39:39 +0100187// Returns an allowed prefix for a jar filepaths declared in a given fragment.
188// For a given apex fragment, it returns the apex path - "/apex/com.android.foo" - as an allowed
189// prefix for jars. This can be used to enforce that an apex fragment only exports jars located in
190// that apex. For system fragment, it returns an empty string to allow any jars to be exported by
191// the platform.
192std::string GetAllowedJarPathPrefix(const std::string& fragment_path) {
193 std::smatch match;
194 if (std::regex_search(fragment_path, match, kApexPathRegex)) {
195 return match[1];
196 }
197 return "";
198}
199
satayevf1b36412021-05-20 21:31:21 +0100200// Finds and parses all classpath fragments on device matching given glob patterns.
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700201bool ParseFragments(const Args& args, Classpaths& classpaths, bool boot_jars) {
satayevacb41b92021-06-30 16:49:53 +0100202 LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath");
satayevf1b36412021-05-20 21:31:21 +0100203
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700204 auto glob_patterns = boot_jars ? getBootclasspathFragmentGlobPatterns(args)
205 : getSystemserverclasspathFragmentGlobPatterns(args);
satayevacb41b92021-06-30 16:49:53 +0100206
207 Filepaths fragments;
satayevf1b36412021-05-20 21:31:21 +0100208 for (const auto& pattern : glob_patterns) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100209 if (!GlobClasspathFragments(&fragments, args.glob_pattern_prefix, pattern)) {
Artur Satayev80516af2021-03-30 11:36:03 +0100210 return false;
211 }
satayevbe3f8ea2021-03-19 11:08:49 +0000212 }
213
satayevd2687422021-05-19 19:39:39 +0100214 for (const auto& fragment_path : fragments) {
Artur Satayev80516af2021-03-30 11:36:03 +0100215 ExportedClasspathsJars exportedJars;
satayevd2687422021-05-19 19:39:39 +0100216 if (!ReadClasspathFragment(&exportedJars, fragment_path)) {
Artur Satayev80516af2021-03-30 11:36:03 +0100217 return false;
218 }
satayevd2687422021-05-19 19:39:39 +0100219
220 // Either a path to the apex, or an empty string
221 const std::string& allowed_jar_prefix = GetAllowedJarPathPrefix(fragment_path);
222
Artur Satayev80516af2021-03-30 11:36:03 +0100223 for (const Jar& jar : exportedJars.jars()) {
satayevd2687422021-05-19 19:39:39 +0100224 const std::string& jar_path = jar.path();
225 CHECK(android::base::StartsWith(jar_path, allowed_jar_prefix))
226 << fragment_path << " must not export a jar from outside of the apex: " << jar_path;
satayev43eba8f2021-06-11 15:07:48 +0100227
satayevf1b36412021-05-20 21:31:21 +0100228 const Classpath classpath = jar.classpath();
229 CHECK(boot_jars ^ (classpath == SYSTEMSERVERCLASSPATH))
230 << fragment_path << " must not export a jar for " << Classpath_Name(classpath);
satayev43eba8f2021-06-11 15:07:48 +0100231
232 if (!jar.min_sdk_version().empty()) {
233 const auto& min_sdk_version = jar.min_sdk_version();
234 if (!android::modules::sdklevel::unbounded::IsAtLeast(min_sdk_version)) {
235 LOG(INFO) << "not installing " << jar_path << " with min_sdk_version " << min_sdk_version;
236 continue;
237 }
238 }
239
240 if (!jar.max_sdk_version().empty()) {
241 const auto& max_sdk_version = jar.max_sdk_version();
242 if (!android::modules::sdklevel::unbounded::IsAtMost(max_sdk_version)) {
243 LOG(INFO) << "not installing " << jar_path << " with max_sdk_version " << max_sdk_version;
244 continue;
245 }
246 }
247
satayevf1b36412021-05-20 21:31:21 +0100248 classpaths[classpath].push_back(jar_path);
Artur Satayev80516af2021-03-30 11:36:03 +0100249 }
250 }
satayevf1b36412021-05-20 21:31:21 +0100251 return true;
252}
Artur Satayev80516af2021-03-30 11:36:03 +0100253
satayeva10318b2021-06-14 12:40:31 +0100254// Generates /data/system/environ/classpath exports file by globing and merging individual
255// classpaths.proto config fragments. The exports file is read by init.rc to setenv *CLASSPATH
256// environ variables at runtime.
Victor Hsieh86d87ad2021-10-01 16:33:17 -0700257bool GenerateClasspathExports(const Args& args) {
satayevf1b36412021-05-20 21:31:21 +0100258 // Parse all known classpath fragments
satayeva10318b2021-06-14 12:40:31 +0100259 CHECK(android::modules::sdklevel::IsAtLeastS())
260 << "derive_classpath must only be run on Android 12 or above";
261
satayevf1b36412021-05-20 21:31:21 +0100262 Classpaths classpaths;
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700263 if (!ParseFragments(args, classpaths, /*boot_jars=*/true)) {
satayevf1b36412021-05-20 21:31:21 +0100264 LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments";
265 return false;
266 }
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700267 if (!ParseFragments(args, classpaths, /*boot_jars=*/false)) {
satayevf1b36412021-05-20 21:31:21 +0100268 LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments";
269 return false;
270 }
271
272 // Write export actions for init.rc
Victor Hsieh86d87ad2021-10-01 16:33:17 -0700273 if (!WriteClasspathExports(classpaths, args.output_path)) {
274 PLOG(ERROR) << "Failed to write " << args.output_path;
Artur Satayev80516af2021-03-30 11:36:03 +0100275 return false;
276 }
satayevbe3f8ea2021-03-19 11:08:49 +0000277 return true;
278}
279
280} // namespace derive_classpath
281} // namespace android