| // Copyright 2021 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "rmad/interface/rmad_interface_impl.h" | |
| #include <cctype> | |
| #include <list> | |
| #include <memory> | |
| #include <optional> | |
| #include <set> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/check.h> | |
| #include <base/files/file_path.h> | |
| #include <base/files/file_util.h> | |
| #include <base/logging.h> | |
| #include <base/memory/scoped_refptr.h> | |
| #include <base/time/time.h> | |
| #include <base/values.h> | |
| #include <re2/re2.h> | |
| #include "rmad/constants.h" | |
| #include "rmad/logs/logs_utils.h" | |
| #include "rmad/metrics/metrics_utils_impl.h" | |
| #include "rmad/proto_bindings/rmad.pb.h" | |
| #include "rmad/system/power_manager_client_impl.h" | |
| #include "rmad/system/runtime_probe_client_impl.h" | |
| #include "rmad/system/shill_client_impl.h" | |
| #include "rmad/system/tpm_manager_client_impl.h" | |
| #include "rmad/udev/udev_device.h" | |
| #include "rmad/udev/udev_utils.h" | |
| #include "rmad/utils/cmd_utils_impl.h" | |
| #include "rmad/utils/dbus_utils.h" | |
| #include "rmad/utils/rpc_utils.h" | |
| namespace rmad { | |
| namespace { | |
| constexpr char kCroslogCmd[] = "/usr/sbin/croslog"; | |
| constexpr char kInitctlCmd[] = "/sbin/initctl"; | |
| constexpr std::array<std::string_view, 1> kWaitServices = {"system-services"}; | |
| constexpr int kWaitServicesPollInterval = 1; // 1 second. | |
| constexpr int kWaitServicesRetries = 10; | |
| constexpr char kDiagnosticsAppSwbnRelPath[] = "diagnostics_app.swbn"; | |
| constexpr char kDiagnosticsAppCrxRelPath[] = "diagnostics_app.crx"; | |
| constexpr char kSummaryDivider[] = | |
| "\n=========================================" | |
| "=================================\n\n"; | |
| bool GetDeviceIdFromDeviceNode(const std::string& device_node, | |
| char* device_id) { | |
| re2::StringPiece string_piece(device_node); | |
| re2::RE2 regexp("/dev/sd([[:lower:]])([[:digit:]]*)"); | |
| std::string device_id_string; | |
| if (RE2::FullMatch(string_piece, regexp, &device_id_string)) { | |
| *device_id = device_id_string[0]; | |
| return true; | |
| } | |
| return false; | |
| } | |
| template <typename T> | |
| bool HasOptionalValue(const std::optional<T>& v) { | |
| return v.has_value(); | |
| } | |
| } // namespace | |
| RmadInterfaceImpl::RmadInterfaceImpl() | |
| : RmadInterface(), | |
| external_utils_initialized_(false), | |
| current_state_case_(RmadState::STATE_NOT_SET) {} | |
| RmadInterfaceImpl::RmadInterfaceImpl( | |
| scoped_refptr<JsonStore> json_store, | |
| const base::FilePath& working_dir_path, | |
| const base::FilePath& unencrypted_rma_dir_path, | |
| std::unique_ptr<StateHandlerManager> state_handler_manager, | |
| std::unique_ptr<RuntimeProbeClient> runtime_probe_client, | |
| std::unique_ptr<ShillClient> shill_client, | |
| std::unique_ptr<TpmManagerClient> tpm_manager_client, | |
| std::unique_ptr<PowerManagerClient> power_manager_client, | |
| std::unique_ptr<UdevUtils> udev_utils, | |
| std::unique_ptr<CmdUtils> cmd_utils, | |
| std::unique_ptr<MetricsUtils> metrics_utils) | |
| : RmadInterface(), | |
| json_store_(json_store), | |
| working_dir_path_(working_dir_path), | |
| unencrypted_rma_dir_path_(unencrypted_rma_dir_path), | |
| state_handler_manager_(std::move(state_handler_manager)), | |
| runtime_probe_client_(std::move(runtime_probe_client)), | |
| shill_client_(std::move(shill_client)), | |
| tpm_manager_client_(std::move(tpm_manager_client)), | |
| power_manager_client_(std::move(power_manager_client)), | |
| udev_utils_(std::move(udev_utils)), | |
| cmd_utils_(std::move(cmd_utils)), | |
| metrics_utils_(std::move(metrics_utils)), | |
| external_utils_initialized_(true), | |
| current_state_case_(RmadState::STATE_NOT_SET) {} | |
| bool RmadInterfaceImpl::StoreStateHistory() { | |
| std::vector<int> state_history; | |
| for (auto s : state_history_) { | |
| state_history.push_back(RmadState::StateCase(s)); | |
| } | |
| return json_store_->SetValue(kStateHistory, state_history); | |
| } | |
| void RmadInterfaceImpl::InitializeExternalUtils( | |
| scoped_refptr<DaemonCallback> daemon_callback) { | |
| json_store_ = base::MakeRefCounted<JsonStore>( | |
| base::FilePath(kDefaultUnencryptedRmaDirPath).Append(kJsonStoreFilePath), | |
| false); | |
| working_dir_path_ = base::FilePath(kDefaultWorkingDirPath); | |
| unencrypted_rma_dir_path_ = base::FilePath(kDefaultUnencryptedRmaDirPath); | |
| state_handler_manager_ = std::make_unique<StateHandlerManager>(json_store_); | |
| state_handler_manager_->RegisterStateHandlers(daemon_callback); | |
| runtime_probe_client_ = | |
| std::make_unique<RuntimeProbeClientImpl>(GetSystemBus()); | |
| shill_client_ = std::make_unique<ShillClientImpl>(GetSystemBus()); | |
| tpm_manager_client_ = std::make_unique<TpmManagerClientImpl>(GetSystemBus()); | |
| power_manager_client_ = | |
| std::make_unique<PowerManagerClientImpl>(GetSystemBus()); | |
| udev_utils_ = std::make_unique<UdevUtilsImpl>(); | |
| cmd_utils_ = std::make_unique<CmdUtilsImpl>(); | |
| } | |
| bool RmadInterfaceImpl::WaitForServices() { | |
| CHECK(external_utils_initialized_); | |
| std::string output; | |
| for (int i = 0; i < kWaitServicesRetries; ++i) { | |
| DLOG(INFO) << "Checking services"; | |
| bool all_running = true; | |
| for (const std::string_view& service : kWaitServices) { | |
| cmd_utils_->GetOutput({kInitctlCmd, "status", service.data()}, &output); | |
| if (output.find("running") == std::string::npos) { | |
| all_running = false; | |
| break; | |
| } | |
| } | |
| if (all_running) { | |
| return true; | |
| } | |
| sleep(kWaitServicesPollInterval); | |
| } | |
| return false; | |
| } | |
| bool RmadInterfaceImpl::StartFromInitialState() { | |
| current_state_case_ = kInitialStateCase; | |
| state_history_.push_back(current_state_case_); | |
| if (!StoreStateHistory()) { | |
| LOG(ERROR) << "Could not store initial state"; | |
| // TODO(chenghan): Send a signal to Chrome that the json store failed so | |
| // a message can be displayed. | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool RmadInterfaceImpl::SetUp(scoped_refptr<DaemonCallback> daemon_callback) { | |
| daemon_callback_ = daemon_callback; | |
| // Initialize external utilities if needed. | |
| if (!external_utils_initialized_) { | |
| InitializeExternalUtils(daemon_callback); | |
| external_utils_initialized_ = true; | |
| metrics_utils_ = std::make_unique<MetricsUtilsImpl>(); | |
| } | |
| // Wait for system services to be ready. | |
| if (!WaitForServices()) { | |
| return false; | |
| } | |
| // Initialize |current state_|, |state_history_|, and |can_abort_| flag. | |
| current_state_case_ = RmadState::STATE_NOT_SET; | |
| state_history_.clear(); | |
| can_abort_ = true; | |
| // Something's wrong with the state file. Try to clear it. | |
| if (json_store_->ReadOnly()) { | |
| LOG(WARNING) << "Corrupted RMA state file. Trying to fix it"; | |
| if (!json_store_->Clear() || !json_store_->InitFromFile(false)) { | |
| LOG(ERROR) << "Failed to fix RMA state file"; | |
| return false; | |
| } | |
| } | |
| DCHECK(!json_store_->ReadOnly()); | |
| if (json_store_->GetReadError() != JsonStore::READ_ERROR_NO_SUCH_FILE) { | |
| if (std::vector<int> state_history; | |
| json_store_->GetReadError() == JsonStore::READ_ERROR_NONE && | |
| json_store_->GetValue(kStateHistory, &state_history) && | |
| state_history.size()) { | |
| for (int state : state_history) { | |
| // Reject any state that does not have a handler. | |
| if (RmadState::StateCase s = RmadState::StateCase(state); | |
| auto handler = state_handler_manager_->GetStateHandler(s)) { | |
| state_history_.push_back(s); | |
| can_abort_ &= handler->IsRepeatable(); | |
| } else { | |
| // TODO(chenghan): Return to welcome screen with an error implying | |
| // an unsupported state. | |
| LOG(ERROR) << "Missing handler for state " << state << "."; | |
| } | |
| } | |
| } | |
| if (state_history_.size() > 0) { | |
| current_state_case_ = state_history_.back(); | |
| } else { | |
| LOG(WARNING) << "Could not read state history from json store, reset to " | |
| "initial state."; | |
| if (!StartFromInitialState()) { | |
| return false; | |
| } | |
| } | |
| } else if (RoVerificationStatus status; | |
| tpm_manager_client_->GetRoVerificationStatus(&status) && | |
| (status == RMAD_RO_VERIFICATION_PASS || | |
| status == RMAD_RO_VERIFICATION_UNSUPPORTED_TRIGGERED)) { | |
| VLOG(1) << "RO verification triggered"; | |
| if (!StartFromInitialState()) { | |
| return false; | |
| } | |
| if (!json_store_->SetValue(kRoFirmwareVerified, | |
| status == RMAD_RO_VERIFICATION_PASS) || | |
| !MetricsUtils::SetMetricsValue(json_store_, kMetricsRoFirmwareVerified, | |
| RoVerificationStatus_Name(status))) { | |
| LOG(ERROR) << "Could not store RO firmware verification status"; | |
| } | |
| } | |
| double current_timestamp = base::Time::Now().InSecondsFSinceUnixEpoch(); | |
| if (!MetricsUtils::UpdateStateMetricsOnStateTransition( | |
| json_store_, RmadState::STATE_NOT_SET, current_state_case_, | |
| current_timestamp)) { | |
| LOG(ERROR) << "Could not store setup time for the current state."; | |
| return false; | |
| } | |
| RecordRepairStartToLogs(json_store_); | |
| // If we are in the RMA process: | |
| // 1. Disable cellular to prevent accidentally using it. | |
| // 2. Start monitoring test files if we are running in test mode. | |
| // TODO(chenghan): Disable cellular in a separate thread to shorten the | |
| // response time. | |
| if (current_state_case_ != RmadState::STATE_NOT_SET) { | |
| if (ComponentsWithIdentifier components; | |
| runtime_probe_client_->ProbeCategories({RMAD_COMPONENT_CELLULAR}, false, | |
| &components) && | |
| components.size() > 0) { | |
| DLOG(INFO) << "Disabling cellular network"; | |
| CHECK(shill_client_->DisableCellular()); | |
| } | |
| } | |
| return true; | |
| } | |
| RmadErrorCode RmadInterfaceImpl::GetInitializedStateHandler( | |
| RmadState::StateCase state_case, | |
| scoped_refptr<BaseStateHandler>* state_handler) const { | |
| auto handler = state_handler_manager_->GetStateHandler(state_case); | |
| if (!handler) { | |
| LOG(ERROR) << "No registered state handler for state " << state_case; | |
| return RMAD_ERROR_STATE_HANDLER_MISSING; | |
| } | |
| if (RmadErrorCode init_error = handler->InitializeState(); | |
| init_error != RMAD_ERROR_OK) { | |
| LOG(ERROR) << "Failed to initialize current state " << state_case; | |
| return init_error; | |
| } | |
| *state_handler = handler; | |
| return RMAD_ERROR_OK; | |
| } | |
| void RmadInterfaceImpl::TryTransitionNextStateFromCurrentState() { | |
| DLOG(INFO) << "Trying a state transition using current state"; | |
| TransitionNextStateInternal(TransitionNextStateRequest(), true); | |
| } | |
| void RmadInterfaceImpl::GetCurrentState(GetStateCallback callback) { | |
| GetStateReply reply = GetCurrentStateInternal(); | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| GetStateReply RmadInterfaceImpl::GetCurrentStateInternal() { | |
| GetStateReply reply; | |
| scoped_refptr<BaseStateHandler> state_handler; | |
| if (current_state_case_ == RmadState::STATE_NOT_SET) { | |
| reply.set_error(RMAD_ERROR_RMA_NOT_REQUIRED); | |
| } else if (RmadErrorCode error = GetInitializedStateHandler( | |
| current_state_case_, &state_handler); | |
| error != RMAD_ERROR_OK) { | |
| reply.set_error(error); | |
| } else { | |
| DLOG(INFO) << "Get current state succeeded: " << current_state_case_; | |
| reply.set_error(RMAD_ERROR_OK); | |
| reply.set_allocated_state(new RmadState(state_handler->GetState(true))); | |
| reply.set_can_go_back(CanGoBack()); | |
| reply.set_can_abort(CanAbort()); | |
| } | |
| return reply; | |
| } | |
| void RmadInterfaceImpl::TransitionNextState( | |
| const TransitionNextStateRequest& request, GetStateCallback callback) { | |
| GetStateReply reply = TransitionNextStateInternal(request, false); | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| GetStateReply RmadInterfaceImpl::TransitionNextStateInternal( | |
| const TransitionNextStateRequest& request, bool try_at_boot) { | |
| GetStateReply reply; | |
| if (current_state_case_ == RmadState::STATE_NOT_SET) { | |
| reply.set_error(RMAD_ERROR_RMA_NOT_REQUIRED); | |
| return reply; | |
| } | |
| scoped_refptr<BaseStateHandler> current_state_handler, next_state_handler; | |
| if (RmadErrorCode error = GetInitializedStateHandler(current_state_case_, | |
| ¤t_state_handler); | |
| error != RMAD_ERROR_OK) { | |
| LOG(ERROR) << "Current state initialization failed"; | |
| reply.set_error(error); | |
| return reply; | |
| } | |
| // Initialize the default reply. | |
| reply.set_error(RMAD_ERROR_NOT_SET); | |
| reply.set_allocated_state(new RmadState(current_state_handler->GetState())); | |
| reply.set_can_go_back(CanGoBack()); | |
| reply.set_can_abort(CanAbort()); | |
| auto [next_state_case_error, next_state_case] = | |
| try_at_boot ? current_state_handler->TryGetNextStateCaseAtBoot() | |
| : current_state_handler->GetNextStateCase(request.state()); | |
| if (next_state_case == current_state_case_) { | |
| DLOG(INFO) << "Transitioning to next state rejected by state " | |
| << current_state_case_; | |
| // Staying at the same state. Run it again. | |
| current_state_handler->RunState(); | |
| reply.set_error(next_state_case_error); | |
| return reply; | |
| } | |
| CHECK(next_state_case_error == RMAD_ERROR_OK) | |
| << "State transition should not happen with errors."; | |
| if (RmadErrorCode error = | |
| GetInitializedStateHandler(next_state_case, &next_state_handler); | |
| error != RMAD_ERROR_OK) { | |
| // Staying at the same state. Run it again. | |
| current_state_handler->RunState(); | |
| reply.set_error(error); | |
| return reply; | |
| } | |
| // Transition to next state. | |
| DLOG(INFO) << "Transition to next state succeeded: from " | |
| << current_state_case_ << " to " << next_state_case; | |
| current_state_handler->CleanUpState(); | |
| // Append next state to stack. | |
| state_history_.push_back(next_state_case); | |
| if (!StoreStateHistory()) { | |
| // TODO(chenghan): Add error replies when failed to write |json_store_|. | |
| LOG(ERROR) << "Could not store history"; | |
| } | |
| // Update state metrics. | |
| if (!MetricsUtils::UpdateStateMetricsOnStateTransition( | |
| json_store_, current_state_case_, next_state_case, | |
| base::Time::Now().InSecondsFSinceUnixEpoch())) { | |
| // TODO(genechang): Add error replies when failed to update state metrics | |
| // in |json_store| -> |metrics| -> |state_metrics|. | |
| LOG(ERROR) << "Could not update state metrics."; | |
| } | |
| // Append to logs. | |
| if (!RecordStateTransitionToLogs(json_store_, current_state_case_, | |
| next_state_case)) { | |
| LOG(ERROR) << "Could not add state transition to logs."; | |
| } | |
| // Update state and run it. | |
| current_state_case_ = next_state_case; | |
| next_state_handler->RunState(); | |
| // This is a one-way transition. |can_abort| cannot go from false to | |
| // true, unless we restart the whole RMA process. | |
| can_abort_ &= next_state_handler->IsRepeatable(); | |
| reply.set_error(RMAD_ERROR_OK); | |
| reply.set_allocated_state(new RmadState(next_state_handler->GetState(true))); | |
| reply.set_can_go_back(CanGoBack()); | |
| reply.set_can_abort(CanAbort()); | |
| return reply; | |
| } | |
| void RmadInterfaceImpl::TransitionPreviousState(GetStateCallback callback) { | |
| GetStateReply reply = TransitionPreviousStateInternal(); | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| GetStateReply RmadInterfaceImpl::TransitionPreviousStateInternal() { | |
| GetStateReply reply; | |
| if (current_state_case_ == RmadState::STATE_NOT_SET) { | |
| reply.set_error(RMAD_ERROR_RMA_NOT_REQUIRED); | |
| return reply; | |
| } | |
| scoped_refptr<BaseStateHandler> current_state_handler, prev_state_handler; | |
| if (RmadErrorCode error = GetInitializedStateHandler(current_state_case_, | |
| ¤t_state_handler); | |
| error != RMAD_ERROR_OK) { | |
| LOG(ERROR) << "Current state initialization failed"; | |
| reply.set_error(error); | |
| return reply; | |
| } | |
| // Initialize the default reply. | |
| reply.set_error(RMAD_ERROR_NOT_SET); | |
| reply.set_allocated_state(new RmadState(current_state_handler->GetState())); | |
| reply.set_can_go_back(CanGoBack()); | |
| reply.set_can_abort(CanAbort()); | |
| if (!CanGoBack()) { | |
| DLOG(INFO) << "Cannot go back to previous state"; | |
| // Staying at the same state. Run it again. | |
| current_state_handler->RunState(); | |
| reply.set_error(RMAD_ERROR_TRANSITION_FAILED); | |
| return reply; | |
| } | |
| RmadState::StateCase prev_state_case = *std::prev(state_history_.end(), 2); | |
| if (RmadErrorCode error = | |
| GetInitializedStateHandler(prev_state_case, &prev_state_handler); | |
| error != RMAD_ERROR_OK) { | |
| // Staying at the same state. Run it again. | |
| current_state_handler->RunState(); | |
| reply.set_error(error); | |
| return reply; | |
| } | |
| // Transition to previous state. | |
| DLOG(INFO) << "Transition to previous state succeeded: from " | |
| << current_state_case_ << " to " << prev_state_case; | |
| current_state_handler->CleanUpState(); | |
| // Remove current state from stack. | |
| state_history_.pop_back(); | |
| if (!StoreStateHistory()) { | |
| LOG(ERROR) << "Could not store history"; | |
| } | |
| // Update state metrics. | |
| if (!MetricsUtils::UpdateStateMetricsOnStateTransition( | |
| json_store_, current_state_case_, prev_state_case, | |
| base::Time::Now().InSecondsFSinceUnixEpoch())) { | |
| // TODO(genechang): Add error replies when failed to update state metrics | |
| // in |json_store| -> |metrics| -> |state_metrics|. | |
| LOG(ERROR) << "Could not update state metrics."; | |
| } | |
| // Append to logs. | |
| if (!RecordStateTransitionToLogs(json_store_, current_state_case_, | |
| prev_state_case)) { | |
| LOG(ERROR) << "Could not add state transition to logs."; | |
| } | |
| // Update state and run it. | |
| current_state_case_ = prev_state_case; | |
| prev_state_handler->RunState(); | |
| reply.set_error(RMAD_ERROR_OK); | |
| reply.set_allocated_state(new RmadState(prev_state_handler->GetState(true))); | |
| reply.set_can_go_back(CanGoBack()); | |
| reply.set_can_abort(CanAbort()); | |
| return reply; | |
| } | |
| void RmadInterfaceImpl::AbortRma(AbortRmaCallback callback) { | |
| AbortRmaReply reply; | |
| if (current_state_case_ == RmadState::STATE_NOT_SET) { | |
| reply.set_error(RMAD_ERROR_RMA_NOT_REQUIRED); | |
| } else if (can_abort_) { | |
| VLOG(1) << "AbortRma: Abort allowed."; | |
| if (!MetricsUtils::UpdateStateMetricsOnAbort( | |
| json_store_, current_state_case_, | |
| base::Time::Now().InSecondsFSinceUnixEpoch())) { | |
| // TODO(genechang): Add error replies when failed to update state metrics | |
| // in |json_store| -> |metrics| -> |state_metrics|. | |
| LOG(ERROR) << "AbortRma: Failed to update state metrics."; | |
| } | |
| if (!metrics_utils_->RecordAll(json_store_)) { | |
| // TODO(genechang): Add error replies when failed to record metrics. | |
| LOG(ERROR) << "AbortRma: Failed to generate and record metrics."; | |
| } | |
| if (json_store_->ClearAndDeleteFile()) { | |
| current_state_case_ = RmadState::STATE_NOT_SET; | |
| reply.set_error(RMAD_ERROR_RMA_NOT_REQUIRED); | |
| } else { | |
| LOG(ERROR) << "AbortRma: Failed to clear RMA state file"; | |
| reply.set_error(RMAD_ERROR_ABORT_FAILED); | |
| } | |
| } else { | |
| VLOG(1) << "AbortRma: Failed to abort."; | |
| reply.set_error(RMAD_ERROR_ABORT_FAILED); | |
| } | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| std::string RmadInterfaceImpl::GetSystemLog() const { | |
| std::string system_log; | |
| if (!cmd_utils_->GetOutput({kCroslogCmd, "--identifier=rmad"}, &system_log)) { | |
| return ""; | |
| } | |
| return system_log; | |
| } | |
| bool RmadInterfaceImpl::GetLogString(std::string* log_string) const { | |
| *log_string = GenerateLogsText(json_store_) + kSummaryDivider + | |
| GenerateLogsJson(json_store_) + kSummaryDivider + | |
| GetSystemLog(); | |
| return true; | |
| } | |
| void RmadInterfaceImpl::GetLog(GetLogCallback callback) { | |
| GetLogReply reply; | |
| if (std::string log_string; GetLogString(&log_string)) { | |
| reply.set_error(RMAD_ERROR_OK); | |
| reply.set_log(log_string); | |
| if (!MetricsUtils::UpdateStateMetricsOnGetLog(json_store_, | |
| current_state_case_)) { | |
| // TODO(genechang): Add error replies when failed to update state metrics | |
| // in |json_store| -> |metrics| -> |state_metrics|. | |
| LOG(ERROR) << "GetLog: Failed to update state metrics."; | |
| } | |
| } else { | |
| LOG(ERROR) << "Failed to generate logs"; | |
| reply.set_error(RMAD_ERROR_CANNOT_GET_LOG); | |
| } | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| void RmadInterfaceImpl::SaveLog(const std::string& diagnostics_log, | |
| SaveLogCallback callback) { | |
| const std::string text_log = GenerateLogsText(json_store_); | |
| const std::string json_log = GenerateLogsJson(json_store_); | |
| const std::string system_log = GetSystemLog(); | |
| if (text_log.empty() || json_log.empty() || system_log.empty()) { | |
| // Failed to generate logs. | |
| SaveLogReply reply; | |
| reply.set_error(RMAD_ERROR_CANNOT_GET_LOG); | |
| ReplyCallback(std::move(callback), reply); | |
| return; | |
| } | |
| RunRpcWithRemovableBlockDevices( | |
| std::move(callback), | |
| base::BindRepeating(&RmadInterfaceImpl::SaveLogRpc, | |
| base::Unretained(this), text_log, json_log, | |
| system_log, diagnostics_log), | |
| base::BindRepeating(&HasOptionalValue<std::string>), | |
| base::BindOnce(&RmadInterfaceImpl::SaveLogSuccessHandler, | |
| base::Unretained(this)), | |
| base::BindOnce(&RmadInterfaceImpl::SaveLogFailHandler, | |
| base::Unretained(this))); | |
| } | |
| void RmadInterfaceImpl::SaveLogRpc( | |
| const std::string& text_log, | |
| const std::string& json_log, | |
| const std::string& system_log, | |
| const std::string& diagnostics_log, | |
| uint8_t device_id, | |
| RpcCallbackType<const std::optional<std::string>&> callback) { | |
| daemon_callback_->GetExecuteMountAndWriteLogCallback().Run( | |
| static_cast<uint8_t>(device_id), text_log, json_log, system_log, | |
| diagnostics_log, std::move(callback)); | |
| } | |
| void RmadInterfaceImpl::SaveLogSuccessHandler( | |
| ReplyCallbackType<SaveLogReply> callback, | |
| const std::optional<std::string>& file_name) { | |
| CHECK(file_name.has_value()); | |
| if (!MetricsUtils::UpdateStateMetricsOnSaveLog(json_store_, | |
| current_state_case_)) { | |
| // TODO(genechang): Add error replies when failed to update state metrics | |
| // in |json_store| -> |metrics| -> |state_metrics|. | |
| LOG(ERROR) << "SaveLog: Failed to update state metrics."; | |
| } | |
| SaveLogReply reply; | |
| reply.set_error(RMAD_ERROR_OK); | |
| reply.set_save_path(file_name.value()); | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| void RmadInterfaceImpl::SaveLogFailHandler( | |
| ReplyCallbackType<SaveLogReply> callback) { | |
| SaveLogReply reply; | |
| reply.set_error(RMAD_ERROR_CANNOT_SAVE_LOG); | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| void RmadInterfaceImpl::RecordBrowserActionMetric( | |
| const RecordBrowserActionMetricRequest& browser_action, | |
| RecordBrowserActionMetricCallback callback) { | |
| std::vector<std::string> additional_activities; | |
| // Ignore the return value, since it may not have been set yet. | |
| MetricsUtils::GetMetricsValue(json_store_, kMetricsAdditionalActivities, | |
| &additional_activities); | |
| // TODO(genechang): Add a table to map all actions to metrics to simplify it. | |
| if (browser_action.diagnostics()) { | |
| additional_activities.push_back( | |
| AdditionalActivity_Name(RMAD_ADDITIONAL_ACTIVITY_DIAGNOSTICS)); | |
| } | |
| if (browser_action.os_update()) { | |
| additional_activities.push_back( | |
| AdditionalActivity_Name(RMAD_ADDITIONAL_ACTIVITY_OS_UPDATE)); | |
| } | |
| RecordBrowserActionMetricReply reply; | |
| if (MetricsUtils::SetMetricsValue(json_store_, kMetricsAdditionalActivities, | |
| additional_activities)) { | |
| reply.set_error(RMAD_ERROR_OK); | |
| } else { | |
| reply.set_error(RMAD_ERROR_CANNOT_RECORD_BROWSER_ACTION); | |
| } | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| void RmadInterfaceImpl::ExtractExternalDiagnosticsApp( | |
| ExtractExternalDiagnosticsAppCallback callback) { | |
| RunRpcWithRemovableBlockDevices( | |
| std::move(callback), | |
| base::BindRepeating(&RmadInterfaceImpl::ExtractExternalDiagnosticsAppRpc, | |
| base::Unretained(this)), | |
| base::BindRepeating(&HasOptionalValue<DiagnosticsAppInfo>), | |
| base::BindOnce(&RmadInterfaceImpl::SetDiagnosticsAppNameHandler, | |
| base::Unretained(this)), | |
| base::BindOnce(&RmadInterfaceImpl::SetDiagnosticsAppNotFoundHandler, | |
| base::Unretained(this))); | |
| } | |
| void RmadInterfaceImpl::ExtractExternalDiagnosticsAppRpc( | |
| uint8_t device_id, | |
| RpcCallbackType<const std::optional<DiagnosticsAppInfo>&> callback) { | |
| daemon_callback_->GetExecuteMountAndCopyDiagnosticsAppCallback().Run( | |
| device_id, std::move(callback)); | |
| } | |
| void RmadInterfaceImpl::SetDiagnosticsAppNameHandler( | |
| ReplyCallbackType<ExtractExternalDiagnosticsAppReply> callback, | |
| const std::optional<DiagnosticsAppInfo>& info) { | |
| CHECK(info.has_value()); | |
| ExtractExternalDiagnosticsAppReply reply; | |
| reply.set_error(RMAD_ERROR_OK); | |
| reply.set_diagnostics_app_swbn_path(info.value().swbn_path); | |
| reply.set_diagnostics_app_crx_path(info.value().crx_path); | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| void RmadInterfaceImpl::SetDiagnosticsAppNotFoundHandler( | |
| ReplyCallbackType<ExtractExternalDiagnosticsAppReply> callback) { | |
| ExtractExternalDiagnosticsAppReply reply; | |
| reply.set_error(RMAD_ERROR_DIAGNOSTICS_APP_NOT_FOUND); | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| void RmadInterfaceImpl::InstallExtractedDiagnosticsApp( | |
| InstallExtractedDiagnosticsAppCallback callback) { | |
| const base::FilePath from_swbn_path = | |
| working_dir_path_.Append(kDiagnosticsAppSwbnRelPath); | |
| const base::FilePath from_crx_path = | |
| working_dir_path_.Append(kDiagnosticsAppCrxRelPath); | |
| const base::FilePath to_swbn_path = | |
| unencrypted_rma_dir_path_.Append(kDiagnosticsAppSwbnRelPath); | |
| const base::FilePath to_crx_path = | |
| unencrypted_rma_dir_path_.Append(kDiagnosticsAppCrxRelPath); | |
| InstallExtractedDiagnosticsAppReply reply; | |
| if (base::PathExists(from_swbn_path) && base::PathExists(from_crx_path)) { | |
| base::CopyFile(from_swbn_path, to_swbn_path); | |
| base::CopyFile(from_crx_path, to_crx_path); | |
| reply.set_error(RMAD_ERROR_OK); | |
| } else { | |
| reply.set_error(RMAD_ERROR_DIAGNOSTICS_APP_NOT_FOUND); | |
| } | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| void RmadInterfaceImpl::GetInstalledDiagnosticsApp( | |
| GetInstalledDiagnosticsAppCallback callback) { | |
| const base::FilePath swbn_path = | |
| unencrypted_rma_dir_path_.Append(kDiagnosticsAppSwbnRelPath); | |
| const base::FilePath crx_path = | |
| unencrypted_rma_dir_path_.Append(kDiagnosticsAppCrxRelPath); | |
| GetInstalledDiagnosticsAppReply reply; | |
| if (base::PathExists(swbn_path) && base::PathExists(crx_path)) { | |
| reply.set_error(RMAD_ERROR_OK); | |
| reply.set_diagnostics_app_swbn_path(swbn_path.value()); | |
| reply.set_diagnostics_app_crx_path(crx_path.value()); | |
| } else { | |
| reply.set_error(RMAD_ERROR_DIAGNOSTICS_APP_NOT_FOUND); | |
| } | |
| ReplyCallback(std::move(callback), reply); | |
| } | |
| bool RmadInterfaceImpl::CanGoBack() const { | |
| if (state_history_.size() > 1) { | |
| const auto current_state_handler = | |
| state_handler_manager_->GetStateHandler(state_history_.back()); | |
| const auto prev_state_handler = state_handler_manager_->GetStateHandler( | |
| *std::prev(state_history_.end(), 2)); | |
| CHECK(current_state_handler); | |
| CHECK(prev_state_handler); | |
| return (current_state_handler->IsRepeatable() && | |
| prev_state_handler->IsRepeatable()); | |
| } | |
| return false; | |
| } | |
| std::list<uint8_t> RmadInterfaceImpl::GetUniqueRemovableBlockDeviceIds() const { | |
| std::list<uint8_t> device_id_list; | |
| std::set<uint8_t> device_id_set; | |
| for (const auto& device : udev_utils_->EnumerateBlockDevices()) { | |
| if (char device_id; | |
| device->IsRemovable() && | |
| GetDeviceIdFromDeviceNode(device->GetDeviceNode(), &device_id) && | |
| device_id_set.find(device_id) == device_id_set.end()) { | |
| device_id_list.push_back(device_id); | |
| device_id_set.insert(device_id); | |
| } | |
| } | |
| return device_id_list; | |
| } | |
| template <typename ReplyProtobufType, typename... RpcReplyTypes> | |
| void RmadInterfaceImpl::RunRpcWithRemovableBlockDevices( | |
| ReplyCallbackType<ReplyProtobufType> callback, | |
| base::RepeatingCallback<void(uint8_t, RpcCallbackType<RpcReplyTypes...>)> | |
| rpc, | |
| base::RepeatingCallback<bool(RpcReplyTypes...)> rpc_output_checker, | |
| base::OnceCallback<void(ReplyCallbackType<ReplyProtobufType>, | |
| RpcReplyTypes...)> success_callback, | |
| base::OnceCallback<void(ReplyCallbackType<ReplyProtobufType>)> | |
| fail_callback) { | |
| std::list<uint8_t> device_ids = GetUniqueRemovableBlockDeviceIds(); | |
| if (device_ids.empty()) { | |
| // No detected external storage. | |
| ReplyProtobufType reply; | |
| reply.set_error(RMAD_ERROR_USB_NOT_FOUND); | |
| ReplyCallback(std::move(callback), reply); | |
| return; | |
| } | |
| RunRpcWithInputs(std::move(callback), rpc, std::move(device_ids), | |
| rpc_output_checker, std::move(success_callback), | |
| std::move(fail_callback)); | |
| } | |
| } // namespace rmad |