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