Skip to content

Commit

Permalink
Add placeholders for "broken" boot entries (#92)
Browse files Browse the repository at this point in the history
This allows to edit valid entries and reorder them without touching
possibly custom vendor data. Previously all unknown/broken boot order
entries were removed on save.
  • Loading branch information
Neverous authored Jul 16, 2023
1 parent 7b6c6fb commit 1aba8b9
Show file tree
Hide file tree
Showing 16 changed files with 1,829 additions and 1,587 deletions.
3 changes: 3 additions & 0 deletions include/bootentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,8 @@ class BootEntry

bool is_current_boot = false;
bool is_next_boot = false;
bool is_error = false;
QString error = "";

OptionalDataFormat optional_data_format = OptionalDataFormat::Base64;

Expand All @@ -510,6 +512,7 @@ class BootEntry

public:
static BootEntry fromEFIBootLoadOption(const EFIBoot::Load_option &load_option);
static BootEntry fromError(const QString &error);
EFIBoot::Load_option toEFIBootLoadOption() const;

static std::optional<BootEntry> fromJSON(const QJsonObject &obj);
Expand Down
1 change: 1 addition & 0 deletions include/bootentrywidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class BootEntryWidget: public QWidget

void setReadOnly(bool readonly);
void showBootOptions(bool is_boot);
void showDevicePath(bool not_error);

void setIndex(const uint32_t index);
void setDescription(const QString &description);
Expand Down
5 changes: 5 additions & 0 deletions include/efiboot.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ inline bool operator==(const efi_guid_t &first, const efi_guid_t &second)
return efi_guid_cmp(&first, &second) == 0;
}

inline bool operator!=(const efi_guid_t &first, const efi_guid_t &second)
{
return efi_guid_cmp(&first, &second) != 0;
}

typedef std::vector<uint8_t> Raw_data;

template <class Type = Raw_data>
Expand Down
15 changes: 15 additions & 0 deletions src/bootentry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,20 @@ auto BootEntry::fromEFIBootLoadOption(
return value;
}

auto BootEntry::fromError(const QString &error) -> BootEntry
{
BootEntry value;
value.is_error = true;
value.description = "Error";
value.error = error;
return value;
}

auto BootEntry::toEFIBootLoadOption() const -> EFIBoot::Load_option
{
if(is_error)
return {};

EFIBoot::Load_option load_option;
load_option.description = description.toStdU16String();
{
Expand Down Expand Up @@ -96,6 +108,9 @@ auto BootEntry::fromJSON(const QJsonObject &obj) -> std::optional<BootEntry>

auto BootEntry::toJSON() const -> QJsonObject
{
if(is_error)
return {};

QJsonObject load_option;
load_option["description"] = description;
load_option["optional_data_format"] = static_cast<int>(optional_data_format);
Expand Down
4 changes: 3 additions & 1 deletion src/bootentrydelegate.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "bootentrydelegate.h"

#include "bootentry.h"
#include "bootentrylistmodel.h"

Expand All @@ -17,7 +18,8 @@ void BootEntryDelegate::setupWidgetFromItem(Widget &widget, const Item &item) co
widget.setReadOnly(options & BootEntryListModel::ReadOnly);
widget.setIndex(item->index);
widget.setDescription(item->description);
widget.setData(item->optional_data);
widget.setData(!item->is_error ? item->optional_data : item->error);
widget.showDevicePath(!item->is_error);
widget.setDevicePath(item->formatDevicePath(false));
widget.showBootOptions(options & BootEntryListModel::IsBoot);
widget.setCurrentBoot(item->is_current_boot);
Expand Down
6 changes: 6 additions & 0 deletions src/bootentryform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ void BootEntryForm::setReadOnly(bool readonly)
ui->device_path->setReadOnly(readonly);
ui->device_path_actions->setDisabled(readonly);
ui->optional_data_format_combo->setDisabled(false);
ui->error_text->setDisabled(readonly);
}

void BootEntryForm::setBootEntryListModel(BootEntryListModel &model)
Expand All @@ -60,6 +61,11 @@ void BootEntryForm::setItem(const QModelIndex &index, const BootEntry *item)
ui->attribute_hidden->setChecked(item && (item->attributes & EFIBoot::Load_option_attribute::HIDDEN) == EFIBoot::Load_option_attribute::HIDDEN);
ui->attribute_force_reconnect->setChecked(item && (item->attributes & EFIBoot::Load_option_attribute::FORCE_RECONNECT) == EFIBoot::Load_option_attribute::FORCE_RECONNECT);
ui->category_combo->setCurrentIndex(item && (item->attributes & EFIBoot::Load_option_attribute::CATEGORY_APP) == EFIBoot::Load_option_attribute::CATEGORY_APP);
ui->error_text->setText(item ? item->error : "");

ui->form_fields->setVisible(item ? !item->is_error : true);
ui->error_text->setVisible(item ? item->is_error : false);
ui->error_note->setVisible(item ? item->is_error : false);

setDisabled(!item);
}
Expand Down
5 changes: 5 additions & 0 deletions src/bootentrywidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ void BootEntryWidget::showBootOptions(bool is_boot)
ui->next_boot->setVisible(is_boot);
}

