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 #include "derive_classpath.h"
18 #include <android-base/file.h>
19 #include <android-base/logging.h>
20 #include <android-base/strings.h>
21 #include <android-modules-utils/sdk_level.h>
22 #include <glob.h>
23 #include <regex>
24 #include <sstream>
25 
26 #include "packages/modules/common/proto/classpaths.pb.h"
27 
28 namespace android {
29 namespace derive_classpath {
30 
31 using Filepaths = std::vector<std::string>;
32 using Classpaths = std::unordered_map<Classpath, Filepaths>;
33 
34 static const std::regex kBindMountedApex("^/apex/[^/]+@[0-9]+/");
35 static const std::regex kApexPathRegex("(/apex/[^/]+)/");
36 
37 // Defines the order of individual fragments to be merged for BOOTCLASSPATH:
38 // 1. Jars in ART module always come first;
39 // 2. Jars defined as part of /system/etc/classpaths;
40 // 3. Jars defined in all non-ART apexes that expose /apex/*/etc/classpaths fragments.
41 //
42 // Notes:
43 // - Relative order in the individual fragment files is not changed when merging.
44 // - If a fragment file is matched by multiple globs, the first one is used; i.e. ART module
45 //   fragment is only parsed once, even if there is a "/apex/*/" pattern later.
46 // - If there are multiple files matched for a glob pattern with wildcards, the results are sorted
47 //   by pathname (default glob behaviour); i.e. all fragment files are sorted within a single
48 //   "pattern block".
49 static const std::vector<std::string> kBootclasspathFragmentGlobPatterns = {
50     // ART module is a special case and must come first before any other classpath entries.
51     "/apex/com.android.art/etc/classpaths/bootclasspath.pb",
52     "/system/etc/classpaths/bootclasspath.pb",
53     "/apex/*/etc/classpaths/bootclasspath.pb",
54 };
55 
56 // Defines the order of individual fragments to be merged for SYSTEMSERVERCLASSPATH.
57 //
58 // ART system server jars are not special in this case, and are considered to be part of all the
59 // other apexes that may expose system server jars.
60 //
61 // All notes from kBootclasspathFragmentGlobPatterns apply here.
62 static const std::vector<std::string> kSystemserverclasspathFragmentGlobPatterns = {
63     "/system/etc/classpaths/systemserverclasspath.pb",
64     "/apex/*/etc/classpaths/systemserverclasspath.pb",
65 };
66 
67 // Finds all classpath fragment files that match the glob pattern and appends them to `fragments`.
68 //
69 // If a newly found fragment is already present in `fragments`, it is skipped to avoid duplicates.
70 // Note that appended fragment files are sorted by pathnames, which is a default behaviour for
71 // glob().
72 bool GlobClasspathFragments(Filepaths* fragments, const std::string& pattern) {
73   glob_t glob_result;
74   const int ret = glob(pattern.c_str(), GLOB_MARK, nullptr, &glob_result);
75   if (ret != 0 && ret != GLOB_NOMATCH) {
76     globfree(&glob_result);
77     LOG(ERROR) << "Failed to glob " << pattern;
78     return false;
79   }
80 
81   for (size_t i = 0; i < glob_result.gl_pathc; i++) {
82     std::string path = glob_result.gl_pathv[i];
83     // Skip <name>@<ver> dirs, as they are bind-mounted to <name>
84     if (std::regex_search(path, kBindMountedApex)) {
85       continue;
86     }
87     // Make sure we don't push duplicate fragments from previously processed patterns
88     if (std::find(fragments->begin(), fragments->end(), path) == fragments->end()) {
89       fragments->push_back(path);
90     }
91   }
92   globfree(&glob_result);
93   return true;
94 }
95 
96 // Writes the contents of *CLASSPATH variables to /data in the format expected by `load_exports`
97 // action from init.rc. See platform/system/core/init/README.md.
98 bool WriteClasspathExports(Classpaths classpaths, std::string_view output_path) {
99   LOG(INFO) << "WriteClasspathExports " << output_path;
100 
101   std::stringstream out;
102   out << "export BOOTCLASSPATH " << android::base::Join(classpaths[BOOTCLASSPATH], ':') << '\n';
103   out << "export DEX2OATBOOTCLASSPATH "
104       << android::base::Join(classpaths[DEX2OATBOOTCLASSPATH], ':') << '\n';
105   out << "export SYSTEMSERVERCLASSPATH "
106       << android::base::Join(classpaths[SYSTEMSERVERCLASSPATH], ':') << '\n';
107 
108   const std::string& content = out.str();
109   LOG(INFO) << "WriteClasspathExports content\n" << content;
110 
111   const std::string path_str(output_path);
112   if (android::base::StartsWith(path_str, "/data/")) {
113     // When writing to /data, write to a temp file first to make sure the partition is not full.
114     const std::string temp_str(path_str + ".tmp");
115     if (!android::base::WriteStringToFile(content, temp_str, /*follow_symlinks=*/true)) {
116       return false;
117     }
118     return rename(temp_str.c_str(), path_str.c_str()) == 0;
119   } else {
120     return android::base::WriteStringToFile(content, path_str, /*follow_symlinks=*/true);
121   }
122 }
123 
124 bool ReadClasspathFragment(ExportedClasspathsJars* fragment, const std::string& filepath) {
125   LOG(INFO) << "ReadClasspathFragment " << filepath;
126   std::string contents;
127   if (!android::base::ReadFileToString(filepath, &contents)) {
128     PLOG(ERROR) << "Failed to read " << filepath;
129     return false;
130   }
131   if (!fragment->ParseFromString(contents)) {
132     LOG(ERROR) << "Failed to parse " << filepath;
133     return false;
134   }
135   return true;
136 }
137 
138 // Returns an allowed prefix for a jar filepaths declared in a given fragment.
139 // For a given apex fragment, it returns the apex path - "/apex/com.android.foo" - as an allowed
140 // prefix for jars. This can be used to enforce that an apex fragment only exports jars located in
141 // that apex. For system fragment, it returns an empty string to allow any jars to be exported by
142 // the platform.
143 std::string GetAllowedJarPathPrefix(const std::string& fragment_path) {
144   std::smatch match;
145   if (std::regex_search(fragment_path, match, kApexPathRegex)) {
146     return match[1];
147   }
148   return "";
149 }
150 
151 // Finds and parses all classpath fragments on device matching given glob patterns.
152 bool ParseFragments(const std::string& globPatternPrefix, Classpaths& classpaths, bool boot_jars) {
153   LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath");
154 
155   auto glob_patterns =
156       boot_jars ? kBootclasspathFragmentGlobPatterns : kSystemserverclasspathFragmentGlobPatterns;
157 
158   Filepaths fragments;
159   for (const auto& pattern : glob_patterns) {
160     if (!GlobClasspathFragments(&fragments, globPatternPrefix + pattern)) {
161       return false;
162     }
163   }
164 
165   for (const auto& fragment_path : fragments) {
166     ExportedClasspathsJars exportedJars;
167     if (!ReadClasspathFragment(&exportedJars, fragment_path)) {
168       return false;
169     }
170 
171     // Either a path to the apex, or an empty string
172     const std::string& allowed_jar_prefix = GetAllowedJarPathPrefix(fragment_path);
173 
174     for (const Jar& jar : exportedJars.jars()) {
175       // TODO(b/180105615): check for SdkVersion ranges;
176       const std::string& jar_path = jar.path();
177       CHECK(android::base::StartsWith(jar_path, allowed_jar_prefix))
178           << fragment_path << " must not export a jar from outside of the apex: " << jar_path;
179       const Classpath classpath = jar.classpath();
180       CHECK(boot_jars ^ (classpath == SYSTEMSERVERCLASSPATH))
181           << fragment_path << " must not export a jar for " << Classpath_Name(classpath);
182       classpaths[classpath].push_back(jar_path);
183     }
184   }
185   return true;
186 }
187 
188 // Generates /data/system/environ/classpath exports file by globing and merging individual
189 // classpaths.proto config fragments. The exports file is read by init.rc to setenv *CLASSPATH
190 // environ variables at runtime.
191 bool GenerateClasspathExports(std::string_view output_path) {
192   // Outside of tests use actual config fragments.
193   return GenerateClasspathExports("", output_path);
194 }
195 
196 // Internal implementation of GenerateClasspathExports that allows putting config fragments in
197 // temporary directories. `globPatternPrefix` is appended to each glob pattern from
198 // kBootclasspathFragmentGlobPatterns and kSystemserverclasspathFragmentGlobPatterns, which allows
199 // adding mock configs in /data/local/tmp for example.
200 bool GenerateClasspathExports(const std::string& globPatternPrefix, std::string_view output_path) {
201   // Parse all known classpath fragments
202   CHECK(android::modules::sdklevel::IsAtLeastS())
203       << "derive_classpath must only be run on Android 12 or above";
204 
205   Classpaths classpaths;
206   if (!ParseFragments(globPatternPrefix, classpaths, /*boot_jars=*/true)) {
207     LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments";
208     return false;
209   }
210   if (!ParseFragments(globPatternPrefix, classpaths, /*boot_jars=*/false)) {
211     LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments";
212     return false;
213   }
214 
215   // Write export actions for init.rc
216   if (!WriteClasspathExports(classpaths, output_path)) {
217     PLOG(ERROR) << "Failed to write " << output_path;
218     return false;
219   }
220   return true;
221 }
222 
223 }  // namespace derive_classpath
224 }  // namespace android
225