• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
21 #include <ctype.h>
22 #include <glob.h>
23 
24 #include <regex>
25 #include <sstream>
26 #include <unordered_map>
27 
28 #include <android-base/file.h>
29 #include <android-base/logging.h>
30 #include <android-base/parseint.h>
31 #include <android-base/strings.h>
32 
33 #include "packages/modules/common/proto/classpaths.pb.h"
34 
35 #ifdef SDKEXT_ANDROID
36 #include <android-modules-utils/sdk_level.h>
37 #include <android-modules-utils/unbounded_sdk_level.h>
38 #endif
39 
40 namespace android {
41 namespace derive_classpath {
42 
43 using Filepaths = std::vector<std::string>;
44 using Classpaths = std::unordered_map<Classpath, Filepaths>;
45 
46 // Matches path of format: /apex/<module-name>@<version-digits-only>/*
47 static const std::regex kBindMountedApex("/apex/[^/]+@[0-9]+/");
48 // Capture module name in following formats:
49 // - /apex/<module-name>/*
50 // - /apex/<module-name>@*/*
51 static const std::regex kApexPathRegex("(/apex/[^@/]+)(?:@[^@/]+)?/");
52 
53 static const std::string kBootclasspathFragmentLocation = "/etc/classpaths/bootclasspath.pb";
54 static const std::string kSystemserverclasspathFragmentLocation =
55     "/etc/classpaths/systemserverclasspath.pb";
56 
GetVersionInt(const std::string & version)57 static 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 
IsCodename(const std::string & version)65 static bool IsCodename(const std::string& version) {
66   LOG_IF(FATAL, version.empty()) << "Empty version";
67   return isupper(version[0]);
68 }
69 
SdkLevelIsAtLeast(const Args & args,const std::string & version)70 static bool SdkLevelIsAtLeast(const Args& args, const std::string& version) {
71 #ifdef SDKEXT_ANDROID
72   if (args.override_device_sdk_version == 0) {
73     // Most common case: no override.
74     return android::modules::sdklevel::unbounded::IsAtLeast(version.c_str());
75   }
76 #endif
77 
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 
SdkLevelIsAtMost(const Args & args,const std::string & version)91 static bool SdkLevelIsAtMost(const Args& args, const std::string& version) {
92 #ifdef SDKEXT_ANDROID
93   if (args.override_device_sdk_version == 0) {
94     // Most common case: no override.
95     return android::modules::sdklevel::unbounded::IsAtMost(version.c_str());
96   }
97 #endif
98 
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 
getBootclasspathFragmentGlobPatterns(const Args & args)113 std::vector<std::string> getBootclasspathFragmentGlobPatterns(const Args& args) {
114   // 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 
123   // 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.
137       "/apex/com.android.art" + kBootclasspathFragmentLocation,
138   };
139   if (args.system_bootclasspath_fragment.empty()) {
140     patterns.emplace_back("/system" + kBootclasspathFragmentLocation);
141   } 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   }
147   patterns.emplace_back("/apex/*" + kBootclasspathFragmentLocation);
148   return patterns;
149 }
150 
getSystemserverclasspathFragmentGlobPatterns(const Args & args)151 std::vector<std::string> getSystemserverclasspathFragmentGlobPatterns(const Args& args) {
152   // 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 
161   // 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()) {
169     patterns.emplace_back("/system" + kSystemserverclasspathFragmentLocation);
170   } else {
171     // TODO: Avoid applying glob(3) expansion later to this path. See above.
172     patterns.emplace_back(args.system_systemserverclasspath_fragment);
173   }
174   patterns.emplace_back("/apex/*" + kSystemserverclasspathFragmentLocation);
175   return patterns;
176 };
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().
183 //
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.
GlobClasspathFragments(Filepaths * fragments,const std::string & glob_pattern_prefix,const std::string & pattern)186 bool GlobClasspathFragments(Filepaths* fragments, const std::string& glob_pattern_prefix,
187                             const std::string& pattern) {
188   glob_t glob_result;
189   const int ret = glob((glob_pattern_prefix + pattern).c_str(), GLOB_MARK, nullptr, &glob_result);
190   if (ret != 0 && ret != GLOB_NOMATCH) {
191     globfree(&glob_result);
192     LOG(ERROR) << "Failed to glob " << glob_pattern_prefix + pattern;
193     return false;
194   }
195 
196   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>
199     // Remove glob_pattern_prefix first since kBindMountedAPex has prefix requirement
200     if (std::regex_search(path.substr(glob_pattern_prefix.size()), kBindMountedApex)) {
201       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.
WriteClasspathExports(Classpaths classpaths,std::string_view output_path)214 bool WriteClasspathExports(Classpaths classpaths, std::string_view output_path) {
215   LOG(INFO) << "WriteClasspathExports " << output_path;
216 
217   std::stringstream out;
218   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';
223   out << "export STANDALONE_SYSTEMSERVER_JARS "
224       << android::base::Join(classpaths[STANDALONE_SYSTEMSERVER_JARS], ':') << '\n';
225 
226   const std::string& content = out.str();
227   LOG(INFO) << "WriteClasspathExports content\n" << content;
228 
229   const std::string path_str(output_path);
230   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   }
240 }
241 
ReadClasspathFragment(ExportedClasspathsJars * fragment,const std::string & filepath)242 bool ReadClasspathFragment(ExportedClasspathsJars* fragment, const std::string& filepath) {
243   LOG(INFO) << "ReadClasspathFragment " << filepath;
244   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 
256 // 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.
GetAllowedJarPathPrefix(const std::string & fragment_path)261 std::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 
269 // Finds and parses all classpath fragments on device matching given glob patterns.
ParseFragments(const Args & args,Classpaths & classpaths,bool boot_jars)270 bool ParseFragments(const Args& args, Classpaths& classpaths, bool boot_jars) {
271   LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath");
272 
273   auto glob_patterns = boot_jars ? getBootclasspathFragmentGlobPatterns(args)
274                                  : getSystemserverclasspathFragmentGlobPatterns(args);
275 
276   Filepaths fragments;
277   for (const auto& pattern : glob_patterns) {
278     if (!GlobClasspathFragments(&fragments, args.glob_pattern_prefix, pattern)) {
279       return false;
280     }
281   }
282 
283   for (const auto& fragment_path : fragments) {
284     ExportedClasspathsJars exportedJars;
285     if (!ReadClasspathFragment(&exportedJars, fragment_path)) {
286       return false;
287     }
288 
289     // Either a path to the apex, or an empty string
290     const std::string& allowed_jar_prefix = GetAllowedJarPathPrefix(fragment_path);
291 
292     for (const Jar& jar : exportedJars.jars()) {
293       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;
296 
297       const Classpath classpath = jar.classpath();
298       CHECK(boot_jars ^
299             (classpath == SYSTEMSERVERCLASSPATH || classpath == STANDALONE_SYSTEMSERVER_JARS))
300           << fragment_path << " must not export a jar for " << Classpath_Name(classpath);
301 
302       if (!jar.min_sdk_version().empty()) {
303         const auto& min_sdk_version = jar.min_sdk_version();
304         if (!SdkLevelIsAtLeast(args, min_sdk_version)) {
305           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();
312         if (!SdkLevelIsAtMost(args, max_sdk_version)) {
313           LOG(INFO) << "not installing " << jar_path << " with max_sdk_version " << max_sdk_version;
314           continue;
315         }
316       }
317 
318       classpaths[classpath].push_back(jar_path);
319     }
320   }
321   return true;
322 }
323 
324 // 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.
GenerateClasspathExports(const Args & args)327 bool GenerateClasspathExports(const Args& args) {
328 #ifdef SDKEXT_ANDROID
329   // Parse all known classpath fragments
330   CHECK(android::modules::sdklevel::IsAtLeastS())
331       << "derive_classpath must only be run on Android 12 or above";
332 #endif
333 
334   Classpaths classpaths;
335   if (!ParseFragments(args, classpaths, /*boot_jars=*/true)) {
336     LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments";
337     return false;
338   }
339   if (!ParseFragments(args, classpaths, /*boot_jars=*/false)) {
340     LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments";
341     return false;
342   }
343 
344   // Write export actions for init.rc
345   if (!WriteClasspathExports(classpaths, args.output_path)) {
346     PLOG(ERROR) << "Failed to write " << args.output_path;
347     return false;
348   }
349   return true;
350 }
351 
352 }  // namespace derive_classpath
353 }  // namespace android
354