| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "rmad/state_handler/update_ro_firmware_state_handler.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/notreached.h> |
| #include <base/sequence_checker.h> |
| #include <base/task/task_traits.h> |
| #include <base/task/thread_pool.h> |
| #include <base/threading/sequenced_task_runner_handle.h> |
| #include <brillo/file_utils.h> |
| #include <re2/re2.h> |
| |
| #include "rmad/constants.h" |
| #include "rmad/system/cros_disks_client_impl.h" |
| #include "rmad/system/power_manager_client_impl.h" |
| #include "rmad/utils/cmd_utils_impl.h" |
| #include "rmad/utils/crossystem_utils_impl.h" |
| #include "rmad/utils/dbus_utils.h" |
| #include "rmad/utils/flashrom_utils_impl.h" |
| |
| namespace { |
| |
| constexpr char kFirmwareUpdaterFilePath[] = "usr/sbin/chromeos-firmwareupdate"; |
| |
| bool IsRootfsPartition(const std::string& path) { |
| return re2::RE2::FullMatch(path, R"(/dev/sd[a-z]3)"); |
| } |
| |
| } // namespace |
| |
| namespace rmad { |
| |
| UpdateRoFirmwareStateHandler::UpdateRoFirmwareStateHandler( |
| scoped_refptr<JsonStore> json_store) |
| : BaseStateHandler(json_store), |
| is_mocked_(false), |
| active_(false), |
| update_ro_firmware_status_signal_sender_(base::DoNothing()) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| cmd_utils_ = std::make_unique<CmdUtilsImpl>(); |
| crossystem_utils_ = std::make_unique<CrosSystemUtilsImpl>(); |
| flashrom_utils_ = std::make_unique<FlashromUtilsImpl>(); |
| cros_disks_client_ = std::make_unique<CrosDisksClientImpl>(GetSystemBus()); |
| power_manager_client_ = |
| std::make_unique<PowerManagerClientImpl>(GetSystemBus()); |
| } |
| |
| UpdateRoFirmwareStateHandler::UpdateRoFirmwareStateHandler( |
| scoped_refptr<JsonStore> json_store, |
| std::unique_ptr<CmdUtils> cmd_utils, |
| std::unique_ptr<CrosSystemUtils> crossystem_utils, |
| std::unique_ptr<FlashromUtils> flashrom_utils, |
| std::unique_ptr<CrosDisksClient> cros_disks_client, |
| std::unique_ptr<PowerManagerClient> power_manager_client) |
| : BaseStateHandler(json_store), |
| is_mocked_(true), |
| active_(false), |
| update_ro_firmware_status_signal_sender_(base::DoNothing()), |
| cmd_utils_(std::move(cmd_utils)), |
| crossystem_utils_(std::move(crossystem_utils)), |
| flashrom_utils_(std::move(flashrom_utils)), |
| cros_disks_client_(std::move(cros_disks_client)), |
| power_manager_client_(std::move(power_manager_client)) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| RmadErrorCode UpdateRoFirmwareStateHandler::InitializeState() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Make sure HWWP is off before initializing the state. |
| if (int hwwp_status; |
| !crossystem_utils_->GetHwwpStatus(&hwwp_status) || hwwp_status != 0) { |
| return RMAD_ERROR_WP_ENABLED; |
| } |
| |
| if (!state_.has_update_ro_firmware()) { |
| auto update_ro_firmware = std::make_unique<UpdateRoFirmwareState>(); |
| update_ro_firmware->set_optional(CanSkipUpdate()); |
| state_.set_allocated_update_ro_firmware(update_ro_firmware.release()); |
| |
| sequenced_task_runner_ = base::SequencedTaskRunnerHandle::Get(); |
| updater_task_runner_ = base::ThreadPool::CreateTaskRunner( |
| {base::TaskPriority::BEST_EFFORT, base::MayBlock()}); |
| |
| cros_disks_client_->AddMountCompletedHandler( |
| base::BindRepeating(&UpdateRoFirmwareStateHandler::OnMountCompleted, |
| base::Unretained(this))); |
| } |
| |
| if (bool firmware_updated; |
| json_store_->GetValue(kFirmwareUpdated, &firmware_updated) && |
| firmware_updated) { |
| status_ = RMAD_UPDATE_RO_FIRMWARE_COMPLETE; |
| poll_usb_ = false; |
| } else { |
| status_ = RMAD_UPDATE_RO_FIRMWARE_WAIT_USB; |
| poll_usb_ = true; |
| } |
| active_ = true; |
| return RMAD_ERROR_OK; |
| } |
| |
| void UpdateRoFirmwareStateHandler::RunState() { |
| status_signal_timer_.Start( |
| FROM_HERE, kPollInterval, this, |
| &UpdateRoFirmwareStateHandler::SendFirmwareUpdateStatusSignal); |
| check_usb_timer_.Start(FROM_HERE, kTaskInterval, this, |
| &UpdateRoFirmwareStateHandler::WaitUsb); |
| } |
| |
| void UpdateRoFirmwareStateHandler::CleanUpState() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| active_ = false; |
| if (status_signal_timer_.IsRunning()) { |
| status_signal_timer_.Stop(); |
| } |
| if (check_usb_timer_.IsRunning()) { |
| check_usb_timer_.Stop(); |
| } |
| } |
| |
| BaseStateHandler::GetNextStateCaseReply |
| UpdateRoFirmwareStateHandler::GetNextStateCase(const RmadState& state) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!state.has_update_ro_firmware()) { |
| LOG(ERROR) << "RmadState missing |update RO firmware| state."; |
| return NextStateCaseWrapper(RMAD_ERROR_REQUEST_INVALID); |
| } |
| const UpdateRoFirmwareState& update_ro_firmware = state.update_ro_firmware(); |
| if (update_ro_firmware.choice() == |
| UpdateRoFirmwareState::RMAD_UPDATE_CHOICE_UNKNOWN) { |
| LOG(ERROR) << "RmadState missing |udpate| argument."; |
| return NextStateCaseWrapper(RMAD_ERROR_REQUEST_ARGS_MISSING); |
| } |
| if (!state_.update_ro_firmware().optional() && |
| update_ro_firmware.choice() == |
| UpdateRoFirmwareState::RMAD_UPDATE_CHOICE_SKIP) { |
| LOG(ERROR) << "RO firmware update is mandatory."; |
| return NextStateCaseWrapper(RMAD_ERROR_REQUEST_ARGS_VIOLATION); |
| } |
| |
| switch (state.update_ro_firmware().choice()) { |
| case UpdateRoFirmwareState::RMAD_UPDATE_CHOICE_CONTINUE: |
| if (status_ != RMAD_UPDATE_RO_FIRMWARE_COMPLETE) { |
| return NextStateCaseWrapper(RMAD_ERROR_WAIT); |
| } |
| // Firmware update completed. Same behavior as SKIP. |
| [[fallthrough]]; |
| case UpdateRoFirmwareState::RMAD_UPDATE_CHOICE_SKIP: |
| if (bool mlb_repair; |
| json_store_->GetValue(kMlbRepair, &mlb_repair) && mlb_repair) { |
| return NextStateCaseWrapper(RmadState::StateCase::kRestock); |
| } |
| return NextStateCaseWrapper(RmadState::StateCase::kUpdateDeviceInfo); |
| default: |
| break; |
| } |
| NOTREACHED(); |
| return NextStateCaseWrapper(RmadState::StateCase::STATE_NOT_SET, |
| RMAD_ERROR_NOT_SET, AdditionalActivity::NOTHING); |
| } |
| |
| bool UpdateRoFirmwareStateHandler::CanSkipUpdate() { |
| if (bool firmware_updated; |
| json_store_->GetValue(kFirmwareUpdated, &firmware_updated) && |
| firmware_updated) { |
| return true; |
| } |
| if (bool ro_verified; |
| json_store_->GetValue(kRoFirmwareVerified, &ro_verified) && ro_verified) { |
| return true; |
| } |
| return false; |
| } |
| |
| void UpdateRoFirmwareStateHandler::SendFirmwareUpdateStatusSignal() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| update_ro_firmware_status_signal_sender_.Run(status_); |
| } |
| |
| void UpdateRoFirmwareStateHandler::WaitUsb() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!poll_usb_) { |
| return; |
| } |
| bool found_root_partition = false; |
| if (std::vector<std::string> result; |
| cros_disks_client_->EnumerateDevices(&result) && result.size()) { |
| for (const std::string& device : result) { |
| if (DeviceProperties device_properties; |
| cros_disks_client_->GetDeviceProperties(device, &device_properties)) { |
| if (IsRootfsPartition(device_properties.device_file)) { |
| // Only try to mount the first root partition found. |
| found_root_partition = true; |
| poll_usb_ = false; |
| cros_disks_client_->Mount(device_properties.device_file, "ext2", |
| {"ro"}); |
| break; |
| } |
| } |
| } |
| if (!found_root_partition) { |
| // USB is inserted but no root partition found. Treat as file not found. |
| status_ = RMAD_UPDATE_RO_FIRMWARE_FILE_NOT_FOUND; |
| } |
| } else { |
| // No detected USB. |
| status_ = RMAD_UPDATE_RO_FIRMWARE_WAIT_USB; |
| } |
| } |
| |
| void UpdateRoFirmwareStateHandler::OnMountCompleted( |
| const rmad::MountEntry& entry) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // This callback is active even when we're not in the state, so we need this |
| // check to prevent doing a firmware update in other states. |
| // TODO(chenghan): Figure out how to detach the signal handler. |
| if (!active_) { |
| return; |
| } |
| if (entry.success) { |
| if (IsRootfsPartition(entry.source)) { |
| const base::FilePath firmware_updater_path = |
| base::FilePath(entry.mount_path) |
| .AppendASCII(kFirmwareUpdaterFilePath); |
| if (base::PathExists(firmware_updater_path)) { |
| status_ = RMAD_UPDATE_RO_FIRMWARE_UPDATING; |
| updater_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&UpdateRoFirmwareStateHandler::UpdateFirmware, |
| base::Unretained(this), entry.mount_path, |
| firmware_updater_path.MaybeAsASCII())); |
| // Unmount is done after running the firmware updater. |
| // TODO(chenghan): Copy the firmware updater so we can unmount here. |
| return; |
| } |
| } |
| Unmount(entry.mount_path); |
| } |
| |
| LOG(WARNING) << "Cannot find firmware updater"; |
| status_ = RMAD_UPDATE_RO_FIRMWARE_FILE_NOT_FOUND; |
| poll_usb_ = true; |
| } |
| |
| bool UpdateRoFirmwareStateHandler::RunFirmwareUpdater( |
| const std::string& firmware_updater_path) { |
| // For security reasons, we should only run the firmware update when HWWP and |
| // SWWP are off. |
| |
| // First make sure the state handler is not mocked so the following |
| // HWWP/SWWP checks are real. |
| if (is_mocked_) { |
| LOG(ERROR) << "State handler is mocked. Aborting firmware update."; |
| return false; |
| } |
| |
| // Make sure HWWP is off. |
| if (int hwwp_status; |
| !crossystem_utils_->GetHwwpStatus(&hwwp_status) || hwwp_status != 0) { |
| LOG(ERROR) << "HWWP is enabled. Aborting firmware update."; |
| return false; |
| } |
| |
| // Make sure SWWP is off. |
| if (bool swwp_enabled; |
| !flashrom_utils_->GetSoftwareWriteProtectionStatus(&swwp_enabled) || |
| swwp_enabled) { |
| LOG(ERROR) << "SWWP is enabled. Aborting firmware update."; |
| return false; |
| } |
| |
| // All checks pass. Run the firmware updater. |
| bool update_success = false; |
| std::string output; |
| if (cmd_utils_->GetOutputAndError( |
| {"futility", "update", "-a", firmware_updater_path, "--mode=recovery", |
| "--force"}, |
| &output)) { |
| LOG(INFO) << "Firmware updater success"; |
| update_success = true; |
| } else { |
| LOG(ERROR) << "Firmware updater failed"; |
| } |
| |
| VLOG(1) << "Firmware updater output:"; |
| VLOG(1) << output; |
| return update_success; |
| } |
| |
| void UpdateRoFirmwareStateHandler::UpdateFirmware( |
| const std::string& mount_path, const std::string& firmware_updater_path) { |
| bool update_success = RunFirmwareUpdater(firmware_updater_path); |
| sequenced_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateRoFirmwareStateHandler::Unmount, |
| base::Unretained(this), mount_path)); |
| sequenced_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&UpdateRoFirmwareStateHandler::OnUpdateFinished, |
| base::Unretained(this), update_success)); |
| } |
| |
| void UpdateRoFirmwareStateHandler::Unmount(const std::string& mount_path) { |
| if (uint32_t result; |
| cros_disks_client_->Unmount(mount_path, {}, &result) && result == 0) { |
| VLOG(1) << "Unmount success " << result; |
| } else { |
| LOG(ERROR) << "Unmount failed"; |
| } |
| } |
| |
| void UpdateRoFirmwareStateHandler::OnUpdateFinished(bool update_success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (update_success) { |
| json_store_->SetValue(kFirmwareUpdated, true); |
| status_ = RMAD_UPDATE_RO_FIRMWARE_REBOOTING; |
| PostRebootTask(); |
| } else { |
| status_ = RMAD_UPDATE_RO_FIRMWARE_WAIT_USB; |
| // TODO(chenghan): Emit update failed signal. |
| poll_usb_ = true; |
| } |
| } |
| |
| void UpdateRoFirmwareStateHandler::PostRebootTask() { |
| sequenced_task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&UpdateRoFirmwareStateHandler::Reboot, |
| base::Unretained(this)), |
| kRebootDelay); |
| } |
| |
| void UpdateRoFirmwareStateHandler::Reboot() { |
| if (!power_manager_client_->Restart()) { |
| LOG(ERROR) << "Failed to reboot"; |
| } |
| } |
| |
| namespace fake { |
| |
| FakeUpdateRoFirmwareStateHandler::FakeUpdateRoFirmwareStateHandler( |
| scoped_refptr<JsonStore> json_store) |
| : BaseStateHandler(json_store) {} |
| |
| RmadErrorCode FakeUpdateRoFirmwareStateHandler::InitializeState() { |
| if (!state_.has_update_ro_firmware()) { |
| auto update_ro_firmware = std::make_unique<UpdateRoFirmwareState>(); |
| update_ro_firmware->set_optional(true); |
| state_.set_allocated_update_ro_firmware(update_ro_firmware.release()); |
| } |
| |
| return RMAD_ERROR_OK; |
| } |
| |
| void FakeUpdateRoFirmwareStateHandler::RunState() { |
| status_signal_timer_.Start( |
| FROM_HERE, kPollInterval, this, |
| &FakeUpdateRoFirmwareStateHandler::SendFirmwareUpdateStatusSignal); |
| } |
| |
| void FakeUpdateRoFirmwareStateHandler::CleanUpState() { |
| if (status_signal_timer_.IsRunning()) { |
| status_signal_timer_.Stop(); |
| } |
| } |
| |
| BaseStateHandler::GetNextStateCaseReply |
| FakeUpdateRoFirmwareStateHandler::GetNextStateCase(const RmadState& state) { |
| return NextStateCaseWrapper(RmadState::StateCase::kUpdateDeviceInfo); |
| } |
| |
| void FakeUpdateRoFirmwareStateHandler::SendFirmwareUpdateStatusSignal() { |
| update_ro_firmware_status_signal_sender_.Run( |
| RMAD_UPDATE_RO_FIRMWARE_COMPLETE); |
| } |
| |
| } // namespace fake |
| |
| } // namespace rmad |