blob: 9de6912e29d3439968d8f00c1ff6735b6ed8fe9d [file] [log] [blame]
// Copyright 2022 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/executor/executor.h"
#include <sys/types.h>
#include <unistd.h>
#include <cctype>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/functional/bind.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <brillo/asynchronous_signal_handler.h>
#include <brillo/file_utils.h>
#include "rmad/constants.h"
#include "rmad/executor/mojom/executor.mojom.h"
#include "rmad/executor/mount.h"
#include "rmad/utils/cmd_utils_impl.h"
#include "rmad/utils/crossystem_utils.h"
#include "rmad/utils/crossystem_utils_impl.h"
#include "rmad/utils/ec_utils_impl.h"
#include "rmad/utils/futility_utils_impl.h"
namespace {
// The executor process runs in a light sandbox with /tmp mounted as tmpfs.
constexpr char kTmpPath[] = "/tmp";
constexpr char kDevicePathFormat[] = "/dev/sd%c%d";
constexpr char kSourceFirmwareUpdaterRelPath[] =
"usr/sbin/chromeos-firmwareupdate";
constexpr char kTargetFirmwareUpdaterAbsPath[] =
"/var/lib/rmad/chromeos-firmwareupdate";
constexpr char kSourceDiagnosticsAppSwbnRelPath[] = "diagnostics_app.swbn";
constexpr char kSourceDiagnosticsAppCrxRelPath[] = "diagnostics_app.crx";
constexpr char kTargetDiagnosticsAppSwbnAbsPath[] =
"/var/lib/rmad/diagnostics_app.swbn";
constexpr char kTargetDiagnosticsAppCrxAbsPath[] =
"/var/lib/rmad/diagnostics_app.crx";
// chronos uid and gid.
constexpr uid_t kChronosUid = 1000;
constexpr gid_t kChronosGid = 1000;
// Partition for stateful partition.
constexpr int kStatefulPartitionIndex = 1;
// Partition for rootfs A in a ChromeOS image. We don't check rootfs B.
constexpr int kRootfsPartitionIndex = 3;
// Log file format.
constexpr char kDirectoryNameFormat[] = "rma-logs-%s";
constexpr char kTextLogFilename[] = "text-log.txt";
constexpr char kJsonLogFilename[] = "json-log.json";
constexpr char kSystemLogFilename[] = "system-log.txt";
// Supported file systems for stateful partition.
const std::vector<std::string> kStatefulFileSystems = {"vfat", "ext4", "ext3",
"ext2"};
// Powerwash related constants.
constexpr char kPowerwashRequestFilePath[] =
"/mnt/stateful_partition/factory_install_reset";
constexpr char kRmaPowerwashArgs[] = "fast safe keepimg rma factory";
// Reset FpMCU entropy.
constexpr char kDefaultBioWashPath[] = "/usr/bin/bio_wash";
std::string FormatTime(const base::Time& time) {
base::Time::Exploded e;
time.UTCExplode(&e);
// ISO 8601 format.
return base::StringPrintf("%04d%02d%02dT%02d%02d%02dZ", e.year, e.month,
e.day_of_month, e.hour, e.minute, e.second);
}
rmad::Mount TryMount(const base::FilePath& device_file,
const base::FilePath& mount_point,
const std::vector<std::string>& fs_types,
bool read_only) {
for (const std::string& fs_type : fs_types) {
rmad::Mount mount(device_file, mount_point, fs_type, read_only);
if (mount.IsValid()) {
return mount;
}
}
return rmad::Mount();
}
bool CopyAndChown(const base::FilePath& from_path,
const base::FilePath& to_path,
uid_t uid,
gid_t gid) {
if (!base::CopyFile(from_path, to_path)) {
LOG(ERROR) << "Failed to copy " << from_path.value() << " to "
<< to_path.value();
return false;
}
if (chown(to_path.value().c_str(), uid, gid) != 0) {
PLOG(ERROR) << "Failed to chown file " << to_path.value()
<< " with uid = " << uid << " and gid = " << gid;
return false;
}
return true;
}
bool WriteStringToFileAtomic(const base::FilePath& path,
const std::string& data) {
if (!brillo::WriteStringToFile(path, data)) {
LOG(ERROR) << "Failed to write " << path.value();
return false;
}
if (!brillo::SyncFileOrDirectory(path, /*is_directory=*/false,
/*data_sync=*/false)) {
LOG(ERROR) << "Failed to sync " << path.value();
return false;
}
return true;
}
} // namespace
namespace rmad {
Executor::Executor(
mojo::PendingReceiver<chromeos::rmad::mojom::Executor> receiver)
: receiver_{this, std::move(receiver)} {
// Quit the executor when the communication disconnects.
receiver_.set_disconnect_handler(
base::BindOnce([]() { std::exit(EXIT_SUCCESS); }));
ec_utils_ = std::make_unique<EcUtilsImpl>();
cmd_utils_ = std::make_unique<CmdUtilsImpl>();
crossystem_utils_ = std::make_unique<CrosSystemUtilsImpl>();
futility_utils_ = std::make_unique<FutilityUtilsImpl>();
}
void Executor::MountAndWriteLog(uint8_t device_id,
const std::string& text_log,
const std::string& json_log,
const std::string& system_log,
MountAndWriteLogCallback callback) {
// Input argument check.
if (!islower(device_id)) {
std::move(callback).Run(std::nullopt);
return;
}
// Create temporary mount point.
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTmpPath))) {
std::move(callback).Run(std::nullopt);
return;
}
const base::FilePath device_path(base::StringPrintf(
kDevicePathFormat, device_id, kStatefulPartitionIndex));
const base::FilePath mount_point = temp_dir.GetPath();
const Mount mount =
TryMount(device_path, mount_point, kStatefulFileSystems, false);
if (mount.IsValid()) {
const std::string directory_name = base::StringPrintf(
kDirectoryNameFormat, FormatTime(base::Time::Now()).c_str());
const base::FilePath directory_filepath =
mount_point.Append(directory_name);
if (!base::CreateDirectory(directory_filepath)) {
return;
}
const base::FilePath system_log_path =
directory_filepath.Append(kSystemLogFilename);
if (!WriteStringToFileAtomic(system_log_path, system_log)) {
std::move(callback).Run(std::nullopt);
return;
}
const base::FilePath json_log_path =
directory_filepath.Append(kJsonLogFilename);
if (!WriteStringToFileAtomic(json_log_path, json_log)) {
std::move(callback).Run(std::nullopt);
return;
}
const base::FilePath text_log_path =
directory_filepath.Append(kTextLogFilename);
if (!WriteStringToFileAtomic(text_log_path, text_log.c_str())) {
std::move(callback).Run(std::nullopt);
return;
}
// The full log path is not useful because the mount point is a temporary
// directory. Returning the directory containing the logs is enough.
std::move(callback).Run(directory_name);
return;
}
std::move(callback).Run(std::nullopt);
}
void Executor::CopyRootfsFirmwareUpdater(
CopyRootfsFirmwareUpdaterCallback callback) {
const base::FilePath source_updater_path =
base::FilePath{"/"}.Append(kSourceFirmwareUpdaterRelPath);
const base::FilePath target_updater_path(kTargetFirmwareUpdaterAbsPath);
if (base::PathExists(source_updater_path) &&
base::CopyFile(source_updater_path, target_updater_path)) {
brillo::SyncFileOrDirectory(base::FilePath(target_updater_path),
/*is_directory=*/false, /*data_sync=*/false);
std::move(callback).Run(true);
return;
}
std::move(callback).Run(false);
}
void Executor::MountAndCopyFirmwareUpdater(
uint8_t device_id, MountAndCopyFirmwareUpdaterCallback callback) {
// Input argument check.
if (!islower(device_id)) {
std::move(callback).Run(false);
return;
}
// Create temporary mount point.
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTmpPath))) {
std::move(callback).Run(false);
return;
}
const base::FilePath device_path(
base::StringPrintf(kDevicePathFormat, device_id, kRootfsPartitionIndex));
const base::FilePath mount_point = temp_dir.GetPath();
const Mount mount(device_path, mount_point, "ext2", true);
if (mount.IsValid()) {
const base::FilePath source_updater_path =
mount_point.Append(kSourceFirmwareUpdaterRelPath);
const base::FilePath target_updater_path(kTargetFirmwareUpdaterAbsPath);
if (base::PathExists(source_updater_path) &&
base::CopyFile(source_updater_path, target_updater_path)) {
brillo::SyncFileOrDirectory(base::FilePath(target_updater_path),
/*is_directory=*/false, /*data_sync=*/false);
std::move(callback).Run(true);
return;
}
}
std::move(callback).Run(false);
}
void Executor::MountAndCopyDiagnosticsApp(
uint8_t device_id, MountAndCopyDiagnosticsAppCallback callback) {
// Input argument check.
if (!islower(device_id)) {
std::move(callback).Run(nullptr);
return;
}
// Create temporary mount point.
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTmpPath))) {
std::move(callback).Run(nullptr);
return;
}
const base::FilePath device_path(base::StringPrintf(
kDevicePathFormat, device_id, kStatefulPartitionIndex));
const base::FilePath mount_point = temp_dir.GetPath();
const Mount mount = TryMount(device_path, mount_point, kStatefulFileSystems,
/*read_only=*/true);
if (mount.IsValid()) {
const base::FilePath from_swbn_path =
mount_point.Append(kSourceDiagnosticsAppSwbnRelPath);
const base::FilePath from_crx_path =
mount_point.Append(kSourceDiagnosticsAppCrxRelPath);
const base::FilePath to_swbn_path(kTargetDiagnosticsAppSwbnAbsPath);
const base::FilePath to_crx_path(kTargetDiagnosticsAppCrxAbsPath);
if (base::PathExists(from_swbn_path) && base::PathExists(from_crx_path) &&
CopyAndChown(from_swbn_path, to_swbn_path, kChronosUid, kChronosGid) &&
CopyAndChown(from_crx_path, to_crx_path, kChronosUid, kChronosGid)) {
// Send out the reply first.
auto info = chromeos::rmad::mojom::DiagnosticsAppInfo::New(
to_swbn_path.value(), to_crx_path.value());
std::move(callback).Run(std::move(info));
// Then sync the files.
if (!brillo::SyncFileOrDirectory(to_swbn_path, /*is_directory=*/false,
/*data_sync=*/false)) {
LOG(ERROR) << "Failed to sync " << to_swbn_path.value();
}
if (!brillo::SyncFileOrDirectory(to_crx_path, /*is_directory=*/false,
/*data_sync=*/false)) {
LOG(ERROR) << "Failed to sync " << to_crx_path.value();
}
return;
}
}
std::move(callback).Run(nullptr);
}
void Executor::RebootEc(RebootEcCallback callback) {
std::move(callback).Run(ec_utils_->Reboot());
}
void Executor::RequestRmaPowerwash(RequestRmaPowerwashCallback callback) {
const base::FilePath powerwash_file_path(kPowerwashRequestFilePath);
if (!WriteStringToFileAtomic(powerwash_file_path, kRmaPowerwashArgs)) {
std::move(callback).Run(false);
return;
}
std::move(callback).Run(true);
}
void Executor::RequestBatteryCutoff(RequestBatteryCutoffCallback callback) {
if (!crossystem_utils_->SetInt(CrosSystemUtils::kBatteryCutoffRequestProperty,
1)) {
std::move(callback).Run(false);
return;
}
std::move(callback).Run(true);
}
void Executor::ResetFpmcuEntropy(ResetFpmcuEntropyCallback callback) {
if (std::string output;
!cmd_utils_->GetOutputAndError({kDefaultBioWashPath}, &output)) {
LOG(ERROR) << "Failed to reset FpMCU entropy with: " << kDefaultBioWashPath;
LOG(ERROR) << output;
std::move(callback).Run(false);
return;
}
std::move(callback).Run(true);
}
void Executor::GetFlashInfo(GetFlashInfoCallback callback) {
auto info = futility_utils_->GetFlashInfo();
if (!info.has_value()) {
std::move(callback).Run(nullptr);
}
auto result = chromeos::rmad::mojom::FlashInfo::New(info.value().flash_name,
info.value().wpsr_start,
info.value().wpsr_length);
std::move(callback).Run(std::move(result));
}
void Executor::PreseedRmaState(PreseedRmaStateCallback callback) {
sync();
// Preseed rmad state file so it can be preserved across TPM reset.
if (std::string output;
!cmd_utils_->GetOutputAndError({kPreserveRmaStateHelperPath}, &output)) {
LOG(ERROR)
<< "Failed to call preserve_rma_state for preserving rma state file: "
<< output;
std::move(callback).Run(false);
return;
}
std::move(callback).Run(true);
}
} // namespace rmad