blob: c7eb63375d4062ed520ddbe04115be2d52cc9702 [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 <base/strings/string_util.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"
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();
}
NOTREACHED();
}
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;
}
// 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::OptionalOrNullptr<FirmwareFileInfo>(res.main_firmware);
break;
case JournalEntryType::CARRIER:
info = base::OptionalOrNullptr<FirmwareFileInfo>(res.carrier_firmware);
break;
case JournalEntryType::OEM:
info = base::OptionalOrNullptr<FirmwareFileInfo>(res.oem_firmware);
break;
}
auto firmware_file = std::make_unique<FirmwareFile>();
if (info == nullptr || !firmware_file->PrepareFrom(*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));
}
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);
}
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();
}
JournalImpl(const JournalImpl&) = delete;
JournalImpl& operator=(const JournalImpl&) = delete;
void 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));
WriteJournalEntry(entry);
}
void MarkEndOfFlashingFirmware(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) {
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_;
};
} // 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