diff --git a/CMakeLists.txt b/CMakeLists.txt index 735998c1a..6cb1445dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,6 +284,7 @@ set(LIBFASTFETCH_SRC src/common/properties.c src/common/settings.c src/common/temps.c + src/detection/bootmgr/bootmgr.c src/detection/chassis/chassis.c src/detection/cpu/cpu.c src/detection/cpuusage/cpuusage.c @@ -314,6 +315,7 @@ set(LIBFASTFETCH_SRC src/modules/bios/bios.c src/modules/bluetooth/bluetooth.c src/modules/board/board.c + src/modules/bootmgr/bootmgr.c src/modules/brightness/brightness.c src/modules/break/break.c src/modules/camera/camera.c @@ -394,6 +396,7 @@ if(LINUX) src/detection/battery/battery_linux.c src/detection/bios/bios_linux.c src/detection/board/board_linux.c + src/detection/bootmgr/bootmgr_linux.c src/detection/brightness/brightness_linux.c src/detection/chassis/chassis_linux.c src/detection/cpu/cpu_linux.c @@ -520,6 +523,7 @@ elseif(BSD) src/detection/bios/bios_bsd.c src/detection/bluetooth/bluetooth_linux.c src/detection/board/board_bsd.c + src/detection/bootmgr/bootmgr_nosupport.c src/detection/brightness/brightness_bsd.c src/detection/chassis/chassis_bsd.c src/detection/cpu/cpu_bsd.c @@ -588,6 +592,7 @@ elseif(APPLE) src/detection/bios/bios_apple.c src/detection/bluetooth/bluetooth_apple.m src/detection/board/board_apple.c + src/detection/bootmgr/bootmgr_nosupport.c src/detection/brightness/brightness_apple.c src/detection/chassis/chassis_nosupport.c src/detection/cpu/cpu_apple.c @@ -647,6 +652,7 @@ elseif(WIN32) src/detection/bios/bios_windows.c src/detection/bluetooth/bluetooth_windows.c src/detection/board/board_windows.c + src/detection/bootmgr/bootmgr_windows.c src/detection/brightness/brightness_windows.cpp src/detection/chassis/chassis_windows.c src/detection/cpu/cpu_windows.c diff --git a/src/common/modules.c b/src/common/modules.c index ea9958eb3..84a22658d 100644 --- a/src/common/modules.c +++ b/src/common/modules.c @@ -9,6 +9,7 @@ static FFModuleBaseInfo* B[] = { (void*) &instance.config.modules.bios, (void*) &instance.config.modules.bluetooth, (void*) &instance.config.modules.board, + (void*) &instance.config.modules.bootmgr, (void*) &instance.config.modules.break_, (void*) &instance.config.modules.brightness, NULL, diff --git a/src/detection/bios/efi.h b/src/detection/bios/efi.h deleted file mode 100644 index 3878954bd..000000000 --- a/src/detection/bios/efi.h +++ /dev/null @@ -1,47 +0,0 @@ -#include "fastfetch.h" - -// https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#generic-device-path-structures -typedef struct ffEfiDevicePathProtocol -{ - uint8_t Type; - uint8_t SubType; - uint16_t Length; - uint8_t SpecificDevicePathData[]; -} ffEfiDevicePathProtocol; - -// https://uefi.org/specs/UEFI/2.10/03_Boot_Manager.html#load-options -typedef struct FFEfiLoadOption -{ - uint32_t Attributes; - uint16_t FilePathListLength; - uint16_t Description[]; - // ffEfiDevicePathProtocol FilePathList[]; - // uint8_t OptionalData[]; -} FFEfiLoadOption; - -uint8_t ffEvBits(uint16_t val, uint8_t mask, uint8_t shift) -{ - return (uint8_t) ((val & (mask << shift)) >> shift); -} - -static void ffUcs2ToUtf8(const uint16_t *const chars, FFstrbuf* result) -{ - for (uint32_t i = 0; chars[i]; i++) - { - if (chars[i] <= 0x007f) - ffStrbufAppendC(result, (char) chars[i]); - else if (chars[i] > 0x007f && chars[i] <= 0x07ff) - { - ffStrbufAppendC(result, (char) (0xc0 | ffEvBits(chars[i], 0x1f, 6))); - ffStrbufAppendC(result, (char) (0x80 | ffEvBits(chars[i], 0x3f, 0))); - } - else - { - ffStrbufAppendC(result, (char) (0xe0 | ffEvBits(chars[i], 0xf, 12))); - ffStrbufAppendC(result, (char) (0x80 | ffEvBits(chars[i], 0x3f, 6))); - ffStrbufAppendC(result, (char) (0x80 | ffEvBits(chars[i], 0x3f, 0))); - } - } -} - -#define FF_EFI_GLOBAL_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c" diff --git a/src/detection/bootmgr/bootmgr.c b/src/detection/bootmgr/bootmgr.c new file mode 100644 index 000000000..ad38d4bd8 --- /dev/null +++ b/src/detection/bootmgr/bootmgr.c @@ -0,0 +1,26 @@ +#include "efi.h" + +static inline uint8_t evBits(uint16_t val, uint8_t mask, uint8_t shift) +{ + return (uint8_t) ((val & (mask << shift)) >> shift); +} + +void ffEfiUcs2ToUtf8(const uint16_t *const chars, FFstrbuf* result) +{ + for (uint32_t i = 0; chars[i]; i++) + { + if (chars[i] <= 0x007f) + ffStrbufAppendC(result, (char) chars[i]); + else if (chars[i] > 0x007f && chars[i] <= 0x07ff) + { + ffStrbufAppendC(result, (char) (0xc0 | evBits(chars[i], 0x1f, 6))); + ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 0))); + } + else + { + ffStrbufAppendC(result, (char) (0xe0 | evBits(chars[i], 0xf, 12))); + ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 6))); + ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 0))); + } + } +} diff --git a/src/detection/bootmgr/bootmgr.h b/src/detection/bootmgr/bootmgr.h new file mode 100644 index 000000000..082149e8e --- /dev/null +++ b/src/detection/bootmgr/bootmgr.h @@ -0,0 +1,12 @@ +#pragma once + +#include "fastfetch.h" + +typedef struct FFBootmgrResult +{ + FFstrbuf name; + FFstrbuf firmware; + bool secureBoot; +} FFBootmgrResult; + +const char* ffDetectBootmgr(FFBootmgrResult* bios); diff --git a/src/detection/bootmgr/bootmgr_linux.c b/src/detection/bootmgr/bootmgr_linux.c new file mode 100644 index 000000000..dda5f5e90 --- /dev/null +++ b/src/detection/bootmgr/bootmgr_linux.c @@ -0,0 +1,45 @@ +#include "bootmgr.h" +#include "common/io/io.h" +#include "efi.h" + +#define FF_EFIVARS_PATH_PREFIX "/sys/firmware/efi/efivars/" + +const char* ffDetectBootmgr(FFBootmgrResult* result) +{ + uint8_t buffer[2048]; + + if (ffReadFileData(FF_EFIVARS_PATH_PREFIX "BootCurrent-" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) != 6) + return "Failed to read efivar: BootCurrent"; + + uint16_t value = *(uint16_t *)&buffer[4]; + snprintf((char*) buffer, sizeof(buffer), FF_EFIVARS_PATH_PREFIX "Boot%04X-" FF_EFI_GLOBAL_GUID, value); + + ssize_t size = ffReadFileData((const char*) buffer, sizeof(buffer), buffer); + if (size < 5 + (int) sizeof(FFEfiLoadOption) || size == (ssize_t) sizeof(buffer)) + return "Failed to read efivar: Boot####"; + + FFEfiLoadOption *efiOption = (FFEfiLoadOption *)&buffer[4]; + uint32_t descLen = 0; + while (efiOption->Description[descLen]) ++descLen; + + if (descLen) + ffEfiUcs2ToUtf8(efiOption->Description, &result->name); + + for ( + ffEfiDevicePathProtocol* filePathList = (void*) &efiOption->Description[descLen + 1]; + filePathList->Type != 0x7F; // End of Hardware Device Path + filePathList = (void*) ((uint8_t*) filePathList + filePathList->Length)) + { + if (filePathList->Type == 4 && filePathList->SubType == 4) + { + // https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#file-path-media-device-path + ffEfiUcs2ToUtf8((uint16_t*) filePathList->SpecificDevicePathData, &result->firmware); + break; + } + } + + if (ffReadFileData(FF_EFIVARS_PATH_PREFIX "SecureBoot-" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) == 6) + result->secureBoot = buffer[4] == 1; + + return NULL; +} diff --git a/src/detection/bootmgr/bootmgr_nosupport.c b/src/detection/bootmgr/bootmgr_nosupport.c new file mode 100644 index 000000000..a61cce63b --- /dev/null +++ b/src/detection/bootmgr/bootmgr_nosupport.c @@ -0,0 +1,6 @@ +#include "bootmgr.h" + +const char* ffDetectBootmgr(FF_MAYBE_UNUSED FFBootmgrResult* result) +{ + return "Not supported on this platform"; +} diff --git a/src/detection/bootmgr/bootmgr_windows.c b/src/detection/bootmgr/bootmgr_windows.c new file mode 100644 index 000000000..5aed4296b --- /dev/null +++ b/src/detection/bootmgr/bootmgr_windows.c @@ -0,0 +1,71 @@ +#include "bootmgr.h" +#include "efi.h" + +#include + +const char* enablePrivilege(const wchar_t* privilege) +{ + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) + return "OpenProcessToken() failed"; + + TOKEN_PRIVILEGES tp = { + .PrivilegeCount = 1, + .Privileges = { + (LUID_AND_ATTRIBUTES) { .Attributes = SE_PRIVILEGE_ENABLED } + }, + }; + if (!LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid)) + return "LookupPrivilegeValue() failed"; + + if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) + return "AdjustTokenPrivileges() failed"; + + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) + return "The token does not have the specified privilege"; + + return NULL; +} + +const char* ffDetectBootmgr(FFBootmgrResult* result) +{ + if (enablePrivilege(L"SeSystemEnvironmentPrivilege") != NULL) + return "Failed to enable SeSystemEnvironmentPrivilege"; + + uint16_t value; + if (GetFirmwareEnvironmentVariableW(L"BootCurrent", L"{" FF_EFI_GLOBAL_GUID L"}", &value, sizeof(value)) != 2) + return "GetFirmwareEnvironmentVariableW(BootCurrent) failed"; + + uint8_t buffer[2048]; + wchar_t key[16]; + wsprintfW(key, L"Boot%04X", value); + uint32_t size = GetFirmwareEnvironmentVariableW(key, L"{" FF_EFI_GLOBAL_GUID L"}", buffer, sizeof(buffer)); + if (size < sizeof(FFEfiLoadOption) || size == sizeof(buffer)) + return "GetFirmwareEnvironmentVariableW(Boot####) failed"; + + FFEfiLoadOption *efiOption = (FFEfiLoadOption *)buffer; + uint32_t descLen = 0; + while (efiOption->Description[descLen]) ++descLen; + + if (descLen) + ffEfiUcs2ToUtf8(efiOption->Description, &result->name); + + for ( + ffEfiDevicePathProtocol* filePathList = (void*) &efiOption->Description[descLen + 1]; + filePathList->Type != 0x7F; // End of Hardware Device Path + filePathList = (void*) ((uint8_t*) filePathList + filePathList->Length)) + { + if (filePathList->Type == 4 && filePathList->SubType == 4) + { + // https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#file-path-media-device-path + ffEfiUcs2ToUtf8((uint16_t*) filePathList->SpecificDevicePathData, &result->firmware); + break; + } + } + + DWORD uefiSecureBootEnabled = 0, bufSize = 0; + if (RegGetValueW(HKEY_LOCAL_MACHINE, L"SYSTEM\\ControlSet001\\Control\\SecureBoot\\State", L"UEFISecureBootEnabled", RRF_RT_REG_DWORD, NULL, &uefiSecureBootEnabled, &bufSize) == ERROR_SUCCESS) + result->secureBoot = !!uefiSecureBootEnabled; + + return NULL; +} diff --git a/src/detection/bootmgr/efi.h b/src/detection/bootmgr/efi.h new file mode 100644 index 000000000..584915d1f --- /dev/null +++ b/src/detection/bootmgr/efi.h @@ -0,0 +1,24 @@ +#include "fastfetch.h" + +// https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#generic-device-path-structures +typedef struct ffEfiDevicePathProtocol +{ + uint8_t Type; + uint8_t SubType; + uint16_t Length; + uint8_t SpecificDevicePathData[]; +} ffEfiDevicePathProtocol; + +// https://uefi.org/specs/UEFI/2.10/03_Boot_Manager.html#load-options +typedef struct FFEfiLoadOption +{ + uint32_t Attributes; + uint16_t FilePathListLength; + uint16_t Description[]; + // ffEfiDevicePathProtocol FilePathList[]; + // uint8_t OptionalData[]; +} FFEfiLoadOption; + +void ffEfiUcs2ToUtf8(const uint16_t *const chars, FFstrbuf* result); + +#define FF_EFI_GLOBAL_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c" diff --git a/src/modules/bootmgr/bootmgr.c b/src/modules/bootmgr/bootmgr.c new file mode 100644 index 000000000..7e1a2d480 --- /dev/null +++ b/src/modules/bootmgr/bootmgr.c @@ -0,0 +1,140 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "detection/bootmgr/bootmgr.h" +#include "modules/bootmgr/bootmgr.h" +#include "util/stringUtils.h" + +#define FF_BOOTMGR_NUM_FORMAT_ARGS 4 + +void ffPrintBootmgr(FFBootmgrOptions* options) +{ + FFBootmgrResult bootmgr = { + .name = ffStrbufCreate(), + .firmware = ffStrbufCreate(), + }; + + const char* error = ffDetectBootmgr(&bootmgr); + + if(error) + { + ffPrintError(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); + return; + } + + FF_STRBUF_AUTO_DESTROY firmwareName = ffStrbufCreateCopy(&bootmgr.firmware); + ffStrbufSubstrBeforeLastC(&firmwareName, '.'); + ffStrbufSubstrAfterLastC(&firmwareName, '\\'); + + if(options->moduleArgs.outputFormat.length == 0) + { + ffPrintLogoAndKey(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); + ffStrbufWriteTo(&bootmgr.name, stdout); + if (firmwareName.length > 0) + printf(" - %s\n", firmwareName.chars); + else + putchar('\n'); + } + else + { + FF_PRINT_FORMAT_CHECKED(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, FF_BOOTMGR_NUM_FORMAT_ARGS, ((FFformatarg[]) { + {FF_FORMAT_ARG_TYPE_STRBUF, &bootmgr.name}, + {FF_FORMAT_ARG_TYPE_STRBUF, &bootmgr.firmware}, + {FF_FORMAT_ARG_TYPE_STRBUF, &firmwareName}, + {FF_FORMAT_ARG_TYPE_BOOL, &bootmgr.secureBoot}, + })); + } + + ffStrbufDestroy(&bootmgr.name); + ffStrbufDestroy(&bootmgr.firmware); +} + +bool ffParseBootmgrCommandOptions(FFBootmgrOptions* options, const char* key, const char* value) +{ + const char* subKey = ffOptionTestPrefix(key, FF_BOOTMGR_MODULE_NAME); + if (!subKey) return false; + if (ffOptionParseModuleArgs(key, subKey, value, &options->moduleArgs)) + return true; + + return false; +} + +void ffParseBootmgrJsonObject(FFBootmgrOptions* options, yyjson_val* module) +{ + yyjson_val *key_, *val; + size_t idx, max; + yyjson_obj_foreach(module, idx, max, key_, val) + { + const char* key = yyjson_get_str(key_); + if(ffStrEqualsIgnCase(key, "type")) + continue; + + if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) + continue; + + ffPrintError(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", key); + } +} + +void ffGenerateBootmgrJsonConfig(FFBootmgrOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) +{ + __attribute__((__cleanup__(ffDestroyBootmgrOptions))) FFBootmgrOptions defaultOptions; + ffInitBootmgrOptions(&defaultOptions); + + ffJsonConfigGenerateModuleArgsConfig(doc, module, &defaultOptions.moduleArgs, &options->moduleArgs); +} + +void ffGenerateBootmgrJsonResult(FF_MAYBE_UNUSED FFBootmgrOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) +{ + FFBootmgrResult bootmgr = { + .name = ffStrbufCreate(), + .firmware = ffStrbufCreate(), + }; + + const char* error = ffDetectBootmgr(&bootmgr); + + if (error) + { + yyjson_mut_obj_add_str(doc, module, "error", error); + goto exit; + } + + yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); + yyjson_mut_obj_add_strbuf(doc, obj, "name", &bootmgr.name); + yyjson_mut_obj_add_strbuf(doc, obj, "firmware", &bootmgr.firmware); + yyjson_mut_obj_add_bool(doc, obj, "secureBoot", bootmgr.secureBoot); + +exit: + ffStrbufDestroy(&bootmgr.name); + ffStrbufDestroy(&bootmgr.firmware); +} + +void ffPrintBootmgrHelpFormat(void) +{ + FF_PRINT_MODULE_FORMAT_HELP_CHECKED(FF_BOOTMGR_MODULE_NAME, "{4} ({2})", FF_BOOTMGR_NUM_FORMAT_ARGS, ((const char* []) { + "Name / description", + "Firmware file path", + "Firmware file name", + "Is secure boot enabled", + })); +} + +void ffInitBootmgrOptions(FFBootmgrOptions* options) +{ + ffOptionInitModuleBaseInfo( + &options->moduleInfo, + FF_BOOTMGR_MODULE_NAME, + "Print boot manager name, firmware, etc", + ffParseBootmgrCommandOptions, + ffParseBootmgrJsonObject, + ffPrintBootmgr, + ffGenerateBootmgrJsonResult, + ffPrintBootmgrHelpFormat, + ffGenerateBootmgrJsonConfig + ); + ffOptionInitModuleArg(&options->moduleArgs); +} + +void ffDestroyBootmgrOptions(FFBootmgrOptions* options) +{ + ffOptionDestroyModuleArg(&options->moduleArgs); +} diff --git a/src/modules/bootmgr/bootmgr.h b/src/modules/bootmgr/bootmgr.h new file mode 100644 index 000000000..31a3b7946 --- /dev/null +++ b/src/modules/bootmgr/bootmgr.h @@ -0,0 +1,9 @@ +#pragma once + +#include "fastfetch.h" + +#define FF_BOOTMGR_MODULE_NAME "Bootmgr" + +void ffPrintBootmgr(FFBootmgrOptions* options); +void ffInitBootmgrOptions(FFBootmgrOptions* options); +void ffDestroyBootmgrOptions(FFBootmgrOptions* options); diff --git a/src/modules/bootmgr/option.h b/src/modules/bootmgr/option.h new file mode 100644 index 000000000..04a7d3cc5 --- /dev/null +++ b/src/modules/bootmgr/option.h @@ -0,0 +1,11 @@ +#pragma once + +// This file will be included in "fastfetch.h", do NOT put unnecessary things here + +#include "common/option.h" + +typedef struct FFBootmgrOptions +{ + FFModuleBaseInfo moduleInfo; + FFModuleArgs moduleArgs; +} FFBootmgrOptions; diff --git a/src/modules/modules.h b/src/modules/modules.h index 7bb11f8b2..50c2a4e96 100644 --- a/src/modules/modules.h +++ b/src/modules/modules.h @@ -7,6 +7,7 @@ #include "modules/bluetooth/bluetooth.h" #include "modules/brightness/brightness.h" #include "modules/board/board.h" +#include "modules/bootmgr/bootmgr.h" #include "modules/break/break.h" #include "modules/camera/camera.h" #include "modules/chassis/chassis.h" diff --git a/src/modules/options.h b/src/modules/options.h index cfcf931d0..3972eff1b 100644 --- a/src/modules/options.h +++ b/src/modules/options.h @@ -6,6 +6,7 @@ #include "modules/bios/option.h" #include "modules/bluetooth/option.h" #include "modules/board/option.h" +#include "modules/bootmgr/option.h" #include "modules/break/option.h" #include "modules/brightness/option.h" #include "modules/camera/option.h" diff --git a/src/options/modules.c b/src/options/modules.c index d3a501c32..77653becd 100644 --- a/src/options/modules.c +++ b/src/options/modules.c @@ -7,6 +7,7 @@ void ffOptionsInitModules(FFOptionsModules* options) ffInitBiosOptions(&options->bios); ffInitBluetoothOptions(&options->bluetooth); ffInitBoardOptions(&options->board); + ffInitBootmgrOptions(&options->bootmgr); ffInitBreakOptions(&options->break_); ffInitBrightnessOptions(&options->brightness); ffInitCameraOptions(&options->camera); @@ -73,6 +74,7 @@ void ffOptionsDestroyModules(FFOptionsModules* options) ffDestroyBiosOptions(&options->bios); ffDestroyBluetoothOptions(&options->bluetooth); ffDestroyBoardOptions(&options->board); + ffDestroyBootmgrOptions(&options->bootmgr); ffDestroyBreakOptions(&options->break_); ffDestroyBrightnessOptions(&options->brightness); ffDestroyCameraOptions(&options->camera); diff --git a/src/options/modules.h b/src/options/modules.h index dcd6e5bdc..3d5597e31 100644 --- a/src/options/modules.h +++ b/src/options/modules.h @@ -8,6 +8,7 @@ typedef struct FFOptionsModules FFBiosOptions bios; FFBluetoothOptions bluetooth; FFBoardOptions board; + FFBootmgrOptions bootmgr; FFBreakOptions break_; FFBrightnessOptions brightness; FFCPUOptions cpu;