blob: cf2987e45ffc4307bf670107e3fe3e8effc5d893 [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modemfwd/journal.h"
#include <optional>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/types/optional_util.h>
#include <base/unguessable_token.h>
#include <brillo/proto_file_io.h>
#include <chromeos/switches/modemfwd_switches.h>
#include "modemfwd/firmware_file.h"
#include "modemfwd/logging.h"
#include "modemfwd/modem_helper.h"
#include "modemfwd/proto_bindings/journal_entry.pb.h"
#include "modemfwd/scoped_temp_file.h"
namespace modemfwd {
namespace {
std::string JournalTypeToFirmwareType(int t) {
switch (t) {
case JournalEntryType::MAIN:
return modemfwd::kFwMain;
case JournalEntryType::CARRIER:
return modemfwd::kFwCarrier;
case JournalEntryType::OEM:
return modemfwd::kFwOem;
default:
return std::string();
}
}
JournalEntryType FirmwareTypeToJournalType(std::string fw_type) {
if (fw_type == modemfwd::kFwMain)
return JournalEntryType::MAIN;
else if (fw_type == modemfwd::kFwCarrier)
return JournalEntryType::CARRIER;
else if (fw_type == modemfwd::kFwOem)
return JournalEntryType::OEM;
else
return JournalEntryType::UNKNOWN;
}
struct JournalEntryWithId {
std::string id;
JournalEntry entry;
};
// Returns true if the operation was restarted successfully or false if it
// failed.
bool RestartOperation(const JournalEntry& entry,
FirmwareDirectory* firmware_dir,
ModemHelperDirectory* helper_dir) {
ModemHelper* helper = helper_dir->GetHelperForDeviceId(entry.device_id());
if (!helper) {
LOG(ERROR) << "Journal contained unfinished operation for device with ID \""
<< entry.device_id()
<< "\" but no helper was found to restart it";
return false;
}
std::string carrier_id(entry.carrier_id());
FirmwareDirectory::Files res = firmware_dir->FindFirmware(
entry.device_id(), carrier_id.empty() ? nullptr : &carrier_id);
std::vector<FirmwareConfig> flashed_fw;
std::vector<std::string> paths_for_logging;
// Keep a reference to all temporary uncompressed files.
std::vector<std::unique_ptr<FirmwareFile>> all_files;
for (const auto& entry_type : entry.type()) {
std::string fw_type = JournalTypeToFirmwareType(entry_type);
FirmwareFileInfo* info = nullptr;
base::FilePath fw_path;
std::string fw_version;
if (fw_type.empty())
continue;
switch (entry_type) {
case JournalEntryType::MAIN:
info = base::OptionalToPtr<FirmwareFileInfo>(res.main_firmware);
break;
case JournalEntryType::CARRIER:
info = base::OptionalToPtr<FirmwareFileInfo>(res.carrier_firmware);
break;
case JournalEntryType::OEM:
info = base::OptionalToPtr<FirmwareFileInfo>(res.oem_firmware);
break;
}
auto firmware_file = std::make_unique<FirmwareFile>();
if (info == nullptr ||
!firmware_file->PrepareFrom(firmware_dir->GetFirmwarePath(), *info)) {
LOG(ERROR) << "Unfinished \"" << fw_type
<< "\" firmware flash for device with ID \""
<< entry.device_id() << "\" but no firmware was found";
continue;
}
flashed_fw.push_back(
{fw_type, firmware_file->path_on_filesystem(), info->version});
paths_for_logging.push_back(firmware_file->path_for_logging().value());
all_files.push_back(std::move(firmware_file));
// Main firmware may also include associated firmware payloads that we will
// simply reflash as well.
if (entry_type == JournalEntryType::MAIN) {
for (const auto& assoc_entry : res.assoc_firmware) {
auto assoc_file = std::make_unique<FirmwareFile>();
if (!assoc_file->PrepareFrom(firmware_dir->GetFirmwarePath(),
assoc_entry.second)) {
LOG(ERROR) << "Unfinished \"" << fw_type
<< "\" firmware flash for device with ID \""
<< entry.device_id() << "\" but no firmware was found";
continue;
}
flashed_fw.push_back({assoc_entry.first,
assoc_file->path_on_filesystem(),
assoc_entry.second.version});
paths_for_logging.push_back(assoc_file->path_for_logging().value());
}
}
}
if (flashed_fw.size() != entry.type_size() || !flashed_fw.size()) {
LOG(ERROR) << "Malformed journal entry with invalid types.";
return false;
}
ELOG(INFO) << "Journal reflashing firmwares: "
<< base::JoinString(paths_for_logging, ",");
return helper->FlashFirmwares(flashed_fw);
}
std::optional<JournalLog> ParseJournal(base::File& journal_file) {
JournalLog log;
if (brillo::ReadTextProtobuf(journal_file.GetPlatformFile(), &log)) {
return log;
}
// Old versions of the journal may have just a single entry in the file.
journal_file.Seek(base::File::FROM_BEGIN, 0);
JournalEntry entry;
if (brillo::ReadTextProtobuf(journal_file.GetPlatformFile(), &entry)) {
*log.add_entry() = entry;
return log;
}
LOG(WARNING) << "Failed to parse journal";
return std::nullopt;
}
class JournalImpl : public Journal {
public:
explicit JournalImpl(const base::FilePath& journal_path)
: journal_path_(journal_path) {}
JournalImpl(const JournalImpl&) = delete;
JournalImpl& operator=(const JournalImpl&) = delete;
std::optional<std::string> MarkStartOfFlashingFirmware(
const std::vector<std::string>& firmware_types,
const std::string& device_id,
const std::string& carrier_id) override {
JournalEntry entry;
entry.set_device_id(device_id);
entry.set_carrier_id(carrier_id);
for (const auto& t : firmware_types) {
entry.add_type(FirmwareTypeToJournalType(t));
}
std::string entry_id = base::UnguessableToken().Create().ToString();
entries_.emplace_back(entry_id, entry);
if (!SerializeJournal()) {
LOG(INFO) << __func__ << ": failed to serialize journal";
return std::nullopt;
}
return entry_id;
}
void MarkEndOfFlashingFirmware(const std::string& entry_id) override {
std::erase_if(
entries_,
[&entry_id](const std::pair<std::string, JournalEntry>& entry) {
return entry_id == entry.first;
});
if (!SerializeJournal()) {
LOG(INFO) << __func__ << ": failed to serialize journal";
}
}
private:
bool SerializeJournal() {
JournalLog log;
for (const auto& entry : entries_) {
*log.add_entry() = entry.second;
}
// Replace the file atomically.
auto temp_file = ScopedTempFile::Create();
if (!temp_file) {
LOG(ERROR) << "Couldn't create temp file";
return false;
}
base::File new_journal(temp_file->path(), base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_WRITE);
if (!brillo::WriteTextProtobuf(new_journal.GetPlatformFile(), log)) {
LOG(ERROR) << "Couldn't write new journal to temp file";
return false;
}
if (!base::Move(temp_file->path(), journal_path_)) {
LOG(ERROR) << "Couldn't replace journal file";
return false;
}
return true;
}
std::vector<std::pair<std::string, JournalEntry>> entries_;
base::FilePath journal_path_;
};
} // namespace
std::unique_ptr<Journal> OpenJournal(const base::FilePath& journal_path,
FirmwareDirectory* firmware_dir,
ModemHelperDirectory* helper_dir) {
base::File journal_file(journal_path, base::File::FLAG_OPEN_ALWAYS |
base::File::FLAG_READ |
base::File::FLAG_WRITE);
if (!journal_file.IsValid()) {
LOG(ERROR) << "Could not open journal file";
return nullptr;
}
// Check to see if we have uncommitted operations to restart.
JournalLog entries_to_restart;
if (journal_file.GetLength() != 0) {
std::optional<JournalLog> log = ParseJournal(journal_file);
if (log.has_value()) {
entries_to_restart = *log;
}
}
for (const auto& entry : entries_to_restart.entry()) {
if (!RestartOperation(entry, firmware_dir, helper_dir)) {
LOG(ERROR) << "Failed to restart uncommitted operation";
// Note that we don't stop here; we will try to commit every
// operation in the journal.
}
}
// Clearing the journal prevents it from growing without bound but also
// ensures that if we crash after this point, we won't try to restart
// any operations an extra time.
journal_file.SetLength(0);
journal_file.Seek(base::File::FROM_BEGIN, 0);
journal_file.Flush();
return std::make_unique<JournalImpl>(journal_path);
}
} // namespace modemfwd