| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/hash/hash.h> |
| #include <base/logging.h> |
| #include <base/values.h> |
| #include <base/system/sys_info.h> |
| #include <brillo/data_encoding.h> |
| #include <libcrossystem/crossystem.h> |
| |
| #include "libsegmentation/device_info.pb.h" |
| #include "libsegmentation/feature_management.h" |
| #include "libsegmentation/feature_management_impl.h" |
| #include "libsegmentation/feature_management_interface.h" |
| #include "libsegmentation/feature_management_util.h" |
| |
| #include "proto/feature_management.pb.h" |
| |
| // Autogenerated by feature-management-data, |
| // Contains protobuf definitions. |
| #include <libsegmentation/libsegmentation_pb.h> |
| |
| using chromiumos::feature_management::api::software::Feature; |
| |
| namespace segmentation { |
| |
| // Sysfs file corresponding to VPD state. This will be used to persist device |
| // info state and read cache device info state. |
| constexpr const char kVpdSysfsFilePath[] = |
| "/sys/firmware/vpd/rw/feature_device_info"; |
| constexpr const char kTempDeviceInfoPath[] = |
| "/run/libsegmentation/feature_device_info"; |
| |
| #if USE_FEATURE_MANAGEMENT |
| // Sysfs file corresponding to VPD state. This will be used to persist device |
| // info state and read cache device info state. |
| const char* kDeviceInfoFilePath = kVpdSysfsFilePath; |
| #else |
| constexpr char kDeviceInfoFilePath[] = ""; |
| |
| FeatureManagementInterface::FeatureLevel |
| FeatureManagementImpl::GetFeatureLevel() { |
| return FeatureLevel::FEATURE_LEVEL_0; |
| } |
| |
| FeatureManagementInterface::ScopeLevel FeatureManagementImpl::GetScopeLevel() { |
| return ScopeLevel::SCOPE_LEVEL_0; |
| } |
| |
| #endif |
| |
| FeatureManagementInterface::FeatureLevel |
| FeatureManagementImpl::GetMaxFeatureLevel() { |
| return FeatureLevel::kMaxValue; |
| } |
| |
| FeatureManagementImpl::FeatureManagementImpl() |
| : FeatureManagementImpl(nullptr, |
| base::FilePath(kDeviceInfoFilePath), |
| protobuf_features, |
| protobuf_devices, |
| "") {} |
| |
| FeatureManagementImpl::FeatureManagementImpl( |
| crossystem::Crossystem* crossystem, |
| const base::FilePath& device_info_file_path, |
| const std::string& feature_db, |
| const std::string& selection_db, |
| const std::string& os_version) |
| : device_info_file_path_(device_info_file_path), |
| temp_device_info_file_path_(kTempDeviceInfoPath) { |
| persist_via_vpd_ = |
| device_info_file_path_ == base::FilePath(kVpdSysfsFilePath); |
| std::string decoded_pb; |
| brillo::data_encoding::Base64Decode(feature_db, &decoded_pb); |
| feature_bundle_.ParseFromString(decoded_pb); |
| brillo::data_encoding::Base64Decode(selection_db, &decoded_pb); |
| selection_bundle_.ParseFromString(decoded_pb); |
| |
| #if USE_FEATURE_MANAGEMENT |
| std::string version(os_version); |
| if (version.empty()) { |
| base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_VERSION", &version); |
| } |
| |
| if (!version.empty()) { |
| current_version_hash_ = base::PersistentHash(version); |
| } else { |
| current_version_hash_ = 0; |
| LOG(ERROR) << "Failed to retrieve CHROMEOS_RELEASE_VERSION"; |
| } |
| |
| if (crossystem) { |
| crossystem_ = crossystem; |
| } else { |
| crossystem_backend_ = std::make_unique<crossystem::Crossystem>(); |
| crossystem_ = crossystem_backend_.get(); |
| } |
| #endif |
| } |
| |
| bool FeatureManagementImpl::IsFeatureEnabled(const std::string& name) { |
| int prefix_len = strlen(FeatureManagement::kPrefix); |
| if (name.compare(0, prefix_len, FeatureManagement::kPrefix)) |
| return false; |
| |
| enum FeatureLevel feature_level = GetFeatureLevel(); |
| if (feature_level == FEATURE_LEVEL_UNKNOWN) |
| feature_level = FEATURE_LEVEL_0; |
| |
| enum ScopeLevel scope_level = GetScopeLevel(); |
| if (scope_level == SCOPE_LEVEL_UNKNOWN) |
| scope_level = SCOPE_LEVEL_0; |
| |
| // Now we have the feature_level and the scope_level, after we |
| // found that the feature exist, check if its level is equal or |
| // greater than the DUT feature_level and if its scope array contains |
| // the DUT scope_level. |
| std::string in_feature = name.substr(prefix_len); |
| for (const auto& feature : feature_bundle_.features()) { |
| if (!feature.name().compare(in_feature)) { |
| auto it = std::find(feature.scopes().begin(), feature.scopes().end(), |
| scope_level); |
| |
| return feature_level - FEATURE_LEVEL_VALID_OFFSET >= |
| feature.feature_level() && |
| it != feature.scopes().end(); |
| } |
| } |
| return false; |
| } |
| |
| const std::set<std::string> FeatureManagementImpl::ListFeatures( |
| const FeatureUsage usage) { |
| enum FeatureLevel feature_level = GetFeatureLevel(); |
| if (feature_level == FEATURE_LEVEL_UNKNOWN) |
| feature_level = FEATURE_LEVEL_0; |
| |
| enum ScopeLevel scope_level = GetScopeLevel(); |
| if (scope_level == SCOPE_LEVEL_UNKNOWN) |
| scope_level = SCOPE_LEVEL_0; |
| |
| // Now we have the feature_level and the scope_level, return all features |
| // that: |
| // 1. matches the usage and |
| // 2. its feature level is equal or greater than the DUT feature_level and |
| // 3. its scope array contains the DUT scope_level. |
| std::set<std::string> features; |
| for (const auto& feature : feature_bundle_.features()) { |
| auto it = std::find(feature.scopes().begin(), feature.scopes().end(), |
| scope_level); |
| |
| if (std::find(feature.usages().begin(), feature.usages().end(), usage) != |
| feature.usages().end() && |
| feature_level - FEATURE_LEVEL_VALID_OFFSET >= feature.feature_level() && |
| it != feature.scopes().end()) |
| features.emplace(FeatureManagement::kPrefix + feature.name()); |
| } |
| return features; |
| } |
| } // namespace segmentation |