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:
~DeriveClasspathTest()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
working_dir()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.
ParseExportsFile(const char * file="")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
SplitClasspathExportLine(const std::string & line)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.
CheckClasspathGroupOrder(const std::string classpath,const std::vector<std::string> prefixes)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
AddJarToClasspath(const std::string & partition,const std::string & jar_filepath,Classpath classpath)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.
TEST_F(DeriveClasspathTest,DefaultNoUnknownClasspaths)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.
TEST_F(DeriveClasspathTest,TempConfig)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.
TEST_F(DeriveClasspathTest,ModulesAreSorted)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.
TEST_F(DeriveClasspathTest,CustomOutputLocation)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.
TEST_F(DeriveClasspathTest,NonWriteableOutputLocation)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.
TEST_F(DeriveClasspathDeathTest,ApexJarsBelongToApex)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.
TEST_F(DeriveClasspathDeathTest,WrongClasspathInFragments)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
main(int argc,char ** argv)262 int main(int argc, char** argv) {
263 ::testing::InitGoogleTest(&argc, argv);
264 return RUN_ALL_TESTS();
265 }
266