blob: c1faec2e28c69bb464dab961b0fb6daa890618aa [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
Anton Hansson45012df2022-05-25 15:13:07 +000017#define LOG_TAG "derive_classpath"
18
satayevbe3f8ea2021-03-19 11:08:49 +000019#include "derive_classpath.h"
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080020
21#include <ctype.h>
Artur Satayev80516af2021-03-30 11:36:03 +010022#include <glob.h>
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080023
Artur Satayev80516af2021-03-30 11:36:03 +010024#include <regex>
25#include <sstream>
Krzysztof KosiƄskif4898d12024-03-15 17:08:30 +000026#include <unordered_map>
Artur Satayev80516af2021-03-30 11:36:03 +010027
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080028#include <android-base/file.h>
29#include <android-base/logging.h>
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080030#include <android-base/parseint.h>
Jiakai Zhang12a30412025-02-19 14:57:42 +000031#include <android-base/strings.h>
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080032
satayev6dd0d702021-05-10 12:33:24 +010033#include "packages/modules/common/proto/classpaths.pb.h"
satayevbe3f8ea2021-03-19 11:08:49 +000034
Jiakai Zhang12a30412025-02-19 14:57:42 +000035#ifdef SDKEXT_ANDROID
36#include <android-modules-utils/sdk_level.h>
37#include <android-modules-utils/unbounded_sdk_level.h>
38#endif
39
satayevbe3f8ea2021-03-19 11:08:49 +000040namespace android {
41namespace derive_classpath {
42
Artur Satayev80516af2021-03-30 11:36:03 +010043using Filepaths = std::vector<std::string>;
44using Classpaths = std::unordered_map<Classpath, Filepaths>;
satayevbe3f8ea2021-03-19 11:08:49 +000045
Samiul Islam5c7cfec2021-10-09 00:23:57 +010046// Matches path of format: /apex/<module-name>@<version-digits-only>/*
47static const std::regex kBindMountedApex("/apex/[^/]+@[0-9]+/");
48// Capture module name in following formats:
49// - /apex/<module-name>/*
50// - /apex/<module-name>@*/*
51static const std::regex kApexPathRegex("(/apex/[^@/]+)(?:@[^@/]+)?/");
52
53static const std::string kBootclasspathFragmentLocation = "/etc/classpaths/bootclasspath.pb";
54static const std::string kSystemserverclasspathFragmentLocation =
55 "/etc/classpaths/systemserverclasspath.pb";
Artur Satayev80516af2021-03-30 11:36:03 +010056
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080057static int GetVersionInt(const std::string& version) {
58 int version_int = 0;
59 if (!android::base::ParseInt(version, &version_int, /*min=*/1, /*max=*/INT_MAX)) {
60 PLOG(FATAL) << "Failed to convert version \"" << version << "\" to int";
61 }
62 return version_int;
63}
64
65static bool IsCodename(const std::string& version) {
66 LOG_IF(FATAL, version.empty()) << "Empty version";
67 return isupper(version[0]);
68}
69
70static bool SdkLevelIsAtLeast(const Args& args, const std::string& version) {
Jiakai Zhang12a30412025-02-19 14:57:42 +000071#ifdef SDKEXT_ANDROID
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080072 if (args.override_device_sdk_version == 0) {
73 // Most common case: no override.
74 return android::modules::sdklevel::unbounded::IsAtLeast(version.c_str());
75 }
Jiakai Zhang12a30412025-02-19 14:57:42 +000076#endif
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080077
78 // Mirrors the logic in unbounded_sdk_level.h.
79 if (args.override_device_codename == "REL") {
80 if (IsCodename(version)) {
81 return false;
82 }
83 return args.override_device_sdk_version >= GetVersionInt(version);
84 }
85 if (IsCodename(version)) {
86 return args.override_device_known_codenames.contains(version);
87 }
88 return args.override_device_sdk_version >= GetVersionInt(version);
89}
90
91static bool SdkLevelIsAtMost(const Args& args, const std::string& version) {
Jiakai Zhang12a30412025-02-19 14:57:42 +000092#ifdef SDKEXT_ANDROID
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080093 if (args.override_device_sdk_version == 0) {
94 // Most common case: no override.
95 return android::modules::sdklevel::unbounded::IsAtMost(version.c_str());
96 }
Jiakai Zhang12a30412025-02-19 14:57:42 +000097#endif
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -080098
99 // Mirrors the logic in unbounded_sdk_level.h.
100 if (args.override_device_codename == "REL") {
101 if (IsCodename(version)) {
102 return true;
103 }
104 return args.override_device_sdk_version <= GetVersionInt(version);
105 }
106 if (IsCodename(version)) {
107 return !args.override_device_known_codenames.contains(version) ||
108 args.override_device_codename == version;
109 }
110 return args.override_device_sdk_version < GetVersionInt(version);
111}
112
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700113std::vector<std::string> getBootclasspathFragmentGlobPatterns(const Args& args) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100114 // Scan only specific directory for fragments if scan_dir is specified
115 if (!args.scan_dirs.empty()) {
116 std::vector<std::string> patterns;
117 for (const auto& scan_dir : args.scan_dirs) {
118 patterns.push_back(scan_dir + kBootclasspathFragmentLocation);
119 }
120 return patterns;
121 }
122
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700123 // Defines the order of individual fragments to be merged for BOOTCLASSPATH:
124 // 1. Jars in ART module always come first;
125 // 2. Jars defined as part of /system/etc/classpaths;
126 // 3. Jars defined in all non-ART apexes that expose /apex/*/etc/classpaths fragments.
127 //
128 // Notes:
129 // - Relative order in the individual fragment files is not changed when merging.
130 // - If a fragment file is matched by multiple globs, the first one is used; i.e. ART module
131 // fragment is only parsed once, even if there is a "/apex/*/" pattern later.
132 // - If there are multiple files matched for a glob pattern with wildcards, the results are sorted
133 // by pathname (default glob behaviour); i.e. all fragment files are sorted within a single
134 // "pattern block".
135 std::vector<std::string> patterns = {
136 // ART module is a special case and must come first before any other classpath entries.
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100137 "/apex/com.android.art" + kBootclasspathFragmentLocation,
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700138 };
139 if (args.system_bootclasspath_fragment.empty()) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100140 patterns.emplace_back("/system" + kBootclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700141 } else {
142 // TODO: Avoid applying glob(3) expansion later to this path. Although the caller should not
143 // provide a path that contains '*', it can technically happen. Instead of checking the string
144 // format, we should just avoid the glob(3) for this string.
145 patterns.emplace_back(args.system_bootclasspath_fragment);
146 }
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100147 patterns.emplace_back("/apex/*" + kBootclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700148 return patterns;
149}
satayevf1b36412021-05-20 21:31:21 +0100150
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700151std::vector<std::string> getSystemserverclasspathFragmentGlobPatterns(const Args& args) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100152 // Scan only specific directory for fragments if scan_dir is specified
153 if (!args.scan_dirs.empty()) {
154 std::vector<std::string> patterns;
155 for (const auto& scan_dir : args.scan_dirs) {
156 patterns.push_back(scan_dir + kSystemserverclasspathFragmentLocation);
157 }
158 return patterns;
159 }
160
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700161 // Defines the order of individual fragments to be merged for SYSTEMSERVERCLASSPATH.
162 //
163 // ART system server jars are not special in this case, and are considered to be part of all the
164 // other apexes that may expose system server jars.
165 //
166 // All notes from getBootclasspathFragmentGlobPatterns apply here.
167 std::vector<std::string> patterns;
168 if (args.system_systemserverclasspath_fragment.empty()) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100169 patterns.emplace_back("/system" + kSystemserverclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700170 } else {
171 // TODO: Avoid applying glob(3) expansion later to this path. See above.
172 patterns.emplace_back(args.system_systemserverclasspath_fragment);
173 }
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100174 patterns.emplace_back("/apex/*" + kSystemserverclasspathFragmentLocation);
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700175 return patterns;
Artur Satayev80516af2021-03-30 11:36:03 +0100176};
177
178// Finds all classpath fragment files that match the glob pattern and appends them to `fragments`.
179//
180// If a newly found fragment is already present in `fragments`, it is skipped to avoid duplicates.
181// Note that appended fragment files are sorted by pathnames, which is a default behaviour for
182// glob().
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100183//
184// glob_pattern_prefix is only populated for unit tests so that we can search for pattern in a test
185// directory instead of from root.
186bool GlobClasspathFragments(Filepaths* fragments, const std::string& glob_pattern_prefix,
187 const std::string& pattern) {
Artur Satayev80516af2021-03-30 11:36:03 +0100188 glob_t glob_result;
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100189 const int ret = glob((glob_pattern_prefix + pattern).c_str(), GLOB_MARK, nullptr, &glob_result);
Artur Satayev80516af2021-03-30 11:36:03 +0100190 if (ret != 0 && ret != GLOB_NOMATCH) {
191 globfree(&glob_result);
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100192 LOG(ERROR) << "Failed to glob " << glob_pattern_prefix + pattern;
satayevbe3f8ea2021-03-19 11:08:49 +0000193 return false;
194 }
195
Artur Satayev80516af2021-03-30 11:36:03 +0100196 for (size_t i = 0; i < glob_result.gl_pathc; i++) {
197 std::string path = glob_result.gl_pathv[i];
198 // Skip <name>@<ver> dirs, as they are bind-mounted to <name>
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100199 // Remove glob_pattern_prefix first since kBindMountedAPex has prefix requirement
200 if (std::regex_search(path.substr(glob_pattern_prefix.size()), kBindMountedApex)) {
Artur Satayev80516af2021-03-30 11:36:03 +0100201 continue;
202 }
203 // Make sure we don't push duplicate fragments from previously processed patterns
204 if (std::find(fragments->begin(), fragments->end(), path) == fragments->end()) {
205 fragments->push_back(path);
206 }
207 }
208 globfree(&glob_result);
209 return true;
210}
211
212// Writes the contents of *CLASSPATH variables to /data in the format expected by `load_exports`
213// action from init.rc. See platform/system/core/init/README.md.
Alex Lightd7937d12021-04-26 16:49:57 -0700214bool WriteClasspathExports(Classpaths classpaths, std::string_view output_path) {
satayevacb41b92021-06-30 16:49:53 +0100215 LOG(INFO) << "WriteClasspathExports " << output_path;
Artur Satayev80516af2021-03-30 11:36:03 +0100216
satayevacb41b92021-06-30 16:49:53 +0100217 std::stringstream out;
Artur Satayev80516af2021-03-30 11:36:03 +0100218 out << "export BOOTCLASSPATH " << android::base::Join(classpaths[BOOTCLASSPATH], ':') << '\n';
219 out << "export DEX2OATBOOTCLASSPATH "
220 << android::base::Join(classpaths[DEX2OATBOOTCLASSPATH], ':') << '\n';
221 out << "export SYSTEMSERVERCLASSPATH "
222 << android::base::Join(classpaths[SYSTEMSERVERCLASSPATH], ':') << '\n';
Jiakai Zhang535bd7f2021-10-29 19:59:18 +0000223 out << "export STANDALONE_SYSTEMSERVER_JARS "
224 << android::base::Join(classpaths[STANDALONE_SYSTEMSERVER_JARS], ':') << '\n';
Artur Satayev80516af2021-03-30 11:36:03 +0100225
satayevacb41b92021-06-30 16:49:53 +0100226 const std::string& content = out.str();
227 LOG(INFO) << "WriteClasspathExports content\n" << content;
228
Alex Lightd7937d12021-04-26 16:49:57 -0700229 const std::string path_str(output_path);
satayev43e7dff2021-08-02 16:18:21 +0100230 if (android::base::StartsWith(path_str, "/data/")) {
231 // When writing to /data, write to a temp file first to make sure the partition is not full.
232 const std::string temp_str(path_str + ".tmp");
233 if (!android::base::WriteStringToFile(content, temp_str, /*follow_symlinks=*/true)) {
234 return false;
235 }
236 return rename(temp_str.c_str(), path_str.c_str()) == 0;
237 } else {
238 return android::base::WriteStringToFile(content, path_str, /*follow_symlinks=*/true);
239 }
Artur Satayev80516af2021-03-30 11:36:03 +0100240}
241
242bool ReadClasspathFragment(ExportedClasspathsJars* fragment, const std::string& filepath) {
satayevacb41b92021-06-30 16:49:53 +0100243 LOG(INFO) << "ReadClasspathFragment " << filepath;
Artur Satayev80516af2021-03-30 11:36:03 +0100244 std::string contents;
245 if (!android::base::ReadFileToString(filepath, &contents)) {
246 PLOG(ERROR) << "Failed to read " << filepath;
247 return false;
248 }
249 if (!fragment->ParseFromString(contents)) {
250 LOG(ERROR) << "Failed to parse " << filepath;
251 return false;
252 }
253 return true;
254}
255
satayevd2687422021-05-19 19:39:39 +0100256// Returns an allowed prefix for a jar filepaths declared in a given fragment.
257// For a given apex fragment, it returns the apex path - "/apex/com.android.foo" - as an allowed
258// prefix for jars. This can be used to enforce that an apex fragment only exports jars located in
259// that apex. For system fragment, it returns an empty string to allow any jars to be exported by
260// the platform.
261std::string GetAllowedJarPathPrefix(const std::string& fragment_path) {
262 std::smatch match;
263 if (std::regex_search(fragment_path, match, kApexPathRegex)) {
264 return match[1];
265 }
266 return "";
267}
268
satayevf1b36412021-05-20 21:31:21 +0100269// Finds and parses all classpath fragments on device matching given glob patterns.
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700270bool ParseFragments(const Args& args, Classpaths& classpaths, bool boot_jars) {
satayevacb41b92021-06-30 16:49:53 +0100271 LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath");
satayevf1b36412021-05-20 21:31:21 +0100272
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700273 auto glob_patterns = boot_jars ? getBootclasspathFragmentGlobPatterns(args)
274 : getSystemserverclasspathFragmentGlobPatterns(args);
satayevacb41b92021-06-30 16:49:53 +0100275
276 Filepaths fragments;
satayevf1b36412021-05-20 21:31:21 +0100277 for (const auto& pattern : glob_patterns) {
Samiul Islam5c7cfec2021-10-09 00:23:57 +0100278 if (!GlobClasspathFragments(&fragments, args.glob_pattern_prefix, pattern)) {
Artur Satayev80516af2021-03-30 11:36:03 +0100279 return false;
280 }
satayevbe3f8ea2021-03-19 11:08:49 +0000281 }
282
satayevd2687422021-05-19 19:39:39 +0100283 for (const auto& fragment_path : fragments) {
Artur Satayev80516af2021-03-30 11:36:03 +0100284 ExportedClasspathsJars exportedJars;
satayevd2687422021-05-19 19:39:39 +0100285 if (!ReadClasspathFragment(&exportedJars, fragment_path)) {
Artur Satayev80516af2021-03-30 11:36:03 +0100286 return false;
287 }
satayevd2687422021-05-19 19:39:39 +0100288
289 // Either a path to the apex, or an empty string
290 const std::string& allowed_jar_prefix = GetAllowedJarPathPrefix(fragment_path);
291
Artur Satayev80516af2021-03-30 11:36:03 +0100292 for (const Jar& jar : exportedJars.jars()) {
satayevd2687422021-05-19 19:39:39 +0100293 const std::string& jar_path = jar.path();
294 CHECK(android::base::StartsWith(jar_path, allowed_jar_prefix))
295 << fragment_path << " must not export a jar from outside of the apex: " << jar_path;
satayev43eba8f2021-06-11 15:07:48 +0100296
satayevf1b36412021-05-20 21:31:21 +0100297 const Classpath classpath = jar.classpath();
Jiakai Zhang535bd7f2021-10-29 19:59:18 +0000298 CHECK(boot_jars ^
299 (classpath == SYSTEMSERVERCLASSPATH || classpath == STANDALONE_SYSTEMSERVER_JARS))
satayevf1b36412021-05-20 21:31:21 +0100300 << fragment_path << " must not export a jar for " << Classpath_Name(classpath);
satayev43eba8f2021-06-11 15:07:48 +0100301
302 if (!jar.min_sdk_version().empty()) {
303 const auto& min_sdk_version = jar.min_sdk_version();
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -0800304 if (!SdkLevelIsAtLeast(args, min_sdk_version)) {
satayev43eba8f2021-06-11 15:07:48 +0100305 LOG(INFO) << "not installing " << jar_path << " with min_sdk_version " << min_sdk_version;
306 continue;
307 }
308 }
309
310 if (!jar.max_sdk_version().empty()) {
311 const auto& max_sdk_version = jar.max_sdk_version();
Jiakai Zhangcbe3e0e2025-01-15 06:19:03 -0800312 if (!SdkLevelIsAtMost(args, max_sdk_version)) {
satayev43eba8f2021-06-11 15:07:48 +0100313 LOG(INFO) << "not installing " << jar_path << " with max_sdk_version " << max_sdk_version;
314 continue;
315 }
316 }
317
satayevf1b36412021-05-20 21:31:21 +0100318 classpaths[classpath].push_back(jar_path);
Artur Satayev80516af2021-03-30 11:36:03 +0100319 }
320 }
satayevf1b36412021-05-20 21:31:21 +0100321 return true;
322}
Artur Satayev80516af2021-03-30 11:36:03 +0100323
satayeva10318b2021-06-14 12:40:31 +0100324// Generates /data/system/environ/classpath exports file by globing and merging individual
325// classpaths.proto config fragments. The exports file is read by init.rc to setenv *CLASSPATH
326// environ variables at runtime.
Victor Hsieh86d87ad2021-10-01 16:33:17 -0700327bool GenerateClasspathExports(const Args& args) {
Jiakai Zhang12a30412025-02-19 14:57:42 +0000328#ifdef SDKEXT_ANDROID
satayevf1b36412021-05-20 21:31:21 +0100329 // Parse all known classpath fragments
satayeva10318b2021-06-14 12:40:31 +0100330 CHECK(android::modules::sdklevel::IsAtLeastS())
331 << "derive_classpath must only be run on Android 12 or above";
Jiakai Zhang12a30412025-02-19 14:57:42 +0000332#endif
satayeva10318b2021-06-14 12:40:31 +0100333
satayevf1b36412021-05-20 21:31:21 +0100334 Classpaths classpaths;
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700335 if (!ParseFragments(args, classpaths, /*boot_jars=*/true)) {
satayevf1b36412021-05-20 21:31:21 +0100336 LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments";
337 return false;
338 }
Victor Hsiehe7d34bf2021-10-06 08:38:52 -0700339 if (!ParseFragments(args, classpaths, /*boot_jars=*/false)) {
satayevf1b36412021-05-20 21:31:21 +0100340 LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments";
341 return false;
342 }
343
344 // Write export actions for init.rc
Victor Hsieh86d87ad2021-10-01 16:33:17 -0700345 if (!WriteClasspathExports(classpaths, args.output_path)) {
346 PLOG(ERROR) << "Failed to write " << args.output_path;
Artur Satayev80516af2021-03-30 11:36:03 +0100347 return false;
348 }
satayevbe3f8ea2021-03-19 11:08:49 +0000349 return true;
350}
351
352} // namespace derive_classpath
353} // namespace android