/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include using android::vintf::KernelVersion; using android::vintf::Level; using android::vintf::RuntimeInfo; using android::vintf::Version; using android::vintf::VintfObject; namespace { const std::string kernel_lifetimes_config_path = "/system/etc/kernel/kernel-lifetimes.xml"; bool parseDate(std::string_view date_string, std::chrono::year_month_day& date) { const std::regex date_regex("(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)"); std::cmatch date_match; if (!std::regex_match(date_string.data(), date_match, date_regex)) { return false; } uint32_t year, month, day; android::base::ParseUint(date_match[1].str(), &year); android::base::ParseUint(date_match[2].str(), &month); android::base::ParseUint(date_match[3].str(), &day); date = std::chrono::year_month_day(std::chrono::year(year), std::chrono::month(month), std::chrono::day(day)); return true; } KernelVersion parseKernelVersion(std::string_view kernel_version_string) { const std::regex kernel_version_regex("(\\d+)\\.(\\d+)\\.(\\d+)"); std::cmatch kernel_version_match; if (!std::regex_match(kernel_version_string.data(), kernel_version_match, kernel_version_regex)) { return {}; } uint32_t v, mj, mi; android::base::ParseUint(kernel_version_match[1].str(), &v); android::base::ParseUint(kernel_version_match[2].str(), &mj); android::base::ParseUint(kernel_version_match[3].str(), &mi); return KernelVersion(v, mj, mi); } } // namespace class EolEnforcementTest : public testing::Test { public: virtual void SetUp() override { // Get current date. today = std::chrono::year_month_day(std::chrono::floor( std::chrono::system_clock::now())); // Get runtime info. auto vintf = VintfObject::GetInstance(); ASSERT_NE(vintf, nullptr); runtime_info = vintf->getRuntimeInfo(RuntimeInfo::FetchFlag::CPU_VERSION | RuntimeInfo::FetchFlag::CONFIG_GZ); ASSERT_NE(runtime_info, nullptr); } bool isReleaseEol(std::string_view date) const; std::chrono::year_month_day today; std::shared_ptr runtime_info; }; bool EolEnforcementTest::isReleaseEol(std::string_view date_string) const { std::chrono::year_month_day date; if (!parseDate(date_string, date)) { ADD_FAILURE() << "Failed to parse date string: " << date_string; } return today > date; } TEST_F(EolEnforcementTest, KernelNotEol) { ASSERT_GE(runtime_info->kernelVersion().dropMinor(), (Version{4, 14})) << "Kernel versions below 4.14 are EOL"; std::string kernel_lifetimes_content; ASSERT_TRUE(android::base::ReadFileToString(kernel_lifetimes_config_path, &kernel_lifetimes_content)) << "Failed to read approved OGKI builds config at " << kernel_lifetimes_config_path; tinyxml2::XMLDocument kernel_lifetimes_xml; const auto xml_error = kernel_lifetimes_xml.Parse(kernel_lifetimes_content.c_str()); ASSERT_EQ(xml_error, tinyxml2::XMLError::XML_SUCCESS) << "Failed to parse approved builds config: " << tinyxml2::XMLDocument::ErrorIDToName(xml_error); const auto kernel_version = runtime_info->kernelVersion(); std::string branch_name; if (kernel_version.dropMinor() < Version{5, 4}) { branch_name = std::format("android-{}.{}", kernel_version.version, kernel_version.majorRev); } else if (kernel_version.dropMinor() == Version{5, 4} && VintfObject::GetInstance()->getKernelLevel() == Level::R) { // Kernel release string on Android 11 is not GKI compatible. branch_name = "android11-5.4"; } else { const auto kernel_release = android::kver::KernelRelease::Parse( android::vintf::VintfObject::GetRuntimeInfo()->osRelease(), /* allow_suffix = */ true); ASSERT_TRUE(kernel_release.has_value()) << "Failed to parse the kernel release string"; branch_name = std::format("android{}-{}.{}", kernel_release->android_release(), kernel_version.version, kernel_version.majorRev); } tinyxml2::XMLElement* branch_element = nullptr; const auto kernels_element = kernel_lifetimes_xml.RootElement(); for (auto branch = kernels_element->FirstChildElement("branch"); branch; branch = branch->NextSiblingElement("branch")) { if (branch->Attribute("name", branch_name.c_str())) { branch_element = branch; break; } } ASSERT_NE(branch_element, nullptr) << "Branch '" << branch_name << "' not found in approved builds config"; // Test against branch EOL is there are no releases for the branch. if (const auto no_releases = branch_element->FirstChildElement("no-releases"); no_releases != nullptr) { std::chrono::year_month_day eol; ASSERT_TRUE(parseDate(branch_element->Attribute("eol"), eol)) << "Failed to parse branch '" << branch_name << "' EOL date: " << branch_element->Attribute("eol"); EXPECT_GE(eol, today); return; } // Test against kernel release EOL. const auto lts_versions = branch_element->FirstChildElement("lts-versions"); const auto release_version = std::format("{}.{}.{}", kernel_version.version, kernel_version.majorRev, kernel_version.minorRev); tinyxml2::XMLElement* latest_release = nullptr; KernelVersion latest_kernel_version; for (auto release = lts_versions->FirstChildElement("release"); release; release = release->NextSiblingElement("release")) { if (release->Attribute("version", release_version.c_str())) { EXPECT_FALSE(isReleaseEol(release->Attribute("eol"))); return; } else if (auto kernel_version = parseKernelVersion(release->Attribute("version")); latest_release == nullptr || kernel_version > latest_kernel_version) { latest_release = release; latest_kernel_version = kernel_version; } } // If current kernel version is newer than the latest kernel version found in // the config, then this might be a kernel release which is yet to get a // release config. Test against the latest kernel release version if this is // the case. if (kernel_version > latest_kernel_version) { EXPECT_FALSE(isReleaseEol(latest_release->Attribute("eol"))); } else { FAIL() << "Kernel release '" << release_version << "' is not recognised"; } }