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 19 #include <android-base/file.h> 20 #include <android-base/logging.h> 21 #include <android-base/properties.h> 22 #include <android-base/stringprintf.h> 23 #include <android-base/strings.h> 24 #include <android-modules-utils/sdk_level.h> 25 #include <gtest/gtest.h> 26 #include <stdlib.h> 27 #include <sys/mman.h> 28 #include <sys/stat.h> 29 30 #include <cstdlib> 31 #include <string_view> 32 33 #include "android-base/unique_fd.h" 34 #include "packages/modules/common/proto/classpaths.pb.h" 35 36 namespace android { 37 namespace derive_classpath { 38 namespace { 39 40 static const std::string kFrameworkJarFilepath = "/system/framework/framework.jar"; 41 static const std::string kLibartJarFilepath = "/apex/com.android.art/javalib/core-libart.jar"; 42 static const std::string kSdkExtensionsJarFilepath = 43 "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar"; 44 static const std::string kServicesJarFilepath = "/system/framework/services.jar"; 45 46 // The fixture for testing derive_classpath. 47 class DeriveClasspathTest : public ::testing::Test { 48 protected: 49 ~DeriveClasspathTest() override { 50 // Not really needed, as a test device will re-generate a proper classpath on reboot, 51 // but it's better to leave it in a clean state after a test. 52 GenerateClasspathExports(); 53 } 54 55 const std::string working_dir() { return std::string(temp_dir_.path); } 56 57 // Parses the generated classpath exports file and returns each line individually. 58 std::vector<std::string> ParseExportsFile(const char* file = "/data/system/environ/classpath") { 59 std::string contents; 60 EXPECT_TRUE(android::base::ReadFileToString(file, &contents, /*follow_symlinks=*/true)); 61 return android::base::Split(contents, "\n"); 62 } 63 64 std::vector<std::string> SplitClasspathExportLine(const std::string& line) { 65 std::vector<std::string> contents = android::base::Split(line, " "); 66 // Export lines are expected to be structured as `export <name> <value>`. 67 EXPECT_EQ(3, contents.size()); 68 EXPECT_EQ("export", contents[0]); 69 return contents; 70 } 71 72 // Checks the order of the jars in a given classpath. 73 // Instead of doing a full order check, it assumes the jars are grouped by partition and checks 74 // that partitions come in order of the `prefixes` that is given. 75 void CheckClasspathGroupOrder(const std::string classpath, 76 const std::vector<std::string> prefixes) { 77 ASSERT_NE(0, prefixes.size()); 78 ASSERT_NE(0, classpath.size()); 79 80 auto jars = android::base::Split(classpath, ":"); 81 82 auto prefix = prefixes.begin(); 83 auto jar = jars.begin(); 84 for (; jar != jars.end() && prefix != prefixes.end(); ++jar) { 85 if (*jar == "/apex/com.android.i18n/javalib/core-icu4j.jar") { 86 // core-icu4j.jar is special and is out of order in BOOTCLASSPATH; 87 // ignore it when checking for general order 88 continue; 89 } 90 if (!android::base::StartsWith(*jar, *prefix)) { 91 ++prefix; 92 } 93 } 94 EXPECT_NE(prefix, prefixes.end()); 95 // All jars have been iterated over, thus they all have valid prefixes 96 EXPECT_EQ(jar, jars.end()); 97 } 98 99 void AddJarToClasspath(const std::string& partition, const std::string& jar_filepath, 100 Classpath classpath) { 101 ExportedClasspathsJars exported_jars; 102 Jar* jar = exported_jars.add_jars(); 103 jar->set_path(jar_filepath); 104 jar->set_classpath(classpath); 105 106 std::string basename = Classpath_Name(classpath) + ".pb"; 107 std::transform(basename.begin(), basename.end(), basename.begin(), 108 [](unsigned char c) { return std::tolower(c); }); 109 110 std::string fragment_path = working_dir() + partition + "/etc/classpaths/" + basename; 111 std::string buf; 112 exported_jars.SerializeToString(&buf); 113 std::string cmd("mkdir -p " + android::base::Dirname(fragment_path)); 114 ASSERT_EQ(0, system(cmd.c_str())); 115 ASSERT_TRUE(android::base::WriteStringToFile(buf, fragment_path, true)); 116 } 117 118 TemporaryDir temp_dir_; 119 }; 120 121 using DeriveClasspathDeathTest = DeriveClasspathTest; 122 123 // Check only known *CLASSPATH variables are exported. 124 TEST_F(DeriveClasspathTest, DefaultNoUnknownClasspaths) { 125 // Re-generate default on device classpaths 126 GenerateClasspathExports(); 127 128 const std::vector<std::string> exportLines = ParseExportsFile(); 129 // The first three lines are tested above. 130 for (int i = 3; i < exportLines.size(); i++) { 131 EXPECT_EQ(exportLines[i], ""); 132 } 133 } 134 135 // Test that temp directory does not pick up actual jars. 136 TEST_F(DeriveClasspathTest, TempConfig) { 137 AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH); 138 AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", 139 SYSTEMSERVERCLASSPATH); 140 141 ASSERT_TRUE(GenerateClasspathExports(working_dir())); 142 143 const std::vector<std::string> exportLines = ParseExportsFile(); 144 145 std::vector<std::string> splitExportLine; 146 147 splitExportLine = SplitClasspathExportLine(exportLines[0]); 148 EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]); 149 EXPECT_EQ("/apex/com.android.foo/javalib/foo", splitExportLine[2]); 150 splitExportLine = SplitClasspathExportLine(exportLines[2]); 151 EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]); 152 EXPECT_EQ("/apex/com.android.baz/javalib/baz", splitExportLine[2]); 153 } 154 155 // Test individual modules are sorted by pathnames. 156 TEST_F(DeriveClasspathTest, ModulesAreSorted) { 157 AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH); 158 AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH); 159 AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH); 160 AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH); 161 AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH); 162 163 ASSERT_TRUE(GenerateClasspathExports(working_dir())); 164 165 const std::vector<std::string> exportLines = ParseExportsFile(); 166 const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[0]); 167 const std::string exportValue = splitExportLine[2]; 168 169 const std::string expectedJars( 170 "/apex/com.android.art/javalib/art" 171 ":/system/framework/jar" 172 ":/apex/com.android.bar/javalib/bar" 173 ":/apex/com.android.baz/javalib/baz" 174 ":/apex/com.android.foo/javalib/foo"); 175 176 EXPECT_EQ(expectedJars, exportValue); 177 } 178 179 // Test we can output to custom files. 180 TEST_F(DeriveClasspathTest, CustomOutputLocation) { 181 AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH); 182 AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH); 183 AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH); 184 AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH); 185 AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH); 186 187 android::base::unique_fd fd(memfd_create("temp_file", MFD_CLOEXEC)); 188 ASSERT_TRUE(fd.ok()) << "Unable to open temp-file"; 189 const std::string file_name = android::base::StringPrintf("/proc/self/fd/%d", fd.get()); 190 ASSERT_TRUE(GenerateClasspathExports(working_dir(), file_name)); 191 192 const std::vector<std::string> exportLines = ParseExportsFile(file_name.c_str()); 193 const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[0]); 194 const std::string exportValue = splitExportLine[2]; 195 196 const std::string expectedJars( 197 "/apex/com.android.art/javalib/art" 198 ":/system/framework/jar" 199 ":/apex/com.android.bar/javalib/bar" 200 ":/apex/com.android.baz/javalib/baz" 201 ":/apex/com.android.foo/javalib/foo"); 202 203 EXPECT_EQ(expectedJars, exportValue); 204 } 205 206 // Test output location that can't be written to. 207 TEST_F(DeriveClasspathTest, NonWriteableOutputLocation) { 208 AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH); 209 AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH); 210 211 ASSERT_FALSE(GenerateClasspathExports(working_dir(), "/system/non_writable_path")); 212 } 213 214 // Test apexes only export their own jars. 215 TEST_F(DeriveClasspathDeathTest, ApexJarsBelongToApex) { 216 // EXPECT_DEATH expects error messages in stderr, log there 217 android::base::SetLogger(android::base::StderrLogger); 218 219 AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH); 220 AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH); 221 AddJarToClasspath("/apex/com.android.bar", "/apex/wrong/path/bar", BOOTCLASSPATH); 222 223 EXPECT_DEATH(GenerateClasspathExports(working_dir()), "must not export a jar.*wrong/path/bar"); 224 } 225 226 // Test classpath fragments export jars for themselves. 227 TEST_F(DeriveClasspathDeathTest, WrongClasspathInFragments) { 228 // Valid configs 229 AddJarToClasspath("/system", "/system/framework/framework-jar", BOOTCLASSPATH); 230 AddJarToClasspath("/system", "/system/framework/service-jar", SYSTEMSERVERCLASSPATH); 231 232 // Manually create an invalid config with both BCP and SSCP jars... 233 ExportedClasspathsJars exported_jars; 234 Jar* jar = exported_jars.add_jars(); 235 jar->set_path("/apex/com.android.foo/javalib/foo"); 236 jar->set_classpath(BOOTCLASSPATH); 237 // note that DEX2OATBOOTCLASSPATH and BOOTCLASSPATH jars are expected to be in the same config 238 jar = exported_jars.add_jars(); 239 jar->set_path("/apex/com.android.foo/javalib/foo"); 240 jar->set_classpath(DEX2OATBOOTCLASSPATH); 241 jar = exported_jars.add_jars(); 242 jar->set_path("/apex/com.android.foo/javalib/service-foo"); 243 jar->set_classpath(SYSTEMSERVERCLASSPATH); 244 245 // ...and write this config to bootclasspath.pb 246 std::string fragment_path = 247 working_dir() + "/apex/com.android.foo/etc/classpaths/bootclasspath.pb"; 248 std::string buf; 249 exported_jars.SerializeToString(&buf); 250 std::string cmd("mkdir -p " + android::base::Dirname(fragment_path)); 251 ASSERT_EQ(0, system(cmd.c_str())); 252 ASSERT_TRUE(android::base::WriteStringToFile(buf, fragment_path, true)); 253 254 EXPECT_DEATH(GenerateClasspathExports(working_dir()), 255 "must not export a jar for SYSTEMSERVERCLASSPATH"); 256 } 257 258 } // namespace 259 } // namespace derive_classpath 260 } // namespace android 261 262 int main(int argc, char** argv) { 263 ::testing::InitGoogleTest(&argc, argv); 264 return RUN_ALL_TESTS(); 265 } 266