First working Linux version

This commit is contained in:
2016-11-11 08:04:51 +01:00
parent fe19a81484
commit a5d238e7d9
9 changed files with 670 additions and 304 deletions
+2
View File
@@ -1 +1,3 @@
build/ build/
bin/
.idea/
+17
View File
@@ -0,0 +1,17 @@
# efivar_FOUND
# efivar_INCLUDE_DIRS
find_path(efivar_INCLUDE_DIR efivar.h
/usr/include/efivar
/usr/local/include/efivar
)
set(efivar_INCLUDE_DIRS ${efivar_INCLUDE_DIR})
find_library(efivar_LIBRARIES libefivar.so
/usr/lib
/usr/local/lib
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(efivar DEFAULT_MSG efivar_INCLUDE_DIR efivar_LIBRARIES)
mark_as_advanced(efivar_FOUND efivar_INCLUDE_DIR)
+2 -1
View File
@@ -17,7 +17,8 @@ else()
endif() endif()
if(CMAKE_COMPILER_IS_GNUCXX) if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
add_definitions(-std=c++14)
elseif(MSVC) elseif(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+19
View File
@@ -2,17 +2,32 @@ project(RebootInto)
find_package(TCLAP REQUIRED) find_package(TCLAP REQUIRED)
find_package(Boost REQUIRED COMPONENTS locale) find_package(Boost REQUIRED COMPONENTS locale)
find_package(Nowide REQUIRED) find_package(Nowide REQUIRED)
if(UNIX)
find_package(efivar REQUIRED)
endif()
include_directories( include_directories(
${INCLUDE_PATH} ${INCLUDE_PATH}
${TCLAP_INCLUDE_DIRS} ${TCLAP_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${Nowide_INCLUDE_DIRS} ${Nowide_INCLUDE_DIRS}
${efivar_INCLUDE_DIRS}
) )
set(SOURCE_FILES set(SOURCE_FILES
UEFI.h
main.cpp main.cpp
) )
if(WIN32)
set(SOURCE_FILES ${SOURCE_FILES}
UEFI_Windows.cpp
)
endif()
if(UNIX)
set(SOURCE_FILES ${SOURCE_FILES}
UEFI_Linux.cpp
)
endif()
source_group("" FILES ${SOURCE_FILES}) source_group("" FILES ${SOURCE_FILES})
if(MSVC) if(MSVC)
@@ -24,3 +39,7 @@ if(MSVC)
endif() endif()
add_executable(RebootInto ${SOURCE_FILES}) add_executable(RebootInto ${SOURCE_FILES})
target_link_libraries(RebootInto
${Boost_LIBRARIES}
${efivar_LIBRARIES}
)
+31
View File
@@ -0,0 +1,31 @@
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
#include <stdexcept>
#include <memory>
const std::size_t EFI_LOAD_OPTION_DESCRIPTION_OFFSET = sizeof(std::uint32_t) + sizeof(std::uint16_t);
class UEFI
{
public:
struct BootOption
{
std::uint16_t ID;
std::string Description;
};
UEFI();
BootOption ReadBootCurrent();
BootOption ReadBootNext();
void WriteBootNext(const BootOption& option);
std::vector<BootOption> ReadBootOrder();
void WriteBootOrder(const std::vector<BootOption>& bootOrder);
private:
std::string ReadDescription(std::uint16_t id);
std::string VarNameFromID(std::uint16_t id);
};
+111
View File
@@ -0,0 +1,111 @@
#include "UEFI.h"
#include <cstdlib>
#include <sstream>
#include <boost/locale.hpp>
extern "C" {
#include <efivar.h>
}
template <typename T>
std::pair<T*, std::size_t> ReadVariable(const std::string& name)
{
std::uint8_t* data = nullptr;
std::size_t size;
std::uint32_t attributes;
if (efi_get_variable(EFI_GLOBAL_GUID, name.c_str(), &data, &size, &attributes) != 0) {
std::stringstream message;
message << "Failed to read EFI variable: " << name;
throw std::runtime_error(message.str());
}
return std::make_pair(reinterpret_cast<T*>(data), size);
}
UEFI::UEFI()
{
efi_error_clear();
if (!efi_variables_supported()) {
throw std::runtime_error("System is not UEFI!");
}
}
UEFI::BootOption UEFI::ReadBootCurrent()
{
auto data = ReadVariable<std::uint16_t>("BootCurrent");
UEFI::BootOption option;
option.ID = *data.first;
option.Description = ReadDescription(option.ID);
std::free(data.first);
return option;
}
UEFI::BootOption UEFI::ReadBootNext()
{
auto data = ReadVariable<std::uint16_t>("BootNext");
UEFI::BootOption option;
option.ID = *data.first;
option.Description = ReadDescription(option.ID);
std::free(data.first);
return option;
}
void UEFI::WriteBootNext(const UEFI::BootOption& option)
{
if (efi_set_variable(EFI_GLOBAL_GUID, "BootNext", reinterpret_cast<std::uint8_t*>(const_cast<std::uint16_t*>(&option.ID)), sizeof(std::uint16_t), EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE, 0600) != 0) {
throw std::runtime_error("Failed to set EFI variable: BootNext");
}
}
std::vector<UEFI::BootOption> UEFI::ReadBootOrder()
{
auto data = ReadVariable<std::uint16_t>("BootOrder");
int numOptions = data.second / sizeof(std::uint16_t);
std::vector<UEFI::BootOption> bootOrder(numOptions);
for (int i = 0; i < numOptions; ++i) {
auto& option = bootOrder[i];
option.ID = *(data.first + i);
option.Description = ReadDescription(option.ID);
}
std::free(data.first);
return bootOrder;
}
void UEFI::WriteBootOrder(const std::vector<UEFI::BootOption>& order)
{
std::vector<std::uint16_t> data(order.size());
for (int i = 0; i < order.size(); ++i) {
data[i] = order[i].ID;
}
if (efi_set_variable(EFI_GLOBAL_GUID, "BootOrder", reinterpret_cast<std::uint8_t*>(data.data()), sizeof(std::uint16_t) * data.size(), EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE, 0600) != 0) {
throw std::runtime_error("Failed to set EFI variable: BootOrder");
}
}
std::string UEFI::ReadDescription(std::uint16_t id)
{
auto data = ReadVariable<char>(VarNameFromID(id));
char16_t* descUTF16 = reinterpret_cast<char16_t*>(data.first + EFI_LOAD_OPTION_DESCRIPTION_OFFSET);
std::string descUTF8 = boost::locale::conv::utf_to_utf<char, char16_t>(descUTF16);
std::free(data.first);
return descUTF8;
}
std::string UEFI::VarNameFromID(std::uint16_t id)
{
char entry[9] = "Boot####";
std::snprintf(entry, 9, "Boot%04X", id);
return std::string(entry);
}
+2
View File
@@ -0,0 +1,2 @@
#include "UEFI.h"
+172 -311
View File
@@ -1,322 +1,183 @@
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <iostream> #include <iostream>
#include <map>
#include <array>
#include <vector>
#include <memory>
#define NOMINMAX
#include <Windows.h>
#include <boost/nowide/convert.hpp>
//#define TCLAP_NAMESTARTSTRING "/"
//#define TCLAP_FLAGSTARTSTRING "/"
#include <tclap/CmdLine.h> #include <tclap/CmdLine.h>
#include <boost/optional.hpp>
#include <unistd.h>
#include <sys/reboot.h>
#include "UEFI.h"
using namespace boost::nowide; #define VERSION "2.0"
bool VERBOSE = false;
#define VERSION "1.0.1" void printBootOrder(UEFI& uefi)
#define EFI_GLOBAL_VARIABLE L"{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"
const std::size_t EFI_LOAD_OPTION_DESCRIPTION_OFFSET = sizeof(std::uint32_t) + sizeof(std::uint16_t);
const std::uint16_t INVALID_OPTION_ID = std::numeric_limits<std::uint16_t>::max();
bool Verbose = false;
std::vector<std::uint16_t> BootOrder;
std::map<std::string, std::uint16_t> DescriptionToID;
std::map<std::uint16_t, std::string> IDToDescription;
DWORD SetPrivilege(HANDLE hToken, LPCWSTR lpszPrivilege, bool bEnablePrivilege)
{ {
LUID luid; auto order = uefi.ReadBootOrder();
if (!LookupPrivilegeValueW(NULL, lpszPrivilege, &luid)) { for (auto& option : order) {
std::cerr << "LookupPrivilegeValue failed: " << GetLastError() << std::endl; std::cout << option.ID << ": \"" << option.Description << "\"" << std::endl;
return GetLastError(); }
}
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege) {
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
} else {
tp.Privileges[0].Attributes = 0;
}
if (!AdjustTokenPrivileges(
hToken,
false,
&tp,
sizeof(TOKEN_PRIVILEGES),
nullptr,
nullptr
)) {
std::cerr << "AdjustTokenPrivileges failed: " << GetLastError() << std::endl;
return GetLastError();
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
std::cerr << "AdjustTokenPrivileges failed: The token does not have the specified privilege" << std::endl;
return GetLastError();
}
return ERROR_SUCCESS;
} }
DWORD SetPrivileges() // Match a BootOption by numerical ID or Description
//const UEFI::BootOption* fuzzyFindOption(const std::vector<UEFI::BootOption>& options, const std::string& identifier)
//{
// // Find by ID
// try {
// std::size_t numNums;
// std::uint16_t id = std::stoi(identifier, &numNums);
// if (numNums == options.size()) {
// auto it = std::find_if(options.begin(), options.end(), [&id](auto& o) { return o.ID == id; });
// if (it != options.end()) {
// if (VERBOSE) { // --verbose
// std::cout << "UEFI entry matched by ID." << std::endl;
// }
// return &(*it);
// }
// }
// } catch (std::invalid_argument& e) {
// } catch (std::out_of_range& e) { }
//
// // Find by description
// {
// auto it = std::find_if(options.begin(), options.end(), [&identifier](auto& o) { return o.Description == identifier; });
// if (it != options.end()) {
// if (VERBOSE) {
// std::cout << "UEFI entry matched by description." << std::endl;
// }
// return &(*it);
// }
// }
//
// return nullptr;
//}
int main(int argc, char* argv[])
{ {
// Get access token UEFI uefi;
HANDLE hInstance = GetCurrentProcess();
HANDLE hToken;
if (OpenProcessToken(hInstance, TOKEN_ADJUST_PRIVILEGES, &hToken) == 0) {
std::cerr << "OpenProcessToken failed: " << GetLastError() << std::endl;
return GetLastError();
}
DWORD result; // Arguments
// Enable environment edit privileges auto cmd = std::make_shared<TCLAP::CmdLine>("Tool to simplify dual-boot scenarios by changing the UEFI boot order to the desired entry and then reboot.", ' ', VERSION);
result = SetPrivilege(hToken, SE_SYSTEM_ENVIRONMENT_NAME, true); TCLAP::SwitchArg arg_list("l", "list", "List current UEFI boot order and exit.");
if (result != ERROR_SUCCESS) { TCLAP::SwitchArg arg_current("c", "current", "Move the currently booted UEFI entry to the top of the boot order and exit. Useful if you want your manual UEFI choices to stick, by automatically running this argument at startup.");
return result; TCLAP::ValueArg<int> arg_offset("o", "offset", "Numerical offset from the top of the boot order that acts as a barrier to how high entries will be moved. Default is 0, which means boot entries will be moved to the very top. Useful if you want UEFI to try another media before proceeding with your chosen boot entry.", false, 0, "offset");
} TCLAP::SwitchArg arg_once("n", "once", "Change boot order for the next boot only");
// Enable shutdown privileges TCLAP::SwitchArg arg_noreboot("b", "noreboot", "Do not reboot.");
result = SetPrivilege(hToken, SE_SHUTDOWN_NAME, true); TCLAP::SwitchArg arg_verbose("v", "verbose", "Verbose output.");
if (result != ERROR_SUCCESS) { TCLAP::UnlabeledValueArg<std::string> arg_entry("entry", "The UEFI entry to reboot into.", false, "", "UEFI entry");
return result;
}
return ERROR_SUCCESS; // Parse command line
} try {
cmd->add(arg_list);
DWORD LoadBootOrder() cmd->add(arg_current);
{ cmd->add(arg_offset);
std::array<std::uint16_t, 32> buffer; cmd->add(arg_once);
std::size_t size = GetFirmwareEnvironmentVariableW(L"BootOrder", EFI_GLOBAL_VARIABLE, buffer.data(), sizeof(std::uint16_t) * buffer.size()); cmd->add(arg_noreboot);
if (size == 0) { cmd->add(arg_verbose);
std::cout << "LoadBootOrder failed: " << GetLastError() << std::endl; cmd->add(arg_entry);
return GetLastError(); cmd->parse(argc, argv);
} } catch (TCLAP::ArgException& e) {
std::cerr << "Error: " << e.error() << " for arg " << e.argId() << std::endl;
int numEntries = size / sizeof(std::uint16_t); return 1;
if (Verbose) { }
std::cout << "Received " << numEntries << " UEFI entries." << std::endl; // Positional argument requirement
} if (arg_entry.getValue().empty() && !arg_list.getValue() && !arg_current.getValue()) {
auto output = cmd->getOutput();
BootOrder.resize(numEntries); output->usage(*cmd);
std::copy_n(buffer.begin(), numEntries, BootOrder.begin()); return 1;
}
return ERROR_SUCCESS; VERBOSE = arg_verbose.getValue();
}
// Print boot order
DWORD LoadBootDescriptions() if (arg_list.getValue()) { // --list
{ printBootOrder(uefi);
char* buffer = new char[1024]; return 0;
for (int i = 0; i < BootOrder.size(); i++) { }
std::uint16_t id = BootOrder.at(i); if (VERBOSE) { // --verbose
char entry[9] = "Boot####"; printBootOrder(uefi);
std::snprintf(entry, 9, "Boot%04X", id); }
std::size_t size = GetFirmwareEnvironmentVariableW(widen(entry).c_str(), EFI_GLOBAL_VARIABLE, buffer, sizeof(char) * 1024); // Fetch boot order
if (size == 0) { auto order = uefi.ReadBootOrder();
std::cerr << "LoadBootDescriptions failed on " << entry << ": " << GetLastError() << std::endl;
return GetLastError(); // Get option ID
} std::vector<UEFI::BootOption>::iterator option = order.end();
if (arg_current.getValue()) { // --current
std::string description(narrow(reinterpret_cast<wchar_t*>(buffer + EFI_LOAD_OPTION_DESCRIPTION_OFFSET))); UEFI::BootOption current = uefi.ReadBootCurrent();
DescriptionToID[description] = id; option = std::find_if(order.begin(), order.end(), [&current](auto& o) { return o.ID == current.ID; });
IDToDescription[id] = description; if (option == order.end()) {
} std::cerr << "Error: Currently booted UEFI entry has been deleted since last boot!" << std::endl;
delete[] buffer; return 1;
}
return ERROR_SUCCESS; if (VERBOSE) { // --verbose
} std::cout << "Currently booted UEFI entry is \"" << (*option).ID << ": " << (*option).Description << "\"" << std::endl;
}
void PrintBootOrder() } else { // <UEFI entry>
{ // Find by ID
for (auto& id : BootOrder) { try {
std::cout << id << ": \"" << IDToDescription.at(id) << "\"" << std::endl; std::size_t numNums;
} std::uint16_t id = std::stoi(arg_entry.getValue(), &numNums);
} if (numNums == arg_entry.getValue().size()) {
option = std::find_if(order.begin(), order.end(), [&id](auto& o) { return o.ID == id; });
std::uint16_t GetCurrentBootOption() if (VERBOSE && option != order.end()) { // --verbose
{ std::cout << "UEFI entry matched by ID." << std::endl;
std::uint16_t current; }
std::size_t size = GetFirmwareEnvironmentVariableW(L"BootCurrent", EFI_GLOBAL_VARIABLE, &current, sizeof(std::uint16_t)); }
if (size == 0) { } catch (std::invalid_argument& e) {
std::cout << "GetCurrentBootOption failed: " << GetLastError() << std::endl; } catch (std::out_of_range& e) { }
return INVALID_OPTION_ID;
} // Find by description if ID didn't match already
return current; if (option == order.end()) {
} auto& identifier = arg_entry.getValue();
option = std::find_if(order.begin(), order.end(), [&identifier](auto& o) { return o.Description == identifier; });
DWORD SetBootNext(std::uint16_t entry) if (VERBOSE && option != order.end()) { // --verbose
{ std::cout << "UEFI entry matched by description." << std::endl;
bool result = SetFirmwareEnvironmentVariableW(L"BootNext", EFI_GLOBAL_VARIABLE, &entry, sizeof(std::uint16_t)); }
if (!result) { }
std::cerr << "SetBootNext failed: " << GetLastError() << std::endl;
return GetLastError(); if (option == order.end()) {
} std::cerr << "Error: No UEFI entry found with ID or description \"" << arg_entry.getValue() << "\"." << std::endl;
std::cerr << "Use --list to list all available entries." << std::endl;
return ERROR_SUCCESS; return 1;
} }
}
DWORD SetBootDefault(std::uint16_t entry, std::size_t offset = 0)
{ // Write boot commands to UEFI
auto currentDefault = BootOrder.begin(); if (arg_once.getValue()) { // --once
std::advance(currentDefault, offset); // Set BootNext
auto newDefault = std::find(BootOrder.begin(), BootOrder.end(), entry); uefi.WriteBootNext(*option);
if (newDefault > currentDefault) { std::cout << "BootNext changed successfully." << std::endl;
// Shift entry to top } else {
std::uint16_t tmp = *newDefault; // Move option to top of boot order
std::copy_backward(currentDefault, newDefault, std::next(newDefault)); auto currentDefault = order.begin();
*currentDefault = tmp; std::advance(currentDefault, arg_offset.getValue()); // --offset=0
auto newDefault = option;
// Apply boot order if (newDefault > currentDefault) {
bool result = SetFirmwareEnvironmentVariableW(L"BootOrder", EFI_GLOBAL_VARIABLE, BootOrder.data(), sizeof(std::uint16_t) * BootOrder.size()); // Shift entry to top
if (!result) { UEFI::BootOption tmp = *newDefault;
std::cerr << "Failed to apply boot order: " << GetLastError() << std::endl; std::copy_backward(currentDefault, newDefault, std::next(newDefault));
return GetLastError(); *currentDefault = tmp;
}
// Apply boot order
std::cout << "Boot order changed successfully." << std::endl; // for (auto& option : order) {
} else if (newDefault < currentDefault) { // std::cout << option.ID << ": \"" << option.Description << "\"" << std::endl;
std::cout << "Boot entry is already above provided offset! Nothing has been changed." << std::endl; // }
} else { uefi.WriteBootOrder(order);
std::cout << "Boot entry is already the default! Nothing has been changed." << std::endl;
} std::cout << "Boot order changed successfully." << std::endl;
} else if (newDefault < currentDefault) {
return ERROR_SUCCESS; std::cout << "Boot entry is already above provided offset! Nothing has been changed." << std::endl;
} } else {
std::cout << "Boot entry is already the default! Nothing has been changed." << std::endl;
int wmain(int argc, wchar_t* argv[]) }
{ }
// Check for UEFI
FIRMWARE_TYPE firmwareType; // Reboot
if (GetFirmwareType(&firmwareType) == 0) { if (!arg_noreboot.getValue() && !arg_current.getValue()) { // --noreboot --current
std::cerr << "GetFirmwareType failed: " << GetLastError() << std::endl; std::cout << "Rebooting..." << std::endl;
return 1; sync();
} if (reboot(RB_AUTOBOOT) != -1) {
if (firmwareType != FirmwareTypeUefi) { std::cerr << "Failed to initiate reboot." << std::endl;
std::cerr << "Error: System is not UEFI!" << std::endl; return 1;
return 1; }
} }
// Create a UTF-8 argument vector for TCLAP return 0;
std::vector<std::string> args(argc);
for (int i = 0; i < argc; i++) {
args[i] = narrow(argv[i]);
}
// Arguments
auto cmd = std::make_shared<TCLAP::CmdLine>("Tool to simplify dual-boot scenarios by changing the UEFI boot order to the desired entry and then reboot.", ' ', VERSION);
TCLAP::SwitchArg arg_list("l", "list", "List current UEFI boot order and exit.");
TCLAP::SwitchArg arg_current("c", "current", "Move the currently booted UEFI entry to the top of the boot order and exit. Useful if you want your manual UEFI choices to stick, by automatically running this argument at startup.");
TCLAP::ValueArg<int> arg_offset("o", "offset", "Numerical offset from the top of the boot order that acts as a barrier to how high entries will be moved. Default is 0, which means boot entries will be moved to the very top. Useful if you want UEFI to try another media before proceeding with your chosen boot entry.", false, 0, "offset");
TCLAP::SwitchArg arg_once("n", "once", "Change boot order for the next boot only");
TCLAP::SwitchArg arg_noreboot("b", "noreboot", "Do not reboot.");
TCLAP::SwitchArg arg_verbose("v", "verbose", "Verbose output.");
TCLAP::UnlabeledValueArg<std::string> arg_entry("entry", "The UEFI entry to reboot into.", false, "", "UEFI entry");
// Parse command line
try {
cmd->add(arg_list);
cmd->add(arg_current);
cmd->add(arg_offset);
cmd->add(arg_once);
cmd->add(arg_noreboot);
cmd->add(arg_verbose);
cmd->add(arg_entry);
cmd->parse(args);
} catch (TCLAP::ArgException& e) {
std::cerr << "Error: " << e.error() << " for arg " << e.argId() << std::endl;
return 1;
}
Verbose = arg_verbose.getValue();
// Positional argument requirement
if (arg_entry.getValue().empty() && !arg_list.getValue() && !arg_current.getValue()) {
auto output = cmd->getOutput();
output->usage(*cmd);
return 1;
}
// Initialize
if (SetPrivileges() != ERROR_SUCCESS) { return 1; }
if (LoadBootOrder() != ERROR_SUCCESS) { return 1; }
if (LoadBootDescriptions() != ERROR_SUCCESS) { return 1; }
// Print boot order
if (arg_list.getValue() || Verbose) { // --list --verbose
PrintBootOrder();
if (!Verbose) {
return 0;
}
}
// Get option ID
std::uint16_t entry = INVALID_OPTION_ID;
if (arg_current.getValue()) { // --current
entry = GetCurrentBootOption();
if (entry == INVALID_OPTION_ID) {
return 1;
}
if (std::find(BootOrder.begin(), BootOrder.end(), entry) == BootOrder.end()) {
std::cerr << "Error: Currently booted UEFI entry has been deleted since last boot!" << std::endl;
return 1;
}
if (Verbose) {
std::cout << "Currently booted UEFI entry is \"" << entry << ": " << IDToDescription.at(entry) << "\"" << std::endl;
}
} else { // <UEFI entry>
// Find by ID
try {
std::size_t numNums;
std::uint16_t id = std::stoi(arg_entry.getValue(), &numNums);
if (numNums == arg_entry.getValue().size()) {
auto it = std::find(BootOrder.begin(), BootOrder.end(), id);
if (it != BootOrder.end()) {
entry = id;
if (Verbose) {
std::cout << "UEFI entry matched by ID." << std::endl;
}
}
}
} catch (std::invalid_argument& e) {
} catch (std::out_of_range& e) { }
// Find by description
if (entry == INVALID_OPTION_ID) {
auto it = DescriptionToID.find(arg_entry.getValue());
if (it == DescriptionToID.end()) {
std::cerr << "Error: No UEFI entry found with ID or description \"" << arg_entry.getValue() << "\"." << std::endl;
std::cerr << "Use --list to list all available entries." << std::endl;
return 1;
}
if (Verbose) {
std::cout << "UEFI entry matched by description." << std::endl;
}
entry = it->second;
}
}
// Write boot commands to UEFI
if (arg_once.getValue()) { // --once
// Set BootNext
if (SetBootNext(entry) != ERROR_SUCCESS) { return 1; }
} else {
// Move option to top of boot order
if (SetBootDefault(entry, arg_offset.getValue()) != ERROR_SUCCESS) { return 1; }
}
// Reboot
if (!arg_noreboot.getValue() && !arg_current.getValue()) { // --noreboot --current
if (InitiateSystemShutdown(nullptr, nullptr, 0, false, true) == 0) {
std::cerr << "Failed to initiate reboot: " << GetLastError() << std::endl;
return 1;
} else {
std::cout << "Rebooting..." << std::endl;
}
}
return 0;
} }
+322
View File
@@ -0,0 +1,322 @@
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <iostream>
#include <map>
#include <array>
#include <vector>
#include <memory>
#define NOMINMAX
#include <Windows.h>
#include <boost/nowide/convert.hpp>
//#define TCLAP_NAMESTARTSTRING "/"
//#define TCLAP_FLAGSTARTSTRING "/"
#include <tclap/CmdLine.h>
using namespace boost::nowide;
#define VERSION "1.0.1"
#define EFI_GLOBAL_VARIABLE L"{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"
const std::size_t EFI_LOAD_OPTION_DESCRIPTION_OFFSET = sizeof(std::uint32_t) + sizeof(std::uint16_t);
const std::uint16_t INVALID_OPTION_ID = std::numeric_limits<std::uint16_t>::max();
bool Verbose = false;
std::vector<std::uint16_t> BootOrder;
std::map<std::string, std::uint16_t> DescriptionToID;
std::map<std::uint16_t, std::string> IDToDescription;
DWORD SetPrivilege(HANDLE hToken, LPCWSTR lpszPrivilege, bool bEnablePrivilege)
{
LUID luid;
if (!LookupPrivilegeValueW(NULL, lpszPrivilege, &luid)) {
std::cerr << "LookupPrivilegeValue failed: " << GetLastError() << std::endl;
return GetLastError();
}
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege) {
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
} else {
tp.Privileges[0].Attributes = 0;
}
if (!AdjustTokenPrivileges(
hToken,
false,
&tp,
sizeof(TOKEN_PRIVILEGES),
nullptr,
nullptr
)) {
std::cerr << "AdjustTokenPrivileges failed: " << GetLastError() << std::endl;
return GetLastError();
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
std::cerr << "AdjustTokenPrivileges failed: The token does not have the specified privilege" << std::endl;
return GetLastError();
}
return ERROR_SUCCESS;
}
DWORD SetPrivileges()
{
// Get access token
HANDLE hInstance = GetCurrentProcess();
HANDLE hToken;
if (OpenProcessToken(hInstance, TOKEN_ADJUST_PRIVILEGES, &hToken) == 0) {
std::cerr << "OpenProcessToken failed: " << GetLastError() << std::endl;
return GetLastError();
}
DWORD result;
// Enable environment edit privileges
result = SetPrivilege(hToken, SE_SYSTEM_ENVIRONMENT_NAME, true);
if (result != ERROR_SUCCESS) {
return result;
}
// Enable shutdown privileges
result = SetPrivilege(hToken, SE_SHUTDOWN_NAME, true);
if (result != ERROR_SUCCESS) {
return result;
}
return ERROR_SUCCESS;
}
DWORD LoadBootOrder()
{
std::array<std::uint16_t, 32> buffer;
std::size_t size = GetFirmwareEnvironmentVariableW(L"BootOrder", EFI_GLOBAL_VARIABLE, buffer.data(), sizeof(std::uint16_t) * buffer.size());
if (size == 0) {
std::cout << "LoadBootOrder failed: " << GetLastError() << std::endl;
return GetLastError();
}
int numEntries = size / sizeof(std::uint16_t);
if (Verbose) {
std::cout << "Received " << numEntries << " UEFI entries." << std::endl;
}
BootOrder.resize(numEntries);
std::copy_n(buffer.begin(), numEntries, BootOrder.begin());
return ERROR_SUCCESS;
}
DWORD LoadBootDescriptions()
{
char* buffer = new char[1024];
for (int i = 0; i < BootOrder.size(); i++) {
std::uint16_t id = BootOrder.at(i);
char entry[9] = "Boot####";
std::snprintf(entry, 9, "Boot%04X", id);
std::size_t size = GetFirmwareEnvironmentVariableW(widen(entry).c_str(), EFI_GLOBAL_VARIABLE, buffer, sizeof(char) * 1024);
if (size == 0) {
std::cerr << "LoadBootDescriptions failed on " << entry << ": " << GetLastError() << std::endl;
return GetLastError();
}
std::string description(narrow(reinterpret_cast<wchar_t*>(buffer + EFI_LOAD_OPTION_DESCRIPTION_OFFSET)));
DescriptionToID[description] = id;
IDToDescription[id] = description;
}
delete[] buffer;
return ERROR_SUCCESS;
}
void PrintBootOrder()
{
for (auto& id : BootOrder) {
std::cout << id << ": \"" << IDToDescription.at(id) << "\"" << std::endl;
}
}
std::uint16_t GetCurrentBootOption()
{
std::uint16_t current;
std::size_t size = GetFirmwareEnvironmentVariableW(L"BootCurrent", EFI_GLOBAL_VARIABLE, &current, sizeof(std::uint16_t));
if (size == 0) {
std::cout << "GetCurrentBootOption failed: " << GetLastError() << std::endl;
return INVALID_OPTION_ID;
}
return current;
}
DWORD SetBootNext(std::uint16_t entry)
{
bool result = SetFirmwareEnvironmentVariableW(L"BootNext", EFI_GLOBAL_VARIABLE, &entry, sizeof(std::uint16_t));
if (!result) {
std::cerr << "SetBootNext failed: " << GetLastError() << std::endl;
return GetLastError();
}
return ERROR_SUCCESS;
}
DWORD SetBootDefault(std::uint16_t entry, std::size_t offset = 0)
{
auto currentDefault = BootOrder.begin();
std::advance(currentDefault, offset);
auto newDefault = std::find(BootOrder.begin(), BootOrder.end(), entry);
if (newDefault > currentDefault) {
// Shift entry to top
std::uint16_t tmp = *newDefault;
std::copy_backward(currentDefault, newDefault, std::next(newDefault));
*currentDefault = tmp;
// Apply boot order
bool result = SetFirmwareEnvironmentVariableW(L"BootOrder", EFI_GLOBAL_VARIABLE, BootOrder.data(), sizeof(std::uint16_t) * BootOrder.size());
if (!result) {
std::cerr << "Failed to apply boot order: " << GetLastError() << std::endl;
return GetLastError();
}
std::cout << "Boot order changed successfully." << std::endl;
} else if (newDefault < currentDefault) {
std::cout << "Boot entry is already above provided offset! Nothing has been changed." << std::endl;
} else {
std::cout << "Boot entry is already the default! Nothing has been changed." << std::endl;
}
return ERROR_SUCCESS;
}
int wmain(int argc, wchar_t* argv[])
{
// Check for UEFI
FIRMWARE_TYPE firmwareType;
if (GetFirmwareType(&firmwareType) == 0) {
std::cerr << "GetFirmwareType failed: " << GetLastError() << std::endl;
return 1;
}
if (firmwareType != FirmwareTypeUefi) {
std::cerr << "Error: System is not UEFI!" << std::endl;
return 1;
}
// Create a UTF-8 argument vector for TCLAP
std::vector<std::string> args(argc);
for (int i = 0; i < argc; i++) {
args[i] = narrow(argv[i]);
}
// Arguments
auto cmd = std::make_shared<TCLAP::CmdLine>("Tool to simplify dual-boot scenarios by changing the UEFI boot order to the desired entry and then reboot.", ' ', VERSION);
TCLAP::SwitchArg arg_list("l", "list", "List current UEFI boot order and exit.");
TCLAP::SwitchArg arg_current("c", "current", "Move the currently booted UEFI entry to the top of the boot order and exit. Useful if you want your manual UEFI choices to stick, by automatically running this argument at startup.");
TCLAP::ValueArg<int> arg_offset("o", "offset", "Numerical offset from the top of the boot order that acts as a barrier to how high entries will be moved. Default is 0, which means boot entries will be moved to the very top. Useful if you want UEFI to try another media before proceeding with your chosen boot entry.", false, 0, "offset");
TCLAP::SwitchArg arg_once("n", "once", "Change boot order for the next boot only");
TCLAP::SwitchArg arg_noreboot("b", "noreboot", "Do not reboot.");
TCLAP::SwitchArg arg_verbose("v", "verbose", "Verbose output.");
TCLAP::UnlabeledValueArg<std::string> arg_entry("entry", "The UEFI entry to reboot into.", false, "", "UEFI entry");
// Parse command line
try {
cmd->add(arg_list);
cmd->add(arg_current);
cmd->add(arg_offset);
cmd->add(arg_once);
cmd->add(arg_noreboot);
cmd->add(arg_verbose);
cmd->add(arg_entry);
cmd->parse(args);
} catch (TCLAP::ArgException& e) {
std::cerr << "Error: " << e.error() << " for arg " << e.argId() << std::endl;
return 1;
}
Verbose = arg_verbose.getValue();
// Positional argument requirement
if (arg_entry.getValue().empty() && !arg_list.getValue() && !arg_current.getValue()) {
auto output = cmd->getOutput();
output->usage(*cmd);
return 1;
}
// Initialize
if (SetPrivileges() != ERROR_SUCCESS) { return 1; }
if (LoadBootOrder() != ERROR_SUCCESS) { return 1; }
if (LoadBootDescriptions() != ERROR_SUCCESS) { return 1; }
// Print boot order
if (arg_list.getValue() || Verbose) { // --list --verbose
PrintBootOrder();
if (!Verbose) {
return 0;
}
}
// Get option ID
std::uint16_t entry = INVALID_OPTION_ID;
if (arg_current.getValue()) { // --current
entry = GetCurrentBootOption();
if (entry == INVALID_OPTION_ID) {
return 1;
}
if (std::find(BootOrder.begin(), BootOrder.end(), entry) == BootOrder.end()) {
std::cerr << "Error: Currently booted UEFI entry has been deleted since last boot!" << std::endl;
return 1;
}
if (Verbose) {
std::cout << "Currently booted UEFI entry is \"" << entry << ": " << IDToDescription.at(entry) << "\"" << std::endl;
}
} else { // <UEFI entry>
// Find by ID
try {
std::size_t numNums;
std::uint16_t id = std::stoi(arg_entry.getValue(), &numNums);
if (numNums == arg_entry.getValue().size()) {
auto it = std::find(BootOrder.begin(), BootOrder.end(), id);
if (it != BootOrder.end()) {
entry = id;
if (Verbose) {
std::cout << "UEFI entry matched by ID." << std::endl;
}
}
}
} catch (std::invalid_argument& e) {
} catch (std::out_of_range& e) { }
// Find by description
if (entry == INVALID_OPTION_ID) {
auto it = DescriptionToID.find(arg_entry.getValue());
if (it == DescriptionToID.end()) {
std::cerr << "Error: No UEFI entry found with ID or description \"" << arg_entry.getValue() << "\"." << std::endl;
std::cerr << "Use --list to list all available entries." << std::endl;
return 1;
}
if (Verbose) {
std::cout << "UEFI entry matched by description." << std::endl;
}
entry = it->second;
}
}
// Write boot commands to UEFI
if (arg_once.getValue()) { // --once
// Set BootNext
if (SetBootNext(entry) != ERROR_SUCCESS) { return 1; }
} else {
// Move option to top of boot order
if (SetBootDefault(entry, arg_offset.getValue()) != ERROR_SUCCESS) { return 1; }
}
// Reboot
if (!arg_noreboot.getValue() && !arg_current.getValue()) { // --noreboot --current
if (InitiateSystemShutdown(nullptr, nullptr, 0, false, true) == 0) {
std::cerr << "Failed to initiate reboot: " << GetLastError() << std::endl;
return 1;
} else {
std::cout << "Rebooting..." << std::endl;
}
}
return 0;
}