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