1 /*
2 * Copyright (C) 2024 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 <chrono>
18 #include <cstdint>
19 #include <format>
20 #include <limits>
21 #include <regex>
22 #include <sstream>
23
24 #include <android-base/file.h>
25 #include <android-base/parseint.h>
26 #include <gtest/gtest.h>
27 #include <kver/kernel_release.h>
28 #include <tinyxml2.h>
29 #include <vintf/Version.h>
30 #include <vintf/VintfObject.h>
31
32 using android::vintf::KernelVersion;
33 using android::vintf::Level;
34 using android::vintf::RuntimeInfo;
35 using android::vintf::Version;
36 using android::vintf::VintfObject;
37
38 namespace {
39
40 const std::string kernel_lifetimes_config_path =
41 "/system/etc/kernel/kernel-lifetimes.xml";
42
parseDate(std::string_view date_string,std::chrono::year_month_day & date)43 bool parseDate(std::string_view date_string,
44 std::chrono::year_month_day& date) {
45 const std::regex date_regex("(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)");
46 std::cmatch date_match;
47 if (!std::regex_match(date_string.data(), date_match, date_regex)) {
48 return false;
49 }
50
51 uint32_t year, month, day;
52 android::base::ParseUint(date_match[1].str(), &year);
53 android::base::ParseUint(date_match[2].str(), &month);
54 android::base::ParseUint(date_match[3].str(), &day);
55 date = std::chrono::year_month_day(std::chrono::year(year),
56 std::chrono::month(month),
57 std::chrono::day(day));
58 return true;
59 }
60
parseKernelVersion(std::string_view kernel_version_string)61 KernelVersion parseKernelVersion(std::string_view kernel_version_string) {
62 const std::regex kernel_version_regex("(\\d+)\\.(\\d+)\\.(\\d+)");
63 std::cmatch kernel_version_match;
64 if (!std::regex_match(kernel_version_string.data(), kernel_version_match,
65 kernel_version_regex)) {
66 return {};
67 }
68
69 uint32_t v, mj, mi;
70 android::base::ParseUint(kernel_version_match[1].str(), &v);
71 android::base::ParseUint(kernel_version_match[2].str(), &mj);
72 android::base::ParseUint(kernel_version_match[3].str(), &mi);
73 return KernelVersion(v, mj, mi);
74 }
75
76 } // namespace
77
78 class EolEnforcementTest : public testing::Test {
79 public:
SetUp()80 virtual void SetUp() override {
81 // Get current date.
82 today = std::chrono::year_month_day(std::chrono::floor<std::chrono::days>(
83 std::chrono::system_clock::now()));
84
85 // Get runtime info.
86 auto vintf = VintfObject::GetInstance();
87 ASSERT_NE(vintf, nullptr);
88 runtime_info = vintf->getRuntimeInfo(RuntimeInfo::FetchFlag::CPU_VERSION |
89 RuntimeInfo::FetchFlag::CONFIG_GZ);
90 ASSERT_NE(runtime_info, nullptr);
91 }
92
93 bool isReleaseEol(std::string_view date) const;
94
95 std::chrono::year_month_day today;
96 std::shared_ptr<const RuntimeInfo> runtime_info;
97 };
98
isReleaseEol(std::string_view date_string) const99 bool EolEnforcementTest::isReleaseEol(std::string_view date_string) const {
100 std::chrono::year_month_day date;
101 if (!parseDate(date_string, date)) {
102 ADD_FAILURE() << "Failed to parse date string: " << date_string;
103 }
104 return today > date;
105 }
106
TEST_F(EolEnforcementTest,KernelNotEol)107 TEST_F(EolEnforcementTest, KernelNotEol) {
108 ASSERT_GE(runtime_info->kernelVersion().dropMinor(), (Version{4, 14}))
109 << "Kernel versions below 4.14 are EOL";
110
111 std::string kernel_lifetimes_content;
112 ASSERT_TRUE(android::base::ReadFileToString(kernel_lifetimes_config_path,
113 &kernel_lifetimes_content))
114 << "Failed to read approved OGKI builds config at "
115 << kernel_lifetimes_config_path;
116
117 tinyxml2::XMLDocument kernel_lifetimes_xml;
118 const auto xml_error =
119 kernel_lifetimes_xml.Parse(kernel_lifetimes_content.c_str());
120 ASSERT_EQ(xml_error, tinyxml2::XMLError::XML_SUCCESS)
121 << "Failed to parse approved builds config: "
122 << tinyxml2::XMLDocument::ErrorIDToName(xml_error);
123
124 const auto kernel_version = runtime_info->kernelVersion();
125 std::string branch_name;
126 if (kernel_version.dropMinor() < Version{5, 4}) {
127 branch_name = std::format("android-{}.{}", kernel_version.version,
128 kernel_version.majorRev);
129 } else if (kernel_version.dropMinor() == Version{5, 4} &&
130 VintfObject::GetInstance()->getKernelLevel() == Level::R) {
131 // Kernel release string on Android 11 is not GKI compatible.
132 branch_name = "android11-5.4";
133 } else {
134 const auto kernel_release = android::kver::KernelRelease::Parse(
135 android::vintf::VintfObject::GetRuntimeInfo()->osRelease(),
136 /* allow_suffix = */ true);
137 ASSERT_TRUE(kernel_release.has_value())
138 << "Failed to parse the kernel release string";
139 branch_name =
140 std::format("android{}-{}.{}", kernel_release->android_release(),
141 kernel_version.version, kernel_version.majorRev);
142 }
143
144 tinyxml2::XMLElement* branch_element = nullptr;
145 const auto kernels_element = kernel_lifetimes_xml.RootElement();
146 for (auto branch = kernels_element->FirstChildElement("branch"); branch;
147 branch = branch->NextSiblingElement("branch")) {
148 if (branch->Attribute("name", branch_name.c_str())) {
149 branch_element = branch;
150 break;
151 }
152 }
153 ASSERT_NE(branch_element, nullptr)
154 << "Branch '" << branch_name << "' not found in approved builds config";
155
156 // Test against branch EOL is there are no releases for the branch.
157 if (const auto no_releases = branch_element->FirstChildElement("no-releases");
158 no_releases != nullptr) {
159 std::chrono::year_month_day eol;
160 ASSERT_TRUE(parseDate(branch_element->Attribute("eol"), eol))
161 << "Failed to parse branch '" << branch_name
162 << "' EOL date: " << branch_element->Attribute("eol");
163 EXPECT_GE(eol, today);
164 return;
165 }
166
167 // Test against kernel release EOL.
168 const auto lts_versions = branch_element->FirstChildElement("lts-versions");
169 const auto release_version =
170 std::format("{}.{}.{}", kernel_version.version, kernel_version.majorRev,
171 kernel_version.minorRev);
172 tinyxml2::XMLElement* latest_release = nullptr;
173 KernelVersion latest_kernel_version;
174 for (auto release = lts_versions->FirstChildElement("release"); release;
175 release = release->NextSiblingElement("release")) {
176 if (release->Attribute("version", release_version.c_str())) {
177 EXPECT_FALSE(isReleaseEol(release->Attribute("eol")));
178 return;
179 } else if (auto kernel_version =
180 parseKernelVersion(release->Attribute("version"));
181 latest_release == nullptr ||
182 kernel_version > latest_kernel_version) {
183 latest_release = release;
184 latest_kernel_version = kernel_version;
185 }
186 }
187
188 // If current kernel version is newer than the latest kernel version found in
189 // the config, then this might be a kernel release which is yet to get a
190 // release config. Test against the latest kernel release version if this is
191 // the case.
192 if (kernel_version > latest_kernel_version) {
193 EXPECT_FALSE(isReleaseEol(latest_release->Attribute("eol")));
194 } else {
195 FAIL() << "Kernel release '" << release_version << "' is not recognised";
196 }
197 }
198