1 /*
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 #define LOG_TAG "derive_classpath"
18
19 #include "derive_classpath.h"
20 #include <android-base/file.h>
21 #include <android-base/logging.h>
22 #include <android-base/strings.h>
23 #include <android-modules-utils/sdk_level.h>
24 #include <android-modules-utils/unbounded_sdk_level.h>
25 #include <glob.h>
26 #include <regex>
27 #include <sstream>
28 #include <unordered_map>
29
30 #include "packages/modules/common/proto/classpaths.pb.h"
31
32 namespace android {
33 namespace derive_classpath {
34
35 using Filepaths = std::vector<std::string>;
36 using Classpaths = std::unordered_map<Classpath, Filepaths>;
37
38 // Matches path of format: /apex/<module-name>@<version-digits-only>/*
39 static const std::regex kBindMountedApex("/apex/[^/]+@[0-9]+/");
40 // Capture module name in following formats:
41 // - /apex/<module-name>/*
42 // - /apex/<module-name>@*/*
43 static const std::regex kApexPathRegex("(/apex/[^@/]+)(?:@[^@/]+)?/");
44
45 static const std::string kBootclasspathFragmentLocation = "/etc/classpaths/bootclasspath.pb";
46 static const std::string kSystemserverclasspathFragmentLocation =
47 "/etc/classpaths/systemserverclasspath.pb";
48
getBootclasspathFragmentGlobPatterns(const Args & args)49 std::vector<std::string> getBootclasspathFragmentGlobPatterns(const Args& args) {
50 // Scan only specific directory for fragments if scan_dir is specified
51 if (!args.scan_dirs.empty()) {
52 std::vector<std::string> patterns;
53 for (const auto& scan_dir : args.scan_dirs) {
54 patterns.push_back(scan_dir + kBootclasspathFragmentLocation);
55 }
56 return patterns;
57 }
58
59 // Defines the order of individual fragments to be merged for BOOTCLASSPATH:
60 // 1. Jars in ART module always come first;
61 // 2. Jars defined as part of /system/etc/classpaths;
62 // 3. Jars defined in all non-ART apexes that expose /apex/*/etc/classpaths fragments.
63 //
64 // Notes:
65 // - Relative order in the individual fragment files is not changed when merging.
66 // - If a fragment file is matched by multiple globs, the first one is used; i.e. ART module
67 // fragment is only parsed once, even if there is a "/apex/*/" pattern later.
68 // - If there are multiple files matched for a glob pattern with wildcards, the results are sorted
69 // by pathname (default glob behaviour); i.e. all fragment files are sorted within a single
70 // "pattern block".
71 std::vector<std::string> patterns = {
72 // ART module is a special case and must come first before any other classpath entries.
73 "/apex/com.android.art" + kBootclasspathFragmentLocation,
74 };
75 if (args.system_bootclasspath_fragment.empty()) {
76 patterns.emplace_back("/system" + kBootclasspathFragmentLocation);
77 } else {
78 // TODO: Avoid applying glob(3) expansion later to this path. Although the caller should not
79 // provide a path that contains '*', it can technically happen. Instead of checking the string
80 // format, we should just avoid the glob(3) for this string.
81 patterns.emplace_back(args.system_bootclasspath_fragment);
82 }
83 patterns.emplace_back("/apex/*" + kBootclasspathFragmentLocation);
84 return patterns;
85 }
86
getSystemserverclasspathFragmentGlobPatterns(const Args & args)87 std::vector<std::string> getSystemserverclasspathFragmentGlobPatterns(const Args& args) {
88 // Scan only specific directory for fragments if scan_dir is specified
89 if (!args.scan_dirs.empty()) {
90 std::vector<std::string> patterns;
91 for (const auto& scan_dir : args.scan_dirs) {
92 patterns.push_back(scan_dir + kSystemserverclasspathFragmentLocation);
93 }
94 return patterns;
95 }
96
97 // Defines the order of individual fragments to be merged for SYSTEMSERVERCLASSPATH.
98 //
99 // ART system server jars are not special in this case, and are considered to be part of all the
100 // other apexes that may expose system server jars.
101 //
102 // All notes from getBootclasspathFragmentGlobPatterns apply here.
103 std::vector<std::string> patterns;
104 if (args.system_systemserverclasspath_fragment.empty()) {
105 patterns.emplace_back("/system" + kSystemserverclasspathFragmentLocation);
106 } else {
107 // TODO: Avoid applying glob(3) expansion later to this path. See above.
108 patterns.emplace_back(args.system_systemserverclasspath_fragment);
109 }
110 patterns.emplace_back("/apex/*" + kSystemserverclasspathFragmentLocation);
111 return patterns;
112 };
113
114 // Finds all classpath fragment files that match the glob pattern and appends them to `fragments`.
115 //
116 // If a newly found fragment is already present in `fragments`, it is skipped to avoid duplicates.
117 // Note that appended fragment files are sorted by pathnames, which is a default behaviour for
118 // glob().
119 //
120 // glob_pattern_prefix is only populated for unit tests so that we can search for pattern in a test
121 // directory instead of from root.
GlobClasspathFragments(Filepaths * fragments,const std::string & glob_pattern_prefix,const std::string & pattern)122 bool GlobClasspathFragments(Filepaths* fragments, const std::string& glob_pattern_prefix,
123 const std::string& pattern) {
124 glob_t glob_result;
125 const int ret = glob((glob_pattern_prefix + pattern).c_str(), GLOB_MARK, nullptr, &glob_result);
126 if (ret != 0 && ret != GLOB_NOMATCH) {
127 globfree(&glob_result);
128 LOG(ERROR) << "Failed to glob " << glob_pattern_prefix + pattern;
129 return false;
130 }
131
132 for (size_t i = 0; i < glob_result.gl_pathc; i++) {
133 std::string path = glob_result.gl_pathv[i];
134 // Skip <name>@<ver> dirs, as they are bind-mounted to <name>
135 // Remove glob_pattern_prefix first since kBindMountedAPex has prefix requirement
136 if (std::regex_search(path.substr(glob_pattern_prefix.size()), kBindMountedApex)) {
137 continue;
138 }
139 // Make sure we don't push duplicate fragments from previously processed patterns
140 if (std::find(fragments->begin(), fragments->end(), path) == fragments->end()) {
141 fragments->push_back(path);
142 }
143 }
144 globfree(&glob_result);
145 return true;
146 }
147
148 // Writes the contents of *CLASSPATH variables to /data in the format expected by `load_exports`
149 // action from init.rc. See platform/system/core/init/README.md.
WriteClasspathExports(Classpaths classpaths,std::string_view output_path)150 bool WriteClasspathExports(Classpaths classpaths, std::string_view output_path) {
151 LOG(INFO) << "WriteClasspathExports " << output_path;
152
153 std::stringstream out;
154 out << "export BOOTCLASSPATH " << android::base::Join(classpaths[BOOTCLASSPATH], ':') << '\n';
155 out << "export DEX2OATBOOTCLASSPATH "
156 << android::base::Join(classpaths[DEX2OATBOOTCLASSPATH], ':') << '\n';
157 out << "export SYSTEMSERVERCLASSPATH "
158 << android::base::Join(classpaths[SYSTEMSERVERCLASSPATH], ':') << '\n';
159 out << "export STANDALONE_SYSTEMSERVER_JARS "
160 << android::base::Join(classpaths[STANDALONE_SYSTEMSERVER_JARS], ':') << '\n';
161
162 const std::string& content = out.str();
163 LOG(INFO) << "WriteClasspathExports content\n" << content;
164
165 const std::string path_str(output_path);
166 if (android::base::StartsWith(path_str, "/data/")) {
167 // When writing to /data, write to a temp file first to make sure the partition is not full.
168 const std::string temp_str(path_str + ".tmp");
169 if (!android::base::WriteStringToFile(content, temp_str, /*follow_symlinks=*/true)) {
170 return false;
171 }
172 return rename(temp_str.c_str(), path_str.c_str()) == 0;
173 } else {
174 return android::base::WriteStringToFile(content, path_str, /*follow_symlinks=*/true);
175 }
176 }
177
ReadClasspathFragment(ExportedClasspathsJars * fragment,const std::string & filepath)178 bool ReadClasspathFragment(ExportedClasspathsJars* fragment, const std::string& filepath) {
179 LOG(INFO) << "ReadClasspathFragment " << filepath;
180 std::string contents;
181 if (!android::base::ReadFileToString(filepath, &contents)) {
182 PLOG(ERROR) << "Failed to read " << filepath;
183 return false;
184 }
185 if (!fragment->ParseFromString(contents)) {
186 LOG(ERROR) << "Failed to parse " << filepath;
187 return false;
188 }
189 return true;
190 }
191
192 // Returns an allowed prefix for a jar filepaths declared in a given fragment.
193 // For a given apex fragment, it returns the apex path - "/apex/com.android.foo" - as an allowed
194 // prefix for jars. This can be used to enforce that an apex fragment only exports jars located in
195 // that apex. For system fragment, it returns an empty string to allow any jars to be exported by
196 // the platform.
GetAllowedJarPathPrefix(const std::string & fragment_path)197 std::string GetAllowedJarPathPrefix(const std::string& fragment_path) {
198 std::smatch match;
199 if (std::regex_search(fragment_path, match, kApexPathRegex)) {
200 return match[1];
201 }
202 return "";
203 }
204
205 // Finds and parses all classpath fragments on device matching given glob patterns.
ParseFragments(const Args & args,Classpaths & classpaths,bool boot_jars)206 bool ParseFragments(const Args& args, Classpaths& classpaths, bool boot_jars) {
207 LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath");
208
209 auto glob_patterns = boot_jars ? getBootclasspathFragmentGlobPatterns(args)
210 : getSystemserverclasspathFragmentGlobPatterns(args);
211
212 Filepaths fragments;
213 for (const auto& pattern : glob_patterns) {
214 if (!GlobClasspathFragments(&fragments, args.glob_pattern_prefix, pattern)) {
215 return false;
216 }
217 }
218
219 for (const auto& fragment_path : fragments) {
220 ExportedClasspathsJars exportedJars;
221 if (!ReadClasspathFragment(&exportedJars, fragment_path)) {
222 return false;
223 }
224
225 // Either a path to the apex, or an empty string
226 const std::string& allowed_jar_prefix = GetAllowedJarPathPrefix(fragment_path);
227
228 for (const Jar& jar : exportedJars.jars()) {
229 const std::string& jar_path = jar.path();
230 CHECK(android::base::StartsWith(jar_path, allowed_jar_prefix))
231 << fragment_path << " must not export a jar from outside of the apex: " << jar_path;
232
233 const Classpath classpath = jar.classpath();
234 CHECK(boot_jars ^
235 (classpath == SYSTEMSERVERCLASSPATH || classpath == STANDALONE_SYSTEMSERVER_JARS))
236 << fragment_path << " must not export a jar for " << Classpath_Name(classpath);
237
238 if (!jar.min_sdk_version().empty()) {
239 const auto& min_sdk_version = jar.min_sdk_version();
240 if (!android::modules::sdklevel::unbounded::IsAtLeast(min_sdk_version.c_str())) {
241 LOG(INFO) << "not installing " << jar_path << " with min_sdk_version " << min_sdk_version;
242 continue;
243 }
244 }
245
246 if (!jar.max_sdk_version().empty()) {
247 const auto& max_sdk_version = jar.max_sdk_version();
248 if (!android::modules::sdklevel::unbounded::IsAtMost(max_sdk_version.c_str())) {
249 LOG(INFO) << "not installing " << jar_path << " with max_sdk_version " << max_sdk_version;
250 continue;
251 }
252 }
253
254 classpaths[classpath].push_back(jar_path);
255 }
256 }
257 return true;
258 }
259
260 // Generates /data/system/environ/classpath exports file by globing and merging individual
261 // classpaths.proto config fragments. The exports file is read by init.rc to setenv *CLASSPATH
262 // environ variables at runtime.
GenerateClasspathExports(const Args & args)263 bool GenerateClasspathExports(const Args& args) {
264 // Parse all known classpath fragments
265 CHECK(android::modules::sdklevel::IsAtLeastS())
266 << "derive_classpath must only be run on Android 12 or above";
267
268 Classpaths classpaths;
269 if (!ParseFragments(args, classpaths, /*boot_jars=*/true)) {
270 LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments";
271 return false;
272 }
273 if (!ParseFragments(args, classpaths, /*boot_jars=*/false)) {
274 LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments";
275 return false;
276 }
277
278 // Write export actions for init.rc
279 if (!WriteClasspathExports(classpaths, args.output_path)) {
280 PLOG(ERROR) << "Failed to write " << args.output_path;
281 return false;
282 }
283 return true;
284 }
285
286 } // namespace derive_classpath
287 } // namespace android
288