| // 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 |