| /* |
| * 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 <chrono> |
| #include <cstdint> |
| #include <format> |
| #include <limits> |
| #include <regex> |
| #include <sstream> |
| |
| #include <android-base/file.h> |
| #include <android-base/parseint.h> |
| #include <gtest/gtest.h> |
| #include <kver/kernel_release.h> |
| #include <tinyxml2.h> |
| #include <vintf/Version.h> |
| #include <vintf/VintfObject.h> |
| |
| 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::days>( |
| 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<const RuntimeInfo> 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"; |
| } |
| } |