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