/* * Copyright (C) 2022 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 using namespace std::literals; namespace { void VerifyDlkmPartition(const std::string &name) { const auto TAG = __FUNCTION__ + "("s + name + ")"; const auto dlkm_symlink = "/" + name + "/lib/modules"; const auto dlkm_partition = name + "_dlkm"; const auto dlkm_directory = "/" + dlkm_partition + "/lib/modules"; // Check existence of /{name}/lib/modules. if (access(dlkm_symlink.c_str(), F_OK)) { if (errno == ENOENT) { GTEST_LOG_(INFO) << TAG << ": '" << dlkm_symlink << "' doesn't exist, skip checking it."; SUCCEED(); } else { ADD_FAILURE() << "access(" << dlkm_symlink << "): " << strerror(errno); } return; } // If it exists then make sure it is a directory. struct stat st; ASSERT_EQ(0, stat(dlkm_symlink.c_str(), &st)) << "stat(" << dlkm_symlink << "): " << strerror(errno); if (!S_ISDIR(st.st_mode)) { ADD_FAILURE() << "'" << dlkm_symlink << "' is not a directory."; return; } // If it is a directory then check if it is empty or not. auto not_empty_callback = [](const char *, const struct stat *, int) { return 1; }; int ret = ftw(dlkm_symlink.c_str(), not_empty_callback, 128); ASSERT_NE(-1, ret) << "ftw(" << dlkm_symlink << "): " << strerror(errno); if (ret == 0) { // ftw() returns without visiting any file, so the directory must be empty. GTEST_LOG_(INFO) << TAG << ": '" << dlkm_symlink << "' is empty directory, skip checking it."; SUCCEED(); return; } // Otherwise ftw() must had returned 1, which means the callback is called at // least once, so /{name}/lib/modules must not be empty. ASSERT_EQ(1, ret); // We want to ensure /{name}/lib/modules symlinks to /{name}_dlkm/lib/modules. ASSERT_EQ(0, lstat(dlkm_symlink.c_str(), &st)) << "lstat(" << dlkm_symlink << "): " << strerror(errno); if (!S_ISLNK(st.st_mode)) { ADD_FAILURE() << "'" << dlkm_symlink << "' is not a symlink."; return; } std::string link_target; ASSERT_TRUE(android::base::Readlink(dlkm_symlink, &link_target)) << "readlink(" << dlkm_symlink << "): " << strerror(errno); if (link_target != dlkm_directory) { ADD_FAILURE() << "'" << dlkm_symlink << "' must be a symlink pointing at '" << dlkm_directory << "'."; } else { GTEST_LOG_(INFO) << TAG << ": '" << dlkm_symlink << "' -> '" << dlkm_directory << "'."; } // Ensure {name}_dlkm is a logical partition. const auto super_device = fs_mgr_get_super_partition_name(); const auto slot_suffix = fs_mgr_get_slot_suffix(); const auto slot_number = android::fs_mgr::SlotNumberForSlotSuffix(slot_suffix); auto lp_metadata = android::fs_mgr::ReadMetadata(super_device, slot_number); ASSERT_NE(nullptr, lp_metadata) << "ReadMetadata(" << super_device << "): " << strerror(errno); auto lp_partition = android::fs_mgr::FindPartition( *lp_metadata, dlkm_partition + slot_suffix); EXPECT_NE(nullptr, lp_partition) << "Cannot find logical partition of '" << dlkm_partition << "'"; } } // namespace class DlkmPartitionTest : public testing::Test { protected: void SetUp() override { // Fetch device runtime information. runtime_info = android::vintf::VintfObject::GetRuntimeInfo(); ASSERT_NE(nullptr, runtime_info); product_first_api_level = android::base::GetIntProperty("ro.product.first_api_level", 0); ASSERT_NE(0, product_first_api_level) << "ro.product.first_api_level is undefined."; const auto board_api_level = android::base::GetIntProperty( "ro.board.api_level", __ANDROID_API_FUTURE__); const auto board_first_api_level = android::base::GetIntProperty( "ro.board.first_api_level", __ANDROID_API_FUTURE__); vendor_api_level = android::base::GetIntProperty( "ro.vendor.api_level", std::min(product_first_api_level, std::max(board_api_level, board_first_api_level))); ASSERT_NE(0, vendor_api_level) << "ro.vendor.api_level is undefined."; } std::shared_ptr runtime_info; int vendor_api_level; int product_first_api_level; }; TEST_F(DlkmPartitionTest, VendorDlkmPartition) { if (vendor_api_level < __ANDROID_API_S__) { GTEST_SKIP() << "Exempt from vendor_dlkm partition test. ro.vendor.api_level (" << vendor_api_level << ") < " << __ANDROID_API_S__; } // Only enforce this test on products launched with Android T and later. if (product_first_api_level < __ANDROID_API_T__) { GTEST_SKIP() << "Exempt from vendor_dlkm partition test. " "ro.product.first_api_level (" << product_first_api_level << ") < " << __ANDROID_API_T__; } if (runtime_info->kernelVersion().dropMinor() != android::vintf::Version{5, 4} && runtime_info->kernelVersion().dropMinor() < android::vintf::Version{5, 10}) { GTEST_SKIP() << "Exempt from vendor_dlkm partition test. kernel: " << runtime_info->kernelVersion(); } ASSERT_NO_FATAL_FAILURE(VerifyDlkmPartition("vendor")); ASSERT_NO_FATAL_FAILURE(VerifyDlkmPartition("odm")); } TEST_F(DlkmPartitionTest, SystemDlkmPartition) { if (vendor_api_level < __ANDROID_API_T__) { GTEST_SKIP() << "Exempt from system_dlkm partition test. ro.vendor.api_level (" << vendor_api_level << ") < " << __ANDROID_API_T__; } if (runtime_info->kernelVersion().dropMinor() < android::vintf::Version{5, 10}) { GTEST_SKIP() << "Exempt from system_dlkm partition test. kernel: " << runtime_info->kernelVersion(); } ASSERT_NO_FATAL_FAILURE(VerifyDlkmPartition("system")); } int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); android::base::InitLogging(argv, android::base::StderrLogger); return RUN_ALL_TESTS(); }