blob: 7ddbf741ca3d20e4772f0d923f7ee24ad84fdc3a [file] [log] [blame]
// Copyright 2018 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 "modemfwd/journal.h"
#include <utility>
#include <vector>
#include <base/files/file.h>
#include <base/strings/string_split.h>
#include <brillo/proto_file_io.h>
#include "modemfwd/logging.h"
#include "modemfwd/modem_helper.h"
#include "modemfwd/proto_bindings/journal_entry.pb.h"
namespace modemfwd {
namespace {
// 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;
}
if (!JournalEntryType_IsValid(entry.type())) {
LOG(ERROR) << "Malformed journal entry with type "
<< JournalEntryType_Name(entry.type());
return false;
}
std::string carrier_id(entry.carrier_id());
FirmwareDirectory::Files res = firmware_dir->FindFirmware(
entry.device_id(), carrier_id.empty() ? nullptr : &carrier_id);
if (entry.type() == JournalEntryType::CARRIER ||
(entry.type() == JournalEntryType::UNKNOWN && !carrier_id.empty())) {
if (!res.carrier_firmware.has_value()) {
LOG(ERROR) << "Unfinished carrier firmware flash for device with ID \""
<< entry.device_id() << "\" but no firmware was found";
return false;
}
const FirmwareFileInfo& firmware_file = res.carrier_firmware.value();
ELOG(INFO) << "Journal reflashing carrier firmware "
<< firmware_file.firmware_path.value();
return helper->FlashCarrierFirmware(firmware_file.firmware_path,
firmware_file.version);
}
DCHECK(entry.type() == JournalEntryType::MAIN ||
(entry.type() == JournalEntryType::UNKNOWN && carrier_id.empty()));
if (!res.main_firmware.has_value()) {
LOG(ERROR) << "Unfinished main firmware flash for device with ID \""
<< entry.device_id() << "\" but no firmware was found";
return false;
}
const FirmwareFileInfo& firmware_file = res.main_firmware.value();
ELOG(INFO) << "Journal reflashing main firmware "
<< firmware_file.firmware_path.value();
return helper->FlashMainFirmware(firmware_file.firmware_path,
firmware_file.version);
}
class JournalImpl : public Journal {
public:
explicit JournalImpl(base::File journal_file)
: journal_file_(std::move(journal_file)) {
// 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.
ClearJournalFile();
}
void MarkStartOfFlashingMainFirmware(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);
entry.set_type(JournalEntryType::MAIN);
WriteJournalEntry(entry);
}
void MarkEndOfFlashingMainFirmware(const std::string& device_id,
const std::string& carrier_id) override {
JournalEntry entry;
if (!ReadJournalEntry(&entry)) {
LOG(ERROR) << __func__ << ": no journal entry to commit";
return;
}
if (entry.device_id() != device_id || entry.carrier_id() != carrier_id ||
entry.type() != JournalEntryType::MAIN) {
LOG(ERROR) << __func__ << ": found journal entry, but it didn't match";
return;
}
ClearJournalFile();
}
void MarkStartOfFlashingCarrierFirmware(
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);
entry.set_type(JournalEntryType::CARRIER);
WriteJournalEntry(entry);
}
void MarkEndOfFlashingCarrierFirmware(
const std::string& device_id, const std::string& carrier_id) override {
JournalEntry entry;
if (!ReadJournalEntry(&entry)) {
LOG(ERROR) << __func__ << ": no journal entry to commit";
return;
}
if (entry.device_id() != device_id || entry.carrier_id() != carrier_id ||
entry.type() != JournalEntryType::CARRIER) {
LOG(ERROR) << __func__ << ": found journal entry, but it didn't match";
return;
}
ClearJournalFile();
}
private:
bool ReadJournalEntry(JournalEntry* entry) {
if (journal_file_.GetLength() == 0) {
ELOG(INFO) << "Tried to read from empty journal";
return false;
}
journal_file_.Seek(base::File::FROM_BEGIN, 0);
return brillo::ReadTextProtobuf(journal_file_.GetPlatformFile(), entry);
}
bool WriteJournalEntry(const JournalEntry& entry) {
if (journal_file_.GetLength() > 0) {
ELOG(INFO) << "Tried to write to journal with uncommitted entry";
return false;
}
journal_file_.Seek(base::File::FROM_BEGIN, 0);
return brillo::WriteTextProtobuf(journal_file_.GetPlatformFile(), entry);
}
void ClearJournalFile() {
journal_file_.SetLength(0);
journal_file_.Seek(base::File::FROM_BEGIN, 0);
journal_file_.Flush();
}
base::File journal_file_;
DISALLOW_COPY_AND_ASSIGN(JournalImpl);
};
} // 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;
}
// Restart operations if necessary.
if (journal_file.GetLength() > 0) {
JournalEntry last_entry;
if (brillo::ReadTextProtobuf(journal_file.GetPlatformFile(), &last_entry) &&
!RestartOperation(last_entry, firmware_dir, helper_dir)) {
LOG(ERROR) << "Failed to restart uncommitted operation";
}
}
return std::make_unique<JournalImpl>(std::move(journal_file));
}
} // namespace modemfwd