| #include "tlbmc/metrics/software_metrics.h" |
| |
| #include <dirent.h> |
| #include <sys/stat.h> |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstdint> |
| #include <filesystem> // NOLINT: filesystem is commonly used in bmc codebase |
| #include <fstream> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <system_error> // NOLINT: system_error is commonly used in bmc |
| #include <utility> |
| #include <vector> |
| |
| #include "google/protobuf/duration.pb.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/functional/any_invocable.h" |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/strings/strip.h" |
| #include "absl/synchronization/mutex.h" |
| #include "g3/macros.h" |
| #include <nlohmann/json.hpp> |
| #include "tlbmc/central_config/config.h" |
| #include "resource.pb.h" |
| #include "software_metrics.pb.h" |
| #include "tlbmc/time/time.h" |
| #include "tlbmc/utils/command_executor.h" |
| namespace milotic_tlbmc { |
| |
| namespace { |
| |
| // Helper to unescape systemd unit names from PSI directory paths. |
| // Systemd escapes special characters as \xNN. |
| // absl::CUnescape is unsuitable here because it is "greedy" and consumes more |
| // than 2 hex digits if available. For example, in a path like "\x2dconsole", |
| // absl::CUnescape tries to parse "2dc" as a single hex value (0x2DC > 255), |
| // causing an error. This function strictly parses exactly 2 hex digits after |
| // "\x". |
| std::string SystemdUnescape(absl::string_view filename) { |
| std::string unescaped; |
| unescaped.reserve(filename.size()); |
| for (size_t i = 0; i < filename.size(); ++i) { |
| if (filename[i] == '\\' && i + 3 < filename.size() && |
| filename[i + 1] == 'x') { |
| int32_t v = 0; |
| if (absl::SimpleHexAtoi(filename.substr(i + 2, 2), &v)) { |
| unescaped.push_back(static_cast<char>(v)); |
| i += 3; |
| continue; |
| } |
| } |
| unescaped.push_back(filename[i]); |
| } |
| return unescaped; |
| } |
| |
| constexpr std::string_view kBiosKeyVersionPath = |
| "run/bios_key/bios_key_version"; |
| constexpr std::string_view kBiosKeyImageFamilyPath = |
| "run/bios_key/bios_key_image_family"; |
| constexpr std::string_view kBiosKeyValidationMethodPath = |
| "run/bios_key/bios_key_validation_method"; |
| constexpr std::string_view kBiosKeyValidationKeyDataPath = |
| "run/bios_key/bios_key_validation_key_data"; |
| |
| } // namespace |
| |
| SoftwareMetricsAttributesStatic SoftwareMetrics::CreateStaticAttributes( |
| const SoftwareMetricsConfig& software_metrics_config) { |
| SoftwareMetricsAttributesStatic metric_attributes_static; |
| *metric_attributes_static.mutable_software_metrics_config() = |
| software_metrics_config; |
| return metric_attributes_static; |
| } |
| |
| absl::StatusOr<SocketStatStates> SoftwareMetrics::UpdateSocketStats() { |
| ECCLESIA_ASSIGN_OR_RETURN( |
| std::string cmd_output, |
| executor_->Execute(std::string(kSocketStatCmd), false)); |
| |
| SocketStatStates socket_states; |
| *socket_states.mutable_timestamp() = Now(); |
| |
| std::stringstream ss(cmd_output); |
| std::string line; |
| while (std::getline(ss, line)) { |
| if (line.empty()) { |
| continue; |
| } |
| |
| std::stringstream line_stream(line); |
| std::string state_str; |
| int port_num; |
| SocketStatState* state; |
| if (line_stream >> state_str >> port_num) { |
| if (ss_port_set_.contains(port_num)) { |
| state = &(*socket_states.mutable_states())[port_num]; |
| if (state_str == "LISTEN") { |
| state->set_listen(state->listen() + 1); |
| } else if (state_str == "ESTAB") { |
| state->set_established(state->established() + 1); |
| } else if (state_str == "SYN-SENT") { |
| state->set_syn_sent(state->syn_sent() + 1); |
| } else if (state_str == "SYN-RECV") { |
| state->set_syn_received(state->syn_received() + 1); |
| } else if (state_str == "FIN-WAIT-1") { |
| state->set_fin_wait_one(state->fin_wait_one() + 1); |
| } else if (state_str == "FIN-WAIT-2") { |
| state->set_fin_wait_two(state->fin_wait_two() + 1); |
| } else if (state_str == "TIME-WAIT") { |
| state->set_time_wait(state->time_wait() + 1); |
| } else if (state_str == "CLOSE-WAIT") { |
| state->set_close_wait(state->close_wait() + 1); |
| } else if (state_str == "LAST-ACK") { |
| state->set_last_ack(state->last_ack() + 1); |
| } else if (state_str == "CLOSING") { |
| state->set_closing(state->closing() + 1); |
| } |
| } |
| } |
| } |
| if (socket_states.states_size() > 0) { |
| return socket_states; |
| } |
| return absl::NotFoundError( |
| "No matching NFT rules found for configured ports."); |
| } |
| |
| absl::StatusOr<NetFilterStates> SoftwareMetrics::UpdateNetFilterStats() { |
| ECCLESIA_ASSIGN_OR_RETURN( |
| absl::string_view cmd_output, |
| executor_->Execute(std::string(kNetFilterCmd), false)); |
| NetFilterStates nf_states; |
| *nf_states.mutable_timestamp() = Now(); |
| // Parse JSON output |
| nlohmann::json nft_json = nlohmann::json::parse(cmd_output, nullptr, false); |
| if (nft_json.is_discarded()) { |
| return absl::NotFoundError("Failed to parse JSON output from nft."); |
| } |
| if (!nft_json.contains("nftables") || !nft_json["nftables"].is_array()) { |
| return absl::NotFoundError( |
| "nftables array not found in JSON output from nft."); |
| } |
| |
| // Iterate through the rules and extract data for the ports we need. |
| for (const nlohmann::json& item : nft_json["nftables"]) { |
| if (!item.contains("rule") || !item["rule"].is_object()) { |
| continue; |
| } |
| const nlohmann::json& rule = item["rule"]; |
| if (!rule.contains("comment") || !rule["comment"].is_string()) { |
| continue; |
| } |
| |
| // Ensure the comment refers to one of our counter rules |
| absl::string_view port_str = rule["comment"].get<absl::string_view>(); |
| if (!absl::ConsumePrefix(&port_str, "tcp-server-") || |
| !absl::ConsumeSuffix(&port_str, "-synack")) { |
| continue; |
| } |
| |
| // Make sure we have an actual port and we are configured to collect this. |
| int32_t port_id; |
| if (!absl::SimpleAtoi(port_str, &port_id) || |
| !nf_port_set_.contains(port_id)) { |
| continue; |
| } |
| |
| // This rule is for one of our target ports. Now extract the packet count. |
| if (!rule.contains("expr") || !rule["expr"].is_array() || |
| rule["expr"].size() != 1) { |
| LOG(WARNING) << "Rule for port " << port_id |
| << " has unexpected expr structure."; |
| |
| continue; |
| } |
| const nlohmann::json& counter_expr = rule["expr"][0]; |
| if (!counter_expr.contains("counter") || |
| !counter_expr["counter"].is_object() || |
| !counter_expr["counter"].contains("packets") || |
| !counter_expr["counter"]["packets"].is_number()) { |
| LOG(WARNING) << "Rule for port " << port_id |
| << " is missing packet counter."; |
| |
| continue; |
| } |
| |
| NetFilterState* state = nf_states.add_states(); |
| state->set_port(port_id); |
| state->set_num_connections( |
| counter_expr["counter"]["packets"].get<uint64_t>()); |
| } |
| |
| if (nf_states.states_size() > 0) { |
| return nf_states; |
| } |
| |
| return absl::NotFoundError( |
| "No matching NFT rules found for configured ports."); |
| } |
| |
| void SoftwareMetrics::RefreshTrueHitlessMetrics( |
| absl::AnyInvocable<void()> callback) { |
| absl::StatusOr<std::string> result = |
| executor_->Execute(std::string(kTrueHitlessMetricsUpdateCmd), false); |
| if (!result.ok()) { |
| // Log every hour to avoid spamming the logs. |
| LOG_EVERY_N_SEC(ERROR, 3600) |
| << "Failed to start true-hitless-status-update.service: " |
| << result.status(); |
| } |
| |
| if (callback) { |
| callback(); |
| } |
| } |
| |
| TrueHitlessMetrics SoftwareMetrics::GetTrueHitlessMetrics() { |
| return true_hitless_metrics_parser_.ParseTrueHitlessMetricsFromJson(); |
| } |
| |
| absl::StatusOr<PressureLevels> ParsePressureOutput(absl::string_view output) { |
| PressureLevels levels; |
| std::stringstream ss{std::string(output)}; |
| std::string line; |
| |
| while (std::getline(ss, line)) { |
| std::stringstream line_stream(line); |
| std::string type; |
| std::string avg10_str; |
| std::string avg60_str; |
| std::string avg300_str; |
| std::string total_str; |
| |
| line_stream >> type >> avg10_str >> avg60_str >> avg300_str >> total_str; |
| if (line_stream.fail()) { |
| continue; |
| } |
| |
| PsiPressure* pressure_data = nullptr; |
| if (type == "some") { |
| pressure_data = levels.mutable_some(); |
| } else if (type == "full") { |
| pressure_data = levels.mutable_full(); |
| } else { |
| continue; |
| } |
| |
| absl::string_view avg10_sv = avg10_str; |
| absl::string_view avg60_sv = avg60_str; |
| absl::string_view avg300_sv = avg300_str; |
| absl::string_view total_sv = total_str; |
| double avg10 = 0.0; |
| double avg60 = 0.0; |
| double avg300 = 0.0; |
| int64_t total = 0; |
| |
| if (!absl::ConsumePrefix(&avg10_sv, "avg10=") || |
| !absl::SimpleAtod(avg10_sv, &avg10)) { |
| continue; |
| } |
| if (!absl::ConsumePrefix(&avg60_sv, "avg60=") || |
| !absl::SimpleAtod(avg60_sv, &avg60)) { |
| continue; |
| } |
| if (!absl::ConsumePrefix(&avg300_sv, "avg300=") || |
| !absl::SimpleAtod(avg300_sv, &avg300)) { |
| continue; |
| } |
| if (!absl::ConsumePrefix(&total_sv, "total=") || |
| !absl::SimpleAtoi(total_sv, &total)) { |
| continue; |
| } |
| |
| pressure_data->set_avg10(avg10); |
| pressure_data->set_avg60(avg60); |
| pressure_data->set_avg300(avg300); |
| pressure_data->set_total(total); |
| |
| if (levels.has_some() && levels.has_full()) { |
| break; |
| } |
| } |
| if (!levels.has_some() && !levels.has_full()) { |
| return absl::InvalidArgumentError("No valid pressure data found."); |
| } |
| return levels; |
| } |
| |
| absl::StatusOr<std::string> ReadFileContents(const std::string& file_path) { |
| std::ifstream file(file_path); |
| if (!file.is_open()) { |
| return absl::NotFoundError( |
| absl::StrCat("Failed to open file: ", file_path)); |
| } |
| |
| std::stringstream buffer; |
| buffer << file.rdbuf(); |
| return buffer.str(); |
| } |
| |
| absl::Status SoftwareMetrics::UpdatePsiData(SoftwareMetricsValue* value) { |
| absl::StatusOr<PsiMetrics> psi_metrics = ReadSystemPsiMetrics(); |
| if (psi_metrics.ok()) { |
| *value->mutable_psi_metrics() = *psi_metrics; |
| } |
| |
| absl::StatusOr<std::vector<std::string>> services = ReadAvailableServices(); |
| if (services.ok()) { |
| *value->mutable_available_services() = {services->begin(), services->end()}; |
| value->mutable_service_psi_metrics()->clear(); |
| for (const auto& service : *services) { |
| absl::StatusOr<PsiMetrics> service_metrics = |
| ReadPsiMetricsForService(service); |
| if (service_metrics.ok()) { |
| (*value->mutable_service_psi_metrics())[service] = *service_metrics; |
| } |
| } |
| } else { |
| return services.status(); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<PsiMetrics> SoftwareMetrics::ReadSystemPsiMetrics() { |
| PsiMetrics psi_metrics; |
| absl::StatusOr<PressureLevels> levels; |
| |
| // CPU pressure |
| absl::StatusOr<std::string> cpu_file_content = |
| ReadFileContents(absl::StrCat(psi_base_path_, "/cpu.pressure")); |
| if (!cpu_file_content.ok()) { |
| LOG(WARNING) << "Failed to read CPU pressure file: " |
| << cpu_file_content.status(); |
| } else { |
| levels = ParsePressureOutput(*cpu_file_content); |
| if (levels.ok()) { |
| *psi_metrics.mutable_cpu() = *levels; |
| } else { |
| LOG(WARNING) << "Failed to parse CPU pressure: " << levels.status(); |
| } |
| } |
| |
| // Memory pressure |
| absl::StatusOr<std::string> memory_file_content = |
| ReadFileContents(absl::StrCat(psi_base_path_, "/memory.pressure")); |
| if (!memory_file_content.ok()) { |
| LOG(WARNING) << "Failed to read Memory pressure file: " |
| << memory_file_content.status(); |
| } else { |
| levels = ParsePressureOutput(*memory_file_content); |
| if (levels.ok()) { |
| *psi_metrics.mutable_memory() = *levels; |
| } else { |
| LOG(WARNING) << "Failed to parse Memory pressure: " << levels.status(); |
| } |
| } |
| |
| // I/O pressure |
| absl::StatusOr<std::string> io_file_content = |
| ReadFileContents(absl::StrCat(psi_base_path_, "/io.pressure")); |
| if (!io_file_content.ok()) { |
| LOG(WARNING) << "Failed to read I/O pressure file: " |
| << io_file_content.status(); |
| } else { |
| levels = ParsePressureOutput(*io_file_content); |
| if (levels.ok()) { |
| *psi_metrics.mutable_io() = *levels; |
| } else { |
| LOG(WARNING) << "Failed to parse I/O pressure: " << levels.status(); |
| } |
| } |
| |
| return psi_metrics; |
| } |
| |
| absl::StatusOr<std::vector<std::string>> |
| SoftwareMetrics::ReadAvailableServices() { |
| std::string system_slice_path = absl::StrCat(psi_base_path_, "/system.slice"); |
| |
| std::vector<std::string> services; |
| std::error_code ec; |
| if (!std::filesystem::exists(system_slice_path, ec)) { |
| return absl::NotFoundError( |
| absl::StrCat("System slice directory not found: ", system_slice_path)); |
| } |
| |
| auto dir_it = std::filesystem::directory_iterator(system_slice_path, ec); |
| if (ec) { |
| return absl::InternalError( |
| absl::StrCat("Failed to iterate over directory: ", ec.message())); |
| } |
| |
| for (auto end = std::filesystem::directory_iterator(); !ec && dir_it != end; |
| dir_it.increment(ec)) { |
| const auto& entry = *dir_it; |
| if (entry.is_directory(ec)) { |
| if (std::filesystem::exists(entry.path() / "cpu.pressure", ec)) { |
| services.push_back(SystemdUnescape(entry.path().filename().string())); |
| } |
| } |
| } |
| if (ec) { |
| LOG(WARNING) << "Error during directory iteration: " << ec.message(); |
| } |
| |
| std::sort(services.begin(), services.end()); |
| return services; |
| } |
| |
| absl::StatusOr<PsiMetrics> SoftwareMetrics::ReadPsiMetricsForService( |
| const std::string& service_name) { |
| std::string system_slice_path = absl::StrCat(psi_base_path_, "/system.slice"); |
| |
| // Build the service directory path |
| std::string service_dir = absl::StrCat(system_slice_path, "/", service_name); |
| |
| std::error_code ec; |
| if (!std::filesystem::exists(service_dir, ec)) { |
| // Try to find the escaped name by iterating the directory |
| for (const auto& entry : |
| std::filesystem::directory_iterator(system_slice_path, ec)) { |
| if (!entry.is_directory(ec)) { |
| continue; |
| } |
| if (SystemdUnescape(entry.path().filename().string()) == service_name) { |
| service_dir = entry.path().string(); |
| break; |
| } |
| } |
| } |
| |
| // CPU pressure |
| std::string cpu_path = absl::StrCat(service_dir, "/cpu.pressure"); |
| PsiMetrics psi_metrics; |
| absl::StatusOr<PressureLevels> levels; |
| absl::StatusOr<std::string> cpu_file_content = ReadFileContents(cpu_path); |
| if (cpu_file_content.ok()) { |
| levels = ParsePressureOutput(*cpu_file_content); |
| if (levels.ok()) { |
| *psi_metrics.mutable_cpu() = *levels; |
| } |
| } |
| |
| // Memory pressure |
| std::string memory_path = absl::StrCat(service_dir, "/memory.pressure"); |
| absl::StatusOr<std::string> memory_file_content = |
| ReadFileContents(memory_path); |
| if (memory_file_content.ok()) { |
| levels = ParsePressureOutput(*memory_file_content); |
| if (levels.ok()) { |
| *psi_metrics.mutable_memory() = *levels; |
| } |
| } |
| |
| // I/O pressure |
| std::string io_path = absl::StrCat(service_dir, "/io.pressure"); |
| absl::StatusOr<std::string> io_file_content = ReadFileContents(io_path); |
| if (io_file_content.ok()) { |
| levels = ParsePressureOutput(*io_file_content); |
| if (levels.ok()) { |
| *psi_metrics.mutable_io() = *levels; |
| } |
| } |
| |
| return psi_metrics; |
| } |
| |
| absl::StatusOr<PsiMetrics> SoftwareMetrics::GetPsiMetrics() const { |
| if (!GetTlbmcConfig().metric_collector_module().enable_psi_metrics()) { |
| return absl::NotFoundError("PSI metrics collection is disabled."); |
| } |
| absl::MutexLock lock(metric_data_mutex_); |
| return values_.psi_metrics(); |
| } |
| |
| absl::StatusOr<std::vector<std::string>> SoftwareMetrics::GetAvailableServices() |
| const { |
| if (!GetTlbmcConfig().metric_collector_module().enable_psi_metrics()) { |
| return absl::NotFoundError("PSI metrics collection is disabled."); |
| } |
| absl::MutexLock lock(metric_data_mutex_); |
| return std::vector<std::string>(values_.available_services().begin(), |
| values_.available_services().end()); |
| } |
| |
| absl::StatusOr<PsiMetrics> SoftwareMetrics::GetPsiMetricsForService( |
| const std::string& service_name) const { |
| if (!GetTlbmcConfig().metric_collector_module().enable_psi_metrics()) { |
| return absl::NotFoundError("PSI metrics collection is disabled."); |
| } |
| absl::MutexLock lock(metric_data_mutex_); |
| auto it = values_.service_psi_metrics().find(service_name); |
| if (it == values_.service_psi_metrics().end()) { |
| return absl::NotFoundError( |
| absl::StrCat("Service not found: ", service_name)); |
| } |
| return it->second; |
| } |
| |
| absl::StatusOr<std::unique_ptr<SoftwareMetrics>> |
| SoftwareMetrics::CreateWithExecutorForUnitTest( |
| const SoftwareMetricsConfig& config, |
| absl::flat_hash_map<std::string, absl::StatusOr<std::string>>& |
| command_map) { |
| return CreateWithExecutorForUnitTest(config, command_map, |
| *kDefaultPsiBasePath, "/"); |
| } |
| |
| absl::StatusOr<std::unique_ptr<SoftwareMetrics>> |
| SoftwareMetrics::CreateWithExecutorForUnitTest( |
| const SoftwareMetricsConfig& config, |
| absl::flat_hash_map<std::string, absl::StatusOr<std::string>>& command_map, |
| absl::string_view psi_base_path, absl::string_view root_path) { |
| SoftwareMetricsAttributesStatic software_attributes_static = |
| CreateStaticAttributes(config); |
| auto ptr = absl::WrapUnique(new SoftwareMetrics(software_attributes_static, |
| psi_base_path, root_path)); |
| |
| return ptr; |
| } |
| |
| absl::StatusOr<std::unique_ptr<SoftwareMetrics>> SoftwareMetrics::Create( |
| const SoftwareMetricsConfig& config) { |
| return Create(config, *kDefaultPsiBasePath, "/"); |
| } |
| |
| absl::StatusOr<std::unique_ptr<SoftwareMetrics>> SoftwareMetrics::Create( |
| const SoftwareMetricsConfig& config, absl::string_view psi_base_path, |
| absl::string_view root_path) { |
| SoftwareMetricsAttributesStatic software_attributes_static = |
| CreateStaticAttributes(config); |
| return absl::WrapUnique(new SoftwareMetrics(software_attributes_static, |
| psi_base_path, root_path)); |
| } |
| |
| void SoftwareMetrics::RefreshOnce(absl::AnyInvocable<void()> callback) { |
| bool socket_stats_updated = false; |
| SocketStatStates socket_stats; |
| if (!ss_port_set_.empty()) { |
| absl::StatusOr<SocketStatStates> res = UpdateSocketStats(); |
| if (res.ok()) { |
| socket_stats = std::move(*res); |
| socket_stats_updated = true; |
| } else { |
| LOG(ERROR) << "Failed to update socket stats: " << res.status(); |
| } |
| } |
| |
| bool netfilter_stats_updated = false; |
| NetFilterStates netfilter_stats; |
| if (!nf_port_set_.empty()) { |
| absl::StatusOr<NetFilterStates> res = UpdateNetFilterStats(); |
| if (res.ok()) { |
| netfilter_stats = std::move(*res); |
| netfilter_stats_updated = true; |
| } else { |
| LOG(ERROR) << "Failed to update netfilter stats: " << res.status(); |
| } |
| } |
| |
| bool psi_updated = false; |
| SoftwareMetricsValue psi_values; |
| if (GetTlbmcConfig().metric_collector_module().enable_psi_metrics()) { |
| absl::Status status = UpdatePsiData(&psi_values); |
| if (status.ok()) { |
| psi_updated = true; |
| } else { |
| LOG(ERROR) << "Failed to update PSI stats: " << status; |
| } |
| } |
| |
| if (socket_stats_updated || netfilter_stats_updated || psi_updated) { |
| absl::MutexLock lock(metric_data_mutex_); |
| if (socket_stats_updated) { |
| values_.mutable_socket_stat_state()->Swap(&socket_stats); |
| } |
| if (netfilter_stats_updated) { |
| values_.mutable_netfilter_state()->Swap(&netfilter_stats); |
| } |
| if (psi_updated) { |
| values_.mutable_psi_metrics()->Swap(psi_values.mutable_psi_metrics()); |
| values_.mutable_available_services()->Swap( |
| psi_values.mutable_available_services()); |
| values_.mutable_service_psi_metrics()->swap( |
| *psi_values.mutable_service_psi_metrics()); |
| } |
| } |
| |
| if (callback) { |
| callback(); |
| } |
| } |
| |
| absl::StatusOr<BiosKeyStatus> SoftwareMetrics::ReadBiosKeyStatus() { |
| std::error_code ec; |
| // Use the injected root_path_ (which is "/" in production but set to a temp |
| // dir in tests) to resolve the BIOS key paths. This ensures we are sandboxed |
| // correctly during integration testing without using environment variables. |
| std::string root_path = root_path_; |
| |
| std::filesystem::path version_path = |
| std::filesystem::path(root_path) / kBiosKeyVersionPath; |
| std::filesystem::path family_path = |
| std::filesystem::path(root_path) / kBiosKeyImageFamilyPath; |
| std::filesystem::path method_path = |
| std::filesystem::path(root_path) / kBiosKeyValidationMethodPath; |
| std::filesystem::path key_data_path = |
| std::filesystem::path(root_path) / kBiosKeyValidationKeyDataPath; |
| |
| if (!std::filesystem::exists(version_path, ec) || |
| !std::filesystem::exists(family_path, ec) || |
| !std::filesystem::exists(method_path, ec) || |
| !std::filesystem::exists(key_data_path, ec)) { |
| return absl::NotFoundError("BIOS key status files not yet available."); |
| } |
| |
| BiosKeyStatus status; |
| |
| // Read and parse Version |
| ECCLESIA_ASSIGN_OR_RETURN(std::string version_str, |
| ReadFileContents(version_path.string())); |
| int32_t version; |
| if (!absl::SimpleAtoi(absl::StripAsciiWhitespace(version_str), &version)) { |
| return absl::InternalError("Failed to parse BIOS key version."); |
| } |
| status.set_version(version); |
| |
| // Read and parse Image Family |
| ECCLESIA_ASSIGN_OR_RETURN(std::string family_str, |
| ReadFileContents(family_path.string())); |
| int32_t family; |
| if (!absl::SimpleAtoi(absl::StripAsciiWhitespace(family_str), &family)) { |
| return absl::InternalError("Failed to parse BIOS key image family."); |
| } |
| status.set_image_family(family); |
| |
| // Read Validation Method |
| ECCLESIA_ASSIGN_OR_RETURN(std::string method_str, |
| ReadFileContents(method_path.string())); |
| status.set_validation_method(absl::StripAsciiWhitespace(method_str)); |
| |
| // Read and parse Key Data (Hex string, e.g., "0xc64d2247") |
| ECCLESIA_ASSIGN_OR_RETURN(std::string key_data_str, |
| ReadFileContents(key_data_path.string())); |
| std::string_view key_data_sv = absl::StripAsciiWhitespace(key_data_str); |
| int64_t key_data; |
| if (!absl::SimpleHexAtoi(key_data_sv, &key_data)) { |
| return absl::InternalError("Failed to parse BIOS key validation key data."); |
| } |
| status.set_validation_key_data(key_data); |
| |
| return status; |
| } |
| |
| void SoftwareMetrics::RefreshBiosKeyStatus() { |
| if (!GetTlbmcConfig().metric_collector_module().enable_bios_key_metrics()) { |
| return; |
| } |
| auto kr_status = ReadBiosKeyStatus(); |
| if (kr_status.ok()) { |
| absl::MutexLock lock(metric_data_mutex_); |
| *values_.mutable_bios_key_status() = *kr_status; |
| LOG(INFO) << "Successfully retrieved and cached BIOS Key status."; |
| } else if (!absl::IsNotFound(kr_status.status())) { |
| LOG(ERROR) << "Error reading BIOS Key status: " << kr_status.status(); |
| } |
| } |
| |
| } // namespace milotic_tlbmc |