blob: f66dcff9e4511345dc893c511faaa2ef5d5fbeb7 [file] [log] [blame]
/*
* 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 <ftw.h>
#include <unistd.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <fs_mgr.h>
#include <gtest/gtest.h>
#include <liblp/liblp.h>
#include <vintf/VintfObject.h>
#include <vintf/parse_string.h>
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);
const auto 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<const android::vintf::RuntimeInfo> runtime_info;
int vendor_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;
}
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();
}