void BootEntryWidget::showDevicePath(bool not_error)
{
ui->device_path->setVisible(not_error);
}

void BootEntryWidget::setIndex(const uint32_t index)
{
ui->index->setText(toHex(index, 4));
Expand Down
63 changes: 52 additions & 11 deletions src/efibootdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include <QJsonObject>
#include <unordered_set>

static bool is_bootentry(const std::tstring &name, const std::tstring &prefix)
static bool is_bootentry(const std::tstring_view &name, const std::tstring_view &prefix)
{
if(name.length() != prefix.length() + 4 || name.substr(0, prefix.length()) != prefix)
return false;
Expand Down Expand Up @@ -70,6 +70,10 @@ void EFIBootData::reload()
int32_t current_boot = -1;
int32_t next_boot = -1;
QStringList errors;
auto save_error = [&](const QString &error)
{
errors.push_back(error);
};

const auto name_to_guid = EFIBoot::get_variables(
[](const EFIBoot::efi_guid_t &guid, const std::tstring_view)
Expand All @@ -84,13 +88,13 @@ void EFIBootData::reload()
size_t step = 0;
const size_t total_steps = name_to_guid.size() + 1u;

auto process_entry = [&](const auto &name, const auto &read_fn, const auto &process_fn, bool optional = false)
auto process_entry = [&](const auto &name, const auto &read_fn, const auto &process_fn, const auto &error_fn, bool optional = false)
{
const auto tname = QStringToStdTString(name);
if(!name_to_guid.count(tname))
{
if(!optional)
errors.push_back(tr("%1: not found").arg(name));
error_fn(tr("%1: not found").arg(name));

return;
}
Expand All @@ -99,7 +103,7 @@ void EFIBootData::reload()
const auto variable = read_fn(name_to_guid.at(tname), tname);
if(!variable)
{
errors.push_back(tr("%1: failed deserialization").arg(name));
error_fn(tr("%1: failed deserialization").arg(name));
return;
}

Expand All @@ -110,51 +114,61 @@ void EFIBootData::reload()
process_entry(
"Timeout", EFIBoot::get_variable<uint16_t>, [&](const uint16_t &value, const auto &)
{ setTimeout(value); },
save_error,
true);

process_entry(
"BootCurrent", EFIBoot::get_variable<uint16_t>, [&](const uint16_t &value, const auto &)
{ current_boot = value; },
save_error,
true);

process_entry(
"BootNext", EFIBoot::get_variable<uint16_t>, [&](const uint16_t &value, const auto &)
{ next_boot = value; },
save_error,
true);

process_entry(
"SecureBoot", EFIBoot::get_variable<uint8_t>, [&](const uint8_t &value, const auto &)
{ setSecureBoot(value); },
save_error,
true);

process_entry(
"VendorKeys", EFIBoot::get_variable<uint8_t>, [&](const uint8_t &value, const auto &)
{ setVendorKeys(value); },
save_error,
true);

process_entry(
"SetupMode", EFIBoot::get_variable<uint8_t>, [&](const uint8_t &value, const auto &)
{ setSetupMode(value); },
save_error,
true);

process_entry(
"AuditMode", EFIBoot::get_variable<uint8_t>, [&](const uint8_t &value, const auto &)
{ setAuditMode(value); },
save_error,
true);

process_entry(
"DeployedMode", EFIBoot::get_variable<uint8_t>, [&](const uint8_t &value, const auto &)
{ setDeployedMode(value); },
save_error,
true);

process_entry(
"OsIndicationsSupported", EFIBoot::get_variable<uint64_t>, [&](const uint64_t &value, const auto &)
{ setOsIndicationsSupported(value); },
save_error,
true);

process_entry(
"OsIndications", EFIBoot::get_variable<uint64_t>, [&](const uint64_t &value, const auto &)
{ setOsIndications(value); },
save_error,
true);

for(const auto &[prefix_, model_]: BOOT_ENTRIES)
Expand All @@ -175,6 +189,7 @@ void EFIBootData::reload()
for(const auto &index: order)
ordered_entry.insert(index);
},
save_error,
true);

// Add entries not in BootOrder at the end
Expand All @@ -197,7 +212,8 @@ void EFIBootData::reload()
{
const auto qname = toHex(index, 4, prefix);

process_entry(qname, EFIBoot::get_variable<EFIBoot::Load_option>,
process_entry(
qname, EFIBoot::get_variable<EFIBoot::Load_option>,
[&](const EFIBoot::Load_option &value, const uint32_t &attributes)
{
// Translate STL to QTL
Expand All @@ -210,6 +226,18 @@ void EFIBootData::reload()
entry.is_next_boot = next_boot == static_cast<int>(index);
}
model.appendRow(entry);
},
[&](const QString &error)
{
errors.push_back(error);
auto entry = BootEntry::fromError(error);
entry.index = index;
if(model.options & BootEntryListModel::IsBoot)
{
entry.is_current_boot = current_boot == static_cast<int>(index);
entry.is_next_boot = next_boot == static_cast<int>(index);
}
model.appendRow(entry);
});
}
}
Expand All @@ -235,9 +263,19 @@ void EFIBootData::save()
int32_t next_boot = -1;

auto old_entries = EFIBoot::get_variables(
[](const EFIBoot::efi_guid_t &guid, const std::tstring_view)
[&](const EFIBoot::efi_guid_t &guid, const std::tstring_view tname)
{
return guid == EFIBoot::efi_guid_global;
if(guid != EFIBoot::efi_guid_global)
return false;

for(const auto &[prefix, model]: BOOT_ENTRIES)
{
(void)model;
if(is_bootentry(tname, QStringToStdTString(prefix)))
return true;
}

return false;
},
[&](size_t step, size_t total)
{
Expand Down Expand Up @@ -283,6 +321,9 @@ void EFIBootData::save()
if(auto _entry = old_entries.find(tname); _entry != old_entries.end())
old_entries.erase(_entry);

if(entry.is_error)
continue;

const auto load_option = entry.toEFIBootLoadOption();
if(!EFIBoot::set_variable(EFIBoot::efi_guid_global, tname, EFIBoot::Variable<EFIBoot::Load_option>{load_option, entry.efi_attributes}, EFIBoot::EFI_VARIABLE_MODE_DEFAULTS))
{
Expand All @@ -293,9 +334,6 @@ void EFIBootData::save()

for(const auto &[tname, guid]: old_entries)
{
if(!is_bootentry(tname, QStringToStdTString(prefix)))
continue;

emit progress(step++, total_steps, tr("Removing old EFI Boot Manager entries (%1)…").arg(QStringFromStdTString(tname)));
if(!EFIBoot::del_variable(guid, tname))
{
Expand Down Expand Up @@ -455,7 +493,8 @@ void EFIBootData::export_(const QString &file_name)
}

emit progress(step++, total_steps, tr("Exporting EFI Boot Manager entries (%1)…").arg(full_name));
entries[name] = entry.toJSON();
if(!entry.is_error)
entries[name] = entry.toJSON();
}

if(!entries.isEmpty())
Expand Down Expand Up @@ -760,6 +799,7 @@ void EFIBootData::importJSONEFIData(const QJsonObject &input)
int32_t current_boot = -1;
int32_t next_boot = -1;
QStringList errors;

size_t step = 0;
const size_t total_steps = static_cast<size_t>(input.size()) + 1u;

Expand Down Expand Up @@ -1014,6 +1054,7 @@ void EFIBootData::importRawEFIData(const QJsonObject &input)
int32_t current_boot = -1;
int32_t next_boot = -1;
QStringList errors;

size_t step = 0;
const size_t total_steps = static_cast<size_t>(input.size()) + 1u;

Expand Down
25 changes: 23 additions & 2 deletions src/efibooteditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ void EFIBootEditor::enableBootEntryEditor(const QModelIndex &index)

const auto item = index.data().value<const BootEntry *>();
ui->entry_form->setItem(index, item);
auto [name, list, model] = currentBootEntryList();
(void)name;
(void)list;
ui->entry_form->setReadOnly((model.options & BootEntryListModel::ReadOnly) || item->is_error);
}

void EFIBootEditor::disableBootEntryEditor()
Expand All @@ -173,7 +177,6 @@ void EFIBootEditor::switchBootEntryEditor(int index)
ui->entry_form->setBootEntryListModel(model);
list.setCurrentIndex(list.currentIndex());
enableBootEntryEditor(list.currentIndex());
ui->entry_form->setReadOnly(model.options & BootEntryListModel::ReadOnly);
ui->entries_actions->setDisabled(model.options & BootEntryListModel::ReadOnly);
}

Expand Down Expand Up @@ -295,9 +298,27 @@ void EFIBootEditor::reorderBootEntries()
disableBootEntryEditor();

undo_stack.beginMacro(tr("Reorder %1 entries").arg(name));
// Skip indexes with errors to not overwrite them accidentally
QSet<uint16_t> errors;
for(int r = 0; r < model.rowCount(); ++r)
{
auto entry = model.index(r).data().value<const BootEntry *>();
if(entry->is_error)
errors.insert(entry->index);
}

uint16_t index = 0;
for(int r = 0; r < model.rowCount(); ++r)
model.setEntryIndex(model.index(r), index++);
{
auto idx = model.index(r);
if(idx.data().value<const BootEntry *>()->is_error)
continue;

while(errors.contains(index))
index++;

model.setEntryIndex(idx, index++);
}

undo_stack.endMacro();
enableBootEntryEditor(list.currentIndex());
Expand Down
Loading

0 comments on commit 1aba8b9

Please sign in to comment.