From e4fdef74e4fc8ec8a25501d173f490b1eb181b8b Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 28 Aug 2024 17:47:35 +0100 Subject: [PATCH] Add persistent console messages * By default, displaying a console alert box or dialog clears any previously displayed message. * To avoid this, enable a recallable Print() function, that can restore onscreen output after a full screen dialog has been displayed. --- src/console.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/console.h | 12 ++++++++++++ src/file.c | 38 +++++++++++++++++------------------- src/mosby.c | 27 +++++++++++++++----------- src/mosby.h | 3 --- src/pki.c | 6 +++--- src/shell.c | 3 ++- 7 files changed, 104 insertions(+), 38 deletions(-) diff --git a/src/console.c b/src/console.c index a149554..8310e9a 100644 --- a/src/console.c +++ b/src/console.c @@ -28,6 +28,12 @@ #include +#define MAX_PRINT_LINES 50 +#define MAX_LINE_SIZE 120 + +STATIC UINTN CurrentLine = 0; +STATIC CHAR16* PrintLine[MAX_PRINT_LINES] = { 0 }; + STATIC __inline INTN CountLines( IN CONST CHAR16 *StrArray[] ) @@ -368,3 +374,50 @@ VOID ConsoleReset(VOID) Console->SetMode(Console, 0); Console->ClearScreen(Console); } + +/* Set of functions enabling printed data to persist after displaying a dialog */ +UINTN EFIAPI RecallPrint( + IN CONST CHAR16 *FormatString, + ... +) +{ + // NB: VA_LIST requires the function call to be EFIAPI decorated + VA_LIST Marker; + UINTN Ret; + + if (CurrentLine >= MAX_PRINT_LINES) + return 0; + + PrintLine[CurrentLine] = AllocateZeroPool(MAX_LINE_SIZE * sizeof(CHAR16)); + if (PrintLine[CurrentLine] == NULL) + return 0; + + VA_START(Marker, FormatString); + Ret = UnicodeVSPrint(PrintLine[CurrentLine], MAX_LINE_SIZE * sizeof(CHAR16), FormatString, Marker); + VA_END(Marker); + // If we truncate a line with that ends with an LF, make sure we keep the LF + if (FormatString[StrLen(FormatString) - 1] == L'\n') + PrintLine[CurrentLine][MAX_LINE_SIZE - 2] = L'\n'; + Print(L"%s", PrintLine[CurrentLine++]); + return Ret; +} + +VOID RecallPrintRestore(VOID) +{ + UINTN i; + + ConsoleReset(); + for (i = 0; i < CurrentLine; i++) + Print(L"%s", PrintLine[i]); +} + +VOID RecallPrintFree(VOID) +{ + UINTN i; + + for (i = 0; i < MAX_PRINT_LINES; i++) { + FreePool(PrintLine[i]); + PrintLine[i] = NULL; + } + CurrentLine = 0; +} diff --git a/src/console.h b/src/console.h index 4c6c340..3cac894 100644 --- a/src/console.h +++ b/src/console.h @@ -23,6 +23,9 @@ #define NOSEL 0x7fffffff +/* Error reporting macro */ +#define ReportErrorAndExit(...) do { RecallPrint(__VA_ARGS__); goto exit; } while(0) + EFI_INPUT_KEY ConsoleGetKeystroke(VOID); INTN ConsoleCheckForKeystroke( @@ -73,3 +76,12 @@ VOID ConsoleError( ); VOID ConsoleReset(VOID); + +UINTN EFIAPI RecallPrint( + IN CONST CHAR16 *FormatString, + ... +); + +VOID RecallPrintRestore(VOID); + +VOID RecallPrintFree(VOID); diff --git a/src/file.c b/src/file.c index d8dfa63..f2510f8 100644 --- a/src/file.c +++ b/src/file.c @@ -33,8 +33,6 @@ #include #include -#define FileReportErrorAndExit(...) do { Print(__VA_ARGS__); goto exit; } while(0) - STATIC EFI_STATUS GeneratePath( IN CONST CHAR16* Name, IN CONST EFI_LOADED_IMAGE_PROTOCOL *LoadedImage, @@ -71,7 +69,7 @@ STATIC EFI_STATUS GeneratePath( if (*PathName == NULL) { Status = EFI_OUT_OF_RESOURCES; - FileReportErrorAndExit(L"Failed to allocate path buffer\n"); + ReportErrorAndExit(L"Failed to allocate path buffer\n"); } StrCpyS(*PathName, PathLen, DevicePathString); @@ -102,12 +100,12 @@ EFI_STATUS SimpleFileOpenByHandle( Status = gBS->HandleProtocol(DeviceHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID**)&Drive); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to locate simple filesystem protocol: %r\n", Status); + ReportErrorAndExit(L"Failed to locate simple filesystem protocol: %r\n", Status); Status = Drive->OpenVolume(Drive, &Root); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to open drive volume: %r\n", Status); + ReportErrorAndExit(L"Failed to open drive volume: %r\n", Status); Status = Root->Open(Root, File, (CHAR16*)Name, Mode, 0); @@ -136,7 +134,7 @@ EFI_STATUS SimpleFileOpen( Status = GeneratePath(Name, LoadedImage, &LoadPath, &PathName); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to generate load path for %s: %r\n", Name, Status); + ReportErrorAndExit(L"Failed to generate load path for %s: %r\n", Name, Status); Device = LoadedImage->DeviceHandle; @@ -172,10 +170,10 @@ EFI_STATUS SimpleDirReadAllByHandle( Size = sizeof(Buffer); Status = File->GetInfo(File, &gEfiFileInfoGuid, &Size, Info); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to get file info: %r\n", Status); + ReportErrorAndExit(L"Failed to get file info: %r\n", Status); if ((Info->Attribute & EFI_FILE_DIRECTORY) == 0) { Status = EFI_INVALID_PARAMETER; - FileReportErrorAndExit(L"Not a directory: '%s'\n", Name); + ReportErrorAndExit(L"Not a directory: '%s'\n", Name); } Size = 0; *Count = 0; @@ -222,7 +220,7 @@ EFI_STATUS SimpleDirReadAll( Status = SimpleFileOpen(Image, Name, &File, EFI_FILE_MODE_READ); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to open '%s': %r\n", Name, Status); + ReportErrorAndExit(L"Failed to open '%s': %r\n", Name, Status); Status = SimpleDirReadAllByHandle(File, Name, Entries, Count); exit: @@ -244,13 +242,13 @@ EFI_STATUS SimpleFileReadAll( Status = File->GetInfo(File, &gEfiFileInfoGuid, Size, Info); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to get file info: %r\n", Status); + ReportErrorAndExit(L"Failed to get file info: %r\n", Status); *Size = Info->FileSize; if (*Size > MAX_FILE_SIZE) { Status = EFI_UNSUPPORTED; - FileReportErrorAndExit(L"File size %d is too large\n", *Size); + ReportErrorAndExit(L"File size %d is too large\n", *Size); } // Might use memory mapped, so align up to nearest page. @@ -258,7 +256,7 @@ EFI_STATUS SimpleFileReadAll( *Buffer = AllocateZeroPool(ALIGN_VALUE(*Size + 1, 4096)); if (*Buffer == NULL) { Status = EFI_OUT_OF_RESOURCES; - FileReportErrorAndExit(L"Failed to allocate buffer of size %d\n", *Size); + ReportErrorAndExit(L"Failed to allocate buffer of size %d\n", *Size); } Status = File->Read(File, Size, *Buffer); @@ -295,7 +293,7 @@ EFI_STATUS SimpleVolumeSelector( Entries = AllocateZeroPool(sizeof(CHAR16 *) * (Count + 1)); if (Entries == NULL) { Status = EFI_OUT_OF_RESOURCES; - FileReportErrorAndExit(L"Failed to allocate volume selector buffer\n"); + ReportErrorAndExit(L"Failed to allocate volume selector buffer\n"); } for (i = 0; i < Count; i++) { @@ -328,7 +326,7 @@ EFI_STATUS SimpleVolumeSelector( Name = ConvertDevicePathToText(DevicePathFromHandle(VolumeHandles[i]), FALSE, FALSE); if (Name == NULL) { Status = EFI_OUT_OF_RESOURCES; - FileReportErrorAndExit(L"Failed to convert device path\n"); + ReportErrorAndExit(L"Failed to convert device path\n"); } } @@ -383,7 +381,7 @@ EFI_STATUS SimpleDirFilter( if (NewFilter == NULL) { Status = EFI_OUT_OF_RESOURCES; - FileReportErrorAndExit(L"Failed to allocate filter buffer\n"); + ReportErrorAndExit(L"Failed to allocate filter buffer\n"); } // Just in case EFI ever stops writeable strings @@ -434,7 +432,7 @@ EFI_STATUS SimpleDirFilter( *Result = AllocateZeroPool(2 * sizeof(VOID *)); if (*Result == NULL) { Status = EFI_OUT_OF_RESOURCES; - FileReportErrorAndExit(L"Failed to allocate filter result buffer\n"); + ReportErrorAndExit(L"Failed to allocate filter result buffer\n"); } *Count = 0; @@ -663,10 +661,10 @@ EFI_STATUS SimpleFileReadAllByPath( Status = SimpleFileOpen(DeviceHandle == NULL ? Image : DeviceHandle, PathStart, &File, EFI_FILE_MODE_READ); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to open '%s': %r\n", Path, Status); + ReportErrorAndExit(L"Failed to open '%s': %r\n", Path, Status); Status = SimpleFileReadAll(File, Size, Buffer); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to read '%s': %r\n", Path, Status); + ReportErrorAndExit(L"Failed to read '%s': %r\n", Path, Status); exit: SimpleFileClose(File); @@ -689,10 +687,10 @@ EFI_STATUS SimpleFileWriteAllByPath( Status = SimpleFileOpen(DeviceHandle == NULL ? Image : DeviceHandle, PathStart, &File, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to create '%s': %r\n", Path, Status); + ReportErrorAndExit(L"Failed to create '%s': %r\n", Path, Status); Status = SimpleFileWriteAll(File, Size, Buffer); if (EFI_ERROR(Status)) - FileReportErrorAndExit(L"Failed to write '%s': %r", Path, Status); + ReportErrorAndExit(L"Failed to write '%s': %r", Path, Status); exit: SimpleFileClose(File); return Status; diff --git a/src/mosby.c b/src/mosby.c index 059d81f..f19e0f3 100644 --- a/src/mosby.c +++ b/src/mosby.c @@ -230,7 +230,7 @@ STATIC EFI_STATUS SetSecureBootVariable( Status = gRT->GetTime(&Time, NULL); if (EFI_ERROR(Status)) { - Print(L"Failed to get current time: %r\n", Status); + RecallPrint(L"Failed to get current time: %r\n", Status); return Status; } @@ -246,7 +246,7 @@ STATIC EFI_STATUS SetSecureBootVariable( Status = CreateTimeBasedPayload(&VarSize, &VarData, &Time); if (EFI_ERROR(Status)) { SafeFree(Esl); - Print(L"Failed to create time-based data payload: %r\n", Status); + RecallPrint(L"Failed to create time-based data payload: %r\n", Status); return Status; } @@ -254,7 +254,7 @@ STATIC EFI_STATUS SetSecureBootVariable( VarAttributes | (Append ? EFI_VARIABLE_APPEND_WRITE : 0), VarSize, VarData); SafeFree(VarData); if (EFI_ERROR(Status)) - Print(L"Failed to set Secure Boot variable: %r\n", Status); + RecallPrint(L"Failed to set Secure Boot variable: %r\n", Status); return EFI_SUCCESS; } @@ -276,7 +276,8 @@ EFI_STATUS EFIAPI efi_main( CHAR16 **Argv = NULL, **ArgvCopy, Path[MAX_PATH]; INSTALLABLE_COLLECTION Installable = { 0 }; - Print(L"Mosby %s\n", VERSION_STRING); + ConsoleReset(); + RecallPrint(L"Mosby %s\n", VERSION_STRING); gBaseImageHandle = BaseImageHandle; /* 0. Parse arguments */ @@ -334,6 +335,7 @@ EFI_STATUS EFIAPI efi_main( L"Only the first one will be used.", NULL }); + RecallPrintRestore(); Installable.List[PK].NumEntries = 1; }; @@ -350,6 +352,7 @@ EFI_STATUS EFIAPI efi_main( L"If this is not what you want, please select 'Cancel' now.", NULL }); + RecallPrintRestore(); if (Sel != 0) goto exit; } @@ -396,7 +399,7 @@ EFI_STATUS EFIAPI efi_main( L"DON'T INSTALL", NULL }, 1); - // TODO: Clear screen after selection + RecallPrintRestore(); SafeFree(Installable.List[Type].Path[Entry]); if (Sel == 0) { Installable.List[Type].Path[Entry] = StrDup(L"[SELECT]"); @@ -421,10 +424,11 @@ EFI_STATUS EFIAPI efi_main( Title, NULL }, L"\\", L".cer|.crt|.esl|.bin", &Installable.List[Type].Path[Entry]); + RecallPrintRestore(); if (EFI_ERROR(Status)) continue; if (!SimpleFileExistsByPath(gBaseImageHandle, Installable.List[Type].Path[Entry])) { - Print(L"No valid file selected for %a[%d] - Ignoring\n", KeyInfo[Type].Name, Entry); + RecallPrint(L"No valid file selected for %a[%d] - Ignoring\n", KeyInfo[Type].Name, Entry); continue; } } @@ -432,7 +436,7 @@ EFI_STATUS EFIAPI efi_main( Installable.List[Type].Esl[Entry] = LoadToEsl(Installable.List[Type].Path[Entry]); if (Installable.List[Type].Esl[Entry] == NULL) { - Print(L"Failed to load %a[%d] - Aborting\n", KeyInfo[Type].Name, Entry); + RecallPrint(L"Failed to load %a[%d] - Aborting\n", KeyInfo[Type].Name, Entry); goto exit; } } @@ -440,7 +444,7 @@ EFI_STATUS EFIAPI efi_main( /* 6. Generate a keyless PK cert if none was specified */ if (Installable.List[PK].Esl[0] == NULL) { - Print(L"Generating PK certificate...\n"); + RecallPrint(L"Generating PK certificate...\n"); SafeFree(Installable.List[PK].Path[0]); Installable.List[PK].Path[0] = StrDup(L"AutoGenerated"); Cert = GenerateCredentials("Mosby Generated PK", NULL); @@ -455,7 +459,7 @@ EFI_STATUS EFIAPI efi_main( for (Entry = 0; Entry < Installable.List[DB].NumEntries && StrCmp(Installable.List[DB].Path[Entry], L"[GENERATE]") != 0; Entry++); if (Entry < Installable.List[DB].NumEntries) { - Print(L"Generating Secure Boot signing credentials...\n"); + RecallPrint(L"Generating Secure Boot signing credentials...\n"); SafeFree(Installable.List[DB].Path[Entry]); Installable.List[DB].Path[Entry] = StrDup(L"AutoGenerated"); Cert = GenerateCredentials("Secure Boot signing", &Key);; @@ -467,14 +471,14 @@ EFI_STATUS EFIAPI efi_main( Status = SaveCredentials(Cert, Key, MOSBY_CRED_NAME); if (EFI_ERROR(Status)) goto exit; - Print(L"Saved Secure Boot signing credentials as '%s'\n", MOSBY_CRED_NAME); + RecallPrint(L"Saved Secure Boot signing credentials as '%s'\n", MOSBY_CRED_NAME); } /* 8. Install the cert and DBX variables, making sure that we finish with the PK. */ for (Type = MAX_TYPES - 1; Type >= 0; Type--) { for (Entry = 0; Entry < Installable.List[Type].NumEntries; Entry++) { if (Installable.List[Type].Esl[Entry] != NULL) { - Print(L"Installing %a[%d] (from %s)\n", KeyInfo[Type].Name, Entry, Installable.List[Type].Path[Entry]); + RecallPrint(L"Installing %a[%d] (from %s)\n", KeyInfo[Type].Name, Entry, Installable.List[Type].Path[Entry]); SetStatus = SetSecureBootVariable(Installable.List[Type].Esl[Entry], Type, (Entry != 0)); if (EFI_ERROR(SetStatus)) Status = SetStatus; @@ -490,5 +494,6 @@ EFI_STATUS EFIAPI efi_main( } FreePool(Installable.ListData); FreePool(Argv); + RecallPrintFree(); return Status; } diff --git a/src/mosby.h b/src/mosby.h index 8c8d34f..896b92a 100644 --- a/src/mosby.h +++ b/src/mosby.h @@ -70,6 +70,3 @@ typedef struct { /* FreePool() replacement, that NULLs the freed pointer. */ #define SafeFree(p) do { FreePool(p); p = NULL; } while(0) - -/* Error reporting macro */ -#define ReportErrorAndExit(...) do { Print(__VA_ARGS__); goto exit; } while(0) diff --git a/src/pki.c b/src/pki.c index 2aa5b1b..d41b2f8 100644 --- a/src/pki.c +++ b/src/pki.c @@ -55,7 +55,7 @@ STATIC int OpenSSLErrorCallback( VOID *UserData ) { - Print(L"%s %a\n", (CHAR16*)UserData, AsciiString); + RecallPrint(L"%s %a\n", (CHAR16*)UserData, AsciiString); return 0; } @@ -75,7 +75,7 @@ EFI_STATUS InitializePki(VOID) if (Seed != NULL && Seed[0] != L'\0') { RAND_seed(Seed, StrLen(Seed)); } else { - Print(L"Notice: Using hardcoded default random seed\n"); + RecallPrint(L"Notice: Using hardcoded default random seed\n"); RAND_seed(DefaultSeed, sizeof(DefaultSeed)); } FreePool(Seed); @@ -190,7 +190,7 @@ VOID* GenerateCredentials( ReportOpenSSLErrorAndExit(EFI_PROTOCOL_ERROR); // Might as well verify the signature while we're at it if (!X509_verify(Cert, Key)) - Print(L"WARNING: Failed to verify autogenerated X509 credentials\n"); + RecallPrint(L"WARNING: Failed to verify autogenerated X509 credentials\n"); Status = EFI_SUCCESS; exit: diff --git a/src/shell.c b/src/shell.c index 6526a8e..c4fda1a 100644 --- a/src/shell.c +++ b/src/shell.c @@ -17,6 +17,7 @@ */ #include "shell.h" +#include "console.h" #include #include @@ -42,7 +43,7 @@ EFI_STATUS ArgSplit( Status = gBS->HandleProtocol(Image, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage); if (EFI_ERROR(Status)) { - Print(L"Failed to get arguments\n"); + RecallPrint(L"Failed to get arguments\n"); return Status; }