From 22cfebdf6fcd3299bfb4fd4b6e13e0b6f545b60b Mon Sep 17 00:00:00 2001 From: MikeBeaton Date: Wed, 18 Aug 2021 19:12:54 +0100 Subject: [PATCH] Platform: OpenLinuxBoot.efi --- Application/CsrUtil/CsrUtil.c | 1 - Changelog.md | 5 + Docs/Configuration.tex | 230 +++- Docs/Flavours.md | 5 +- Docs/Sample.plist | 142 +- Docs/SampleCustom.plist | 142 +- .../Acidanthera/Library/OcBootManagementLib.h | 269 +++- .../Acidanthera/Library/OcConfigurationLib.h | 10 +- Include/Acidanthera/Library/OcFileLib.h | 98 +- Include/Acidanthera/Library/OcFlexArrayLib.h | 263 ++++ Include/Acidanthera/Library/OcMiscLib.h | 5 + Include/Acidanthera/Library/OcStringLib.h | 42 + Include/Acidanthera/Protocol/OcBootEntry.h | 99 ++ Library/OcBootManagementLib/BootArguments.c | 384 +++++- .../OcBootManagementLib/BootEntryManagement.c | 461 +++++-- .../OcBootManagementLib/BootEntryProtocol.c | 199 +++ .../BootEntryProtocolInternal.h | 56 + .../BootManagementInternal.h | 75 +- .../OcBootManagementLib/DefaultEntryChoice.c | 232 +++- .../OcBootManagementLib.inf | 7 +- .../OcBootManagementLib/PolicyManagement.c | 23 +- .../OcConfigurationLib/OcConfigurationLib.c | 11 +- Library/OcDeviceMiscLib/OcDeviceMiscLib.inf | 2 +- Library/OcFileLib/FileProtocol.c | 128 +- Library/OcFileLib/OpenFile.c | 12 +- Library/OcFileLib/ReadFile.c | 28 +- Library/OcFlexArrayLib/AsciiStringBuffer.c | 196 +++ Library/OcFlexArrayLib/FlexArray.c | 195 +++ Library/OcFlexArrayLib/OcFlexArrayLib.inf | 27 + Library/OcMainLib/OpenCoreKernel.c | 4 +- Library/OcMainLib/OpenCoreUefi.c | 59 +- Library/OcMiscLib/OcMiscLib.inf | 2 +- Library/OcMp3Lib/OcMp3Lib.inf | 2 +- Library/OcStringLib/OcAsciiLib.c | 17 + Library/OcStringLib/OcStringLib.inf | 2 + Library/OcStringLib/OcUnicodeLib.c | 19 + OpenCorePkg.dec | 6 + OpenCorePkg.dsc | 3 + Platform/OpenLinuxBoot/Autodetect.c | 693 ++++++++++ Platform/OpenLinuxBoot/GrubCfg.c | 472 +++++++ Platform/OpenLinuxBoot/GrubEnv.c | 85 ++ Platform/OpenLinuxBoot/GrubVars.c | 273 ++++ Platform/OpenLinuxBoot/LinuxBootInternal.h | 360 +++++ Platform/OpenLinuxBoot/LoaderEntry.c | 1169 +++++++++++++++++ Platform/OpenLinuxBoot/OpenLinuxBoot.c | 307 +++++ Platform/OpenLinuxBoot/OpenLinuxBoot.inf | 44 + User/Include/UserGlobalVar.h | 1 + User/Library/UserGlobalVar.c | 1 + User/Makefile | 3 +- Utilities/ocvalidate/OcValidateLib.c | 21 +- Utilities/ocvalidate/OcValidateLib.h | 6 +- Utilities/ocvalidate/ValidateBooter.c | 30 +- Utilities/ocvalidate/ValidateMisc.c | 38 +- Utilities/ocvalidate/ValidateUEFI.c | 15 +- build_oc.tool | 1 + 55 files changed, 6618 insertions(+), 362 deletions(-) create mode 100644 Include/Acidanthera/Library/OcFlexArrayLib.h create mode 100644 Include/Acidanthera/Protocol/OcBootEntry.h create mode 100644 Library/OcBootManagementLib/BootEntryProtocol.c create mode 100644 Library/OcBootManagementLib/BootEntryProtocolInternal.h create mode 100644 Library/OcFlexArrayLib/AsciiStringBuffer.c create mode 100644 Library/OcFlexArrayLib/FlexArray.c create mode 100755 Library/OcFlexArrayLib/OcFlexArrayLib.inf create mode 100644 Platform/OpenLinuxBoot/Autodetect.c create mode 100644 Platform/OpenLinuxBoot/GrubCfg.c create mode 100644 Platform/OpenLinuxBoot/GrubEnv.c create mode 100644 Platform/OpenLinuxBoot/GrubVars.c create mode 100644 Platform/OpenLinuxBoot/LinuxBootInternal.h create mode 100644 Platform/OpenLinuxBoot/LoaderEntry.c create mode 100644 Platform/OpenLinuxBoot/OpenLinuxBoot.c create mode 100644 Platform/OpenLinuxBoot/OpenLinuxBoot.inf diff --git a/Application/CsrUtil/CsrUtil.c b/Application/CsrUtil/CsrUtil.c index 383dc5c8..34a84c17 100644 --- a/Application/CsrUtil/CsrUtil.c +++ b/Application/CsrUtil/CsrUtil.c @@ -5,7 +5,6 @@ SPDX-License-Identifier: BSD-3-Clause **/ - #include #include #include diff --git a/Changelog.md b/Changelog.md index 999f8a92..58366bc8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,11 @@ OpenCore Changelog - Added pattern-based automatic variable initialisation for better security - Updated underlying EDK II package to edk2-stable202108 - Updated Apple Secure Boot variables for `x86legacy` +- Update Linux variants in Flavours.md +- Implement Boot Entry Protocol, allowing plug-in boot entry drivers +- Add StringBuffer and FlexArray libraries +- Update Drivers to support arguments (requires config.plist update, see samples) +- Add OpenLinuxBoot driver: OC-native Linux autodetect and boot without chaining via GRUB #### v0.7.2 - Fixed OSBundleLibraries/OSBundleLibaries64 handling diff --git a/Docs/Configuration.tex b/Docs/Configuration.tex index 1a0a143b..715bf642 100755 --- a/Docs/Configuration.tex +++ b/Docs/Configuration.tex @@ -3682,6 +3682,7 @@ nvram 4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:boot-log | \item \texttt{GSTT} --- GoptStop \item \texttt{HDA} --- AudioDxe \item \texttt{KKT} --- KeyTester + \item \texttt{LNX} --- OpenLinuxBoot \item \texttt{MMDD} --- MmapDump \item \texttt{OCPAVP} --- PavpProvision \item \texttt{OCRST} --- ResetSystem @@ -4106,8 +4107,13 @@ rm vault.pub of EFI System Partition file system. \item \texttt{0x00000800} (bit \texttt{11}) --- \texttt{OC\_SCAN\_ALLOW\_FS\_NTFS}, allows scanning of NTFS (Msft Basic Data) file system. - \item \texttt{0x00001000} (bit \texttt{12}) --- \texttt{OC\_SCAN\_ALLOW\_FS\_EXT}, allows scanning - of EXT (Linux Root) file system. + \item \texttt{0x00001000} (bit \texttt{12}) --- \texttt{OC\_SCAN\_ALLOW\_FS\_LINUX\_ROOT}, allows + scanning of Linux Root file systems. + \item \texttt{0x00002000} (bit \texttt{13}) --- \texttt{OC\_SCAN\_ALLOW\_FS\_LINUX\_DATA}, allows + scanning of Linux Data file systems. + \item \texttt{0x00004000} (bit \texttt{14}) --- \texttt{OC\_SCAN\_ALLOW\_FS\_XBOOTLDR}, allows + scanning the Extended Boot Loader Partition as defined by the + \href{https://systemd.io/BOOT\_LOADER\_SPECIFICATION/}{Boot Loader Specification}. \item \texttt{0x00010000} (bit \texttt{16}) --- \texttt{OC\_SCAN\_ALLOW\_DEVICE\_SATA}, allow scanning SATA devices. \item \texttt{0x00020000} (bit \texttt{17}) --- \texttt{OC\_SCAN\_ALLOW\_DEVICE\_SASEX}, allow @@ -5916,11 +5922,14 @@ Depending on the firmware, a different set of drivers may be required. Loading an incompatible driver may lead the system to unbootable state or even cause permanent firmware damage. Some of the known drivers are listed below: -\begin{tabular}{p{1.3in}p{5.55in}} +\begin{longtable}{p{1.3in}p{5.55in}} \href{https://github.com/acidanthera/OpenCorePkg}{\texttt{AudioDxe}}\textbf{*} & HDA audio support driver in UEFI firmware for most Intel and some other analog audio controllers. Staging driver, refer to \href{https://github.com/acidanthera/bugtracker/issues/740}{acidanthera/bugtracker\#740} for known issues in AudioDxe. \\ +\href{https://github.com/acidanthera/OcBinaryData}{\texttt{btrfs\_x64}} +& Open source BTRFS file system driver, required for booting with \hyperref[uefilinux]{OpenLinuxBoot} + from a file system which is now quite commonly used with Linux. \\ \href{https://github.com/acidanthera/OpenCorePkg}{\texttt{CrScreenshotDxe}}\textbf{*} & Screenshot making driver saving images to the root of OpenCore partition (ESP) or any available writeable filesystem upon pressing \texttt{F10}. @@ -5930,6 +5939,9 @@ even cause permanent firmware damage. Some of the known drivers are listed below & Proprietary ExFAT file system driver for Bootcamp support commonly found in Apple firmware. For Sandy Bridge and earlier CPUs, the \texttt{ExFatDxeLegacy} driver should be used due to the lack of \texttt{RDRAND} instruction support. \\ +\href{https://github.com/acidanthera/OcBinaryData}{\texttt{ext4\_x64}} +& Open source EXT4 file system driver, required for booting with \hyperref[uefilinux]{OpenLinuxBoot} + from the file system most commonly used with Linux. \\ \href{https://github.com/acidanthera/OcBinaryData}{\texttt{HfsPlus}} & Recommended. Proprietary HFS file system driver with bless support commonly found in Apple firmware. For Sandy Bridge and earlier CPUs, the \texttt{HfsPlusLegacy} driver should be @@ -5952,6 +5964,10 @@ even cause permanent firmware damage. Some of the known drivers are listed below & \hyperref[ueficanopy]{OpenCore plugin} implementing graphical interface. \\ \href{https://github.com/acidanthera/OpenCorePkg}{\texttt{OpenRuntime}}\textbf{*} & \hyperref[uefiruntime]{OpenCore plugin} implementing \texttt{OC\_FIRMWARE\_RUNTIME} protocol. \\ +\href{https://github.com/acidanthera/OpenCorePkg}{\texttt{OpenLinuxBoot}}\textbf{*} +& \hyperref[uefilinux]{OpenCore plugin} implementing \texttt{OC\_BOOT\_ENTRY\_PROTOCOL} + to allow direct detection and booting of Linux distributiuons from OpenCore, without + chainloading via GRUB. \\ \href{https://github.com/acidanthera/OpenCorePkg}{\texttt{OpenUsbKbDxe}}\textbf{*} & USB keyboard driver adding support for \texttt{AppleKeyMapAggregator} protocols on top of a custom USB keyboard driver implementation. This is an alternative to @@ -5982,7 +5998,7 @@ even cause permanent firmware damage. Some of the known drivers are listed below & XHCI USB controller support driver from \texttt{MdeModulePkg}. This driver is included in most types of firmware starting with the Sandy Bridge generation. For earlier firmware or legacy systems, it may be used to support external USB 3.0 PCI cards. -\end{tabular} +\end{longtable} Driver marked with \textbf{*} are bundled with OpenCore. To compile the drivers from UDK (EDK II) the same command used for @@ -6158,6 +6174,178 @@ functioning. Feature highlights: mapping (e.g. \texttt{EnableWriteUnprotector}). \end{itemize} +\subsection{OpenLinuxBoot}\label{uefilinux} + +\texttt{OpenLinuxBoot} is an OpenCore plugin implementing \texttt{OC\_BOOT\_ENTRY\_PROTOCOL}. +It detects and boots Linux distros which are installed according to the +\href{https://systemd.io/BOOT_LOADER_SPECIFICATION/}{Boot Loader Specification} +or to the closely related (but not identical, see next paragraph) +\href{https://fedoraproject.org/wiki/Changes/BootLoaderSpecByDefault}{systemd BootLoaderSpecByDefault}. +In effect this means Linux distributions where the available boot options are found in +\texttt{\{ESP\}/loader/entries/*.conf} files (for instance \texttt{/boot/efi/loader/entries/*.conf}) +or in \texttt{\{boot\}/loader/entries/*.conf} files (for instance \texttt{/boot/loader/entries/*.conf}). +The former layout -- pure Boot Loader Specification, using kernel files on the EFI System Partition or +Extended Boot Loader Partition -- is specific to systemd-boot, the latter +layout with kernel files typically on the partition which will be mounted as \texttt{/boot} +applies to most Fedora-related distros including Fedora itself, RHEL and variants. + +BootLoaderSpecByDefault includes the possibility of expanding GRUB variables +in its \texttt{*.conf} files -- and this is used in practice in certain distros such as CentOS. +In order to correctly handle this, \texttt{OpenLinuxBoot} extracts all variables from +\texttt{\{boot\}/grub2/grubenv} and any unconditionally set variables from \texttt{\{boot\}/grub2/grub.cfg}. +This has proved sufficient in practice to extract the required variables seen so far in distros which use this +GRUB-specific feature. + +For distributions which do not use either of the above schemes, \texttt{OpenLinuxBoot} will autodetect and +boot \texttt{\{boot\}/vmlinuz*} kernel files directly, after linking these automatically -- based on the +kernel version in the filename -- to their associated \texttt{\{boot\}/init*} ramdisk files, and after +searching in \texttt{/etc/default/grub} for kernel boot options and \texttt{/etc/os-release} for the +distro name. +This layout applies to most Debian-related distros, including Debian itself, Ubuntu and variants. + +The method of starting the kernel relies on it being compiled with EFISTUB, however this applies +to almost all modern distros, particularly those which use systemd. Most modern distros +use systemd as their system manager (even though at the same time most do \emph{not} use systemd-boot as +their bootloader). + +The latest kernel version of a given install is always shown in the boot menu. Additional versions, +recovery versions, etc. are added as auxiliary boot entries, so depending on OpenCore's +\texttt{HideAuxiliary} setting may not be shown until the space key is pressed. + +\emph{Note 1}: \texttt{OpenLinuxBoot} requires filesystem drivers that may not be available in +firmware such as EXT4 and BTRFS drivers. These drivers can be obtained from external sources. +Drivers tested in basic scenarios can be downloaded from \href{https://github.com/acidanthera/OcBinaryData}{OcBinaryData}. +Be aware that these drivers are neither tested for reliability in all scenarious, nor underwent any +tamper-resistance testing, therefore have may carry potential security or data-loss risks. + +Most Linux distributions keep their boot files on the EXT4 file system even when the distribution's +main filesystem is something else such as BTRFS, therefore a suitable UEFI EXT4 file system +driver such as \href{https://github.com/acidanthera/OcBinaryData}{\texttt{ext4\_x64}} is normally required. +A BTRFS driver such as \href{https://github.com/acidanthera/OcBinaryData}{\texttt{btrfs\_x64}} +will be required in a somewhat less standard setup where the boot files are on a BTRFS partition, +e.g. as by default in openSUSE. + +Pure Boot Loader Spec (e.g. as implemented by systemd-boot) keeps all kernel and ramdisk images directly +on the EFI System Partition (or an Extended Boot Loader Partition), therefore it requires no additional +filesystem driver - but it is not widely used except in Arch Linux. + +\emph{Note 2}: systemd-boot users (probably almost exclusively Arch Linux users) should be aware that \texttt{OpenLinuxBoot} +does not support the systemd-boot--specific \href{https://systemd.io/BOOT\_LOADER\_INTERFACE/}{Boot Loader Interface}; +therefore use \texttt{efibootmgr} rather than \texttt{bootctl} for any low-level Linux command line interaction with +the boot menu. + +The default parameter values should work well, but if you need to parameterise this driver the following +options may be specified in \texttt{UEFI/Drivers/Arguments}: + +\begin{itemize} + \tightlist + \item \texttt{flags} - Default: all flags except \texttt{LINUX\_BOOT\_ADD\_DEBUG\_INFO} are set. \medskip + + Available flags are: \medskip + + \begin{itemize} + \tightlist + \item \texttt{0x00000001} (bit \texttt{0}) --- \texttt{LINUX\_BOOT\_SCAN\_ESP}, + Allows scanning for entries on EFI System Partition. + \item \texttt{0x00000002} (bit \texttt{1}) --- \texttt{LINUX\_BOOT\_SCAN\_XBOOTLDR}, + Allows scanning for entries on Extended Boot Loader Partition. + \item \texttt{0x00000004} (bit \texttt{2}) --- \texttt{LINUX\_BOOT\_SCAN\_LINUX\_ROOT}, + Allows scanning for entries on Linux Root filesystems. + \item \texttt{0x00000008} (bit \texttt{3}) --- \texttt{LINUX\_BOOT\_SCAN\_LINUX\_DATA}, + Allows scanning for entries on Linux Data filesystems. + \item \texttt{0x00000080} (bit \texttt{7}) --- \texttt{LINUX\_BOOT\_SCAN\_OTHER}, + Allows scanning for entries on file systems not matched by any of the above. \medskip + + The following notes apply to all of the above options: \medskip + + \emph{Note 1}: Apple filesystems APFS and HFS are never scanned. + \medskip + + \emph{Note 2}: Regardless of the above flags, a file system must first be + allowed by \texttt{Misc/Security/ScanPolicy} before it can be seen by + \texttt{OpenLinuxBoot} or any other \texttt{OC\_BOOT\_ENTRY\_PROTOCOL} driver. + \medskip + + \emph{Note 3}: It is recommended to enable scanning \texttt{LINUX\_ROOT} and \texttt{LINUX\_DATA} + in both \texttt{OpenLinuxBoot} flags and \texttt{Misc/Security/ScanPolicy} in order to be sure + to detect all valid Linux installs. + \medskip + + \item \texttt{0x00000100} (bit \texttt{8}) --- \texttt{LINUX\_BOOT\_ALLOW\_AUTODETECT}, + If set allows autodetecting and linking \texttt{vmlinuz*} and \texttt{init*} ramdisk files + when \texttt{loader/entries} files are not found. + \item \texttt{0x00000200} (bit \texttt{9}) --- \texttt{LINUX\_BOOT\_USE\_LATEST}, + When a Linux entry generated by \texttt{OpenLinuxBoot} is selected as the default boot entry + in OpenCore, automatically switch to the latest kernel when a new version is installed. \medskip + + When this option is set, an internal menu entry id is shared between kernel versions from the same install + of Linux. Linux boot options are always sorted highest kernel version first, so this means that + the latest kernel version of the same install always shows as the default, with this option set. \medskip + + \emph{Note}: This option is recommended on all systems. \medskip + + \item \texttt{0x00000400} (bit \texttt{10}) --- \texttt{LINUX\_BOOT\_ADD\_RO}, + This option applies to autodetected Linux only (i.e. to Debian-style distrubutions, not to BLSpec and + Fedora-style distributions with \texttt{/loader/entries/*.conf} files). + Some distrubtions run a filesystem check on loading which requires the root + filesystem to initially be mounted read-only via the \texttt{ro} kernel option. Set this bit to add this + option on autodetected distros; should be harmless but very slightly slow down boot time (due to requried + remount as read-write) on distros which do not require it. To specify this option for specific + distros only, use \texttt{partuuidopts:\{partuuid\}+=ro} instead of this flag. + \item \texttt{0x00008000} (bit \texttt{15}) --- \texttt{LINUX\_BOOT\_ADD\_DEBUG\_INFO}, + Adds a human readable file system type, followed by the first eight characters of the + partition's unique partition uuid, to each generated entry name. Can help with debugging + the origin of entries generated by the driver when there are multiple Linux installs on + one system. + \end{itemize} \medskip + + Flag values can be specified in hexadecimal beginning with \texttt{0x} or in decimal, + e.g. \texttt{flags=0x80} or \texttt{flags=128}. \medskip + + \item \texttt{partuuidopts:\{partuuid\}[+]="\{options\}"} - Default: not set. \medskip + + Allows specifying kernel options for a given partition only. If specified with \texttt{+=} then + these are used in addition to autodetected options, if specified with \texttt{=} they are used instead. + Used for autodetected Linux only. Values specified here are never used for entries created from + \texttt{/loader/entries/*.conf} files. + \medskip + + \emph{Note}: The \texttt{partuuid} value to be specified here is typically the same as the \texttt{PARTUUID} + seen in \texttt{root=PARTUUID=...} in the Linux kernel boot options (view using + \texttt{cat /proc/cmdline}) for autodetected Debian-style distros, but is NOT the same for + Fedora-style distros booted from \texttt{/loader/entries/*.conf} files. \medskip + + Typically you should not need this option in the latter case, but in case you do, to find out the unique + partition uuid to use, look for \texttt{LNX:} entries in the OpenCore debug log file. Alternatively, and + for more advanced scenarios, you may wish to examine how your drives are mounted using the + Linux \texttt{mount} command, and then find out the partuuid of relevant mounted drives by examining the + output of \texttt{ls -l /dev/disk/by-partuuid}. \medskip + + \item \texttt{autoopts[+]="\{options\}"} - Default: None specified. The kernel options to use + for autodetected Linux only. The value here is never used for entries created from + \texttt{/loader/entries/*.conf} files. \texttt{partuuidopts} may be more suitable where there are multiple + distros, but \texttt{autoopts} with no PARTUUID required is more convenient for just one distro. + If specified with \texttt{+=} then these are used in addition to autodetected options, if specified + with \texttt{=} they are used instead. As example usage, it is possible to use \texttt{+=} format to add + a \texttt{vt.handoff} options, such as \texttt{autopts+="vt.handoff=7"} or \texttt{autopts+="vt.handoff=3"} + (check \texttt{cat /proc/cmdline} when booted via your existing bootloader) on Ubuntu and related distros, + in order to add the \texttt{vt.handoff} option to the auto-detected GRUB defaults, and avoid a flash of text + showing before the distro splash screen. + \medskip + + Users may wish to compare their Linux boot options (shown with \texttt{cat /proc/cmdline}) seen when booting via + \texttt{OpenLinuxBoot} and via their distro's original bootloader, which is normally GRUB (but might also be e.g. + systemd-boot or EXTLINUX). Expect the options generated by \texttt{OpenLinuxBoot} not to + contain a \texttt{BOOT\_IMAGE=...} value where GRUB options do, and to contain an + \texttt{initrd=...} value where the GRUB options do not, since GRUB hands over ramdisks in a different way. + All remaining parameters should match, however -- perhaps excluding less important graphics handover options, + such as in the Ubuntu example given in \texttt{autoopts}. + \texttt{OpenLinuxBoot} will not start a distro unless it can find some configured options to use, therefore in + the hopefully unlikely case where no auto-detectable options are available, the user will need to specify the correct options + with \texttt{partuuidopts} or \texttt{autoopts} before the distro will boot. Examine the OpenCore debug log + for \texttt{LNX:} entries containing further information about what was found. +\end{itemize} + \subsection{Properties}\label{uefiprops} \begin{enumerate} @@ -6228,12 +6416,11 @@ functioning. Feature highlights: \item \texttt{Drivers}\\ - \textbf{Type}: \texttt{plist\ array}\\ + \textbf{Type}: \texttt{plist\ dict}\\ \textbf{Failsafe}: None\\ \textbf{Description}: Load selected drivers from \texttt{OC/Drivers} - directory. - - To be filled with string filenames meant to be loaded as UEFI drivers. + directory using the settings specified in the + \hyperref[uefidriversprops]{Drivers Properties} section below. \item \texttt{Input}\\ @@ -6681,6 +6868,33 @@ functioning. Feature highlights: \end{enumerate} +\subsection{Drivers Properties}\label{uefidriversprops} + +\begin{enumerate} + +\item + \texttt{Path}\\ + \textbf{Type}: \texttt{plist\ string}\\ + \textbf{Failsafe}: Empty\\ + \textbf{Description}: Path of file to be loaded as a UEFI driver + from \texttt{OC/Drivers} directory. + +\item + \texttt{Enabled}\\ + \textbf{Type}: \texttt{plist\ boolean}\\ + \textbf{Failsafe}: \texttt{false}\\ + \textbf{Description}: If \texttt{false} this driver entry will be ignored. + +\item + \texttt{Arguments}\\ + \textbf{Type}: \texttt{plist\ string}\\ + \textbf{Failsafe}: Empty\\ + \textbf{Description}: Some OC plugins accept optional additional arguments + which may be specified as a string here. + +\end{enumerate} + + \subsection{Input Properties}\label{uefiinputprops} \begin{enumerate} diff --git a/Docs/Flavours.md b/Docs/Flavours.md index 6e6be7af..5434aeb0 100644 --- a/Docs/Flavours.md +++ b/Docs/Flavours.md @@ -78,6 +78,7 @@ Please open an Issue or Pull Request if an additional Linux flavour is required. - **Linux** - Base icon for Linux (`Linux.icns`) - **Arch:Linux** - Arch Linux (`Arch.icns`, etc.) - **Astra:Linux** - Astra Linux + - **CentOS:Linux** - CentOS - **Debian:Linux** - Debian - **Deepin:Linux** - Deepin - **elementaryOS:Linux** - elementary OS @@ -90,16 +91,18 @@ Please open an Issue or Pull Request if an additional Linux flavour is required. - **Mageia:Linux** - Mageia (fork of former Mandriva) - **Manjaro:Linux** - Manjaro - **Mint:Linux** - Linux Mint + - **openSUSE:Linux** - openSUSE - **Oracle:Linux** - Oracle Linux - **PopOS:Linux** - Pop!_OS - **RHEL:Linux** - Red Hat Enterprise Linux + - **Rocky:Linux** - Rocky Linux - **Solus:Linux** - Solus - **Ubuntu:Linux** - Ubuntu - **Lubuntu:Ubuntu:Linux** - Lubuntu (`Lubuntu.icns`, etc.) - **UbuntuMATE:Ubuntu:Linux** - Ubuntu MATE + - **Void:Linux** - Void Linux - **Xubuntu:Ubuntu:Linux** - Xubuntu - **Zorin:Linux** - Zorin OS - - **openSUSE:Linux** - openSUSE ## Other Operating Systems diff --git a/Docs/Sample.plist b/Docs/Sample.plist index fe256314..785f6624 100644 --- a/Docs/Sample.plist +++ b/Docs/Sample.plist @@ -1305,20 +1305,134 @@ Drivers - HfsPlus.efi - OpenRuntime.efi - #OpenCanopy.efi - #AudioDxe.efi - #OpenPartitionDxe.efi - #OpenUsbKbDxe.efi - #UsbMouseDxe.efi - #Ps2KeyboardDxe.efi - #Ps2MouseDxe.efi - #HiiDatabase.efi - #NvmExpressDxe.efi - #XhciDxe.efi - #ExFatDxe.efi - #CrScreenshotDxe.efi + + Path + HfsPlus.efi + Enabled + + Arguments + + + + Path + OpenRuntime.efi + Enabled + + Arguments + + + + Path + OpenCanopy.efi + Enabled + + Arguments + + + + Path + AudioDxe.efi + Enabled + + Arguments + + + + Path + OpenPartitionDxe.efi + Enabled + + Arguments + + + + Path + OpenUsbKbDxe.efi + Enabled + + Arguments + + + + Path + UsbMouseDxe.efi + Enabled + + Arguments + + + + Path + Ps2KeyboardDxe.efi + Enabled + + Arguments + + + + Path + Ps2MouseDxe.efi + Enabled + + Arguments + + + + Path + HiiDatabase.efi + Enabled + + Arguments + + + + Path + NvmExpressDxe.efi + Enabled + + Arguments + + + + Path + XhciDxe.efi + Enabled + + Arguments + + + + Path + ExFatDxe.efi + Enabled + + Arguments + + + + Path + CrScreenshotDxe.efi + Enabled + + Arguments + + + + Path + ext4_x64.efi + Enabled + + Arguments + + + + Path + OpenLinuxBoot.efi + Enabled + + Arguments + + Input diff --git a/Docs/SampleCustom.plist b/Docs/SampleCustom.plist index 3850be43..f81a687a 100644 --- a/Docs/SampleCustom.plist +++ b/Docs/SampleCustom.plist @@ -1643,20 +1643,134 @@ Drivers - HfsPlus.efi - OpenRuntime.efi - #OpenCanopy.efi - #AudioDxe.efi - #OpenPartitionDxe.efi - #OpenUsbKbDxe.efi - #UsbMouseDxe.efi - #Ps2KeyboardDxe.efi - #Ps2MouseDxe.efi - #HiiDatabase.efi - #NvmExpressDxe.efi - #XhciDxe.efi - #ExFatDxe.efi - #CrScreenshotDxe.efi + + Path + HfsPlus.efi + Enabled + + Arguments + + + + Path + OpenRuntime.efi + Enabled + + Arguments + + + + Path + OpenCanopy.efi + Enabled + + Arguments + + + + Path + AudioDxe.efi + Enabled + + Arguments + + + + Path + OpenPartitionDxe.efi + Enabled + + Arguments + + + + Path + OpenUsbKbDxe.efi + Enabled + + Arguments + + + + Path + UsbMouseDxe.efi + Enabled + + Arguments + + + + Path + Ps2KeyboardDxe.efi + Enabled + + Arguments + + + + Path + Ps2MouseDxe.efi + Enabled + + Arguments + + + + Path + HiiDatabase.efi + Enabled + + Arguments + + + + Path + NvmExpressDxe.efi + Enabled + + Arguments + + + + Path + XhciDxe.efi + Enabled + + Arguments + + + + Path + ExFatDxe.efi + Enabled + + Arguments + + + + Path + CrScreenshotDxe.efi + Enabled + + Arguments + + + + Path + ext4_x64.efi + Enabled + + Arguments + + + + Path + OpenLinuxBoot.efi + Enabled + + Arguments + + Input diff --git a/Include/Acidanthera/Library/OcBootManagementLib.h b/Include/Acidanthera/Library/OcBootManagementLib.h index 260b91c2..8cd517f4 100755 --- a/Include/Acidanthera/Library/OcBootManagementLib.h +++ b/Include/Acidanthera/Library/OcBootManagementLib.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,10 @@ //#define BUILTIN_DEMONSTRATE_TYPING #endif +#if !defined(OC_TRACE_PARSE_VARS) +#define OC_TRACE_PARSE_VARS DEBUG_VERBOSE +#endif + /** Primary picker context. **/ @@ -180,7 +185,7 @@ EFI_STATUS /** Discovered boot entry. - Note, inner resources must be freed with OcResetBootEntry. + Note, inner resources must be freed with FreeBootEntry. **/ typedef struct OC_BOOT_ENTRY_ { // @@ -197,6 +202,10 @@ typedef struct OC_BOOT_ENTRY_ { // OC_BOOT_SYSTEM_ACTION SystemAction; // + // Id under which to save entry as default. + // + CHAR16 *Id; + // // Obtained human visible name. // CHAR16 *Name; @@ -235,6 +244,10 @@ typedef struct OC_BOOT_ENTRY_ { // BOOLEAN IsCustom; // + // Set when entry was created by OC_BOOT_ENTRY_PROTOCOL. + // + BOOLEAN IsBootEntryProtocol; + // // Should make this option default boot option. // BOOLEAN SetDefault; @@ -247,6 +260,11 @@ typedef struct OC_BOOT_ENTRY_ { // BOOLEAN ExposeDevicePath; // + // Partition UUID of entry device. + // Set for boot entry protocol boot entries only. + // + EFI_GUID UniquePartitionGUID; + // // Load option data (usually "boot args") size. // UINT32 LoadOptionsSize; @@ -256,6 +274,24 @@ typedef struct OC_BOOT_ENTRY_ { VOID *LoadOptions; } OC_BOOT_ENTRY; +/** + Parsed load option or shell variable. +**/ +typedef struct OC_PARSED_VAR_ASCII_ { + CHAR8 *Name; + CHAR8 *Value; +} OC_PARSED_VAR_ASCII; + +typedef struct OC_PARSED_VAR_UNICODE_ { + CHAR16 *Name; + CHAR16 *Value; +} OC_PARSED_VAR_UNICODE; + +typedef union OC_PARSED_VAR_ { + OC_PARSED_VAR_ASCII Ascii; + OC_PARSED_VAR_UNICODE Unicode; +} OC_PARSED_VAR; + /** Boot filesystem containing boot entries. **/ @@ -356,9 +392,21 @@ typedef struct OC_BOOT_CONTEXT_ { #define OC_SCAN_ALLOW_FS_NTFS BIT11 /** - Allow scanning EXT filesystems (e.g. EXT4). + Allow scanning Linux Root filesystems. + https://systemd.io/DISCOVERABLE_PARTITIONS/ **/ -#define OC_SCAN_ALLOW_FS_EXT BIT12 +#define OC_SCAN_ALLOW_FS_LINUX_ROOT BIT12 + +/** + Allow scanning Linux Data filesystems. + https://systemd.io/DISCOVERABLE_PARTITIONS/ +**/ +#define OC_SCAN_ALLOW_FS_LINUX_DATA BIT13 + +/** + Allow scanning XBOOTLDR filesystems. +**/ +#define OC_SCAN_ALLOW_FS_XBOOTLDR BIT14 /** Allow scanning SATA devices. @@ -416,11 +464,12 @@ typedef struct OC_BOOT_CONTEXT_ { OC_SCAN_ALLOW_DEVICE_PCI) /** - All device bits used by OC_SCAN_DEVICE_LOCK. + All file system bits used by OC_SCAN_DEVICE_LOCK. **/ #define OC_SCAN_FILE_SYSTEM_BITS ( \ - OC_SCAN_ALLOW_FS_APFS | OC_SCAN_ALLOW_FS_HFS | OC_SCAN_ALLOW_FS_ESP | \ - OC_SCAN_ALLOW_FS_NTFS | OC_SCAN_ALLOW_FS_EXT) + OC_SCAN_ALLOW_FS_APFS | OC_SCAN_ALLOW_FS_HFS | OC_SCAN_ALLOW_FS_ESP | \ + OC_SCAN_ALLOW_FS_NTFS | OC_SCAN_ALLOW_FS_LINUX_ROOT | \ + OC_SCAN_ALLOW_FS_LINUX_DATA | OC_SCAN_ALLOW_FS_XBOOTLDR ) /** By default allow booting from APFS from internal drives. @@ -473,14 +522,23 @@ EFI_STATUS /** Custom picker entry. + Note that OpenLinuxBoot OC_BOOT_ENTRY_PROTOCOL_REVISION needs incrementing + when this structure is updated. **/ typedef struct { + // + // Used by OC_BOOT_ENTRY_PROTOCOL to reidentify entry. + // Multiple entries may share an id - allows e.g. newest version + // of Linux install to automatically become selected default. + // + CONST CHAR8 *Id; // // Entry name. // CONST CHAR8 *Name; // - // Entry path. + // Absolute device path to file for user custom entries, + // file path relative to device root for boot entry protocol. // CONST CHAR8 *Path; // @@ -1378,6 +1436,18 @@ typedef struct OC_BOOT_ARGUMENTS_ { EFI_SYSTEM_TABLE *SystemTable; } OC_BOOT_ARGUMENTS; +/// +/// Boot services does not zero LoadOptions and LoadOptionsSize of a +/// loaded image by default. Applying a limit to accepted LoadOptionsSize +/// is intended to spot this and make it less likely to cause a segmentation +/// fault if a newer driver (using LoadOptions) is called by an older version +/// of OC (or anything else) which leaves these uninitialised. +/// However the 'uninitialised' (?) value in LoadOptionsSize seems to be small +/// but not zero, therefore unfortunately this approach - while not harmful - is +/// not helping to detect this situation. +/// +#define MAX_LOAD_OPTIONS_SIZE SIZE_4KB + /** Parse macOS kernel into unified boot arguments structure. @@ -1747,4 +1817,189 @@ OcImageLoaderLoad ( OUT EFI_HANDLE *ImageHandle ); +/** + Parse loaded image protocol load options. + + Assumes CHAR_NULL terminated Unicode string of space separated options, + each of form {name} or {name}={value}. Double quotes can be used round {value} to + include spaces, and '\' can be used within quoted or unquoted values to escape any + character (including space and '"'). + + NB Var names and values are left as pointers to within the original raw LoadOptions + string, which may be modified during processing. + + @param[in] LoadedImage Loaded image handle. + @param[out] ParsedVars Parsed load options if successful, NULL otherwise. + Caller may free after use with OcFlexArrayFree + if required. + + @retval EFI_SUCCESS Success. + @retval EFI_NOT_FOUND Missing or empty load options. + @retval EFI_OUT_OF_RESOURCES Out of memory. + @retval EFI_INVALID_PARAMETER Invalid load options detected. +**/ +EFI_STATUS +OcParseLoadOptions ( + IN CONST EFI_LOADED_IMAGE_PROTOCOL *LoadedImage, + OUT OC_FLEX_ARRAY **ParsedVars + ); + +/** + Parse Unix-style var file or string. Parses a couple of useful ASCII + GRUB config files (multi-line, name=var, with optinal comments) and + defines a standard format for Unicode UEFI LoadOptions. + + Assumes CHAR_NULL terminated Unicode string of space separated options, + each of form {name} or {name}={value}. Double quotes can be used round {value} to + include spaces, and '\' can be used within quoted or unquoted values to escape any + character (including space and '"'). + Comments (if any) run from '#' to end of same line. + + NB Var names and values are left as pointers to within the raw string, which may + be modified during processing. + + @param[in] StrVars Raw var string. + @param[out] ParsedVars Parsed variables if successful, NULL otherwise. + Caller may free after use with OcFlexArrayFree + if required. + @param[in] IsUnicode Are option names and values Unicode or ASCII? + + @retval EFI_SUCCESS Success. + @retval EFI_NOT_FOUND Missing or empty load options. + @retval EFI_OUT_OF_RESOURCES Out of memory. + @retval EFI_INVALID_PARAMETER Invalid load options detected. +**/ +EFI_STATUS +OcParseVars ( + IN VOID *StrVars, + OUT OC_FLEX_ARRAY **ParsedVars, + IN CONST BOOLEAN IsUnicode + ); + +/** + Get string value of parsed var or load option. + Returned value is in same format as raw options. + Return value points directly into original raw option memory, + so may need to be copied if it is to be retained, and must not + be freed directly. + + @param[in] ParsedVars Parsed variables. + @param[in] Name Option name. + @param[in] StrValue Option value if successful, not modified otherwise; + note that NULL is returned if option exists with no value. + Caller must not attempt to free this memory. + @param[in] IsUnicode Are option names and values Unicode or ASCII? + + @retval TRUE Option exists. + @retval FALSE Option not found. +**/ +BOOLEAN +OcParsedVarsGetStr ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + OUT VOID **StrValue, + IN CONST BOOLEAN IsUnicode + ); + +/** + Get string value of parsed var or load option. + Return value points directly into original raw option memory, + so may need to be copied if it is to be retained, and must not + be freed directly. + + @param[in] ParsedVars Parsed variables. + @param[in] Name Option name. + @param[in] StrValue Option value if successful, not modified otherwise; + note that NULL is returned if option exists with no value. + Caller must not attempt to free this memory. + + @retval TRUE Option exists. + @retval FALSE Option not found. +**/ +BOOLEAN +OcParsedVarsGetUnicodeStr ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST CHAR16 *Name, + OUT CHAR16 **StrValue + ); + +/** + Get ASCII string value of parsed var or load option. + Return value points directly into original raw option memory, + so may need to be copied if it is to be retained, and must not + be freed directly. + + @param[in] ParsedVars Parsed variables. + @param[in] Name Option name. + @param[in] StrValue Option value if successful, not modified otherwise; + note that NULL is returned if option exists with no value. + Caller must not attempt to free this memory. + + @retval TRUE Option exists. + @retval FALSE Option not found. +**/ +BOOLEAN +OcParsedVarsGetAsciiStr ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST CHAR8 *Name, + OUT CHAR8 **StrValue + ); + +/** + Get presence or absence of parsed shell var or load option. + + @param[in] ParsedVars Parsed variables. + @param[in] Name Option name. + @param[in] IsUnicode Are option names and values Unicode or ASCII? + + @retval TRUE Option exists (with or without a value). + @retval FALSE Option not found. +**/ +BOOLEAN +OcHasParsedVar ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + IN CONST BOOLEAN IsUnicode + ); + +/** + Get integer value of parsed shell var or load option (parses hex and decimal representations). + + @param[in] ParsedVars Parsed variables. + @param[in] Name Option name. + @param[in] Value Option value if successful, not modified otherwise. + @param[in] IsUnicode Are option names and values Unicode or ASCII? + + @retval EFI_SUCCESS Success. + @retval EFI_NOT_FOUND Option not found, or has no value. + @retval other Error encountered when parsing option as int. +**/ +EFI_STATUS +OcParsedVarsGetInt ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + OUT UINTN *Value, + IN CONST BOOLEAN IsUnicode + ); + +/** + Get guid value of parsed shell var or load option. + + @param[in] ParsedVars Parsed variables. + @param[in] Name Option name. + @param[in] Value Option value if successful, not modified otherwise. + @param[in] IsUnicode Are option names and values Unicode or ASCII? + + @retval EFI_SUCCESS Success. + @retval EFI_NOT_FOUND Option not found, or has no value. + @retval other Error encountered when parsing option as guid. +**/ +EFI_STATUS +OcParsedVarsGetGuid ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + OUT EFI_GUID *Value, + IN CONST BOOLEAN IsUnicode + ); + #endif // OC_BOOT_MANAGEMENT_LIB_H diff --git a/Include/Acidanthera/Library/OcConfigurationLib.h b/Include/Acidanthera/Library/OcConfigurationLib.h index a7c11734..95ce1c65 100644 --- a/Include/Acidanthera/Library/OcConfigurationLib.h +++ b/Include/Acidanthera/Library/OcConfigurationLib.h @@ -569,10 +569,16 @@ typedef enum { **/ /// -/// Drivers is a sorted array of strings containing driver paths. +/// Drivers is an ordered array of drivers to load. /// +#define OC_UEFI_DRIVER_ENTRY_FIELDS(_, __) \ + _(OC_STRING , Path , , OC_STRING_CONSTR ("", _, __) , OC_DESTR (OC_STRING) ) \ + _(BOOLEAN , Enabled , , FALSE , ()) \ + _(OC_STRING , Arguments , , OC_STRING_CONSTR ("", _, __) , OC_DESTR (OC_STRING) ) + OC_DECLARE (OC_UEFI_DRIVER_ENTRY) + #define OC_UEFI_DRIVER_ARRAY_FIELDS(_, __) \ - OC_ARRAY (OC_STRING, _, __) + OC_ARRAY (OC_UEFI_DRIVER_ENTRY, _, __) OC_DECLARE (OC_UEFI_DRIVER_ARRAY) /// diff --git a/Include/Acidanthera/Library/OcFileLib.h b/Include/Acidanthera/Library/OcFileLib.h index 0c891f58..a3476c4b 100755 --- a/Include/Acidanthera/Library/OcFileLib.h +++ b/Include/Acidanthera/Library/OcFileLib.h @@ -116,11 +116,11 @@ OcGetVolumeLabel ( */ EFI_STATUS OcSafeFileOpen ( - IN EFI_FILE_PROTOCOL *Protocol, - OUT EFI_FILE_PROTOCOL **NewHandle, - IN CONST CHAR16 *FileName, - IN UINT64 OpenMode, - IN UINT64 Attributes + IN CONST EFI_FILE_PROTOCOL *Protocol, + OUT EFI_FILE_PROTOCOL **NewHandle, + IN CONST CHAR16 *FileName, + IN CONST UINT64 OpenMode, + IN CONST UINT64 Attributes ); /** @@ -137,10 +137,10 @@ OcSafeFileOpen ( **/ VOID * OcReadFile ( - IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem, - IN CONST CHAR16 *FilePath, - OUT UINT32 *FileSize OPTIONAL, - IN UINT32 MaxFileSize OPTIONAL + IN CONST EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem, + IN CONST CHAR16 *FilePath, + OUT UINT32 *FileSize OPTIONAL, + IN CONST UINT32 MaxFileSize OPTIONAL ); /** @@ -148,19 +148,19 @@ OcReadFile ( Null termination does not affect the returned file size. Depending on the implementation 0 byte files may return null. - @param[in] RootFile A pointer to the file protocol of the directory. - @param[in] FilePath The full path to the file on the device. - @param[out] FileSize The size of the file read (optional). - @param[in] MaxFileSize Upper file size bound (optional). + @param[in] RootDirectory A pointer to the file protocol of the directory. + @param[in] FilePath The full path to the file on the device. + @param[out] FileSize The size of the file read (optional). + @param[in] MaxFileSize Upper file size bound (optional). @retval A pointer to a buffer containing file read or NULL. **/ VOID * -OcReadFileFromFile ( - IN EFI_FILE_PROTOCOL *RootFile, - IN CONST CHAR16 *FilePath, - OUT UINT32 *FileSize OPTIONAL, - IN UINT32 MaxFileSize OPTIONAL +OcReadFileFromDirectory ( + IN CONST EFI_FILE_PROTOCOL *RootDirectory, + IN CONST CHAR16 *FilePath, + OUT UINT32 *FileSize OPTIONAL, + IN UINT32 MaxFileSize OPTIONAL ); /** @@ -260,6 +260,68 @@ OcGetNewestFileFromDirectory ( OUT EFI_FILE_INFO **FileInfo ); +/** + Ensure specified file is directory or file as specified by IsDirectory. + + @param[in] File The file to check. + @param[in] IsDirectory Require that file is directory. + + @retval EFI_SUCCESS File is directory/file as specified. + @retval EFI_INVALID_PARAMETER File is not directory/file as specified. +**/ +EFI_STATUS +OcEnsureDirectory ( + IN EFI_FILE_PROTOCOL *File, + IN BOOLEAN IsDirectory + ); + +/** + Process directory item. + + NB Successful processing must return EFI_SUCCESS or EFI_NOT_FOUND, or further + processing will be aborted. + + Return EFI_NOT_FOUND to continue processing but act if no file found. + + @param[in] Directory Parent directory file handle. + @param[in] FileInfo EFI_FILE_INFO allocated from pool memory, + will be freed after this call, + data to preserve must be copied. + @param[in] FileInfoSize FileInfoSize. + @param[in,out] Context Optional application-specific context. + + @retval EFI_SUCCESS File found and successfully processed. + @retval EFI_NOT_FOUND (Act as if) no matching file was found. + @retval other Error processing file (aborts directory scan). +**/ +typedef +EFI_STATUS +(*OC_PROCESS_DIRECTORY_ENTRY) ( + EFI_FILE_HANDLE Directory, + EFI_FILE_INFO *FileInfo, + UINTN FileInfoSize, + VOID *Context OPTIONAL + ); + +/** + Scan directory, calling specified procedure for each directory entry. + + @param[in] Directory The directory to scan. + @param[in] ProcessEntry Process entry, called for each directory entry matching filter. + @param[in,out] Context Optional application-specific context. + + @retval EFI_NOT_FOUND Successful processing, no entries matching filter were found. + @retval EFI_SUCCESS Successful processing, at least one entry matching filter was found. + @retval EFI_OUT_OF_RESOURCES Out of memory. + @retval other Other error returned by file system or ProcessEntry during processing +**/ +EFI_STATUS +OcScanDirectory ( + IN EFI_FILE_HANDLE Directory, + IN OC_PROCESS_DIRECTORY_ENTRY ProcessEntry, + IN OUT VOID *Context OPTIONAL + ); + /** Get file information of specified type. diff --git a/Include/Acidanthera/Library/OcFlexArrayLib.h b/Include/Acidanthera/Library/OcFlexArrayLib.h new file mode 100644 index 00000000..bb893b6b --- /dev/null +++ b/Include/Acidanthera/Library/OcFlexArrayLib.h @@ -0,0 +1,263 @@ +/** @file + Copyright (C) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#ifndef OC_FLEX_ARRAY_LIB_H +#define OC_FLEX_ARRAY_LIB_H + +#include +#include +#include + +/* + Forward declaration of OC_FLEX_ARRAY structure. +*/ +typedef struct OC_FLEX_ARRAY_ OC_FLEX_ARRAY; + +/** + Free any allocated memory pointed to by flex array item. + + @param[in] Item Item to free. +**/ +typedef +VOID +(* OC_FLEX_ARRAY_FREE_ITEM) ( + IN VOID *Item + ); + +/** + Utility method to free memory pointed to by flex array + item when the item is a single pointer, or when the only + allocated memory is pointed to by a pointer which is the + first element. + + @param[in] Item Flex array item to free. +**/ +VOID +OcFlexArrayFreePointerItem ( + IN VOID *Item + ); + +/** + Initialize flex array. + + @param[in] ItemSize Size of each item in the array. + @param[in] FreeItem Method to free one item, called once per item by + OcFlexArrayFree; may be NULL if there is nothing + pointed to by pool items which needs freeing. + + @retval Non-null Flex array was created. + @retval NULL Out of memory. +**/ +OC_FLEX_ARRAY * +OcFlexArrayInit ( + IN CONST UINTN ItemSize, + IN CONST OC_FLEX_ARRAY_FREE_ITEM FreeItem OPTIONAL + ); + +/** + Add new item to flex array, resizing if necessary. New item memory is zeroed. + + @param[in,out] FlexArray Flex array to modify. + + @retval Non-null Address of item created. + @retval NULL Out of memory, in which case FlexArray->Items will be set + to NULL, but FlexArray itself will still be allocated. +**/ +VOID * +OcFlexArrayAddItem ( + IN OUT OC_FLEX_ARRAY *FlexArray + ); + +/** + Insert new item at position in flex array, resizing array if necessary. New item memory is zeroed. + + @param[in,out] FlexArray Flex array to modify. + @param[in] InsertIndex Index at which to insert; must be less than or equal to current item count. + + @retval Non-null Address of item created. + @retval NULL Out of memory, in which case FlexArray->Items will be set + to NULL, but FlexArray itself will still be allocated. +**/ +VOID * +OcFlexArrayInsertItem ( + IN OUT OC_FLEX_ARRAY *FlexArray, + IN CONST UINTN InsertIndex + ); + +/** + Return item at index in array. + + @param[in,out] FlexArray Flex array to access. + @param[in,out] Index Index of item to return. + + @retval Non-null Item. + @retval NULL Out-of-range item requested. This also ASSERTs. +**/ +VOID * +OcFlexArrayItemAt ( + IN CONST OC_FLEX_ARRAY *FlexArray, + IN CONST UINTN Index + ); + +/** + Free flex array. + + @param[in,out] FlexArray Flex array to free. +**/ +VOID +OcFlexArrayFree ( + IN OUT OC_FLEX_ARRAY **FlexArray + ); + +/** + Free dynamic container object, but do not free and + pass back out allocated items with item count. + + @param[in,out] FlexArray Flex array to free. + @param[in,out] Items Pool allocated flex array item list. + @param[in,out] Count Item list count. +**/ +VOID +OcFlexArrayFreeContainer ( + IN OUT OC_FLEX_ARRAY **FlexArray, + IN OUT VOID **Items, + IN OUT UINTN *Count + ); + +/* + Flex array. +*/ +struct OC_FLEX_ARRAY_ { + // + // Allocated array. + // + VOID *Items; + // + // Item size. + // + UINTN ItemSize; + // + // Current used count. + // + UINTN Count; + // + // Current allocated count. + // + UINTN AllocatedCount; + // + // Optional method to free memory pointed to from item. + // + OC_FLEX_ARRAY_FREE_ITEM FreeItem; +}; + +/* + Forward declaration of OC_STRING_BUFFER structure. +*/ +typedef struct OC_STRING_BUFFER_ OC_STRING_BUFFER; + +/** + Initialize string buffer. + + @retval Non-null Buffer was created. + @retval NULL Out of memory. +**/ +OC_STRING_BUFFER * +OcAsciiStringBufferInit ( + VOID + ); + +/** + Append new string to buffer, resizing if necessary. + + @param[in,out] StringBuffer Buffer to modify. + @param[in] AppendString String to append. If NULL, nothing will be modified. + + @retval EFI_SUCCESS String was appended. + @retval EFI_OUT_OF_RESOURCES Out of memory. + @retval EFI_UNSUPPORTED Internal error. +**/ +EFI_STATUS +OcAsciiStringBufferAppend ( + IN OUT OC_STRING_BUFFER *Buffer, + IN CONST CHAR8 *AppendString OPTIONAL + ); + +/** + Append new substring to buffer, resizing if necessary. + + @param[in,out] StringBuffer Buffer to modify. + @param[in] AppendString String to append. If NULL, nothing will be modified. + @param[in] Length Maxiumum length of substring to use. 0 means use 0 characters. + Use MAX_UINTN for all chars. Ignored if AppendString is NULL. + + @retval EFI_SUCCESS String was appended. + @retval EFI_OUT_OF_RESOURCES Out of memory. + @retval EFI_UNSUPPORTED Internal error. +**/ +EFI_STATUS +OcAsciiStringBufferAppendN ( + IN OUT OC_STRING_BUFFER *Buffer, + IN CONST CHAR8 *AppendString, OPTIONAL + IN CONST UINTN Length + ); + + +/** + Safely print to string buffer. + + @param[in,out] StringBuffer Buffer to modify. + @param[in] FormatString A Null-terminated ASCII format string. + @param[in] ... Variable argument list whose contents are accessed based on the + format string specified by FormatString. + + @retval EFI_SUCCESS When data was printed to string buffer. + @retval EFI_OUT_OF_RESOURCES Out of memory increasing string buffer sufficiently to print to. +**/ +EFI_STATUS +EFIAPI +OcAsciiStringBufferSPrint ( + IN OUT OC_STRING_BUFFER *Buffer, + IN CONST CHAR8 *FormatString, + ... + ); + +/** + Free string buffer memory and return pointer to pool allocated resultant string. + Note that if no data was ever appended to the string then the return value + will be NULL, not a zero length string. + + @param[in,out] StringBuffer StringBuffer to free. + + @retval Non-NULL Pointer to pool allocated accumulated string. + @retval NULL No data was ever added to the string. +**/ +CHAR8 * +OcAsciiStringBufferFreeContainer ( + IN OUT OC_STRING_BUFFER **StringBuffer + ); + +/** + Free string buffer memory; free and discard any allocated resultant string. + + @param[in,out] StringBuffer StringBuffer to free. + + @retval Non-NULL Pointer to pool allocated accumulated string. + @retval NULL No data was ever added to the string. +**/ +VOID +OcAsciiStringBufferFree ( + IN OUT OC_STRING_BUFFER **StringBuffer + ); + +/* + String buffer. +*/ +struct OC_STRING_BUFFER_ { + CHAR8 *String; + UINTN StringLength; + UINTN BufferSize; + }; + +#endif // OC_FLEX_ARRAY_LIB_H diff --git a/Include/Acidanthera/Library/OcMiscLib.h b/Include/Acidanthera/Library/OcMiscLib.h index 7c49f3c6..43c9840e 100755 --- a/Include/Acidanthera/Library/OcMiscLib.h +++ b/Include/Acidanthera/Library/OcMiscLib.h @@ -29,6 +29,11 @@ **/ #define SECONDS_TO_MICROSECONDS(x) ((x)*1000000) +/** + Character length of EFI_GUID string representation. +**/ +#define OC_EFI_GUID_STR_LEN (sizeof (EFI_GUID) * 2 + 4) + BOOLEAN FindPattern ( IN CONST UINT8 *Pattern, diff --git a/Include/Acidanthera/Library/OcStringLib.h b/Include/Acidanthera/Library/OcStringLib.h index 08fa05a2..1d4b9297 100755 --- a/Include/Acidanthera/Library/OcStringLib.h +++ b/Include/Acidanthera/Library/OcStringLib.h @@ -434,6 +434,19 @@ OcAsciiPrintBuffer ( ... ); +/** + Convert a null-terminated ASCII string, in-place, to all lowercase. + Then return it. + + @param Str The null-terminated string to be converted to all lowercase. + + @return The null-terminated string converted into all lowercase. +**/ +CHAR8 * +OcAsciiToLower ( + CHAR8 *Str + ); + /** Returns the first occurrence of a Null-terminated Unicode sub-string in a Null-terminated Unicode string through a case insensitive comparison. @@ -682,4 +695,33 @@ MixedStrCmp ( IN CONST CHAR8 *SecondString ); +/** + Function to reverse sort when comparing by Unicode strings using UefiSortLib PerformQuickSort. + + @param[in] Buffer1 The pointer to String to compare (CHAR16**). + @param[in] Buffer2 The pointer to second String to compare (CHAR16**). + + @retval 0 Buffer1 equal to Buffer2. + @return < 0 Buffer1 is less than Buffer2. + @return > 0 Buffer1 is greater than Buffer2. +**/ +INTN +EFIAPI +OcReverseStringCompare ( + IN CONST VOID *Buffer1, + IN CONST VOID *Buffer2 + ); + +/** + Determine if a particular character is whitespace. + + @param[in] Ch The character to check. + + @return Returns TRUE if Ch is a whitespace character. +**/ +BOOLEAN +OcIsSpace ( + CHAR16 Ch + ); + #endif // OC_STRING_LIB_H diff --git a/Include/Acidanthera/Protocol/OcBootEntry.h b/Include/Acidanthera/Protocol/OcBootEntry.h new file mode 100644 index 00000000..7822aac8 --- /dev/null +++ b/Include/Acidanthera/Protocol/OcBootEntry.h @@ -0,0 +1,99 @@ +/** @file + OpenCore Boot Entry Protocol. + + Copyright (C) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#ifndef OC_BOOT_ENTRY_PROTOCOL_H +#define OC_BOOT_ENTRY_PROTOCOL_H + +#include +#include + +#include + +/** + 8604716E-ADD4-45B4-8495-08E36D497F4F +**/ +#define OC_BOOT_ENTRY_PROTOCOL_GUID \ + { \ + 0x8604716E, 0xADD4, 0x45B4, { 0x84, 0x95, 0x08, 0xE3, 0x6D, 0x49, 0x7F, 0x4F } \ + } + +/** + Currently supported OC_BOOT_ENTRY_PROTOCOL protocol revision. + Needs to be changed every time the contract changes, including when + passed-in structures OC_PICKER_ENTRY and OC_PICKER_ENTRY change. + + WARNING: This protocol is currently undergoing active design. +**/ +#define OC_BOOT_ENTRY_PROTOCOL_REVISION 1 + +/** + Forward declaration of OC_BOOT_ENTRY_PROTOCOL structure. +**/ +typedef struct OC_BOOT_ENTRY_PROTOCOL_ OC_BOOT_ENTRY_PROTOCOL; + +/** + Return list of OpenCore boot entries associated with filesystem. + + @param[in] PickerContext Picker context. + @param[in] Device The handle of the device to scan. NULL is passed in to + request custom entries. All implementations must support a + NULL input value, but may immediately return EFI_NOT_FOUND + if they do not provide any custom entries. + @param[out] BootEntries List of boot entries associated with the filesystem. + On EFI_SUCCESS BootEntries must be freed by the caller + with FreePool after use, and each individual boot entry + should eventually be freed by FreeBootEntry (as for boot + entries created within OC itself). + Does not point to allocated memory on return, if any status + other than EFI_SUCCESS was returned. + @param[out] NumEntries The number of entries returned in the BootEntries list. + If any status other than EFI_SUCCESS was returned, this + value may not have been initialised and should be ignored + by the caller. + + @retval EFI_SUCCESS At least one matching entry was found, and the list and + count of boot entries has been returned. + @retval EFI_NOT_FOUND No matching boot entries were found. + @retval EFI_OUT_OF_RESOURCES Memory allocation failure. + @retval other An error returned by a sub-operation. +**/ +typedef +EFI_STATUS +(EFIAPI *OC_GET_BOOT_ENTRIES) ( + IN OC_PICKER_CONTEXT *PickerContext, + IN CONST EFI_HANDLE Device, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ); + +/** + Free list of OpenCore boot entries from previous call to OC_GET_BOOT_ENTRIES. + + @param[in] Entries List of boot entries, as returned by previous call. + Correct implementation of interface should additionally + zero this pointer before returning. + @param[in] NumEntries The number of entries, as returned by previous call. +**/ +typedef +VOID +(EFIAPI *OC_FREE_BOOT_ENTRIES) ( + IN OC_PICKER_ENTRY **Entries, + IN UINTN NumEntries + ); + +/** + The structure exposed by the OC_BOOT_ENTRY_PROTOCOL. +**/ +struct OC_BOOT_ENTRY_PROTOCOL_ { + UINTN Revision; + OC_GET_BOOT_ENTRIES GetBootEntries; + OC_FREE_BOOT_ENTRIES FreeBootEntries; +}; + +extern EFI_GUID gOcBootEntryProtocolGuid; + +#endif // OC_BOOT_ENTRY_PROTOCOL_H diff --git a/Library/OcBootManagementLib/BootArguments.c b/Library/OcBootManagementLib/BootArguments.c index 72957308..d0d74c17 100644 --- a/Library/OcBootManagementLib/BootArguments.c +++ b/Library/OcBootManagementLib/BootArguments.c @@ -1,15 +1,6 @@ /** @file - Copyright (C) 2019, vit9696. All rights reserved. - - All rights reserved. - - This program and the accompanying materials - are licensed and made available under the terms and conditions of the BSD License - which accompanies this distribution. The full text of the license may be found at - http://opensource.org/licenses/bsd-license.php - - THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + Copyright (C) 2019-2021, vit9696, mikebeaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause **/ #include @@ -18,13 +9,37 @@ #include #include #include -#include +#include #include #include +#include #include #include "BootManagementInternal.h" +/* + Shell var and load options processing states. +*/ +typedef enum PARSE_VARS_STATE_ { + PARSE_VARS_WHITE_SPACE, + PARSE_VARS_COMMENT, + PARSE_VARS_NAME, + PARSE_VARS_VALUE_FIRST, + PARSE_VARS_VALUE, + PARSE_VARS_QUOTED_VALUE, + PARSE_VARS_SHELL_EXPANSION +} PARSE_VARS_STATE; + + +// +// Shift from token start to current position forwards by offset characters. +// +#define SHIFT_TOKEN(pos, token, offset) { \ + CopyMem ((UINT8 *)(token) + (offset), (token), (UINT8 *)(pos) - (UINT8 *)(token)); \ + (token) = (UINT8 *)(token) + (offset); \ + (pos) = (UINT8 *)(token) + (offset); \ +} + VOID OcParseBootArgs ( OUT OC_BOOT_ARGUMENTS *Arguments, @@ -360,3 +375,348 @@ OcCheckArgumentFromEnv ( return HasArgument; } + +EFI_STATUS +OcParseLoadOptions ( + IN CONST EFI_LOADED_IMAGE_PROTOCOL *LoadedImage, + OUT OC_FLEX_ARRAY **ParsedVars + ) +{ + EFI_STATUS Status; + + ASSERT (LoadedImage != NULL); + ASSERT (ParsedVars != NULL); + *ParsedVars = NULL; + + if (LoadedImage->LoadOptionsSize % sizeof (CHAR16) != 0 || LoadedImage->LoadOptionsSize > MAX_LOAD_OPTIONS_SIZE) { + DEBUG ((DEBUG_ERROR, "OCB: Invalid LoadOptions (%p:%u)\n", LoadedImage->LoadOptions, LoadedImage->LoadOptionsSize)); + return EFI_INVALID_PARAMETER; + } + + if (LoadedImage->LoadOptions == NULL || + LoadedImage->LoadOptionsSize == 0 || + ((CHAR16 *)LoadedImage->LoadOptions)[0] == CHAR_NULL) { + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: No LoadOptions (%p:%u)\n", LoadedImage->LoadOptions, LoadedImage->LoadOptionsSize)); + return EFI_NOT_FOUND; + } + + Status = OcParseVars (LoadedImage->LoadOptions, ParsedVars, TRUE); + + if (Status == EFI_INVALID_PARAMETER) { + DEBUG ((DEBUG_ERROR, "OCB: Invalid LoadOptions (%p:%u)\n", LoadedImage->LoadOptions, LoadedImage->LoadOptionsSize)); + } else if (Status == EFI_NOT_FOUND) { + DEBUG ((DEBUG_WARN, "OCB: Empty LoadOptions\n")); + } + + return Status; +} + +EFI_STATUS +OcParseVars ( + IN VOID *StrVars, + OUT OC_FLEX_ARRAY **ParsedVars, + IN CONST BOOLEAN IsUnicode + ) +{ + VOID *Pos; + PARSE_VARS_STATE State; + PARSE_VARS_STATE PushState; + BOOLEAN Retake; + CHAR16 Ch; + VOID *Name; + VOID *Value; + OC_PARSED_VAR *Option; + + if (StrVars == NULL || (IsUnicode ? ((CHAR16 *) StrVars)[0] == CHAR_NULL : ((CHAR8 *) StrVars)[0] == '\0')) { + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: No vars (%p)\n", StrVars)); + return EFI_NOT_FOUND; + } + + *ParsedVars = OcFlexArrayInit (sizeof (OC_PARSED_VAR), NULL); + if (*ParsedVars == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Pos = StrVars; + State = PARSE_VARS_WHITE_SPACE; + PushState = PARSE_VARS_WHITE_SPACE; + Retake = FALSE; + + do { + Ch = IsUnicode ? *((CHAR16 *) Pos) : *((CHAR8 *) Pos); + switch (State) { + case PARSE_VARS_WHITE_SPACE: + if (Ch == '#') { + State = PARSE_VARS_COMMENT; + } else if (!(OcIsSpace (Ch) || Ch == CHAR_NULL)) { + Name = Pos; + State = PARSE_VARS_NAME; + } + break; + + case PARSE_VARS_COMMENT: + if (Ch == '\n') { + State = PARSE_VARS_WHITE_SPACE; + } + break; + + case PARSE_VARS_NAME: + if (Ch == L'=' || OcIsSpace (Ch) || Ch == CHAR_NULL) { + if (IsUnicode) { + *((CHAR16 *) Pos) = CHAR_NULL; + } else { + *((CHAR8 *) Pos) = '\0'; + } + if (Ch == L'=') { + State = PARSE_VARS_VALUE_FIRST; + } else { + State = PARSE_VARS_WHITE_SPACE; + } + Option = OcFlexArrayAddItem (*ParsedVars); + if (Option == NULL) { + OcFlexArrayFree (ParsedVars); + return EFI_OUT_OF_RESOURCES; + } + if (IsUnicode) { + Option->Unicode.Name = Name; + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: Name=\"%s\"\n", Name)); + } else { + Option->Ascii.Name = Name; + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: Name=\"%a\"\n", Name)); + } + if (State == PARSE_VARS_WHITE_SPACE) { + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: No value %u\n", 1)); + } + Name = NULL; + } + break; + + case PARSE_VARS_VALUE_FIRST: + if (Ch == L'"') { + State = PARSE_VARS_QUOTED_VALUE; + Value = (UINT8 *) Pos + (IsUnicode ? sizeof (CHAR16) : sizeof (CHAR8)); + } else { + State = PARSE_VARS_VALUE; + Value = Pos; + Retake = TRUE; + } + break; + + case PARSE_VARS_SHELL_EXPANSION: + if (Ch == '`') { + ASSERT (PushState != PARSE_VARS_WHITE_SPACE); + State = PushState; + } + break; + + case PARSE_VARS_VALUE: + case PARSE_VARS_QUOTED_VALUE: + if (Ch == L'`') { + PushState = State; + State = PARSE_VARS_SHELL_EXPANSION; + } else if (Ch == L'\\') { + SHIFT_TOKEN (Pos, Value, IsUnicode ? sizeof (CHAR16) : sizeof (CHAR8)); + } else if ( + (State == PARSE_VARS_VALUE && (OcIsSpace (Ch) || Ch == CHAR_NULL)) || + (State == PARSE_VARS_QUOTED_VALUE && Ch == '"')) { + // + // Explicitly quoted empty string needs to be stored detectably + // differently from missing value. + // + if (State != PARSE_VARS_QUOTED_VALUE && Pos == Value) { + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: No value %u\n", 2)); + } else { + if (PushState != PARSE_VARS_WHITE_SPACE) { + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: Found shell expansion, cancelling value\n")); + PushState = PARSE_VARS_WHITE_SPACE; + } else { + if (IsUnicode) { + *((CHAR16 *) Pos) = CHAR_NULL; + Option->Unicode.Value = Value; + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: Value=\"%s\"\n", Value)); + } else { + *((CHAR8 *) Pos) = '\0'; + Option->Ascii.Value = Value; + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: Value=\"%a\"\n", Value)); + } + } + } + Value = NULL; + Option = NULL; + State = PARSE_VARS_WHITE_SPACE; + } + break; + + default: + ASSERT (FALSE); + break; + } + + if (Retake) { + Retake = FALSE; + } else { + Pos = (UINT8 *) Pos + (IsUnicode ? sizeof (CHAR16) : sizeof (CHAR8)); + } + } while (Ch != CHAR_NULL); + + if (State != PARSE_VARS_WHITE_SPACE || PushState != PARSE_VARS_WHITE_SPACE) { + // + // E.g. for GRUB config files this may potentially be caused by a file + // neither we nor the user directly controls, so better warn than error. + // + DEBUG ((DEBUG_WARN, "OCB: Invalid vars (%u)\n", State)); + OcFlexArrayFree (ParsedVars); + return EFI_INVALID_PARAMETER; + } + + if ((*ParsedVars)->Items == NULL) { + OcFlexArrayFree (ParsedVars); + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +BOOLEAN +OcParsedVarsGetStr ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + OUT VOID **Value, + IN CONST BOOLEAN IsUnicode + ) +{ + UINTN Index; + OC_PARSED_VAR *Option; + + ASSERT (Name != NULL); + ASSERT (Value != NULL); + + if (ParsedVars == NULL) { + return FALSE; + } + + ASSERT (ParsedVars->Items != NULL); + + for (Index = 0; Index < ParsedVars->Count; ++Index) { + Option = OcFlexArrayItemAt (ParsedVars, Index); + if (IsUnicode) { + ASSERT (Option->Unicode.Name != NULL); + if (StrCmp (Option->Unicode.Name, Name) == 0) { + *Value = Option->Unicode.Value; + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: Using \"%s\"=\"%s\"\n", Name, *Value)); + return TRUE; + } + } else { + ASSERT (Option->Ascii.Name != NULL); + if (AsciiStrCmp (Option->Ascii.Name, Name) == 0) { + *Value = Option->Ascii.Value; + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: Using \"%a\"=\"%a\"\n", Name, *Value)); + return TRUE; + } + } + } + + if (IsUnicode) { + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: No value for \"%s\"\n", Name)); + } else { + DEBUG ((OC_TRACE_PARSE_VARS, "OCB: No value for \"%a\"\n", Name)); + } + + return FALSE; +} + +BOOLEAN +OcParsedVarsGetUnicodeStr ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST CHAR16 *Name, + OUT CHAR16 **Value + ) +{ + return OcParsedVarsGetStr (ParsedVars, Name, (VOID**)Value, TRUE); +} + +BOOLEAN +OcParsedVarsGetAsciiStr ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST CHAR8 *Name, + OUT CHAR8 **Value + ) +{ + return OcParsedVarsGetStr (ParsedVars, Name, (VOID**)Value, FALSE); +} + +BOOLEAN +OcHasParsedVar ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + IN CONST BOOLEAN IsUnicode + ) +{ + VOID *Value; + + return OcParsedVarsGetStr (ParsedVars, Name, &Value, IsUnicode); +} + +EFI_STATUS +OcParsedVarsGetInt ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + OUT UINTN *Value, + IN CONST BOOLEAN IsUnicode + ) +{ + EFI_STATUS Status; + VOID *StrValue; + + if (!OcParsedVarsGetStr (ParsedVars, Name, &StrValue, IsUnicode)) { + return EFI_NOT_FOUND; + } + + if (StrValue == NULL) { + return EFI_NOT_FOUND; + } + + if (IsUnicode){ + if (OcUnicodeStartsWith (StrValue, L"0x", TRUE)) { + Status = StrHexToUintnS (StrValue, NULL, Value); + } else { + Status = StrDecimalToUintnS (StrValue, NULL, Value); + } + } else { + if (OcAsciiStartsWith (StrValue, "0x", TRUE)) { + Status = AsciiStrHexToUintnS (StrValue, NULL, Value); + } else { + Status = AsciiStrDecimalToUintnS (StrValue, NULL, Value); + } + } + + return Status; +} + +EFI_STATUS +OcParsedVarsGetGuid ( + IN CONST OC_FLEX_ARRAY *ParsedVars, + IN CONST VOID *Name, + OUT EFI_GUID *Value, + IN CONST BOOLEAN IsUnicode + ) +{ + EFI_STATUS Status; + VOID *StrValue; + + if (!OcParsedVarsGetStr (ParsedVars, Name, &StrValue, IsUnicode)) { + return EFI_NOT_FOUND; + } + + if (StrValue == NULL) { + return EFI_NOT_FOUND; + } + + if (IsUnicode) { + Status = StrToGuid (StrValue, Value); + } else { + Status = AsciiStrToGuid (StrValue, Value); + } + + return Status; +} diff --git a/Library/OcBootManagementLib/BootEntryManagement.c b/Library/OcBootManagementLib/BootEntryManagement.c index 5b168ab0..4c5e4767 100644 --- a/Library/OcBootManagementLib/BootEntryManagement.c +++ b/Library/OcBootManagementLib/BootEntryManagement.c @@ -1,18 +1,10 @@ /** @file - Copyright (C) 2019, vit9696. All rights reserved. - - All rights reserved. - - This program and the accompanying materials - are licensed and made available under the terms and conditions of the BSD License - which accompanies this distribution. The full text of the license may be found at - http://opensource.org/licenses/bsd-license.php - - THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + Copyright (C) 2019-2021, vit9696, mikebeaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause **/ #include "BootManagementInternal.h" +#include "BootEntryProtocolInternal.h" #include #include @@ -22,6 +14,7 @@ #include #include #include +#include #include #include @@ -136,7 +129,7 @@ ExpandShortFormBootPath ( // // Check whether we are allowed to boot from this filesystem. // - *FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan); + *FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan, NULL); if (*FileSystem == NULL) { continue; } @@ -276,13 +269,14 @@ RegisterBootOption ( DEBUG (( DEBUG_INFO, - "OCB: Registering entry %s [%a] (T:%d|F:%d|G:%d|E:%d) - %s\n", + "OCB: Registering entry %s [%a] (T:%d|F:%d|G:%d|E:%d|B:%d) - %s\n", BootEntry->Name, BootEntry->Flavour, BootEntry->Type, BootEntry->IsFolder, BootEntry->IsGeneric, BootEntry->IsExternal, + BootEntry->IsBootEntryProtocol, OC_HUMAN_STRING (TextDevicePath) )); @@ -507,6 +501,51 @@ AddBootEntryOnFileSystem ( return EFI_SUCCESS; } +/** + Release boot entry contents allocated from pool. + + @param[in,out] BootEntry Located boot entry. +**/ +STATIC +VOID +FreeBootEntry ( + IN OC_BOOT_ENTRY *BootEntry + ) +{ + if (BootEntry->DevicePath != NULL) { + FreePool (BootEntry->DevicePath); + BootEntry->DevicePath = NULL; + } + + if (BootEntry->Id != NULL) { + FreePool (BootEntry->Id); + BootEntry->Id = NULL; + } + + if (BootEntry->Name != NULL) { + FreePool (BootEntry->Name); + BootEntry->Name = NULL; + } + + if (BootEntry->PathName != NULL) { + FreePool (BootEntry->PathName); + BootEntry->PathName = NULL; + } + + if (BootEntry->LoadOptions != NULL) { + FreePool (BootEntry->LoadOptions); + BootEntry->LoadOptions = NULL; + BootEntry->LoadOptionsSize = 0; + } + + if (BootEntry->Flavour != NULL) { + FreePool (BootEntry->Flavour); + BootEntry->Flavour = NULL; + } + + FreePool (BootEntry); +} + /** Create bootable entry from custom entry. @@ -516,12 +555,12 @@ AddBootEntryOnFileSystem ( @retval EFI_SUCCESS on success. **/ -STATIC EFI_STATUS -AddBootEntryFromCustomEntry ( +InternalAddBootEntryFromCustomEntry ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OUT OC_BOOT_FILESYSTEM *FileSystem, - IN OC_PICKER_ENTRY *CustomEntry + IN OC_PICKER_ENTRY *CustomEntry, + IN BOOLEAN IsBootEntryProtocol ) { EFI_STATUS Status; @@ -532,8 +571,17 @@ AddBootEntryFromCustomEntry ( CHAR16 *BootDirectoryName; EFI_HANDLE Device; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem; + CONST EFI_PARTITION_ENTRY *PartitionEntry; if (CustomEntry->Auxiliary && BootContext->PickerContext->HideAuxiliary) { + DEBUG (( + DEBUG_INFO, + "OCB: Not adding hidden auxiliary entry %a (%a|B:%d) -> %a\n", + CustomEntry->Name, + CustomEntry->Tool ? "tool" : "os", + IsBootEntryProtocol, + CustomEntry->Path + )); return EFI_UNSUPPORTED; } @@ -545,32 +593,38 @@ AddBootEntryFromCustomEntry ( return EFI_OUT_OF_RESOURCES; } + if (CustomEntry->Id != NULL) { + BootEntry->Id = AsciiStrCopyToUnicode (CustomEntry->Id, 0); + if (BootEntry->Id == NULL) { + FreeBootEntry (BootEntry); + return EFI_OUT_OF_RESOURCES; + } + } + BootEntry->Name = AsciiStrCopyToUnicode (CustomEntry->Name, 0); if (BootEntry->Name == NULL) { - FreePool (BootEntry); + FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } PathName = AsciiStrCopyToUnicode (CustomEntry->Path, 0); if (PathName == NULL) { - FreePool (BootEntry->Name); - FreePool (BootEntry); + FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } BootEntry->Flavour = AllocateCopyPool (AsciiStrSize (CustomEntry->Flavour), CustomEntry->Flavour); if (BootEntry->Flavour == NULL) { - FreePool (PathName); - FreePool (BootEntry->Name); - FreePool (BootEntry); + FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } DEBUG (( DEBUG_INFO, - "OCB: Adding custom entry %s (%a) -> %a\n", + "OCB: Adding custom entry %s (%a|B:%d) -> %a\n", BootEntry->Name, CustomEntry->Tool ? "tool" : "os", + IsBootEntryProtocol, CustomEntry->Path )); @@ -581,12 +635,19 @@ AddBootEntryFromCustomEntry ( } else { BootEntry->Type = OC_BOOT_EXTERNAL_OS; - BootEntry->DevicePath = ConvertTextToDevicePath (PathName); + // + // For boot entry protocol path is relative to device root, + // for user entry path is absolute device path. + // + if (IsBootEntryProtocol) { + UnicodeUefiSlashes (PathName); + BootEntry->DevicePath = FileDevicePath (FileSystem->Handle, PathName); + } else { + BootEntry->DevicePath = ConvertTextToDevicePath (PathName); + } FreePool (PathName); if (BootEntry->DevicePath == NULL) { - FreePool (BootEntry->Flavour); - FreePool (BootEntry->Name); - FreePool (BootEntry); + FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } @@ -598,10 +659,7 @@ AddBootEntryFromCustomEntry ( ) ); if (FilePath == NULL) { - FreePool (BootEntry->Flavour); - FreePool (BootEntry->Name); - FreePool (BootEntry->DevicePath); - FreePool (BootEntry); + FreeBootEntry (BootEntry); return EFI_UNSUPPORTED; } @@ -610,10 +668,7 @@ AddBootEntryFromCustomEntry ( FilePath->PathName ); if (BootEntry->PathName == NULL) { - FreePool (BootEntry->Flavour); - FreePool (BootEntry->Name); - FreePool (BootEntry->DevicePath); - FreePool (BootEntry); + FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } @@ -663,6 +718,15 @@ AddBootEntryFromCustomEntry ( } BootEntry->IsCustom = TRUE; + BootEntry->IsBootEntryProtocol = IsBootEntryProtocol; + if (IsBootEntryProtocol) { + PartitionEntry = OcGetGptPartitionEntry (FileSystem->Handle); + if (PartitionEntry == NULL) { + BootEntry->UniquePartitionGUID = gEfiPartTypeUnusedGuid; + } else { + BootEntry->UniquePartitionGUID = PartitionEntry->UniquePartitionGUID; + } + } RegisterBootOption ( BootContext, @@ -953,7 +1017,7 @@ AddBootEntryFromBless ( // Obtain recovery file system and ensure scan policy if it was not done before. // if (FileSystem->RecoveryFs == NULL) { - FileSystem->RecoveryFs = InternalFileSystemForHandle (BootContext, RecoveryDeviceHandle, LazyScan); + FileSystem->RecoveryFs = InternalFileSystemForHandle (BootContext, RecoveryDeviceHandle, LazyScan, NULL); } // @@ -1047,9 +1111,18 @@ AddBootEntryFromSelfRecovery ( /** Create bootable entries from boot options. - @param[in,out] BootContext Context of filesystems. - @param[in] BootOption Boot option number. - @param[in] LazyScan Lazy filesystem scanning. + @param[in,out] BootContext Context of filesystems. + @param[in] BootOption Boot option number. + @param[in] LazyScan Lazy filesystem scanning. + @param[in,out] CustomFileSystem File system on which to add user defined custom option. + If non-NULL still searching for first (normally only) OC + custom entry, either user defined or entry protocol. + @param[out] CustomIndex Index of custom user defined entry, if matched. + @param[in] EntryProtocolHandles Installed Boot Entry Protocol handles. + @param[in] EntryProtocolHandleCount Installed Boot Entry Protocol handle count. + @param[out] EntryProtocolPartuuid Unique partition UUID of parition with entry protocol + custom entry, if matched. + @param[out] EntryProtocolId Id of entry protocol custom entry, if matched. @retval EFI_SUCCESS if at least one option was added. **/ @@ -1060,7 +1133,11 @@ AddBootEntryFromBootOption ( IN UINT16 BootOption, IN BOOLEAN LazyScan, IN OUT OC_BOOT_FILESYSTEM *CustomFileSystem, - IN OUT UINT32 *CustomIndex + OUT UINT32 *CustomIndex, OPTIONAL + IN EFI_HANDLE *EntryProtocolHandles, + IN UINTN EntryProtocolHandleCount, + OUT EFI_GUID *EntryProtocolPartuuid, OPTIONAL + OUT CHAR16 **EntryProtocolId OPTIONAL ) { EFI_STATUS Status; @@ -1075,10 +1152,13 @@ AddBootEntryFromBootOption ( BOOLEAN IsRoot; EFI_LOAD_OPTION *LoadOption; UINTN LoadOptionSize; + LIST_ENTRY *Link; + UINT32 Index; + INTN CmpResult; - CONST OC_CUSTOM_BOOT_DEVICE_PATH *CustomDevPath; - UINT32 Index; - INTN CmpResult; + CONST EFI_PARTITION_ENTRY *PartitionEntry; + CONST OC_CUSTOM_BOOT_DEVICE_PATH *CustomDevPath; + CONST OC_ENTRY_PROTOCOL_DEVICE_PATH *EntryProtocolDevPath; DEBUG ((DEBUG_INFO, "OCB: Building entry from Boot%04x\n", BootOption)); @@ -1186,7 +1266,7 @@ AddBootEntryFromBootOption ( // Ensure that we are allowed to boot from this filesystem. // if (DevicePath != NULL) { - FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan); + FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan, NULL); if (FileSystem == NULL) { DevicePath = NULL; } @@ -1247,28 +1327,87 @@ AddBootEntryFromBootOption ( NULL ); } while (NumPatchedNodes > 0); - // - // If requested, pre-construct a custom entry found in BOOT#### so it can be - // set as default. - // + if (ExpandedDevicePath == NULL && CustomFileSystem != NULL) { - ASSERT (CustomIndex != NULL); + // + // If non-standard device path, attempt to pre-construct a user config + // custom entry found in BOOT#### so it can be set as default. + // + ASSERT (CustomIndex == NULL || *CustomIndex == MAX_UINT32); CustomDevPath = InternalGetOcCustomDevPath (DevicePath); - for (Index = 0; Index < BootContext->PickerContext->AllCustomEntryCount; ++Index) { - CmpResult = MixedStrCmp ( - CustomDevPath->EntryName.PathName, - BootContext->PickerContext->CustomEntries[Index].Name - ); - if (CmpResult == 0) { - *CustomIndex = Index; - AddBootEntryFromCustomEntry ( - BootContext, - CustomFileSystem, - &BootContext->PickerContext->CustomEntries[Index] + if (CustomDevPath != NULL) { + for (Index = 0; Index < BootContext->PickerContext->AllCustomEntryCount; ++Index) { + CmpResult = MixedStrCmp ( + CustomDevPath->EntryName.PathName, + BootContext->PickerContext->CustomEntries[Index].Name ); - break; + if (CmpResult == 0) { + if (CustomIndex != NULL) { + *CustomIndex = Index; + } + InternalAddBootEntryFromCustomEntry ( + BootContext, + CustomFileSystem, + &BootContext->PickerContext->CustomEntries[Index], + FALSE + ); + break; + } + } + } else { + // + // If still unknown device path, attempt to pre-construct an entry protocol + // entry found in BOOT#### so it can be set as default. + // + ASSERT (EntryProtocolId == NULL || *EntryProtocolId == NULL); + ASSERT ((EntryProtocolPartuuid == NULL) == (EntryProtocolId == NULL)); + + EntryProtocolDevPath = InternalGetOcEntryProtocolDevPath (DevicePath); + + if (EntryProtocolDevPath != NULL) { + for ( + Link = GetFirstNode (&BootContext->FileSystems); + !IsNull (&BootContext->FileSystems, Link); + Link = GetNextNode (&BootContext->FileSystems, Link)) { + FileSystem = BASE_CR (Link, OC_BOOT_FILESYSTEM, Link); + + // + // Search for ID on matching device only. + // Note that on, e.g., OVMF, every single device has partition UUID 00000000-0000-0000-0000000000000000, + // therefore the first matching entry protocol ID on *any* filesystem will match. + // + PartitionEntry = OcGetGptPartitionEntry (FileSystem->Handle); + if (PartitionEntry == NULL) { + continue; + } + if (CompareMem ( + &PartitionEntry->UniquePartitionGUID, + &EntryProtocolDevPath->Partuuid, + sizeof (EFI_GUID)) == 0) { + Status = AddEntriesFromBootEntryProtocol ( + BootContext, + FileSystem, + EntryProtocolHandles, + EntryProtocolHandleCount, + EntryProtocolDevPath->EntryName.PathName, + TRUE + ); + if (!EFI_ERROR (Status)) { + if (EntryProtocolPartuuid != NULL) { + *EntryProtocolPartuuid = PartitionEntry->UniquePartitionGUID; + } + if (EntryProtocolId != NULL) { + *EntryProtocolId = AllocateCopyPool (StrSize (EntryProtocolDevPath->EntryName.PathName), EntryProtocolDevPath->EntryName.PathName); + // + // If NULL allocated, just continue as if we had not matched. + // + } + break; + } + } + } } } } @@ -1366,46 +1505,6 @@ AddBootEntryFromBootOption ( return Status; } -/** - Release boot entry contents allocated from pool. - - @param[in,out] BootEntry Located boot entry. -**/ -STATIC -VOID -FreeBootEntry ( - IN OC_BOOT_ENTRY *BootEntry - ) -{ - if (BootEntry->DevicePath != NULL) { - FreePool (BootEntry->DevicePath); - BootEntry->DevicePath = NULL; - } - - if (BootEntry->Name != NULL) { - FreePool (BootEntry->Name); - BootEntry->Name = NULL; - } - - if (BootEntry->PathName != NULL) { - FreePool (BootEntry->PathName); - BootEntry->PathName = NULL; - } - - if (BootEntry->LoadOptions != NULL) { - FreePool (BootEntry->LoadOptions); - BootEntry->LoadOptions = NULL; - BootEntry->LoadOptionsSize = 0; - } - - if (BootEntry->Flavour != NULL) { - FreePool (BootEntry->Flavour); - BootEntry->Flavour = NULL; - } - - FreePool (BootEntry); -} - /** Allocate a new filesystem entry in boot entries in case it can be used according to current ScanPolicy. @@ -1549,10 +1648,11 @@ AddFileSystemEntryForCustom ( continue; } - Status = AddBootEntryFromCustomEntry ( + Status = InternalAddBootEntryFromCustomEntry ( BootContext, FileSystem, - &BootContext->PickerContext->CustomEntries[Index] + &BootContext->PickerContext->CustomEntries[Index], + FALSE ); if (!EFI_ERROR (Status)) { @@ -1621,14 +1721,19 @@ FreeFileSystemEntry ( OC_BOOT_FILESYSTEM * InternalFileSystemForHandle ( - IN OC_BOOT_CONTEXT *BootContext, - IN EFI_HANDLE FileSystemHandle, - IN BOOLEAN LazyScan + IN OC_BOOT_CONTEXT *BootContext, + IN EFI_HANDLE FileSystemHandle, + IN BOOLEAN LazyScan, + OUT BOOLEAN *AlreadySeen OPTIONAL ) { EFI_STATUS Status; LIST_ENTRY *Link; - OC_BOOT_FILESYSTEM *FileSystem; + OC_BOOT_FILESYSTEM *FileSystem; + + if (AlreadySeen != NULL) { + *AlreadySeen = FALSE; + } for ( Link = GetFirstNode (&BootContext->FileSystems); @@ -1638,6 +1743,9 @@ InternalFileSystemForHandle ( if (FileSystem->Handle == FileSystemHandle) { DEBUG ((DEBUG_INFO, "OCB: Matched fs %p%a\n", FileSystemHandle, LazyScan ? " (lazy)" : "")); + if (AlreadySeen != NULL) { + *AlreadySeen = TRUE; + } return FileSystem; } } @@ -1824,6 +1932,11 @@ OcScanForBootEntries ( OC_BOOT_FILESYSTEM *CustomFileSystem; OC_BOOT_FILESYSTEM *CustomFileSystemDefault; UINT32 DefaultCustomIndex; + EFI_GUID DefaultEntryProtocolPartuuid; + CHAR16 *DefaultEntryProtocolId; + EFI_HANDLE *EntryProtocolHandles; + UINTN EntryProtocolHandleCount; + CONST EFI_PARTITION_ENTRY *PartitionEntry; // // Obtain the list of filesystems filtered by scan policy. @@ -1838,6 +1951,11 @@ OcScanForBootEntries ( DEBUG ((DEBUG_INFO, "OCB: Found %u potentially bootable filesystems\n", (UINT32) BootContext->FileSystemCount)); + // + // Locate loaded boot entry protocol drivers. + // + LocateBootEntryProtocolHandles (&EntryProtocolHandles, &EntryProtocolHandleCount); + // // Create primary boot options from BootOrder. // @@ -1855,7 +1973,8 @@ OcScanForBootEntries ( // Delay CustomFileSystem insertion to have custom entries at the end. // - DefaultCustomIndex = MAX_UINT32; + DefaultCustomIndex = MAX_UINT32; + DefaultEntryProtocolId = NULL; if (Context->BootOrder != NULL) { CustomFileSystemDefault = CustomFileSystem; @@ -1866,14 +1985,18 @@ OcScanForBootEntries ( Context->BootOrder[Index], FALSE, CustomFileSystemDefault, - &DefaultCustomIndex + &DefaultCustomIndex, + EntryProtocolHandles, + EntryProtocolHandleCount, + &DefaultEntryProtocolPartuuid, + &DefaultEntryProtocolId ); // // Pre-create at most one custom entry. Under normal circumstances, no // more than one entry should exist anyway. // - if (DefaultCustomIndex != MAX_UINT32) { + if (DefaultCustomIndex != MAX_UINT32 || DefaultEntryProtocolId != NULL) { CustomFileSystemDefault = NULL; } } @@ -1905,12 +2028,37 @@ OcScanForBootEntries ( ); } + // + // Try boot entry protocol. + // Entry protocol entries should almost certainly be added regardless + // of bless; e.g. user might well have /loader/entries in ESP, in addition + // to normal blessed files. + // + PartitionEntry = OcGetGptPartitionEntry (FileSystem->Handle); + if (PartitionEntry != NULL) { + AddEntriesFromBootEntryProtocol ( + BootContext, + FileSystem, + EntryProtocolHandles, + EntryProtocolHandleCount, + CompareMem (&DefaultEntryProtocolPartuuid, &PartitionEntry->UniquePartitionGUID, sizeof (EFI_GUID)) == 0 ? + DefaultEntryProtocolId : + NULL, + FALSE + ); + } + // // Record predefined recoveries. // AddBootEntryFromSelfRecovery (BootContext, FileSystem); } + if (DefaultEntryProtocolId != NULL) { + FreePool (DefaultEntryProtocolId); + DefaultEntryProtocolId = NULL; + } + if (CustomFileSystem != NULL) { // // Insert the custom file system last for entry order. @@ -1922,8 +2070,22 @@ OcScanForBootEntries ( // Build custom and system options. // AddFileSystemEntryForCustom (BootContext, CustomFileSystem, DefaultCustomIndex); + + // + // Boot entry protocol also supports custom and system entries. + // + AddEntriesFromBootEntryProtocol ( + BootContext, + CustomFileSystem, + EntryProtocolHandles, + EntryProtocolHandleCount, + NULL, + FALSE + ); } + FreeBootEntryProtocolHandles (&EntryProtocolHandles); + if (BootContext->BootEntryCount == 0) { OcFreeBootContext (BootContext); return NULL; @@ -1947,11 +2109,13 @@ OcScanForDefaultBootEntry ( OC_BOOT_CONTEXT *BootContext; UINTN Index; OC_BOOT_FILESYSTEM *FileSystem; + BOOLEAN AlreadySeen; EFI_STATUS Status; UINTN NoHandles; EFI_HANDLE *Handles; - UINT32 DefaultCustomIndex; OC_BOOT_FILESYSTEM *CustomFileSystem; + EFI_HANDLE *EntryProtocolHandles; + UINTN EntryProtocolHandleCount; // // Obtain empty list of filesystems. @@ -1963,6 +2127,11 @@ OcScanForDefaultBootEntry ( DEBUG ((DEBUG_INFO, "OCB: Looking up for default entry\n")); + // + // Locate loaded boot entry protocol drivers. + // + LocateBootEntryProtocolHandles (&EntryProtocolHandles, &EntryProtocolHandleCount); + // // Create primary boot options from BootOrder. // @@ -1986,20 +2155,26 @@ OcScanForDefaultBootEntry ( if (Context->BootOrder != NULL) { for (Index = 0; Index < Context->BootOrderCount; ++Index) { // - // DefaultCustomIndex is not used as the entry list will never be shown. + // Returned default entry values not required, as no other + // entries will be created after a match here. // AddBootEntryFromBootOption ( BootContext, Context->BootOrder[Index], TRUE, CustomFileSystem, - &DefaultCustomIndex + NULL, + EntryProtocolHandles, + EntryProtocolHandleCount, + NULL, + NULL ); // // Return as long as we are good. // if (BootContext->DefaultEntry != NULL) { + FreeBootEntryProtocolHandles (&EntryProtocolHandles); return BootContext; } } @@ -2022,36 +2197,50 @@ OcScanForDefaultBootEntry ( if (!EFI_ERROR (Status)) { for (Index = 0; Index < NoHandles; ++Index) { // - // Do not add filesystems twice. + // If file system has been seen during BOOT#### entry processing then + // bless has already been processed (and failed or we would not be here). // - if (InternalFileSystemForHandle (BootContext, Handles[Index], FALSE) != NULL) { + FileSystem = InternalFileSystemForHandle (BootContext, Handles[Index], TRUE, &AlreadySeen); + if (FileSystem == NULL) { continue; } - - Status = AddFileSystemEntry ( - BootContext, - Handles[Index], - &FileSystem - ); - if (EFI_ERROR (Status)) { - continue; + if (!AlreadySeen) { + AddBootEntryFromBless ( + BootContext, + FileSystem, + gAppleBootPolicyPredefinedPaths, + gAppleBootPolicyNumPredefinedPaths, + FALSE, + FALSE + ); + if (BootContext->DefaultEntry != NULL) { + FreeBootEntryProtocolHandles (&EntryProtocolHandles); + FreePool (Handles); + return BootContext; + } } - AddBootEntryFromBless ( + // + // Try boot entry protocol. No need to deduplicate as won't reach + // here if default entry from BOOT#### was successfully created. + // + AddEntriesFromBootEntryProtocol ( BootContext, FileSystem, - gAppleBootPolicyPredefinedPaths, - gAppleBootPolicyNumPredefinedPaths, - FALSE, + EntryProtocolHandles, + EntryProtocolHandleCount, + NULL, FALSE ); if (BootContext->DefaultEntry != NULL) { + FreeBootEntryProtocolHandles (&EntryProtocolHandles); FreePool (Handles); return BootContext; } AddBootEntryFromSelfRecovery (BootContext, FileSystem); if (BootContext->DefaultEntry != NULL) { + FreeBootEntryProtocolHandles (&EntryProtocolHandles); FreePool (Handles); return BootContext; } @@ -2066,6 +2255,24 @@ OcScanForDefaultBootEntry ( // as the list is never shown. // AddFileSystemEntryForCustom (BootContext, CustomFileSystem, MAX_UINT32); + if (BootContext->DefaultEntry != NULL) { + FreeBootEntryProtocolHandles (&EntryProtocolHandles); + return BootContext; + } + + // + // Boot entry protocol for custom and system entries. + // + AddEntriesFromBootEntryProtocol ( + BootContext, + CustomFileSystem, + EntryProtocolHandles, + EntryProtocolHandleCount, + NULL, + FALSE + ); + + FreeBootEntryProtocolHandles (&EntryProtocolHandles); } if (BootContext->DefaultEntry == NULL) { diff --git a/Library/OcBootManagementLib/BootEntryProtocol.c b/Library/OcBootManagementLib/BootEntryProtocol.c new file mode 100644 index 00000000..b38702e8 --- /dev/null +++ b/Library/OcBootManagementLib/BootEntryProtocol.c @@ -0,0 +1,199 @@ +/** @file + Boot Entry Protocol. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "BootEntryProtocolInternal.h" +#include "BootManagementInternal.h" + +#include + +#include +#include +#include +#include + +VOID +LocateBootEntryProtocolHandles ( + IN OUT EFI_HANDLE **EntryProtocolHandles, + IN OUT UINTN *EntryProtocolHandleCount + ) +{ + EFI_STATUS Status; + + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gOcBootEntryProtocolGuid, + NULL, + EntryProtocolHandleCount, + EntryProtocolHandles + ); + + if (EFI_ERROR (Status)) { + // + // No loaded drivers is fine + // + if (Status != EFI_NOT_FOUND) { + DEBUG ((DEBUG_ERROR, "BEP: Error locating driver handles - %r\n", Status)); + } + + *EntryProtocolHandleCount = 0; + *EntryProtocolHandles = NULL; + } +} + +VOID +FreeBootEntryProtocolHandles ( + EFI_HANDLE **EntryProtocolHandles + ) +{ + if (*EntryProtocolHandles == NULL) { + return; + } + + FreePool (*EntryProtocolHandles); + *EntryProtocolHandles = NULL; +} + +EFI_STATUS +AddEntriesFromBootEntryProtocol ( + IN OUT OC_BOOT_CONTEXT *BootContext, + IN OUT OC_BOOT_FILESYSTEM *FileSystem, + IN EFI_HANDLE *EntryProtocolHandles, + IN UINTN EntryProtocolHandleCount, + IN CONST CHAR16 *DefaultEntryId, OPTIONAL + IN CONST BOOLEAN CreateDefault + ) +{ + EFI_STATUS ReturnStatus; + EFI_STATUS Status; + UINTN Index; + UINTN EntryIndex; + OC_BOOT_ENTRY_PROTOCOL *BootEntryProtocol; + OC_PICKER_ENTRY *Entries; + UINTN NumEntries; + + DEBUG_CODE_BEGIN (); + if (CreateDefault) { + ASSERT ((DefaultEntryId != NULL)); + } + DEBUG_CODE_END (); + + ReturnStatus = EFI_NOT_FOUND; + + for (Index = 0; Index < EntryProtocolHandleCount; ++Index) { + // + // Previously marked as invalid protocol revision. + // + if (EntryProtocolHandles[Index] == NULL) { + continue; + } + + Status = gBS->HandleProtocol ( + EntryProtocolHandles[Index], + &gOcBootEntryProtocolGuid, + (VOID **) &BootEntryProtocol + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "BEP: HandleProtocol failed - %r\n", Status)); + continue; + } + + if (BootEntryProtocol->Revision != OC_BOOT_ENTRY_PROTOCOL_REVISION) { + DEBUG (( + DEBUG_ERROR, + "BEP: Invalid revision %u (!= %u) in loaded driver\n", + BootEntryProtocol->Revision, + OC_BOOT_ENTRY_PROTOCOL_REVISION + )); + EntryProtocolHandles[Index] = NULL; + continue; + } + + Status = BootEntryProtocol->GetBootEntries ( + BootContext->PickerContext, + FileSystem->Handle == OC_CUSTOM_FS_HANDLE ? NULL : FileSystem->Handle, + &Entries, + &NumEntries + ); + + if (EFI_ERROR (Status)) { + // + // No entries for any given driver on any given filesystem is normal. + // + if (Status != EFI_NOT_FOUND) { + DEBUG ((DEBUG_WARN, "BEP: Unable to fetch boot entries - %r\n", Status)); + } + continue; + } + + for (EntryIndex = 0; EntryIndex < NumEntries; EntryIndex++) { + if (Entries[EntryIndex].Id == NULL) { + DEBUG ((DEBUG_WARN, "BEP: Entry->Id is required, ignoring entry.\n")); + } + if (DefaultEntryId == NULL || + (MixedStrCmp (DefaultEntryId, Entries[EntryIndex].Id) == 0) == CreateDefault) { + Status = InternalAddBootEntryFromCustomEntry ( + BootContext, + FileSystem, + &Entries[EntryIndex], + TRUE + ); + + if (EFI_ERROR (Status)) { + // + // EFI_UNSUPPORTED is auxiliary entry when HideAuxiliary=true. + // + if (Status != EFI_UNSUPPORTED) { + DEBUG ((DEBUG_WARN, "BEP: Error adding entries - %r\n", Status)); + break; + } + } else { + ReturnStatus = EFI_SUCCESS; + + // + // Stop searching after first match for default entry. Possible additional + // matches, e.g. older versions of Linux kernel, are normal. + // + if (CreateDefault) { + break; + } + } + } else { + // + // Create remaining matches after skipping first match for pre-created entry. + // + if (!CreateDefault) { + DefaultEntryId = NULL; + } + } + } + + BootEntryProtocol->FreeBootEntries ( + &Entries, + NumEntries + ); + + // + // If not found, keep hunting for default entry on other installed drivers. + // + if (CreateDefault) { + if (ReturnStatus == EFI_NOT_FOUND) { + continue; + } + break; + } + + // + // On other error adding entry (should not fail), abort. + // + if (EFI_ERROR (Status) && Status != EFI_UNSUPPORTED) { + break; + } + } + + return ReturnStatus; +} diff --git a/Library/OcBootManagementLib/BootEntryProtocolInternal.h b/Library/OcBootManagementLib/BootEntryProtocolInternal.h new file mode 100644 index 00000000..79e33dd3 --- /dev/null +++ b/Library/OcBootManagementLib/BootEntryProtocolInternal.h @@ -0,0 +1,56 @@ +/** @file + Copyright (C) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#ifndef BOOT_ENTRY_PROTOCOL_INTERNAL_H +#define BOOT_ENTRY_PROTOCOL_INTERNAL_H + +#include +#include + +/** + Locate boot entry protocol handles. + + @param[in,out] EntryProtocolHandles Boot entry protocol handles, or NULL if none. + @param[in,out] EntryProtocolHandleCount Count of boot entry protocol handles. +**/ +VOID +LocateBootEntryProtocolHandles ( + IN OUT EFI_HANDLE **EntryProtocolHandles, + IN OUT UINTN *EntryProtocolHandleCount + ); + +/** + Free boot entry protocol handles. + + @param[in,out] EntryProtocolHandles Boot entry protocol handles, or NULL if none. +**/ +VOID +FreeBootEntryProtocolHandles ( + EFI_HANDLE **EntryProtocolHandles + ); + +/** + Request bootable entries from installed boot entry protocol drivers. + + @param[in,out] BootContext Context of filesystems. + @param[in,out] FileSystem Filesystem to scan for entries. + @param[in] EntryProtocolHandles Boot entry protocol handles, or NULL if none. + @param[in] EntryProtocolHandleCount Count of boot entry protocol handles. + @param[in] DefaultEntryId Id of saved default entry on this file system. + @param[in] CreateDefault Create default entry if TRUE, create all others otherwise. + + @retval EFI_SUCCESS At least one entry was created. +**/ +EFI_STATUS +AddEntriesFromBootEntryProtocol ( + IN OUT OC_BOOT_CONTEXT *BootContext, + IN OUT OC_BOOT_FILESYSTEM *FileSystem, + IN EFI_HANDLE *EntryProtocolHandles, + IN UINTN EntryProtocolHandleCount, + IN CONST CHAR16 *DefaultEntryId, OPTIONAL + IN CONST BOOLEAN CreateDefault + ); + +#endif // BOOT_ENTRY_PROTOCOL_INTERNAL_H diff --git a/Library/OcBootManagementLib/BootManagementInternal.h b/Library/OcBootManagementLib/BootManagementInternal.h index fcb520ef..3f86d59f 100644 --- a/Library/OcBootManagementLib/BootManagementInternal.h +++ b/Library/OcBootManagementLib/BootManagementInternal.h @@ -33,6 +33,13 @@ { 0xd6f263f9, 0x0b19, 0x4670, \ { 0xb0, 0xa4, 0x9d, 0x95, 0x9f, 0x58, 0xdf, 0x65 } } +/// +/// Identifies the DevicePath structure for Boot Entry Brotocol custom entries. +/// +#define OC_ENTRY_PROTOCOL_DEVICE_PATH_GUID \ + { 0x669bf063, 0x78c1, 0x4c29, \ + { 0x93, 0x34, 0x2f, 0xf0, 0x15, 0xfe, 0xa2, 0xfe } } + #pragma pack(1) /// @@ -43,6 +50,16 @@ typedef PACKED struct { FILEPATH_DEVICE_PATH EntryName; } OC_CUSTOM_BOOT_DEVICE_PATH; +/// +/// DevicePath to describe Boot Entry Protocol custom entries. +/// Include partuuid of boot drive in VenHw custom memory. +/// +typedef PACKED struct { + VENDOR_DEVICE_PATH Hdr; + EFI_GUID Partuuid; + FILEPATH_DEVICE_PATH EntryName; +} OC_ENTRY_PROTOCOL_DEVICE_PATH; + // // Ideally, a variant of FILEPATH_DEVICE_PATH will be used with PathName as a // flexible array. Such cannot be used for declarations, so provide an @@ -53,6 +70,15 @@ typedef PACKED struct { EFI_DEVICE_PATH_PROTOCOL EntryName; } OC_CUSTOM_BOOT_DEVICE_PATH_DECL; +// +// Version not including first char of path name. +// +typedef PACKED struct { + VENDOR_DEVICE_PATH Header; + EFI_GUID Partuuid; + EFI_DEVICE_PATH_PROTOCOL EntryName; +} OC_ENTRY_PROTOCOL_DEVICE_PATH_DECL; + #pragma pack() /// @@ -61,6 +87,12 @@ typedef PACKED struct { #define SIZE_OF_OC_CUSTOM_BOOT_DEVICE_PATH \ (sizeof (VENDOR_DEVICE_PATH) + SIZE_OF_FILEPATH_DEVICE_PATH) +/// +/// The size of a OC_ENTRY_PROTOCOL_DEVICE_PATH structure excluding the name. +/// +#define SIZE_OF_OC_ENTRY_PROTOCOL_DEVICE_PATH \ + (sizeof (VENDOR_DEVICE_PATH) + sizeof (EFI_GUID) + SIZE_OF_FILEPATH_DEVICE_PATH) + // // Max. supported Apple version string size // @@ -166,6 +198,24 @@ InternalGetBootOptionPath ( IN UINTN LoadOptionSize ); +/** + Create bootable entry from custom entry. + + @param[in,out] BootContext Context of filesystems. + @param[in,out] FileSystem Filesystem to add custom entry. + @param[in] CustomEntry Custom entry. + @param[in] IsBootEntryProtocol Is entry from OC_BOOT_ENTRY_PROTOCOL. + + @retval EFI_SUCCESS on success. +**/ +EFI_STATUS +InternalAddBootEntryFromCustomEntry ( + IN OUT OC_BOOT_CONTEXT *BootContext, + IN OUT OC_BOOT_FILESYSTEM *FileSystem, + IN OC_PICKER_ENTRY *CustomEntry, + IN BOOLEAN IsBootEntryProtocol + ); + /** Describe boot entry contents by setting fields other than DevicePath. @@ -190,18 +240,21 @@ InternalIsAppleLegacyLoadApp ( This solves the problem of checking scan policy multiple times as well as the problem of finding the filesystem to add entries too. - @param[in] BootContext Context of filesystems. - @param[in] FileSystemHandle Partition handle. - @param[in] LazyScan Lazy filesystem scanning. + @param[in] BootContext Context of filesystems. + @param[in] FileSystemHandle Partition handle. + @param[in] LazyScan Lazy filesystem scanning. + @param[out] AlreadySeen Set to TRUE if file system was + already present in context. @retval discovered filesystem (legit). @retcal NULL when booting is not allowed from this filesystem. **/ OC_BOOT_FILESYSTEM * InternalFileSystemForHandle ( - IN OC_BOOT_CONTEXT *BootContext, - IN EFI_HANDLE FileSystemHandle, - IN BOOLEAN LazyScan + IN OC_BOOT_CONTEXT *BootContext, + IN EFI_HANDLE FileSystemHandle, + IN BOOLEAN LazyScan, + OUT BOOLEAN *AlreadySeen OPTIONAL ); /** @@ -230,6 +283,16 @@ InternalGetOcCustomDevPath ( IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath ); +/** + Determines whether DevicePath is a Boot Entry Protocol custom boot entry. + + @returns The Boot Entry Protocol custom boot entry, or NULL. +**/ +CONST OC_ENTRY_PROTOCOL_DEVICE_PATH * +InternalGetOcEntryProtocolDevPath ( + IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath + ); + EFI_STATUS InternalRunRequestPrivilege ( IN OC_PICKER_CONTEXT *PickerContext, diff --git a/Library/OcBootManagementLib/DefaultEntryChoice.c b/Library/OcBootManagementLib/DefaultEntryChoice.c index e332e499..b0d0130b 100644 --- a/Library/OcBootManagementLib/DefaultEntryChoice.c +++ b/Library/OcBootManagementLib/DefaultEntryChoice.c @@ -1,15 +1,6 @@ /** @file - Copyright (C) 2019, vit9696. All rights reserved. - - All rights reserved. - - This program and the accompanying materials - are licensed and made available under the terms and conditions of the BSD License - which accompanies this distribution. The full text of the license may be found at - http://opensource.org/licenses/bsd-license.php - - THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + Copyright (C) 2019-2021, vit9696, mikebeaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause **/ #include @@ -19,6 +10,7 @@ #include #include #include +#include #include #include @@ -40,7 +32,7 @@ #include /// -/// Template for an OpenCore custom boot entry DevicePath node. +/// Template for OpenCore custom boot entry DevicePath. /// STATIC CONST OC_CUSTOM_BOOT_DEVICE_PATH_DECL mOcCustomBootDevPathTemplate = { { @@ -58,6 +50,26 @@ STATIC CONST OC_CUSTOM_BOOT_DEVICE_PATH_DECL mOcCustomBootDevPathTemplate = { } }; +/// +/// Template for Boot Entry Protocol custom boot entry DevicePath. +/// +STATIC CONST OC_ENTRY_PROTOCOL_DEVICE_PATH_DECL mOcEntryProtocolDevPathTemplate = { + { + { + HARDWARE_DEVICE_PATH, + HW_VENDOR_DP, + { sizeof (VENDOR_DEVICE_PATH) + sizeof (EFI_GUID), 0 } + }, + OC_ENTRY_PROTOCOL_DEVICE_PATH_GUID + }, + EFI_PART_TYPE_UNUSED_GUID, + { + MEDIA_DEVICE_PATH, + MEDIA_FILEPATH_DP, + { SIZE_OF_FILEPATH_DEVICE_PATH, 0 } + } +}; + CONST OC_CUSTOM_BOOT_DEVICE_PATH * InternalGetOcCustomDevPath ( IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath @@ -90,6 +102,38 @@ InternalGetOcCustomDevPath ( return CustomDevPath; } +CONST OC_ENTRY_PROTOCOL_DEVICE_PATH * +InternalGetOcEntryProtocolDevPath ( + IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath + ) +{ + UINTN DevicePathSize; + INTN CmpResult; + CONST OC_ENTRY_PROTOCOL_DEVICE_PATH *EntryProtocolDevPath; + + DevicePathSize = GetDevicePathSize (DevicePath); + if (DevicePathSize < SIZE_OF_OC_ENTRY_PROTOCOL_DEVICE_PATH) { + return NULL; + } + + CmpResult = CompareMem ( + DevicePath, + &mOcEntryProtocolDevPathTemplate.Header, + sizeof (mOcEntryProtocolDevPathTemplate.Header) + ); + if (CmpResult != 0) { + return NULL; + } + + EntryProtocolDevPath = (CONST OC_ENTRY_PROTOCOL_DEVICE_PATH *) DevicePath; + if (EntryProtocolDevPath->EntryName.Header.Type != MEDIA_DEVICE_PATH + || EntryProtocolDevPath->EntryName.Header.SubType != MEDIA_FILEPATH_DP) { + return NULL; + } + + return EntryProtocolDevPath; +} + EFI_LOAD_OPTION * InternalGetBootOptionData ( OUT UINTN *OptionSize, @@ -358,7 +402,7 @@ InternalMatchCustomBootEntryByDevicePath ( { INTN CmpResult; - if (!BootEntry->IsCustom) { + if (!BootEntry->IsCustom || BootEntry->IsBootEntryProtocol) { return FALSE; } @@ -370,6 +414,32 @@ InternalMatchCustomBootEntryByDevicePath ( return TRUE; } +STATIC +BOOLEAN +InternalMatchEntryProtocolEntryByDevicePath ( + IN OUT OC_BOOT_ENTRY *BootEntry, + IN CONST OC_ENTRY_PROTOCOL_DEVICE_PATH *DevicePath + ) +{ + INTN CmpResult; + + if (!BootEntry->IsCustom || !BootEntry->IsBootEntryProtocol || BootEntry->Id == NULL) { + return FALSE; + } + + CmpResult = CompareMem (&BootEntry->UniquePartitionGUID, &DevicePath->Partuuid, sizeof (EFI_GUID)); + if (CmpResult != 0) { + return FALSE; + } + + CmpResult = StrCmp (BootEntry->Id, DevicePath->EntryName.PathName); + if (CmpResult != 0) { + return FALSE; + } + + return TRUE; +} + STATIC VOID InternalClearNextVariables ( @@ -735,23 +805,34 @@ OcSetDefaultBootEntry ( EFI_DEVICE_PATH *BootOptionRemainingDevicePath; EFI_HANDLE DeviceHandle; BOOLEAN MatchedEntry; + BOOLEAN IsOverflow; + BOOLEAN IsAsciiOptionName; EFI_GUID *BootVariableGuid; CHAR16 *BootOrderName; CHAR16 *BootVariableName; + CHAR16 *LoadOptionId; + VOID *LoadOptionName; + CHAR8 *FirstFlavourEnd; UINT16 *BootOrder; UINT16 *NewBootOrder; UINT16 BootTmp; + UINT16 EntryIdLength; UINTN BootOrderCount; UINTN BootChosenIndex; UINTN Index; UINTN DevicePathSize; UINTN LoadOptionSize; + UINTN LoadOptionIdSize; UINTN LoadOptionNameSize; + UINTN LoadOptionNameLen; + UINTN CopiedLength; EFI_LOAD_OPTION *LoadOption; - CONST OC_CUSTOM_BOOT_DEVICE_PATH *CustomDevPath; - OC_CUSTOM_BOOT_DEVICE_PATH *DestCustomDevPath; - EFI_DEVICE_PATH_PROTOCOL *DestCustomEndNode; + CONST OC_CUSTOM_BOOT_DEVICE_PATH *CustomDevPath; + CONST OC_ENTRY_PROTOCOL_DEVICE_PATH *EntryProtocolDevPath; + VENDOR_DEVICE_PATH *DestCustomDevPath; + FILEPATH_DEVICE_PATH *DestCustomEntryName; + EFI_DEVICE_PATH_PROTOCOL *DestCustomEndNode; // // Do not allow when prohibited. @@ -839,6 +920,14 @@ OcSetDefaultBootEntry ( Entry, CustomDevPath ); + } else { + EntryProtocolDevPath = InternalGetOcEntryProtocolDevPath (BootOptionDevicePath); + if (EntryProtocolDevPath != NULL) { + MatchedEntry = InternalMatchEntryProtocolEntryByDevicePath ( + Entry, + EntryProtocolDevPath + ); + } } } @@ -849,12 +938,61 @@ OcSetDefaultBootEntry ( // // Write to Boot0080 // - LoadOptionNameSize = StrSize (Entry->Name); + ASSERT (Entry->Name != NULL); + IsAsciiOptionName = FALSE; + if (Entry->Id == NULL) { + // + // Re-use user defined entry name as stored id. + // + LoadOptionName = Entry->Name; + LoadOptionNameSize = StrSize (Entry->Name); + + LoadOptionId = LoadOptionName; + LoadOptionIdSize = LoadOptionNameSize; + } else { + // + // Re-use first part of flavour as option name if available, it is more human + // readable than entry id, but is not version specific, unlike entry name. + // + LoadOptionId = Entry->Id; + LoadOptionIdSize = StrSize (Entry->Id); + + if (Entry->Flavour != NULL && Entry->Flavour[0] != '\0' && Entry->Flavour[0] != ':') { + FirstFlavourEnd = OcAsciiStrChr (Entry->Flavour, ':'); + if (FirstFlavourEnd != NULL) { + LoadOptionNameLen = FirstFlavourEnd - Entry->Flavour; + } else { + LoadOptionNameLen = AsciiStrLen (Entry->Flavour); + } + IsAsciiOptionName = TRUE; + LoadOptionNameSize = (LoadOptionNameLen + 1) * sizeof (CHAR16) / sizeof (CHAR8); + LoadOptionName = Entry->Flavour; + } else { + LoadOptionName = LoadOptionId; + LoadOptionNameSize = LoadOptionIdSize; + } + } if (!Entry->IsCustom) { DevicePathSize = GetDevicePathSize (Entry->DevicePath); } else { - DevicePathSize = SIZE_OF_OC_CUSTOM_BOOT_DEVICE_PATH + LoadOptionNameSize + sizeof (EFI_DEVICE_PATH_PROTOCOL); + DevicePathSize = SIZE_OF_OC_CUSTOM_BOOT_DEVICE_PATH + + (Entry->IsBootEntryProtocol ? sizeof (EFI_GUID) : 0) + + LoadOptionIdSize + + sizeof (EFI_DEVICE_PATH_PROTOCOL); + + if (LoadOptionIdSize > MAX_UINT16) { + IsOverflow = TRUE; + } else { + IsOverflow = OcOverflowAddU16 (SIZE_OF_FILEPATH_DEVICE_PATH, (UINT16)LoadOptionIdSize, &EntryIdLength); + } + if (IsOverflow) { + DEBUG ((DEBUG_ERROR, "OCB: Overflowing option id size (%u)\n", LoadOptionIdSize)); + if (BootOrder != NULL) { + FreePool (BootOrder); + } + return EFI_INVALID_PARAMETER; + } } LoadOptionSize = sizeof (EFI_LOAD_OPTION) + LoadOptionNameSize + DevicePathSize; @@ -870,35 +1008,61 @@ OcSetDefaultBootEntry ( LoadOption->Attributes = LOAD_OPTION_ACTIVE | LOAD_OPTION_CATEGORY_BOOT; LoadOption->FilePathListLength = (UINT16) DevicePathSize; - CopyMem (LoadOption + 1, Entry->Name, LoadOptionNameSize); + if (IsAsciiOptionName) { + Status = AsciiStrnToUnicodeStrS (LoadOptionName, LoadOptionNameLen, (CHAR16 *)(LoadOption + 1), LoadOptionNameSize / sizeof (CHAR16), &CopiedLength); + ASSERT (!EFI_ERROR (Status)); + ASSERT (CopiedLength == LoadOptionNameLen); + } else { + CopyMem (LoadOption + 1, LoadOptionName, LoadOptionNameSize); + } if (!Entry->IsCustom) { CopyMem ((UINT8 *) (LoadOption + 1) + LoadOptionNameSize, Entry->DevicePath, DevicePathSize); } else { - DestCustomDevPath = (OC_CUSTOM_BOOT_DEVICE_PATH *) ( + DestCustomDevPath = (VENDOR_DEVICE_PATH *) ( (UINT8 *) (LoadOption + 1) + LoadOptionNameSize ); + if (Entry->IsBootEntryProtocol) { + CopyMem ( + DestCustomDevPath, + &mOcEntryProtocolDevPathTemplate, + sizeof (mOcEntryProtocolDevPathTemplate) + ); + CopyMem ( + DestCustomDevPath + 1, + &Entry->UniquePartitionGUID, + sizeof (EFI_GUID) + ); + DestCustomEntryName = (FILEPATH_DEVICE_PATH *) ( + (UINT8 *) (DestCustomDevPath + 1) + + sizeof (EFI_GUID) + ); + } else { + CopyMem ( + DestCustomDevPath, + &mOcCustomBootDevPathTemplate, + sizeof (mOcCustomBootDevPathTemplate) + ); + DestCustomEntryName = (FILEPATH_DEVICE_PATH *) ( + (UINT8 *) (DestCustomDevPath + 1) + ); + } + CopyMem ( - DestCustomDevPath, - &mOcCustomBootDevPathTemplate, - sizeof (mOcCustomBootDevPathTemplate) + DestCustomEntryName->PathName, + LoadOptionId, + LoadOptionIdSize ); - CopyMem ( - DestCustomDevPath->EntryName.PathName, - Entry->Name, - LoadOptionNameSize - ); - // - // FIXME: This may theoretically overflow. - // - DestCustomDevPath->EntryName.Header.Length[0] += (UINT8) LoadOptionNameSize; + + DestCustomEntryName->Header.Length[0] = (UINT8) EntryIdLength; + DestCustomEntryName->Header.Length[1] = (UINT8) (EntryIdLength >> 8); DestCustomEndNode = (EFI_DEVICE_PATH_PROTOCOL *) ( - (UINT8 *) DestCustomDevPath + SIZE_OF_OC_CUSTOM_BOOT_DEVICE_PATH + LoadOptionNameSize + (UINT8 *) DestCustomEntryName + EntryIdLength ); SetDevicePathEndNode (DestCustomEndNode); - ASSERT (GetDevicePathSize (&DestCustomDevPath->Hdr.Header) == DevicePathSize); + ASSERT (GetDevicePathSize ((EFI_DEVICE_PATH_PROTOCOL *) DestCustomDevPath) == DevicePathSize); } Status = gRT->SetVariable ( diff --git a/Library/OcBootManagementLib/OcBootManagementLib.inf b/Library/OcBootManagementLib/OcBootManagementLib.inf index 879fc8b4..8a556e3c 100644 --- a/Library/OcBootManagementLib/OcBootManagementLib.inf +++ b/Library/OcBootManagementLib/OcBootManagementLib.inf @@ -39,6 +39,8 @@ BootEntryInfo.c BootEntryManagement.c BootManagementInternal.h + BootEntryProtocol.c + BootEntryProtocolInternal.h BuiltinPicker.c DefaultEntryChoice.c DmgBootSupport.c @@ -77,6 +79,7 @@ gAppleSecureBootVariableGuid ## SOMETIMES_CONSUMES gAppleTamperResistantBootSecureVariableGuid ## SOMETIMES_CONSUMES gAppleTamperResistantBootEfiUserVariableGuid ## SOMETIMES_CONSUMES + gEfiPartTypeUnusedGuid ## SOMETIMES_CONSUMES gOcVendorVariableGuid ## SOMETIMES_CONSUMES gOcReadOnlyVariableGuid ## SOMETIMES_CONSUMES gOcWriteOnlyVariableGuid ## SOMETIMES_CONSUMES @@ -92,6 +95,7 @@ gOcFirmwareRuntimeProtocolGuid ## SOMETIMES_CONSUMES gOcAudioProtocolGuid ## SOMETIMES_CONSUMES gAppleBeepGenProtocolGuid ## SOMETIMES_CONSUMES + gOcBootEntryProtocolGuid ## CONSUMES [LibraryClasses] BaseLib @@ -112,8 +116,9 @@ OcCryptoLib OcDeviceMiscLib OcDevicePathLib - OcGuardLib OcFileLib + OcFlexArrayLib + OcGuardLib OcMachoLib OcMiscLib OcPeCoffLib diff --git a/Library/OcBootManagementLib/PolicyManagement.c b/Library/OcBootManagementLib/PolicyManagement.c index 0277c4f4..be76b1db 100644 --- a/Library/OcBootManagementLib/PolicyManagement.c +++ b/Library/OcBootManagementLib/PolicyManagement.c @@ -192,6 +192,7 @@ EFI_GUID mMsftRecoveryPartitionTypeGuid = { /** Linux partitions. https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs + https://systemd.io/DISCOVERABLE_PARTITIONS/ **/ EFI_GUID mLinuxRootX86PartitionTypeGuid = { 0x44479540, 0xF297, 0x41B2, {0x9A, 0xF7, 0xD1, 0x31, 0xD5, 0xF0, 0x45, 0x8A} @@ -201,6 +202,18 @@ EFI_GUID mLinuxRootX8664PartitionTypeGuid = { 0x4F68BCE3, 0xE8CD, 0x4DB1, {0x96, 0xE7, 0xFB, 0xCA, 0xF9, 0x84, 0xB7, 0x09} }; +EFI_GUID mLinuxFileSystemPartitionTypeGuid = { + 0x0FC63DAF, 0x8483, 0x4772, { 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4 } +}; + +/** + Extended Boot Loader Partition (XBOOTLDR). + https://systemd.io/BOOT_LOADER_SPECIFICATION/ +**/ +EFI_GUID mXBootLdrPartitionTypeGuid = { + 0xBC13C2FF, 0x59E6, 0x4262, { 0xA3, 0x52, 0xB2, 0x75, 0xFD, 0x6F, 0x71, 0x72 } +}; + UINT32 OcGetFileSystemPolicyType ( IN EFI_HANDLE Handle @@ -223,6 +236,10 @@ OcGetFileSystemPolicyType ( return OC_SCAN_ALLOW_FS_ESP; } + if (CompareGuid (&PartitionEntry->PartitionTypeGUID, &mXBootLdrPartitionTypeGuid)) { + return OC_SCAN_ALLOW_FS_XBOOTLDR; + } + // // Unsure whether these two should be separate, likely not. // @@ -237,7 +254,11 @@ OcGetFileSystemPolicyType ( if (CompareGuid (&PartitionEntry->PartitionTypeGUID, &mLinuxRootX86PartitionTypeGuid) || CompareGuid (&PartitionEntry->PartitionTypeGUID, &mLinuxRootX8664PartitionTypeGuid)) { - return OC_SCAN_ALLOW_FS_EXT; + return OC_SCAN_ALLOW_FS_LINUX_ROOT; + } + + if (CompareGuid (&PartitionEntry->PartitionTypeGUID, &mLinuxFileSystemPartitionTypeGuid)) { + return OC_SCAN_ALLOW_FS_LINUX_DATA; } return 0; diff --git a/Library/OcConfigurationLib/OcConfigurationLib.c b/Library/OcConfigurationLib/OcConfigurationLib.c index a9d9f871..e83ce4e0 100644 --- a/Library/OcConfigurationLib/OcConfigurationLib.c +++ b/Library/OcConfigurationLib/OcConfigurationLib.c @@ -71,6 +71,7 @@ OC_STRUCTORS (OC_PLATFORM_NVRAM_CONFIG, ()) OC_STRUCTORS (OC_PLATFORM_SMBIOS_CONFIG, ()) OC_STRUCTORS (OC_PLATFORM_CONFIG, ()) +OC_STRUCTORS (OC_UEFI_DRIVER_ENTRY, ()) OC_ARRAY_STRUCTORS (OC_UEFI_DRIVER_ARRAY) OC_STRUCTORS (OC_UEFI_APFS, ()) OC_STRUCTORS (OC_UEFI_APPLEINPUT, ()) @@ -667,7 +668,15 @@ mPlatformConfigurationSchema[] = { STATIC OC_SCHEMA -mUefiDriversSchema = OC_SCHEMA_STRING (NULL); +mUefiDriversSchemaEntry[] = { + OC_SCHEMA_STRING_IN ("Arguments", OC_UEFI_DRIVER_ENTRY, Arguments), + OC_SCHEMA_BOOLEAN_IN ("Enabled", OC_UEFI_DRIVER_ENTRY, Enabled), + OC_SCHEMA_STRING_IN ("Path", OC_UEFI_DRIVER_ENTRY, Path), +}; + +STATIC +OC_SCHEMA +mUefiDriversSchema = OC_SCHEMA_DICT (NULL, mUefiDriversSchemaEntry); STATIC OC_SCHEMA diff --git a/Library/OcDeviceMiscLib/OcDeviceMiscLib.inf b/Library/OcDeviceMiscLib/OcDeviceMiscLib.inf index 50857993..55353387 100755 --- a/Library/OcDeviceMiscLib/OcDeviceMiscLib.inf +++ b/Library/OcDeviceMiscLib/OcDeviceMiscLib.inf @@ -1,6 +1,6 @@ ## @file # -# Component description file for OcMisclibrary. +# Component description file for OcDeviceMiscLib. # # Copyright (C) 2016 - 2018, The HermitCrabs Lab. All rights reserved.
# diff --git a/Library/OcFileLib/FileProtocol.c b/Library/OcFileLib/FileProtocol.c index 68c8dfa0..7ff2e9cb 100644 --- a/Library/OcFileLib/FileProtocol.c +++ b/Library/OcFileLib/FileProtocol.c @@ -1,15 +1,6 @@ /** @file - Copyright (C) 2019, vit9696. All rights reserved. - - All rights reserved. - - This program and the accompanying materials - are licensed and made available under the terms and conditions of the BSD License - which accompanies this distribution. The full text of the license may be found at - http://opensource.org/licenses/bsd-license.php - - THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + Copyright (C) 2019-2021, vit9696, Goldfish64, mikebeaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause **/ #include @@ -420,6 +411,30 @@ OcDirectorySeachContextInit ( ZeroMem (Context, sizeof (*Context)); } +EFI_STATUS +OcEnsureDirectory ( + IN EFI_FILE_PROTOCOL *File, + IN BOOLEAN IsDirectory + ) +{ + EFI_FILE_INFO *FileInfo; + + // + // Ensure this is a directory/file. + // + FileInfo = OcGetFileInfo (File, &gEfiFileInfoGuid, 0, NULL); + if (FileInfo == NULL) { + return EFI_INVALID_PARAMETER; + } + if (((FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0) != IsDirectory) { + FreePool (FileInfo); + return EFI_INVALID_PARAMETER; + } + FreePool (FileInfo); + + return EFI_SUCCESS; +} + EFI_STATUS OcGetNewestFileFromDirectory ( IN OUT DIRECTORY_SEARCH_CONTEXT *Context, @@ -445,18 +460,10 @@ OcGetNewestFileFromDirectory ( LatestIndex = 0; LatestEpoch = 0; - // - // Ensure this is a directory. - // - FileInfoCurrent = OcGetFileInfo (Directory, &gEfiFileInfoGuid, 0, NULL); - if (FileInfoCurrent == NULL) { - return EFI_INVALID_PARAMETER; + Status = OcEnsureDirectory (Directory, TRUE); + if (EFI_ERROR (Status)) { + return Status; } - if (!(FileInfoCurrent->Attribute & EFI_FILE_DIRECTORY)) { - FreePool (FileInfoCurrent); - return EFI_INVALID_PARAMETER; - } - FreePool (FileInfoCurrent); // // Allocate two FILE_INFO structures. @@ -564,3 +571,80 @@ OcGetNewestFileFromDirectory ( return EFI_SUCCESS; } + +// +// TODO: OcGetNewestFileFromDirectory above and ScanExtensions in CachelessContext.c could be redone using this. +// TODO: I am unclear exactly what the Apple 32-bit HFS is being described as doing (see also OcGetFileInfo), so +// have just copied the existing handling. +// +EFI_STATUS +OcScanDirectory ( + IN EFI_FILE_HANDLE Directory, + IN OC_PROCESS_DIRECTORY_ENTRY ProcessEntry, + IN OUT VOID *Context OPTIONAL + ) +{ + EFI_STATUS Status; + EFI_STATUS TempStatus; + EFI_FILE_INFO *FileInfo; + UINTN FileInfoSize; + + ASSERT (Directory != NULL); + ASSERT (ProcessEntry != NULL); + + Status = OcEnsureDirectory (Directory, TRUE); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Allocate FILE_INFO structure. + // + FileInfo = AllocatePool (SIZE_1KB); + if (FileInfo == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_NOT_FOUND; + Directory->SetPosition (Directory, 0); + + do { + // + // Apple's HFS+ driver does not adhere to the spec and will return zero for + // EFI_BUFFER_TOO_SMALL. EFI_FILE_INFO structures larger than 1KB are + // unrealistic as the filename is the only variable. + // + FileInfoSize = SIZE_1KB - sizeof (CHAR16); + TempStatus = Directory->Read (Directory, &FileInfoSize, FileInfo); + if (EFI_ERROR (TempStatus)) { + Status = TempStatus; + break; + } + + if (FileInfoSize > 0) { + TempStatus = ProcessEntry (Directory, FileInfo, FileInfoSize, Context); + + // + // Act as if no matching file was found. + // + if (TempStatus == EFI_NOT_FOUND) { + continue; + } + + if (EFI_ERROR (TempStatus)) { + Status = TempStatus; + break; + } + + // + // At least one file found. + // + Status = EFI_SUCCESS; + } + } while (FileInfoSize > 0); + + Directory->SetPosition (Directory, 0); + FreePool (FileInfo); + + return Status; +} diff --git a/Library/OcFileLib/OpenFile.c b/Library/OcFileLib/OpenFile.c index f63c4b85..18932383 100644 --- a/Library/OcFileLib/OpenFile.c +++ b/Library/OcFileLib/OpenFile.c @@ -27,11 +27,11 @@ EFI_STATUS OcSafeFileOpen ( - IN EFI_FILE_PROTOCOL *Protocol, - OUT EFI_FILE_PROTOCOL **NewHandle, - IN CONST CHAR16 *FileName, - IN UINT64 OpenMode, - IN UINT64 Attributes + IN CONST EFI_FILE_PROTOCOL *Protocol, + OUT EFI_FILE_PROTOCOL **NewHandle, + IN CONST CHAR16 *FileName, + IN CONST UINT64 OpenMode, + IN CONST UINT64 Attributes ) { EFI_STATUS Status; @@ -48,7 +48,7 @@ OcSafeFileOpen ( *NewHandle = NULL; Status = Protocol->Open ( - Protocol, + (EFI_FILE_PROTOCOL *) Protocol, NewHandle, (CHAR16 *) FileName, OpenMode, diff --git a/Library/OcFileLib/ReadFile.c b/Library/OcFileLib/ReadFile.c index 3ee4cb7f..d8f62504 100755 --- a/Library/OcFileLib/ReadFile.c +++ b/Library/OcFileLib/ReadFile.c @@ -31,10 +31,10 @@ VOID * OcReadFile ( - IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem, - IN CONST CHAR16 *FilePath, - OUT UINT32 *FileSize OPTIONAL, - IN UINT32 MaxFileSize OPTIONAL + IN CONST EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem, + IN CONST CHAR16 *FilePath, + OUT UINT32 *FileSize OPTIONAL, + IN CONST UINT32 MaxFileSize OPTIONAL ) { EFI_STATUS Status; @@ -48,7 +48,7 @@ OcReadFile ( ASSERT (FilePath != NULL); Status = FileSystem->OpenVolume ( - FileSystem, + (EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *) FileSystem, &Volume ); if (EFI_ERROR (Status)) { @@ -58,7 +58,7 @@ OcReadFile ( Status = OcSafeFileOpen ( Volume, &FileHandle, - (CHAR16 *) FilePath, + FilePath, EFI_FILE_MODE_READ, 0 ); @@ -151,11 +151,11 @@ OcReadFileSize ( } VOID * -OcReadFileFromFile ( - IN EFI_FILE_PROTOCOL *RootFile, - IN CONST CHAR16 *FilePath, - OUT UINT32 *FileSize OPTIONAL, - IN UINT32 MaxFileSize OPTIONAL +OcReadFileFromDirectory ( + IN CONST EFI_FILE_PROTOCOL *RootDirectory, + IN CONST CHAR16 *FilePath, + OUT UINT32 *FileSize OPTIONAL, + IN UINT32 MaxFileSize OPTIONAL ) { EFI_STATUS Status; @@ -163,13 +163,13 @@ OcReadFileFromFile ( UINT32 Size; UINT8 *FileBuffer; - ASSERT (RootFile != NULL); + ASSERT (RootDirectory != NULL); ASSERT (FilePath != NULL); Status = OcSafeFileOpen ( - RootFile, + RootDirectory, &File, - (CHAR16 *) FilePath, + FilePath, EFI_FILE_MODE_READ, 0 ); diff --git a/Library/OcFlexArrayLib/AsciiStringBuffer.c b/Library/OcFlexArrayLib/AsciiStringBuffer.c new file mode 100644 index 00000000..cb5dec54 --- /dev/null +++ b/Library/OcFlexArrayLib/AsciiStringBuffer.c @@ -0,0 +1,196 @@ +/** @file + String buffer. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include +#include +#include +#include +#include +#include +#include + +OC_STRING_BUFFER * +OcAsciiStringBufferInit ( + VOID + ) +{ + OC_STRING_BUFFER *Buffer; + + Buffer = AllocateZeroPool (sizeof (OC_STRING_BUFFER)); + + return Buffer; +} + +EFI_STATUS +OcAsciiStringBufferAppend ( + IN OUT OC_STRING_BUFFER *Buffer, + IN CONST CHAR8 *AppendString OPTIONAL + ) +{ + return OcAsciiStringBufferAppendN (Buffer, AppendString, MAX_UINTN); +} + +STATIC +EFI_STATUS +InternalAsciiStringBufferExtendBy ( + IN OUT OC_STRING_BUFFER *Buffer, + IN CONST UINTN AppendLength, + OUT UINTN *TargetLength + ) +{ + UINTN NewSize; + + ASSERT (AppendLength != 0); + + if (Buffer->String == NULL) { + ASSERT (Buffer->BufferSize == 0); + + Buffer->String = AllocatePool (AppendLength + 1); + if (Buffer->String == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Buffer->BufferSize = AppendLength + 1; + *TargetLength = AppendLength; + } else { + if (Buffer->BufferSize == 0) { + ASSERT (FALSE); + return EFI_UNSUPPORTED; + } + + NewSize = Buffer->BufferSize; + if (OcOverflowAddUN (Buffer->StringLength, AppendLength, TargetLength)) { + return EFI_OUT_OF_RESOURCES; + } + + while (NewSize <= *TargetLength) { + if (OcOverflowMulUN (NewSize, 2, &NewSize)) { + return EFI_OUT_OF_RESOURCES; + } + } + + if (NewSize > Buffer->BufferSize) { + Buffer->String = ReallocatePool (Buffer->BufferSize, NewSize, Buffer->String); + if (Buffer->String == NULL) { + return EFI_OUT_OF_RESOURCES; + } + Buffer->BufferSize = NewSize; + } + } + + return EFI_SUCCESS; +} + +EFI_STATUS +OcAsciiStringBufferAppendN ( + IN OUT OC_STRING_BUFFER *Buffer, + IN CONST CHAR8 *AppendString, OPTIONAL + IN CONST UINTN Length + ) +{ + EFI_STATUS Status; + UINTN AppendLength; + UINTN TargetLength; + + if (AppendString == NULL) { + return EFI_SUCCESS; + } + + AppendLength = AsciiStrnLenS (AppendString, Length); + + // + // Buffer stays NULL if zero appended. + // + if (AppendLength == 0) { + return EFI_SUCCESS; + } + + Status = InternalAsciiStringBufferExtendBy (Buffer, AppendLength, &TargetLength); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = AsciiStrnCpyS (&Buffer->String[Buffer->StringLength], AppendLength + 1, AppendString, AppendLength); + if (EFI_ERROR (Status)) { + return Status; + } + + Buffer->StringLength = TargetLength; + return EFI_SUCCESS; +} + +EFI_STATUS +EFIAPI +OcAsciiStringBufferSPrint ( + IN OUT OC_STRING_BUFFER *Buffer, + IN CONST CHAR8 *FormatString, + ... + ) +{ + EFI_STATUS Status; + VA_LIST Marker; + VA_LIST Marker2; + UINTN NumberOfPrinted; + UINTN TargetLength; + + ASSERT (FormatString != NULL); + + VA_START (Marker, FormatString); + + VA_COPY (Marker2, Marker); + NumberOfPrinted = SPrintLengthAsciiFormat (FormatString, Marker2); + VA_END (Marker2); + + // + // Buffer stays NULL if zero appended. + // + if (NumberOfPrinted == 0) { + Status = EFI_SUCCESS; + } else { + Status = InternalAsciiStringBufferExtendBy (Buffer, NumberOfPrinted, &TargetLength); + if (!EFI_ERROR (Status)) { + AsciiVSPrint (&Buffer->String[Buffer->StringLength], NumberOfPrinted + 1, FormatString, Marker); + Buffer->StringLength = TargetLength; + } + } + + VA_END (Marker); + return Status; +} + +CHAR8 * +OcAsciiStringBufferFreeContainer ( + IN OUT OC_STRING_BUFFER **Buffer + ) +{ + CHAR8 *String; + + if (Buffer == NULL || *Buffer == NULL) { + ASSERT (FALSE); + return NULL; + } + + String = (*Buffer)->String; + FreePool (*Buffer); + *Buffer = NULL; + + return String; +} + +VOID +OcAsciiStringBufferFree ( + IN OUT OC_STRING_BUFFER **StringBuffer + ) +{ + CHAR8 *Result; + + Result = OcAsciiStringBufferFreeContainer (StringBuffer); + if (Result != NULL) { + FreePool (Result); + } +} + diff --git a/Library/OcFlexArrayLib/FlexArray.c b/Library/OcFlexArrayLib/FlexArray.c new file mode 100644 index 00000000..3db12c70 --- /dev/null +++ b/Library/OcFlexArrayLib/FlexArray.c @@ -0,0 +1,195 @@ +/** @file + Auto-resizing array. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include +#include +#include +#include +#include +#include + +#define INITIAL_NUM_ITEMS (8) + +OC_FLEX_ARRAY * +OcFlexArrayInit ( + IN CONST UINTN ItemSize, + IN CONST OC_FLEX_ARRAY_FREE_ITEM FreeItem OPTIONAL + ) +{ + OC_FLEX_ARRAY *FlexArray; + + ASSERT (ItemSize > 0); + + FlexArray = AllocateZeroPool (sizeof (OC_FLEX_ARRAY)); + if (FlexArray != NULL) { + FlexArray->ItemSize = ItemSize; + FlexArray->FreeItem = FreeItem; + } + return FlexArray; +} + +STATIC +VOID * +InternalFlexArrayAddItem ( + IN OUT OC_FLEX_ARRAY *FlexArray + ) +{ + VOID *TmpBuffer; + UINTN NewSize; + VOID *Item; + + ASSERT (FlexArray != NULL); + + if (FlexArray->Items == NULL) { + FlexArray->AllocatedCount = INITIAL_NUM_ITEMS; + if (OcOverflowMulUN (FlexArray->AllocatedCount, FlexArray->ItemSize, &NewSize)) { + return NULL; + } + FlexArray->Count = 1; + FlexArray->Items = AllocatePool (NewSize); + if (FlexArray->Items == NULL) { + return NULL; + } + } else { + ASSERT (FlexArray->Count > 0); + ASSERT (FlexArray->AllocatedCount > 0); + ASSERT (FlexArray->Count <= FlexArray->AllocatedCount); + ++(FlexArray->Count); + if (FlexArray->Count > FlexArray->AllocatedCount) { + if (OcOverflowMulUN (FlexArray->AllocatedCount * FlexArray->ItemSize, 2, &NewSize)) { + return NULL; + } + TmpBuffer = ReallocatePool (FlexArray->AllocatedCount * FlexArray->ItemSize, NewSize, FlexArray->Items); + if (TmpBuffer == NULL) { + return NULL; + } + FlexArray->Items = TmpBuffer; + FlexArray->AllocatedCount = FlexArray->AllocatedCount * 2; + } + } + + Item = OcFlexArrayItemAt (FlexArray, FlexArray->Count - 1); + + return Item; +} + +VOID * +OcFlexArrayAddItem ( + IN OUT OC_FLEX_ARRAY *FlexArray + ) +{ + VOID *Item; + + ASSERT (FlexArray != NULL); + + Item = InternalFlexArrayAddItem (FlexArray); + + if (Item != NULL) { + ZeroMem (Item, FlexArray->ItemSize); + } + + return Item; +} + +VOID * +OcFlexArrayInsertItem ( + IN OUT OC_FLEX_ARRAY *FlexArray, + IN CONST UINTN InsertIndex + ) +{ + VOID *Item; + VOID *Dest; + + ASSERT (FlexArray != NULL); + ASSERT (InsertIndex <= FlexArray->Count); + + if (InsertIndex == FlexArray->Count) { + return OcFlexArrayAddItem (FlexArray); + } + + Item = InternalFlexArrayAddItem (FlexArray); + + if (Item == NULL) { + return Item; + } + + Item = OcFlexArrayItemAt (FlexArray, InsertIndex); + Dest = OcFlexArrayItemAt (FlexArray, InsertIndex + 1); + CopyMem (Dest, Item, (FlexArray->Count - InsertIndex) * FlexArray->ItemSize); + + ZeroMem (Item, FlexArray->ItemSize); + + return Item; +} + +VOID * +OcFlexArrayItemAt ( + IN CONST OC_FLEX_ARRAY *FlexArray, + IN CONST UINTN Index + ) +{ + if (Index >= FlexArray->Count || FlexArray->Items == NULL) { + ASSERT (FALSE); + return NULL; + } + + return ((UINT8 *) FlexArray->Items) + Index * FlexArray->ItemSize; +} + +VOID +OcFlexArrayFree ( + IN OC_FLEX_ARRAY **FlexArray + ) +{ + UINTN Index; + + ASSERT (FlexArray != NULL); + + if (*FlexArray != NULL) { + if ((*FlexArray)->Items != NULL) { + if ((*FlexArray)->FreeItem) { + for (Index = 0; Index < (*FlexArray)->Count; Index++) { + (*FlexArray)->FreeItem (OcFlexArrayItemAt (*FlexArray, Index)); + } + } + FreePool ((*FlexArray)->Items); + } + FreePool (*FlexArray); + *FlexArray = NULL; + } +} + +VOID +OcFlexArrayFreeContainer ( + IN OC_FLEX_ARRAY **FlexArray, + IN VOID **Items, + IN UINTN *Count + ) +{ + if (FlexArray == NULL || *FlexArray == NULL) { + ASSERT (FALSE); + *Items = NULL; + *Count = 0; + } else { + *Items = (*FlexArray)->Items; + *Count = (*FlexArray)->Count; + FreePool (*FlexArray); + *FlexArray = NULL; + } +} + +VOID +OcFlexArrayFreePointerItem ( + IN VOID *Item + ) +{ + ASSERT (Item != NULL); + if (*(VOID **)Item != NULL) { + FreePool (*(VOID **)Item); + *(VOID **)Item = NULL; + } +} diff --git a/Library/OcFlexArrayLib/OcFlexArrayLib.inf b/Library/OcFlexArrayLib/OcFlexArrayLib.inf new file mode 100755 index 00000000..999fde94 --- /dev/null +++ b/Library/OcFlexArrayLib/OcFlexArrayLib.inf @@ -0,0 +1,27 @@ +## @file +# Component description file for OcFlexArray library. +# +# Copyright (C) 2021, Mike Beaton. All rights reserved.
+# SPDX-License-Identifier: BSD-3-Clause +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = OcFlexArrayLib + FILE_GUID = 38906C95-DCBA-491C-9FFA-E0AAFFF88EA0 + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = OcFlexArrayLib|PEIM DXE_DRIVER DXE_RUNTIME_DRIVER UEFI_DRIVER UEFI_APPLICATION DXE_SMM_DRIVER + +# VALID_ARCHITECTURES = IA32 X64 + +[Packages] + OpenCorePkg/OpenCorePkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + +[Sources] + FlexArray.c + AsciiStringBuffer.c diff --git a/Library/OcMainLib/OpenCoreKernel.c b/Library/OcMainLib/OpenCoreKernel.c index bdec84e3..378880ed 100644 --- a/Library/OcMainLib/OpenCoreKernel.c +++ b/Library/OcMainLib/OpenCoreKernel.c @@ -331,7 +331,7 @@ OcKernelLoadAndReserveKext ( UnicodeUefiSlashes (FullPath); if (IsForced) { - Kext->PlistData = OcReadFileFromFile ( + Kext->PlistData = OcReadFileFromDirectory ( RootFile, FullPath, &Kext->PlistDataSize, @@ -388,7 +388,7 @@ OcKernelLoadAndReserveKext ( UnicodeUefiSlashes (FullPath); if (IsForced) { - Kext->ImageData = OcReadFileFromFile ( + Kext->ImageData = OcReadFileFromDirectory ( RootFile, FullPath, &Kext->ImageDataSize, diff --git a/Library/OcMainLib/OpenCoreUefi.c b/Library/OcMainLib/OpenCoreUefi.c index d26e6310..b8b3a625 100644 --- a/Library/OcMainLib/OpenCoreUefi.c +++ b/Library/OcMainLib/OpenCoreUefi.c @@ -88,15 +88,18 @@ OcLoadDrivers ( OUT EFI_HANDLE **DriversToConnect OPTIONAL ) { - EFI_STATUS Status; - VOID *Driver; - UINT32 DriverSize; - UINT32 Index; - CHAR16 DriverPath[OC_STORAGE_SAFE_PATH_MAX]; - EFI_HANDLE ImageHandle; - EFI_HANDLE *DriversToConnectIterator; - VOID *DriverBinding; - BOOLEAN SkipDriver; + EFI_STATUS Status; + VOID *Driver; + UINT32 DriverSize; + UINT32 Index; + CHAR16 DriverPath[OC_STORAGE_SAFE_PATH_MAX]; + EFI_HANDLE ImageHandle; + EFI_HANDLE *DriversToConnectIterator; + VOID *DriverBinding; + BOOLEAN SkipDriver; + OC_UEFI_DRIVER_ENTRY *DriverEntry; + CHAR8 *DriverFileName; + CONST CHAR8 *DriverArguments; DriversToConnectIterator = NULL; if (DriversToConnect != NULL) { @@ -106,18 +109,22 @@ OcLoadDrivers ( DEBUG ((DEBUG_INFO, "OC: Got %u drivers\n", Config->Uefi.Drivers.Count)); for (Index = 0; Index < Config->Uefi.Drivers.Count; ++Index) { - SkipDriver = OC_BLOB_GET (Config->Uefi.Drivers.Values[Index])[0] == '#'; + DriverEntry = Config->Uefi.Drivers.Values[Index]; + DriverFileName = OC_BLOB_GET (&DriverEntry->Path); + DriverArguments = OC_BLOB_GET (&DriverEntry->Arguments); + + SkipDriver = !DriverEntry->Enabled || DriverFileName == NULL || DriverFileName[0] == '\0'; DEBUG (( DEBUG_INFO, "OC: Driver %a at %u is %a\n", - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]), + DriverFileName, Index, SkipDriver ? "skipped!" : "being loaded..." )); // - // Skip drivers marked as comments. + // Skip disabled drivers. // if (SkipDriver) { continue; @@ -127,14 +134,14 @@ OcLoadDrivers ( DriverPath, sizeof (DriverPath), OPEN_CORE_UEFI_DRIVER_PATH "%a", - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]) + DriverFileName ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "OC: Driver %s%a does not fit path!\n", OPEN_CORE_UEFI_DRIVER_PATH, - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]) + DriverFileName )); continue; } @@ -144,7 +151,7 @@ OcLoadDrivers ( DEBUG (( DEBUG_ERROR, "OC: Driver %a at %u cannot be found!\n", - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]), + DriverFileName, Index )); // @@ -169,7 +176,7 @@ OcLoadDrivers ( DEBUG (( DEBUG_ERROR, "OC: Driver %a at %u cannot be loaded - %r!\n", - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]), + DriverFileName, Index, Status )); @@ -177,6 +184,20 @@ OcLoadDrivers ( continue; } + if (DriverArguments != NULL && DriverArguments[0] != '\0') { + OcAppendArgumentsToLoadedImage (ImageHandle, &DriverArguments, 1, TRUE); + } else { + // + // These are not zeroed by boot services image loader, which means new drivers + // expecting load options loaded with old OC may crash horribly, instead + // of just seeing no options. Annoyingly the value in LoadOptions is non-randome + // and lowish, therefore setting an upper limit on option size, to attempt to + // reject unitialized values, does not help. + // + ((EFI_LOADED_IMAGE_PROTOCOL *)ImageHandle)->LoadOptionsSize = 0; + ((EFI_LOADED_IMAGE_PROTOCOL *)ImageHandle)->LoadOptions = NULL; + } + Status = gBS->StartImage ( ImageHandle, NULL, @@ -187,7 +208,7 @@ OcLoadDrivers ( DEBUG (( DEBUG_ERROR, "OC: Driver %a at %u cannot be started - %r!\n", - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]), + DriverFileName, Index, Status )); @@ -198,7 +219,7 @@ OcLoadDrivers ( DEBUG (( DEBUG_INFO, "OC: Driver %a at %u is successfully loaded!\n", - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]), + DriverFileName, Index )); @@ -233,7 +254,7 @@ OcLoadDrivers ( DEBUG (( DEBUG_INFO, "OC: Driver %a at %u needs connection.\n", - OC_BLOB_GET (Config->Uefi.Drivers.Values[Index]), + DriverFileName, Index )); } diff --git a/Library/OcMiscLib/OcMiscLib.inf b/Library/OcMiscLib/OcMiscLib.inf index e22cfcc8..b04f0e08 100755 --- a/Library/OcMiscLib/OcMiscLib.inf +++ b/Library/OcMiscLib/OcMiscLib.inf @@ -1,6 +1,6 @@ ## @file # -# Component description file for OcMisclibrary. +# Component description file for OcMiscLib. # # Copyright (C) 2016 - 2018, The HermitCrabs Lab. All rights reserved.
# diff --git a/Library/OcMp3Lib/OcMp3Lib.inf b/Library/OcMp3Lib/OcMp3Lib.inf index 6adbc2da..d8b37dba 100755 --- a/Library/OcMp3Lib/OcMp3Lib.inf +++ b/Library/OcMp3Lib/OcMp3Lib.inf @@ -1,6 +1,6 @@ ## @file # -# Component description file for OcMisclibrary. +# Component description file for OcMp3Lib. # # Copyright (C) 2016 - 2018, The HermitCrabs Lab. All rights reserved.
# diff --git a/Library/OcStringLib/OcAsciiLib.c b/Library/OcStringLib/OcAsciiLib.c index 2d2d67fd..0917b907 100755 --- a/Library/OcStringLib/OcAsciiLib.c +++ b/Library/OcStringLib/OcAsciiLib.c @@ -526,3 +526,20 @@ OcAsciiPrintBuffer ( AsciiStrCatS (*AsciiBuffer, *AsciiBufferSize, Tmp); } } + +CHAR8 * +OcAsciiToLower ( + CHAR8 *Str + ) +{ + UINTN Index; + + ASSERT (Str != NULL); + + for (Index = 0; Str[Index] != '\0'; ++Index) { + if (Str[Index] >= 'A' && Str[Index] <= 'Z') { + Str[Index] -= ('A' - 'a'); + } + } + return Str; +} diff --git a/Library/OcStringLib/OcStringLib.inf b/Library/OcStringLib/OcStringLib.inf index 97974d2e..151264bc 100755 --- a/Library/OcStringLib/OcStringLib.inf +++ b/Library/OcStringLib/OcStringLib.inf @@ -37,9 +37,11 @@ [Packages] OpenCorePkg/OpenCorePkg.dec MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec [LibraryClasses] BaseLib BaseMemoryLib MemoryAllocationLib PrintLib + SortLib diff --git a/Library/OcStringLib/OcUnicodeLib.c b/Library/OcStringLib/OcUnicodeLib.c index 303d9aaf..12597af0 100755 --- a/Library/OcStringLib/OcUnicodeLib.c +++ b/Library/OcStringLib/OcUnicodeLib.c @@ -21,6 +21,7 @@ #include #include #include +#include INTN EFIAPI @@ -503,3 +504,21 @@ MixedStrCmp ( return *FirstString - *SecondString; } + +INTN +EFIAPI +OcReverseStringCompare ( + IN CONST VOID *Buffer1, + IN CONST VOID *Buffer2 + ) +{ + return -StringCompare (Buffer1, Buffer2); +} + +BOOLEAN +OcIsSpace ( + CHAR16 Ch + ) +{ + return (Ch == L' ') || (Ch == L'\t') || (Ch == L'\r') || (Ch == L'\n') || (Ch == L'\v') || (Ch == L'\f'); +} diff --git a/OpenCorePkg.dec b/OpenCorePkg.dec index 494e1633..522ffbf2 100755 --- a/OpenCorePkg.dec +++ b/OpenCorePkg.dec @@ -504,6 +504,9 @@ ## Include/Acidanthera/Protocol/OcForceResolution.h gOcForceResolutionProtocolGuid = { 0xBC7EC589, 0x2390, 0x4DA3, { 0x80, 0x25, 0x77, 0xDA, 0xD3, 0x4F, 0x36, 0x09 }} + ## Include/Acidanthera/Protocol/OcBootEntry.h + gOcBootEntryProtocolGuid = { 0x8604716E, 0xADD4, 0x45B4, { 0x84, 0x95, 0x08, 0xE3, 0x6D, 0x49, 0x7F, 0x4F }} + ## Include/AMI/Protocol/AmiPointer.h gAmiEfiPointerProtocolGuid = { 0x15A10CE7, 0xEAB5, 0x43BF, { 0x90, 0x42, 0x74, 0x43, 0x2E, 0x69, 0x63, 0x77 }} @@ -849,6 +852,9 @@ ## @libraryclass OcFirmwareVolumeLib|Include/Acidanthera/Library/OcFirmwareVolumeLib.h + ## @libraryclass + OcFlexArrayLib|Include/Acidanthera/Library/OcFlexArrayLib.h + ## @libraryclass OcGuardLib|Include/Acidanthera/Library/OcGuardLib.h diff --git a/OpenCorePkg.dsc b/OpenCorePkg.dsc index 35cc7440..3414a603 100755 --- a/OpenCorePkg.dsc +++ b/OpenCorePkg.dsc @@ -87,6 +87,7 @@ OcFileLib|OpenCorePkg/Library/OcFileLib/OcFileLib.inf OcFirmwarePasswordLib|OpenCorePkg/Library/OcFirmwarePasswordLib/OcFirmwarePasswordLib.inf OcFirmwareVolumeLib|OpenCorePkg/Library/OcFirmwareVolumeLib/OcFirmwareVolumeLib.inf + OcFlexArrayLib|OpenCorePkg/Library/OcFlexArrayLib/OcFlexArrayLib.inf OcGuardLib|OpenCorePkg/Library/OcGuardLib/OcGuardLib.inf OcHashServicesLib|OpenCorePkg/Library/OcHashServicesLib/OcHashServicesLib.inf OcHdaDevicesLib|OpenCorePkg/Library/OcHdaDevicesLib/OcHdaDevicesLib.inf @@ -229,6 +230,7 @@ OpenCorePkg/Library/OcFileLib/OcFileLib.inf OpenCorePkg/Library/OcFirmwarePasswordLib/OcFirmwarePasswordLib.inf OpenCorePkg/Library/OcFirmwareVolumeLib/OcFirmwareVolumeLib.inf + OpenCorePkg/Library/OcFlexArrayLib/OcFlexArrayLib.inf OpenCorePkg/Library/OcGuardLib/OcGuardLib.inf OpenCorePkg/Library/OcHashServicesLib/OcHashServicesLib.inf OpenCorePkg/Library/OcHdaDevicesLib/OcHdaDevicesLib.inf @@ -259,6 +261,7 @@ OpenCorePkg/Library/OcXmlLib/OcXmlLib.inf OpenCorePkg/Platform/CrScreenshotDxe/CrScreenshotDxe.inf OpenCorePkg/Platform/OpenCanopy/OpenCanopy.inf + OpenCorePkg/Platform/OpenLinuxBoot/OpenLinuxBoot.inf OpenCorePkg/Platform/OpenPartitionDxe/PartitionDxe.inf OpenCorePkg/Platform/OpenRuntime/OpenRuntime.inf OpenCorePkg/Platform/OpenUsbKbDxe/UsbKbDxe.inf diff --git a/Platform/OpenLinuxBoot/Autodetect.c b/Platform/OpenLinuxBoot/Autodetect.c new file mode 100644 index 00000000..b9a78ad0 --- /dev/null +++ b/Platform/OpenLinuxBoot/Autodetect.c @@ -0,0 +1,693 @@ +/** @file + vmlinuz and initramfs/initrd autodetect. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "LinuxBootInternal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GRUB_DEFAULT_FILE L"\\etc\\default\\grub" +#define OS_RELEASE_FILE L"\\etc\\os-release" +#define AUTODETECT_DIR L"\\boot" +#define ROOT_FS_FILE L"\\bin\\sh" + +STATIC +OC_FLEX_ARRAY +*mVmlinuzFiles; + +STATIC +OC_FLEX_ARRAY +*mInitrdFiles; + +STATIC +OC_FLEX_ARRAY +*mEtcOsReleaseOptions; + +STATIC +CHAR8 +*mPrettyName; + +STATIC +OC_FLEX_ARRAY +*mEtcDefaultGrubOptions; + +STATIC +EFI_STATUS +ProcessVmlinuzFile ( + EFI_FILE_HANDLE Directory, + EFI_FILE_INFO *FileInfo, + UINTN FileInfoSize, + VOID *Context OPTIONAL + ) +{ + CHAR16 *Dash; + VMLINUZ_FILE *VmlinuzFile; + + if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0) { + return EFI_NOT_FOUND; + } + + // + // Do not use files without '-' in the name, i.e. we do not need and + // do not try to use `vmlinuz` or `initrd` symlinks even if present + // (and even though we can in fact specify them as filenames and boot + // fine from them). + // + Dash = OcStrChr (FileInfo->FileName, L'-'); + if (Dash == NULL || Dash[1] == L'\0') { + return EFI_NOT_FOUND; + } + + if (StrnCmp (L"vmlinuz", FileInfo->FileName, L_STR_LEN (L"vmlinuz")) == 0) { + VmlinuzFile = OcFlexArrayAddItem (mVmlinuzFiles); + } else if (StrnCmp (L"init", FileInfo->FileName, L_STR_LEN (L"init")) == 0) { + // + // initrd* or initramfs* + // + VmlinuzFile = OcFlexArrayAddItem (mInitrdFiles); + } else { + return EFI_NOT_FOUND; + } + + if (VmlinuzFile == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + VmlinuzFile->FileName = AllocateCopyPool (StrSize (FileInfo->FileName), FileInfo->FileName); + if (VmlinuzFile->FileName == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + VmlinuzFile->Version = &VmlinuzFile->FileName[&Dash[1] - FileInfo->FileName]; + VmlinuzFile->StrLen = StrLen (FileInfo->FileName); + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +CreateAsciiRelativePath ( + CHAR8 **Dest, + CHAR16 *DirectoryPath, + UINTN DirectoryPathLength, + CHAR16 *FilePath, + UINTN FilePathLength + ) +{ + UINTN Size; + + Size = DirectoryPathLength + FilePathLength + 2; + *Dest = AllocatePool (Size); + if (*Dest == NULL) { + return EFI_OUT_OF_RESOURCES; + } + AsciiSPrint (*Dest, Size, "%s\\%s", DirectoryPath, FilePath); + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +CreateRootPartuuid ( + CHAR8 **Dest + ) +{ + UINTN Length; + UINTN NumPrinted; + + Length = L_STR_LEN ("root=PARTUUID=") + OC_EFI_GUID_STR_LEN; + + *Dest = AllocatePool (Length + 1); + if (*Dest == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + NumPrinted = AsciiSPrint (*Dest, Length + 1, "%a%g", "root=PARTUUID=", gPartuuid); + ASSERT (NumPrinted == Length); + + OcAsciiToLower (&(*Dest)[L_STR_LEN ("root=PARTUUID=")]); + + return EFI_SUCCESS; +} + +STATIC +VOID +AutodetectTitle ( + VOID + ) +{ + UINTN Index; + CHAR8 *AsciiStrValue; + BOOLEAN Found; + + mPrettyName = NULL; + if (mEtcOsReleaseOptions != NULL) { + // + // If neither are present, default title gets set later to "Linux". + // + Found = FALSE; + for (Index = 0; Index < 2; Index++) { + if (OcParsedVarsGetAsciiStr ( + mEtcOsReleaseOptions, + Index == 0 ? "PRETTY_NAME" : "NAME", + &AsciiStrValue + ) && + AsciiStrValue != NULL) { + mPrettyName = AsciiStrValue; + Found = TRUE; + break; + } + } + + if (Found) { + DEBUG ((DEBUG_INFO, "LNX: Found distro %a\n", mPrettyName)); + } else { + DEBUG ((DEBUG_WARN, "LNX: Neither %a nor %a found in %s\n", "PRETTY_NAME", "NAME", OS_RELEASE_FILE)); + } + } +} + +STATIC +EFI_STATUS +LoadEtcFiles ( + IN CONST EFI_FILE_PROTOCOL *RootDirectory + ) +{ + EFI_STATUS Status; + CHAR8 *Contents; + + Status = EFI_SUCCESS; + + mEtcOsReleaseOptions = NULL; + + // + // Load distro name from /etc/os-release. + // + Contents = OcReadFileFromDirectory (RootDirectory, OS_RELEASE_FILE, NULL, 0); + if (Contents == NULL) { + DEBUG ((DEBUG_WARN, "LNX: %s not found\n", OS_RELEASE_FILE)); + } else { + DEBUG ((DEBUG_INFO, "LNX: Reading %s\n", OS_RELEASE_FILE)); + Status = OcParseVars (Contents, &mEtcOsReleaseOptions, FALSE); + if (EFI_ERROR (Status)) { + FreePool (Contents); + DEBUG ((DEBUG_WARN, "LNX: Cannot parse %s - %r\n", OS_RELEASE_FILE, Status)); + return Status; + } + + // + // Do this early purely to give a nicer log entry order - distro is named + // before reports about it (esp. e.g. error below if it is not GRUB-based). + // + AutodetectTitle (); + } + + // + // Load kernel options from /etc/default/grub. + // + Contents = OcReadFileFromDirectory (RootDirectory, GRUB_DEFAULT_FILE, NULL, 0); + if (Contents == NULL) { + DEBUG ((DEBUG_WARN, "LNX: %s not found (bootloader is not GRUB?)\n", GRUB_DEFAULT_FILE)); + } else { + DEBUG ((DEBUG_INFO, "LNX: Reading %s\n", GRUB_DEFAULT_FILE)); + Status = OcParseVars (Contents, &mEtcDefaultGrubOptions, FALSE); + if (EFI_ERROR (Status)) { + FreePool (Contents); + DEBUG ((DEBUG_WARN, "LNX: Cannot parse %s - %r\n", GRUB_DEFAULT_FILE, Status)); + return Status; + } + } + + return EFI_SUCCESS; +} + +STATIC +VOID +FreeEtcFiles ( + VOID + ) +{ + OcFlexArrayFree (&mEtcOsReleaseOptions); + OcFlexArrayFree (&mEtcDefaultGrubOptions); +} + +STATIC +EFI_STATUS +InsertOption ( + IN CONST UINTN InsertIndex, + IN OC_FLEX_ARRAY *Options, + IN CONST VOID *Value, + IN CONST BOOLEAN IsUnicode + ) +{ + EFI_STATUS Status; + UINTN OptionsLength; + UINTN CopiedLength; + CHAR8 **Option; + + if (IsUnicode) { + OptionsLength = StrLen (Value); + } else { + OptionsLength = AsciiStrLen (Value); + } + + if (OptionsLength > 0) { + Option = OcFlexArrayInsertItem (Options, InsertIndex); + if (Option == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + if (IsUnicode) { + *Option = AllocatePool ((OptionsLength + 1) * sizeof (CHAR16)); + if (*Option == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = UnicodeStrnToAsciiStrS (Value, OptionsLength, *Option, OptionsLength + 1, &CopiedLength); + ASSERT (!EFI_ERROR (Status)); + ASSERT (CopiedLength == OptionsLength); + } else { + *Option = AllocateCopyPool (OptionsLength + 1, Value); + if (*Option == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +AddOption ( + IN OC_FLEX_ARRAY *Options, + IN CONST VOID *Value, + IN CONST BOOLEAN IsUnicode + ) +{ + return InsertOption (Options->Count, Options, Value, IsUnicode); +} + +// +// TODO: Options for rescue versions. Would it be better e.g. just to add "ro" and nothing else? +// However on some installs (e.g. where modules to load are specified in the kernel opts) this +// would not boot at all. +// Maybe upgrade to partuuidopts:{partuuid}r="...": user options for rescue kernels on specified partuuid? +// +STATIC +EFI_STATUS +AutodetectBootOptions ( + IN CONST BOOLEAN IsRescue, + IN OC_FLEX_ARRAY *Options +) +{ + EFI_STATUS Status; + UINTN Index; + UINTN InsertIndex; + OC_PARSED_VAR *Option; + EFI_GUID Guid; + CHAR8 *AsciiStrValue; + BOOLEAN Found; + + if ((gLinuxBootFlags & LINUX_BOOT_ADD_RO) != 0) { + DEBUG ((OC_TRACE_KERNEL_OPTS, "LNX: Adding \"ro\"\n")); + Status = AddOption (Options, "ro", FALSE); + } + + Found = FALSE; + InsertIndex = Options->Count; + + // + // Look for user-specified options for this partuuid. + // Remember that although args are ASCII in the OC config file, they are + // Unicode by the time they get passed as UEFI LoadOptions. + // + for (Index = 0; Index < gParsedLoadOptions->Count; Index++) { + Option = OcFlexArrayItemAt (gParsedLoadOptions, Index); + // + // partuuidopts:{partuuid}[+]="...": user options for specified partuuid. + // + if (OcUnicodeStartsWith (Option->Unicode.Name, L"partuuidopts:", TRUE)) { + if (Option->Unicode.Value == NULL) { + DEBUG ((DEBUG_WARN, "LNX: Missing value for %s\n", Option->Unicode.Name)); + continue; + } + + Status = StrToGuid (&Option->Unicode.Name[L_STR_LEN(L"partuuidopts:")], &Guid); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_WARN, "LNX: Cannot parse partuuid from %s - %r\n", Option->Unicode.Name, Status)); + continue; + } + + if (CompareMem (&gPartuuid, &Guid, sizeof (EFI_GUID)) != 0) { + DEBUG ((OC_TRACE_KERNEL_OPTS, "LNX: No match %g != %g\n", &gPartuuid, &Guid)); + } else { + DEBUG ((OC_TRACE_KERNEL_OPTS, "LNX: Using partuuidopts=\"%s\"\n", Option->Unicode.Value)); + + Status = AddOption (Options, Option->Unicode.Value, TRUE); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // partuuidopts:{partuuid}+="...": use user options in addition to detected options. + // + if (!OcUnicodeEndsWith (Option->Unicode.Name, L"+", FALSE)) { + return EFI_SUCCESS; + } + + Found = TRUE; + } + } + } + + // + // Use options from GRUB default location. + // + if (mEtcDefaultGrubOptions != NULL) { + // + // If both are present both should be added, standard grub scripts add them + // in this order. + // Rescue should only use GRUB_CMDLINE_LINUX so this is correct as + // far as it goes; however note that rescue options are unfortunately not + // normally stored here, but are generated in the depths of grub scripts. + // + for (Index = 0; Index < (IsRescue ? 1u : 2u); Index++) { + if (OcParsedVarsGetAsciiStr ( + mEtcDefaultGrubOptions, + Index == 0 ? "GRUB_CMDLINE_LINUX" : "GRUB_CMDLINE_LINUX_DEFAULT", + &AsciiStrValue + ) && + AsciiStrValue != NULL) { + + // + // Insert these after "ro" but before "partuuidopts+". + // + if (AsciiStrValue[0] != '\0') { + Status = InsertOption (InsertIndex, Options, AsciiStrValue, FALSE); + if (EFI_ERROR (Status)) { + return Status; + } + InsertIndex++; + } + + // + // Empty string value is good enough for found: we are operating + // from GRUB cfg files rather than pure guesswork. + // + Found = TRUE; + } + } + } + + // + // Use global defaults, if user has defined any. + // + for (Index = 0; Index < gParsedLoadOptions->Count; Index++) { + Option = OcFlexArrayItemAt (gParsedLoadOptions, Index); + if (!Found && StrCmp (Option->Unicode.Name, L"autoopts") == 0) { + if (Option->Unicode.Value == NULL) { + DEBUG ((DEBUG_WARN, "LNX: Missing value for %s\n", Option->Unicode.Name)); + continue; + } + + Status = AddOption (Options, Option->Unicode.Value, TRUE); + return Status; + } else if (StrCmp (Option->Unicode.Name, L"autoopts+") == 0) { + if (Option->Unicode.Value == NULL) { + DEBUG ((DEBUG_WARN, "LNX: Missing value for %s\n", Option->Unicode.Name)); + continue; + } + + Status = AddOption (Options, Option->Unicode.Value, TRUE); + if (EFI_ERROR (Status)) { + return Status; + } + + Found = TRUE; + } + } + + // + // It might be valid to have no options except "ro", but at least empty + // string "GRUB_CMDLINE_LINUX" needs to be present in that case or we stop. + // + if (!Found) { + DEBUG ((DEBUG_WARN, "LNX: No grub default or user defined options - aborting\n")); + return EFI_INVALID_PARAMETER; + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +GenerateEntriesForVmlinuzFiles ( + IN CHAR16 *DirectoryPath + ) +{ + EFI_STATUS Status; + UINTN VmlinuzIndex; + UINTN InitrdIndex; + UINTN ShortestMatch; + UINTN DirectoryPathLength; + NAMED_LOADER_ENTRY *NamedEntry; + LOADER_ENTRY *Entry; + VMLINUZ_FILE *VmlinuzFile; + VMLINUZ_FILE *InitrdFile; + VMLINUZ_FILE *InitrdMatch; + CHAR8 **Option; + BOOLEAN IsRescue; + + ASSERT (DirectoryPath != NULL); + DirectoryPathLength = StrLen (DirectoryPath); + + for (VmlinuzIndex = 0; VmlinuzIndex < mVmlinuzFiles->Count; VmlinuzIndex++) { + VmlinuzFile = OcFlexArrayItemAt (mVmlinuzFiles, VmlinuzIndex); + + IsRescue = FALSE; + if (OcUnicodeStartsWith (VmlinuzFile->Version, L"0", FALSE) + || StrStr (VmlinuzFile->Version, L"rescue") != NULL + || StrStr (VmlinuzFile->Version, L"recovery") != NULL) { + // + // We might have to scan /boot/grb/grub.cfg as grub os-prober does if + // we want to find rescue version options, or we need to find a way + // for user to pass these in, since they are generated in the depths + // of the grub scripts, and in typical distros are not present in + // /etc/default/grub, even though it looks as if they could be. + // + IsRescue = TRUE; + DEBUG ((DEBUG_INFO, "LNX: %s=rescue\n", VmlinuzFile->Version)); + } + + ShortestMatch = MAX_UINTN; + InitrdMatch = NULL; + + // + // Find shortest init* filename containing the same version string. + // + for (InitrdIndex = 0; InitrdIndex < mInitrdFiles->Count; InitrdIndex++) { + InitrdFile = OcFlexArrayItemAt (mInitrdFiles, InitrdIndex); + if (InitrdFile->StrLen < ShortestMatch) { + if (StrStr (InitrdFile->Version, VmlinuzFile->Version) != NULL) { + InitrdMatch = InitrdFile; + ShortestMatch = InitrdFile->StrLen; + } + } + } + + Entry = InternalAllocateLoaderEntry (); + if (Entry == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Linux. + // + Status = CreateAsciiRelativePath (&Entry->Linux, DirectoryPath, DirectoryPathLength, VmlinuzFile->FileName, VmlinuzFile->StrLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Version. + // + Entry->Version = AllocateCopyPool ( + VmlinuzFile->StrLen - (VmlinuzFile->Version - VmlinuzFile->FileName) + 1, + &Entry->Linux[VmlinuzFile->Version - VmlinuzFile->FileName + DirectoryPathLength + 1] + ); + if (Entry->Version == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // FileName & Id. + // + NamedEntry = InternalCreateNamedLoaderEntry (Entry, VmlinuzFile->FileName); + if (NamedEntry == NULL) { + InternalFreeLoaderEntry (&Entry); + return EFI_OUT_OF_RESOURCES; + } else { + // + // Named entry filename - do not free twice. + // + VmlinuzFile->FileName = NULL; + } + + // + // Use title from os-release file. + // + Entry->Title = AllocateCopyPool (AsciiStrSize (mPrettyName), mPrettyName); + if (Entry->Title == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Initrd. + // + if (InitrdMatch == NULL) { + // + // No need for WARN, where initrd was required user will see clear (and safe) warning from Linux kernel. + // + DEBUG ((DEBUG_INFO, "LNX: No matching initrd/initramfs file found for %a\n", Entry->Linux)); + } else { + Option = OcFlexArrayAddItem (Entry->Initrds); + if (Option == NULL) { + return EFI_OUT_OF_RESOURCES; + } + Status = CreateAsciiRelativePath (Option, DirectoryPath, DirectoryPathLength, InitrdMatch->FileName, InitrdMatch->StrLen); + if (EFI_ERROR (Status)) { + return Status; + } + } + + // + // root=PARTUUID=... option. + // + Option = OcFlexArrayAddItem (Entry->Options); + if (Option == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = CreateRootPartuuid (Option); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Remaining options. + // + Status = AutodetectBootOptions (IsRescue, Entry->Options); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} + +EFI_STATUS +AutodetectLinux ( + IN EFI_FILE_PROTOCOL *RootDirectory, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ) +{ + EFI_STATUS Status; + EFI_FILE_PROTOCOL *VmlinuzDirectory; + EFI_FILE_PROTOCOL *RootFsFile; + + // + // For now we are only searching in /boot. + // vmlinuz files in / should not require autodetect, as + // they should be accompanied by /loader/entries (Fedora-style), + // and vmlinuz files in /boot not accompanied by /loader/entries + // is Debian-style, so it seems sensible to wait to see what + // else there is rather than speculatively adding directories. + // + Status = OcSafeFileOpen (RootDirectory, &VmlinuzDirectory, AUTODETECT_DIR, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR (Status)) { + return Status; + } + + mVmlinuzFiles = NULL; + mInitrdFiles = NULL; + + Status = OcSafeFileOpen (RootDirectory, &RootFsFile, ROOT_FS_FILE, EFI_FILE_MODE_READ, 0); + if (!EFI_ERROR (Status)) { + Status = OcEnsureDirectory (RootFsFile, FALSE); + RootFsFile->Close (RootFsFile); + } + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "LNX: Does not appear to be root filesystem - %r\n", Status)); + } + + if (!EFI_ERROR (Status)){ + mVmlinuzFiles = OcFlexArrayInit (sizeof (VMLINUZ_FILE), OcFlexArrayFreePointerItem); + if (mVmlinuzFiles == NULL) { + Status = EFI_OUT_OF_RESOURCES; + } + } + + if (!EFI_ERROR (Status)){ + mInitrdFiles = OcFlexArrayInit (sizeof (VMLINUZ_FILE), OcFlexArrayFreePointerItem); + if (mInitrdFiles == NULL) { + Status = EFI_OUT_OF_RESOURCES; + } + } + + // + // Place vmlinuz* and init* files into arrays. + // + if (!EFI_ERROR (Status)){ + Status = OcScanDirectory (VmlinuzDirectory, ProcessVmlinuzFile, NULL); + } + + if (!EFI_ERROR (Status)) { + gNamedLoaderEntries = OcFlexArrayInit (sizeof (NAMED_LOADER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM) InternalFreeNamedLoaderEntry); + if (gNamedLoaderEntries == NULL) { + Status = EFI_OUT_OF_RESOURCES; + } else { + Status = LoadEtcFiles (RootDirectory); + if (!EFI_ERROR (Status)) { + Status = GenerateEntriesForVmlinuzFiles (AUTODETECT_DIR); + } + FreeEtcFiles(); + } + + if (!EFI_ERROR (Status)) { + Status = InternalConvertNamedLoaderEntriesToBootEntries ( + RootDirectory, + Entries, + NumEntries + ); + } + + OcFlexArrayFree (&gNamedLoaderEntries); + } + + if (mVmlinuzFiles != NULL) { + OcFlexArrayFree (&mVmlinuzFiles); + } + + if (mInitrdFiles != NULL) { + OcFlexArrayFree (&mInitrdFiles); + } + + VmlinuzDirectory->Close (VmlinuzDirectory); + return Status; +} diff --git a/Platform/OpenLinuxBoot/GrubCfg.c b/Platform/OpenLinuxBoot/GrubCfg.c new file mode 100644 index 00000000..10409b84 --- /dev/null +++ b/Platform/OpenLinuxBoot/GrubCfg.c @@ -0,0 +1,472 @@ +/** @file + Naive GRUB config parser. + + Attemps to respect GRUB escape and line continuation syntax, and + then to extract GRUB set commands for some basic processing. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "LinuxBootInternal.h" + +#include +#include +#include +#include +#include +#include + +/* + grub.cfg processing states. +*/ +typedef enum GRUB_PARSE_STATE_ { + GRUB_LEADING_SPACE, + GRUB_COMMENT, + GRUB_TOKEN, + GRUB_SINGLE_QUOTE, + GRUB_DOUBLE_QUOTE +} GRUB_PARSE_STATE; + +/* + grub.cfg $var processing states. +*/ +typedef enum GRUB_VAR_STATE_ { + GRUB_VAR_NONE, + GRUB_VAR_START, + GRUB_VAR_END, + GRUB_VAR_CHAR +} GRUB_VAR_STATE; + +#define GRUB_LINE "in grub.cfg at line" + +/* + grub.cfg $var processing flags. +*/ +#define VAR_FLAGS_NONE (0) +#define VAR_FLAGS_BRACE BIT0 +#define VAR_FLAGS_NUMERIC BIT1 + +#define SHIFT_TOKEN(offset) { \ + CopyMem (*Token + (offset), *Token, &Content[*Pos] - *Token); \ + *Token += (offset); \ +} + +STATIC +EFI_STATUS +GrubNextToken ( + CHAR8 *Content, + UINTN *Pos, + UINTN *Line, + CHAR8 **Token, + BOOLEAN *IsIndented, + BOOLEAN *ContainsVars + ) +{ + GRUB_PARSE_STATE GrubState; + GRUB_VAR_STATE VarState; + UINTN GrubVarFlags; + + BOOLEAN Escaped; + BOOLEAN TokenCompleted; + BOOLEAN Retake; + + CHAR8 Ch; + CHAR8 Ch2; + + UINTN Add; + + *Token = NULL; + *IsIndented = FALSE; + *ContainsVars = FALSE; + + GrubState = GRUB_LEADING_SPACE; + VarState = GRUB_VAR_NONE; + + Escaped = FALSE; + TokenCompleted = FALSE; + Retake = FALSE; + + do { + Ch = Content[*Pos]; + + if (Ch == '\n') { + *Line += 1; + } else if (!(Ch == '\0' || Ch == '\t' || (Ch >= 32 && Ch <= 127))) { + DEBUG ((DEBUG_WARN, "LNX: Invalid char 0x%x %a %u\n", Ch, GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + + // + // Deal with escape char and line continuation. + // + if (Ch == '\\') { + Ch2 = Content[(*Pos) + 1]; + + if (Ch2 == '\0' || Ch2 == '\n') { + // + // Line continuation. + // + if (VarState != GRUB_VAR_NONE) { + // + // We could handle this fine (just remove this check), but GRUB doesn't: + // https://www.gnu.org/software/grub/manual/grub/html_node/grub_fot.html#FOOT7 + // + DEBUG ((DEBUG_WARN, "LNX: Illegal line continuation within variable name %a %u\n", GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + + // + // '\n' go to char afterwards, '\0' go to '\0'. + // + Add = (Ch2 == '\n' ? 2 : 1); + SHIFT_TOKEN (Add); + *Pos += Add; + Ch = Content[*Pos]; + *Line += 1; + } else { + // + // Escapes. + // + switch (GrubState) { + // + // No escapes in single quote. + // + case GRUB_SINGLE_QUOTE: + break; + + // + // Only these escapes in double quote. + // + case GRUB_DOUBLE_QUOTE: + if (Ch2 == '$' || Ch2 == '"') { + Escaped = TRUE; + } + break; + + // + // Anything can be escaped. + // + default: + Escaped = TRUE; + break; + } + + if (Escaped) { + SHIFT_TOKEN (1); + ++(*Pos); + Ch = Ch2; + Escaped = FALSE; + } + } + } + + // + // Grub var is a special state which can be entered within other states. + // Allowed: $?, $@, $#, $nnn, $alphanumeric + // + if (VarState != GRUB_VAR_NONE) { + ASSERT (GrubState == GRUB_TOKEN || GrubState == GRUB_SINGLE_QUOTE || GrubState == GRUB_DOUBLE_QUOTE); + + switch (VarState) { + case GRUB_VAR_START: + // + // The fact that a token contains a var reference means we cannot use it; + // we are looking for tokens which define vars, not ones which use them. + // + *ContainsVars = TRUE; + + if (Ch == '{') { + if ((GrubVarFlags & VAR_FLAGS_BRACE) != 0) { + DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + GrubVarFlags |= VAR_FLAGS_BRACE; + } else if (Ch == '}') { + // + // Empty var name is valid. + // + if ((GrubVarFlags & VAR_FLAGS_BRACE) == 0) { + DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + VarState = GRUB_VAR_NONE; + } else if (Ch == '@' || Ch == '?' || Ch == '#') { + VarState = GRUB_VAR_END; + } else if (IS_DIGIT (Ch)) { + GrubVarFlags |= VAR_FLAGS_NUMERIC; + VarState = GRUB_VAR_CHAR; + } else if (Ch == '_' || IS_ALPHA (Ch)) { + VarState = GRUB_VAR_CHAR; + } else { + DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + + break; + + case GRUB_VAR_END: + if ((GrubVarFlags & VAR_FLAGS_BRACE) != 0) { + if (Ch != '}') { + DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + } else { + Retake = TRUE; + } + VarState = GRUB_VAR_NONE; + break; + + case GRUB_VAR_CHAR: + if (!(IS_DIGIT (Ch) || + ((GrubVarFlags & VAR_FLAGS_NUMERIC) == 0 + && (Ch == '_' || IS_ALPHA (Ch))))) { + VarState = GRUB_VAR_END; + Retake = TRUE; + } + break; + + default: + ASSERT (FALSE); + break; + } + } else { + switch (GrubState) { + case GRUB_LEADING_SPACE: + if (Ch == '\0' || Ch == '\n' || (!Escaped && Ch == ';')) { + TokenCompleted = TRUE; + Retake = TRUE; + } else if (Ch == ' ' || Ch == '\t') { + *IsIndented = TRUE; + } else if (Ch == '#') { + GrubState = GRUB_COMMENT; + } else { + *Token = &Content[*Pos]; + GrubState = GRUB_TOKEN; + Retake = TRUE; + } + break; + + case GRUB_COMMENT: + if (Ch == '\n' || Ch == '\0') { + TokenCompleted = TRUE; + Retake = TRUE; + } + break; + + case GRUB_TOKEN: + if (Ch == '\n' || Ch == '\0' || (!Escaped && (Ch == ';' || Ch == ' ' || Ch == '\t'))) { + TokenCompleted = TRUE; + Retake = TRUE; + } else if (!Escaped && Ch == '\'') { + SHIFT_TOKEN (1); + GrubState = GRUB_SINGLE_QUOTE; + } else if (!Escaped && Ch == '"') { + SHIFT_TOKEN (1); + GrubState = GRUB_DOUBLE_QUOTE; + } else if (!Escaped && Ch == '$') { + VarState = GRUB_VAR_START; + GrubVarFlags = VAR_FLAGS_NONE; + } + break; + + case GRUB_SINGLE_QUOTE: + if (Ch == '\'') { + SHIFT_TOKEN (1); + GrubState = GRUB_TOKEN; + } else if (Ch == '$') { + VarState = GRUB_VAR_START; + GrubVarFlags = VAR_FLAGS_NONE; + } + break; + + case GRUB_DOUBLE_QUOTE: + if (!Escaped && Ch == '"') { + SHIFT_TOKEN (1); + GrubState = GRUB_TOKEN; + } else if (!Escaped && Ch == '$') { + VarState = GRUB_VAR_START; + GrubVarFlags = VAR_FLAGS_NONE; + } + break; + } + } + + if (Retake) { + Retake = FALSE; + } else if (Ch != '\0') { + ++(*Pos); + } + } + while (Ch != '\0' && !TokenCompleted); + + if (!TokenCompleted && GrubState != GRUB_LEADING_SPACE) { + DEBUG ((DEBUG_WARN, "LNX: Syntax error (state=%u) %a %u\n", GrubState, GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + + return EFI_SUCCESS; +} + +STATIC +BOOLEAN +GrubNextLine ( + CHAR8 Ch, + UINTN *Pos + ) +{ + if (Ch == '\0') { + return TRUE; + } + + if (Ch == ';' || Ch == '\n') { + (*Pos)++; + return TRUE; + } + + ASSERT (Ch == ' ' || Ch == '\t'); + + (*Pos)++; + return FALSE; +} + +STATIC +EFI_STATUS +SetVar ( + UINTN Line, + CHAR8 *Token, + BOOLEAN IsIndented, + BOOLEAN ContainsVars + ) +{ + EFI_STATUS Status; + CHAR8 *Equals; + CHAR8 *Dollar; + UINTN VarStatus; + + // + // Note: It is correct grub2 parsing to treat these tokens with = in (whether after set or not) as one token. + // + Equals = OcAsciiStrChr (Token, '='); + if (Equals == NULL) { + DEBUG ((DEBUG_WARN, "LNX: Invalid set command %a %u\n", GRUB_LINE, Line)); + return EFI_INVALID_PARAMETER; + } + *Equals = '\0'; + + Dollar = OcAsciiStrChr (Token, '$'); + if (Dollar != NULL) { + // + // Non-typical but valid GRUB syntax to use variable replacements within + // variable name; we don't know what the name is (and are probably pretty + // unlikely to find the required values in valid, non-indented variables + // which we do know), so we ignore it. + // + DEBUG ((DEBUG_WARN, "LNX: Ignoring tokenised %a %a %a %u\n", "variable name", Token, GRUB_LINE, Line)); + return EFI_SUCCESS; + } + + VarStatus = 0; + if (IsIndented) { + VarStatus |= VAR_ERR_INDENTED; + } + if (ContainsVars) { + VarStatus |= VAR_ERR_HAS_VARS; + } + Status = InternalSetGrubVar (Token, Equals + 1, VarStatus); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +InternalProcessGrubCfg ( + IN OUT CHAR8 *Content + ) +{ + EFI_STATUS Status; + UINTN Pos; + UINTN Line; + UINTN NextLine; + UINTN TokenIndex; + CHAR8 *Dollar; + CHAR8 *Equals; + CHAR8 *Token; + CHAR8 LastChar; + BOOLEAN IsIndented; + BOOLEAN ContainsVars; + BOOLEAN SetCommand; + BOOLEAN SetIsIndented; + + Pos = 0; + Line = 1; + + do { + TokenIndex = 0; + SetCommand = FALSE; + + do { + // + // Save new line number until we've finished any messages. + // + NextLine = Line; + Status = GrubNextToken (Content, &Pos, &NextLine, &Token, &IsIndented, &ContainsVars); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Terminate token and remember terminator char. + // + LastChar = Content[Pos]; + if (Token != NULL) { + Content[Pos] = '\0'; + + if (TokenIndex == 0) { + // + // Warn on pretty obscure - though valid - syntax of building the command name from variables; + // do not warn for direct setting of grub internal values with no set command, i.e. just name=value, + // where the $ is only in the value. + // + Dollar = OcAsciiStrChr (Token, '$'); + if (Dollar != NULL) { + Equals = OcAsciiStrChr (Token, '='); + if (Equals == NULL || Dollar < Equals) { + DEBUG ((DEBUG_WARN, "LNX: Ignoring tokenised %a %a %a %u\n", "command", Token, GRUB_LINE, Line)); + } + } else { + // + // No non-indented variables after non-indented blscfg command can be used. + // + if (AsciiStrCmp("blscfg", Token) == 0) { + return EFI_SUCCESS; + } + + // + // We could process grub unset command similarly to set, but we probably don't need it. + // + if (AsciiStrCmp("set", Token) == 0) { + SetCommand = TRUE; + SetIsIndented = IsIndented; + } + } + } else if (TokenIndex == 1 && SetCommand) { + Status = SetVar (Line, Token, SetIsIndented, ContainsVars); + if (EFI_ERROR (Status)) { + return Status; + } + } + ++TokenIndex; + } + Line = NextLine; + } while (!GrubNextLine (LastChar, &Pos)); + } while (LastChar != '\0'); + + // + // Possibly allow through on flag? + // + DEBUG ((DEBUG_WARN, "LNX: blscfg command not found in grub.cfg\n")); + return EFI_NOT_FOUND; +} diff --git a/Platform/OpenLinuxBoot/GrubEnv.c b/Platform/OpenLinuxBoot/GrubEnv.c new file mode 100644 index 00000000..911dac8e --- /dev/null +++ b/Platform/OpenLinuxBoot/GrubEnv.c @@ -0,0 +1,85 @@ +/** @file + GRUB environment block parser. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include +#include +#include "LinuxBootInternal.h" + +/* + grubenv processing states. +*/ +typedef enum GRUBENV_STATE_ { + GRUBENV_NEXT_LINE, + GRUBENV_KEY, + GRUBENV_VAR, + GRUBENV_COMMENT +} GRUBENV_STATE; + +EFI_STATUS +InternalProcessGrubEnv ( + IN OUT CHAR8 *Content, + IN CONST UINTN Length + ) +{ + EFI_STATUS Status; + UINTN Pos; + UINTN KeyStart; + UINTN VarStart; + GRUBENV_STATE State; + + State = GRUBENV_NEXT_LINE; + + // + // In a valid grubenv block the last comment, if present, is not + // \n terminated, but all var lines must be. + // + for (Pos = 0; Pos < Length && Content[Pos] != '\0'; Pos++) { + switch (State) { + case GRUBENV_NEXT_LINE: + if (Content[Pos] == '#') { + State = GRUBENV_COMMENT; + } else { + KeyStart = Pos; + State = GRUBENV_KEY; + } + break; + + case GRUBENV_COMMENT: + if (Content[Pos] == '\n') { + State = GRUBENV_NEXT_LINE; + } + break; + + case GRUBENV_KEY: + if (Content[Pos] == '=') { + Content[Pos] = '\0'; + VarStart = Pos + 1; + State = GRUBENV_VAR; + } + break; + + case GRUBENV_VAR: + if (Content[Pos] == '\n') { + Content[Pos] = '\0'; + Status = InternalSetGrubVar (&Content[KeyStart], &Content[VarStart], VAR_ERR_NONE); + if (EFI_ERROR (Status)) { + return Status; + } + State = GRUBENV_NEXT_LINE; + } + break; + + default: + ASSERT (FALSE); + break; + } + } + + ASSERT (State == GRUBENV_COMMENT || State == GRUBENV_NEXT_LINE); + + return EFI_SUCCESS; +} diff --git a/Platform/OpenLinuxBoot/GrubVars.c b/Platform/OpenLinuxBoot/GrubVars.c new file mode 100644 index 00000000..15585d4f --- /dev/null +++ b/Platform/OpenLinuxBoot/GrubVars.c @@ -0,0 +1,273 @@ +/** @file + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "LinuxBootInternal.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +STATIC OC_FLEX_ARRAY *mGrubVars = NULL; + +EFI_STATUS +InternalInitGrubVars ( + VOID + ) +{ + mGrubVars = OcFlexArrayInit (sizeof (GRUB_VAR), NULL); + if (mGrubVars == NULL) { + return EFI_OUT_OF_RESOURCES; + } + return EFI_SUCCESS; +} + +VOID +InternalFreeGrubVars ( + VOID + ) +{ + if (mGrubVars != NULL) { + OcFlexArrayFree (&mGrubVars); + } +} + +EFI_STATUS +InternalSetGrubVar ( + CHAR8 *Key, + CHAR8 *Value, + UINTN Errors + ) +{ + GRUB_VAR *Var; + UINTN Index; + UINTN WereErrors; + + ASSERT (mGrubVars != NULL); + ASSERT (Key[0] != '\0'); + + for (Index = 0; Index < mGrubVars->Count; ++Index) { + Var = OcFlexArrayItemAt (mGrubVars, Index); + if (AsciiStrCmp (Var->Key, Key) == 0) { + break; + } + } + + if (Index < mGrubVars->Count) { + Var->Value = Value; + WereErrors = Var->Errors; + + // + // Probably not worth the two lines of code (because unlikely to + // occur), but: allow later non-indented no-vars value to overwrite + // earlier non-indented has-vars value and thereby become usable. + // + if ((Errors & VAR_ERR_INDENTED) == 0) { + Var->Errors &= ~VAR_ERR_HAS_VARS; + } + + // + // Indentation err stays set because, even if grub.cfg code layout is + // reasonable as we are assuming, we are not parsing enough to tell + // which order (or none) indented vars are set in. + // + Var->Errors |= Errors; + + DEBUG ((OC_TRACE_GRUB_VARS, + "LNX: Repeated %a=%a (0x%x->0x%x)\n", + Key, + Value, + WereErrors, + Var->Errors + )); + } else { + Var = OcFlexArrayAddItem (mGrubVars); + if (Var == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Var->Key = Key; + Var->Value = Value; + Var->Errors = Errors; + + DEBUG ((OC_TRACE_GRUB_VARS, + "LNX: Added %a=%a (0x%x)\n", + Key, + Value, + Errors + )); + } + + return EFI_SUCCESS; +} + +// +// Compatible with Grub2 blscfg's simple definition of what gets replaced, +// cf InternalExpandGrubVars. (If there was more complex logic, it would +// probably make most sense just not to have this pre-check.) +// +BOOLEAN +InternalHasGrubVars ( + CHAR8 *Value + ) +{ + return OcAsciiStrChr (Value, '$') != NULL; +} + +GRUB_VAR * +InternalGetGrubVar ( + IN CONST CHAR8 *Key + ) +{ + UINTN Index; + GRUB_VAR *Var; + + for (Index = 0; Index < mGrubVars->Count; Index++) { + Var = OcFlexArrayItemAt (mGrubVars, Index); + if (AsciiStrCmp (Var->Key, Key) == 0) { + return Var; + } + } + + return NULL; +} + +EFI_STATUS +InternalExpandGrubVarsForArray ( + IN OUT OC_FLEX_ARRAY *Options + ) +{ + EFI_STATUS Status; + UINTN Index; + CHAR8 **Value; + CHAR8 *Result; + + for (Index = 0; Index < Options->Count; Index++) { + Value = OcFlexArrayItemAt (Options, Index); + if (InternalHasGrubVars (*Value)) { + Status = InternalExpandGrubVars (*Value, &Result); + if (EFI_ERROR (Status)) { + return Status; + } + FreePool (*Value); + *Value = Result; + } + } + + return EFI_SUCCESS; +} + +EFI_STATUS +InternalExpandGrubVars ( + IN CONST CHAR8 *Value, + IN OUT CHAR8 **Result + ) +{ + EFI_STATUS Status; + UINTN Pos; + UINTN LastPos; + BOOLEAN InVar; + BOOLEAN Retake; + GRUB_VAR *Var; + CHAR8 Ch; + UINTN VarLength; + OC_STRING_BUFFER *StringBuffer; + + ASSERT (Value != NULL); + ASSERT (Result != NULL); + + *Result = NULL; + + if (Value == NULL) { + return EFI_INVALID_PARAMETER; + } + + StringBuffer = OcAsciiStringBufferInit (); + if (StringBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Pos = 0; + LastPos = 0; + InVar = FALSE; + Status = EFI_SUCCESS; + + // + // These simple checks for what counts as a var to replace (including + // the fact that there is no escape for '$') match Grub2 blscfg module. + // + do { + Ch = Value[Pos]; + if (!InVar) { + if (Ch == '$' || Ch == '\0') { + Status = OcAsciiStringBufferAppendN (StringBuffer, &Value[LastPos], Pos - LastPos); + + InVar = TRUE; + LastPos = Pos + 1; + } + } else if (!(Ch == '_' || IS_DIGIT (Ch) || IS_ALPHA (Ch))) { + ((CHAR8 *)Value)[Pos] = '\0'; + Var = InternalGetGrubVar (&Value[LastPos]); + if (Var == NULL) { + DEBUG ((DEBUG_WARN, "LNX: Missing required grub var $%a\n", &Value[LastPos])); + Status = EFI_INVALID_PARAMETER; + } else if (Var->Errors != 0) { + DEBUG ((DEBUG_WARN, "LNX: Unusable grub var $%a - 0x%x\n", &Value[LastPos], Var->Errors)); + Status = EFI_INVALID_PARAMETER; + } + ((CHAR8 *)Value)[Pos] = Ch; + + // + // Blscfg always appends a space to each expanded token (and the var values + // are often set up to end with a space, too), then later GRUB tokenization + // of the options as grub booter options (which we do not currently do - may + // be needed in some escaped cases?) cleans it up. Here, we try to combine + // obvious doubled up spaces right away. + // + if (!EFI_ERROR (Status)) { + if (Var->Value != NULL) { + VarLength = AsciiStrLen (Var->Value); + if (VarLength > 0 && Var->Value[VarLength - 1] == ' ') { + --VarLength; + } + } + Status = OcAsciiStringBufferAppendN (StringBuffer, Var->Value, VarLength); + } + + if (!EFI_ERROR (Status) && !(Ch == ' ' || Ch == '\0')) { + Status = OcAsciiStringBufferAppend (StringBuffer, " "); + } + + InVar = FALSE; + Retake = TRUE; + LastPos = Pos; + } + + if (Retake) { + Retake = FALSE; + } else { + ++Pos; + } + } + while (Ch != '\0' && !EFI_ERROR (Status)); + + *Result = OcAsciiStringBufferFreeContainer (&StringBuffer); + + if (EFI_ERROR (Status)) { + if (*Result != NULL) { + FreePool (*Result); + *Result = NULL; + } + } + + DEBUG ((OC_TRACE_GRUB_VARS, "LNX: Expanding '%a' => '%a' - %r\n", Value, *Result, Status)); + + return Status; +} diff --git a/Platform/OpenLinuxBoot/LinuxBootInternal.h b/Platform/OpenLinuxBoot/LinuxBootInternal.h new file mode 100644 index 00000000..d01c1109 --- /dev/null +++ b/Platform/OpenLinuxBoot/LinuxBootInternal.h @@ -0,0 +1,360 @@ +/** @file + Copyright (C) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#ifndef LINUX_BOOT_INTERNAL_H +#define LINUX_BOOT_INTERNAL_H + +#if !defined(OC_TRACE_GRUB_VARS) +#define OC_TRACE_GRUB_VARS DEBUG_VERBOSE +#endif + +#if !defined(OC_TRACE_KERNEL_OPTS) +#define OC_TRACE_KERNEL_OPTS DEBUG_VERBOSE +#endif + +#include +#include +#include + +#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) + +/* + Allow scan of ESP. +*/ +#define LINUX_BOOT_SCAN_ESP BIT0 +/* + Allow scan of XBOOTLDR. +*/ +#define LINUX_BOOT_SCAN_XBOOTLDR BIT1 +/* + Allow scan of Linux Root filesystems. +*/ +#define LINUX_BOOT_SCAN_LINUX_ROOT BIT2 +/* + Allow scan of Linux Data filesystems. +*/ +#define LINUX_BOOT_SCAN_LINUX_DATA BIT3 +/* + Some space for additional file systems. +*/ +/* + Allow scan of any filesystem not explicitly mentioned + (including but not limited FAT other than ESP, and NTFS). +*/ +#define LINUX_BOOT_SCAN_OTHER BIT7 +/* + Allow autodetect of vmlinuz-{version} and matching init*-{version}, + if scan for usable /loader/entries fails. +*/ +#define LINUX_BOOT_ALLOW_AUTODETECT BIT8 +/* + Define entry id by the first part (to dash) of the filename + from which it was created. Results in the first matching entry (after sorting) + always being the default entry, which results in updated Linux becoming the + new default automatically. +*/ +#define LINUX_BOOT_USE_LATEST BIT9 +/* + If set, add "ro" as initial option to all distros. Can be sepcified per + FS by using argument partuuidopts:{partuuid}+=ro instead. +*/ +#define LINUX_BOOT_ADD_RO BIT10 +/* + Prepend filesystem type and first 8 hex digits of PARTUUID to discovered + entry titles, to help in debugging where entries came from. +*/ +#define LINUX_BOOT_ADD_DEBUG_INFO BIT15 + +#define LINUX_BOOT_ALL ( \ + LINUX_BOOT_SCAN_ESP | \ + LINUX_BOOT_SCAN_XBOOTLDR | \ + LINUX_BOOT_SCAN_LINUX_ROOT | \ + LINUX_BOOT_SCAN_LINUX_DATA | \ + LINUX_BOOT_SCAN_OTHER | \ + LINUX_BOOT_ALLOW_AUTODETECT | \ + LINUX_BOOT_USE_LATEST | \ + LINUX_BOOT_ADD_RO | \ + LINUX_BOOT_ADD_DEBUG_INFO \ + ) + +/* + GRUB var error codes. +*/ +#define VAR_ERR_NONE (0) +#define VAR_ERR_INDENTED BIT0 // Naive detection of GRUB conditional logic +#define VAR_ERR_HAS_VARS BIT1 // We do not support nested vars (in name or value), even though GRUB does + +/* + Global flags for this instance of OpenLinuxBoot.efi. +*/ +extern UINTN gLinuxBootFlags; + +/* + Boot picker context. +*/ +extern OC_PICKER_CONTEXT *gPickerContext; + +/* + Stored parsed load options. + Would be freed at driver unload, if that happened. + */ +extern OC_FLEX_ARRAY *gParsedLoadOptions; + +/* + The array of loader entries, either really from *.conf files or generated by autodetect. +*/ +extern OC_FLEX_ARRAY *gNamedLoaderEntries; + +/* + The current partuuid. +*/ +extern EFI_GUID gPartuuid; + +/* + Human readable ascii name of current file system type. +*/ +extern CHAR8 *gFileSystemType; + +// TODO: Are all of the below types used outside a single file? +// TODO: Is this file sensibly ordered? + +/* + Forward declaration of GRUB_VAR structure. +*/ +typedef struct GRUB_VAR_ GRUB_VAR; + +/* + Forward declaration of LOADER_ENTRY structure. +*/ +typedef struct LOADER_ENTRY_ LOADER_ENTRY; + +/* + Forward declaration of NAMED_LOADER_ENTRY structure. +*/ +typedef struct NAMED_LOADER_ENTRY_ NAMED_LOADER_ENTRY; + +/* + Forward declaration of VMLINUZ_FILE structure. +*/ +typedef struct VMLINUZ_FILE_ VMLINUZ_FILE; + +/* + GRUB vars. +*/ +EFI_STATUS +InternalInitGrubVars ( + VOID + ); + +VOID +InternalFreeGrubVars ( + VOID + ); + +EFI_STATUS +InternalSetGrubVar ( + CHAR8 *Key, + CHAR8 *Value, + UINTN Errors + ); + +BOOLEAN +InternalHasGrubVars ( + CHAR8 *Options + ); + +GRUB_VAR * +InternalGetGrubVar ( + IN CONST CHAR8 *Key + ); + +EFI_STATUS +InternalExpandGrubVarsForArray ( + IN OUT OC_FLEX_ARRAY *Options + ); + +EFI_STATUS +InternalExpandGrubVars ( + IN CONST CHAR8 *Options, + IN OUT CHAR8 **Result + ); + +/* + Process grubenv file. +*/ +EFI_STATUS +InternalProcessGrubEnv ( + IN OUT CHAR8 *Content, + IN CONST UINTN Length + ); + +/* + Process grub.cfg file. +*/ +EFI_STATUS +InternalProcessGrubCfg ( + IN OUT CHAR8 *Content + ); + +LOADER_ENTRY * +InternalAllocateLoaderEntry ( + VOID + ); + +VOID +InternalFreeLoaderEntry ( + LOADER_ENTRY **Entry + ); + +EFI_STATUS +InternalProcessLoaderEntryFile ( + IN CONST CHAR16 *FileName, + IN OUT CHAR8 *Content, + OUT LOADER_ENTRY **Entry, + IN CONST BOOLEAN Grub2 + ); + +/* + GRUB variable. +*/ +struct GRUB_VAR_ { + // + // Points within loaded file memory. + // + CHAR8 *Key; + // + // Points within loaded file memory, may be empty string. + // + CHAR8 *Value; + // + // GRUB var error code flags. + // + UINTN Errors; +}; + +/* + Loader entries. +*/ + +NAMED_LOADER_ENTRY * +InternalCreateNamedLoaderEntry ( + IN LOADER_ENTRY *Entry, + IN CHAR16 *FileName + ); + +VOID +InternalFreePickerEntry ( + IN OC_PICKER_ENTRY *Entry + ); + +VOID +InternalFreeNamedLoaderEntry ( + NAMED_LOADER_ENTRY *Entry + ); + +EFI_STATUS +InternalConvertNamedLoaderEntriesToBootEntries ( + IN EFI_FILE_PROTOCOL *RootDirectory, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ); + +EFI_STATUS +ScanLoaderEntries ( + IN EFI_FILE_PROTOCOL *RootDirectory, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ); + +/* + BLSpec / blscfg loader entry. + Some items within here probably don't need to be allocated and could stay + pointing within the source file as long as that is in memory (specifically + Version and Linux, which are probably never going to be modified), but to + keep things sane everything is (re)allocated. +*/ +struct LOADER_ENTRY_ { + // + // First(blscfg)/last(sd-boot) title line encountered. + // Otherwise attempted autodetect "{Variant}" (e.g. "Ubuntu", "Fedora") from within kernel image. + // Otherwise "Linux". + // + CHAR8 *Title; + // + // First(blscfg)/last(sd-boot) version line encountered; + // Otherwise version-id from {machine-id}-{kernel-version}.conf or vmlinuz-{kernel-version} filename. + // Otherwise attempted autodetect from within kernel image. + // + CHAR8 *Version; + // + // First(blscfg)/last(sd-boot) linux line encountered. + // Required. + // + CHAR8 *Linux; + // + // Option lines encountered. + // In pure BLSpec (and in sd-boot) all are used; in blscfg only the first option line is used, so we match that. + // TODO: Test starting something (not really a Linux kernel?) with no options and no initrds. + // + OC_FLEX_ARRAY *Options; + // + // Initrd lines encountered. + // (All are used in both blscfg and sd-boot.) + // + OC_FLEX_ARRAY *Initrds; + // + // id line is not read from .conf file even if present. + // OcId is generated from .conf filename, to share machine-id between + // {machine-id}-{kernel-version}.conf files, in order to auto-boot the + // most recent kernel when a new one appears. + // + CHAR8 *OcId; + // + // Autodetect from within kernel image ("{Variant}:Linux"). + // Otherwise just "Linux". + // + CHAR8 *OcFlavour; + // + // Is this an auxiliary entry for OpenCore? + // + BOOLEAN OcAuxiliary; +}; + +// +// Values for NAMED_LOADER_ENTRY DuplicateFlags. +// We previously implemented pure-Boot Loader Spec-style title disambiguation +// only when there is more than one entry with the same name, however +// within OC it works better to always disambiguate when +// HideAuxiliary is FALSE and never otherwise. +// +#define DUPLICATE_ID_SCANNED BIT0 + +/* + Loader entry associated with filename, for sorting. +*/ +struct NAMED_LOADER_ENTRY_ { + CHAR16 *FileName; + LOADER_ENTRY *Entry; + UINTN DuplicateFlags; +}; + +struct VMLINUZ_FILE_ { + CHAR16 *FileName; + CHAR16 *Version; + UINTN StrLen; +}; + +/* + Autodetect. +*/ +EFI_STATUS +AutodetectLinux ( + IN EFI_FILE_PROTOCOL *RootDirectory, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ); + +#endif // LINUX_BOOT_INTERNAL_H diff --git a/Platform/OpenLinuxBoot/LoaderEntry.c b/Platform/OpenLinuxBoot/LoaderEntry.c new file mode 100644 index 00000000..bd764514 --- /dev/null +++ b/Platform/OpenLinuxBoot/LoaderEntry.c @@ -0,0 +1,1169 @@ +/** @file + Boot Loader Spec / Grub2 blscfg module loader entry parser. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "LinuxBootInternal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define LOADER_ENTRIES_DIR L"\\loader\\entries" +#define GRUB2_GRUB_CFG L"\\grub2\\grub.cfg" +#define GRUB2_GRUBENV L"\\grub2\\grubenv" +#define GRUB2_GRUBENV_SIZE SIZE_1KB + +#define BLSPEC_SUFFIX_CONF L".conf" +#define BLSPEC_PREFIX_AUTO L"auto-" + +// +// Put a limit on entry name length, since the base part of the filename +// is used for the id, i.e. may be stored in BOOT#### entry in NVRAM. +// We then re-use the same filename length limit for vmlinuz and initrd files. +// Since we are using pure EFISTUB loading, initrd file paths have to be passed +// in kernel args (which have an unknown max. length of 256-4096 bytes). +// +#define MAX_LOADER_ENTRY_NAME_LEN (127) +#define MAX_LOADER_ENTRY_FILE_INFO_SIZE ( \ + SIZE_OF_EFI_FILE_INFO + \ + (MAX_LOADER_ENTRY_NAME_LEN + L_STR_LEN (BLSPEC_SUFFIX_CONF) + 1) * sizeof (CHAR16) \ + ) + +// +// Might as well put an upper limit for some kind of sanity check. +// Typical files are ~350 bytes. +// +#define MAX_LOADER_ENTRY_FILE_SIZE SIZE_4KB + +STATIC +BOOLEAN +mIsGrub2; + +/* + loader entry processing states +*/ +typedef enum ENTRY_PARSE_STATE_ { + ENTRY_LEADING_SPACE, + ENTRY_COMMENT, + ENTRY_KEY, + ENTRY_KEY_VALUE_SPACE, + ENTRY_VALUE +} ENTRY_PARSE_STATE; + +// +// First match, therefore Ubuntu should come after similar variants, +// and probably very short strings should come last in case they +// occur elsewhere in another kernel version string. +// NB: Should be kept in sync with Flavours.md. +// +STATIC +CHAR8 * +mLinuxVariants[] = { + "Arch", + "Astra", + "CentOS", + "Debian", + "Deepin", + "elementaryOS", + "Endless", + "Gentoo", + "Fedora", + "KDEneon", + "Kali", + "Mageia", + "Manjaro", + "Mint", + "openSUSE", + "Oracle", + "PopOS", + "RHEL", + "Rocky", + "Solus", + "Lubuntu", + "UbuntuMATE", + "Xubuntu", + "Ubuntu", + "Void", + "Zorin", + "MX" +}; + +STATIC +CHAR8 * +ExtractVariantFrom ( + IN CHAR8 *String +) +{ + UINTN Index; + CHAR8 *Variant; + + Variant = NULL; + for (Index = 0; Index < ARRAY_SIZE (mLinuxVariants); Index++) { + if (OcAsciiStriStr (String, mLinuxVariants[Index]) != NULL) { + Variant = mLinuxVariants[Index]; + break; + } + } + + return Variant; +} + +// +// Skips comment lines, and lines with key but no value +// EFI_LOAD_ERROR - File has invalid chars +// +STATIC +EFI_STATUS +GetLoaderEntryLine ( + IN CONST CHAR16 *FileName, + IN OUT CHAR8 *Content, + IN OUT UINTN *Pos, + OUT CHAR8 **Key, + OUT CHAR8 **Value + ) +{ + ENTRY_PARSE_STATE State; + CHAR8 *LastSpace; + CHAR8 Ch; + BOOLEAN IsComplete; + + *Key = NULL; + *Value = NULL; + State = ENTRY_LEADING_SPACE; + IsComplete = FALSE; + + do { + Ch = Content[*Pos]; + + if (!(Ch == '\0' || Ch == '\t' || Ch == '\n' || (Ch >= 20 && Ch < 128))) { + DEBUG ((DEBUG_WARN, "LNX: Invalid char 0x%x in %s\n", Ch, FileName)); + return EFI_INVALID_PARAMETER; + } + + switch (State) { + case ENTRY_LEADING_SPACE: + if (Ch == '\n' || Ch == '\0') { + // + // Skip empty line + // + } else if (Ch == ' ' || Ch == '\t') { + } else if (Ch == '#') { + State = ENTRY_COMMENT; + } else { + *Key = &Content[*Pos]; + State = ENTRY_KEY; + } + break; + + case ENTRY_COMMENT: + if (Ch == '\n') { + State = ENTRY_LEADING_SPACE; + } + break; + + case ENTRY_KEY: + if (Ch == '\n' || Ch == '\0') { + // + // No value, skip line + // + } else if (Ch == ' ' || Ch == '\t') { + Content[*Pos] = '\0'; + State = ENTRY_KEY_VALUE_SPACE; + } + break; + + case ENTRY_KEY_VALUE_SPACE: + if (Ch == '\n' || Ch == '\0') { + // + // No value, skip line + // + } else if (Ch == ' ' || Ch == '\t') { + } else { + *Value = &Content[*Pos]; + State = ENTRY_VALUE; + LastSpace = NULL; + } + break; + + case ENTRY_VALUE: + if (Ch == '\n' || Ch == '\0') { + if (LastSpace != NULL) { + *LastSpace = '\0'; + } else { + Content[*Pos] = '\0'; + } + IsComplete = TRUE; + } else if (Ch == ' ' || Ch == '\t' ) { + LastSpace = &Content[*Pos]; + } else { + LastSpace = NULL; + } + break; + + default: + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + if (Ch != '\0') { + ++(*Pos); + } + } + while (Ch != '\0' && !IsComplete); + + if (!IsComplete) { + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +LOADER_ENTRY * +InternalAllocateLoaderEntry ( + VOID + ) +{ + LOADER_ENTRY *Entry; + + Entry = AllocateZeroPool (sizeof (LOADER_ENTRY)); + + if (Entry != NULL) { + Entry->Options = OcFlexArrayInit (sizeof (CHAR8 *), OcFlexArrayFreePointerItem); + if (Entry->Options == NULL) { + InternalFreeLoaderEntry (&Entry); + } + } + + if (Entry != NULL) { + Entry->Initrds = OcFlexArrayInit (sizeof (CHAR8 *), OcFlexArrayFreePointerItem); + if (Entry->Initrds == NULL) { + InternalFreeLoaderEntry (&Entry); + } + } + + return Entry; +} + +VOID +InternalFreeLoaderEntry ( + LOADER_ENTRY **Entry + ) +{ + ASSERT (Entry != NULL); + ASSERT (*Entry != NULL); + + if (Entry != NULL && *Entry != NULL) { + if ((*Entry)->Title) { + FreePool ((*Entry)->Title); + } + if ((*Entry)->Version) { + FreePool ((*Entry)->Version); + } + if ((*Entry)->Linux) { + FreePool ((*Entry)->Linux); + } + OcFlexArrayFree (&(*Entry)->Initrds); + OcFlexArrayFree (&(*Entry)->Options); + if ((*Entry)->OcId != NULL) { + FreePool ((*Entry)->OcId); + } + if ((*Entry)->OcFlavour != NULL) { + FreePool ((*Entry)->OcFlavour); + } + + FreePool (*Entry); + + *Entry = NULL; + } +} + +STATIC +EFI_STATUS +EntryCopySingleValue ( + IN CONST BOOLEAN Grub2, + IN OUT CHAR8 **Target, + IN CONST CHAR8 *Value + ) +{ + if (!Grub2 || *Target == NULL) { + if (*Target != NULL) { + FreePool (*Target); + } + *Target = AllocateCopyPool (AsciiStrSize (Value), Value); + if (*Target == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EntryCopyMultipleValue ( + IN CONST BOOLEAN Grub2, + IN OC_FLEX_ARRAY *Array, + IN CONST CHAR8 *Value + ) +{ + VOID **NewItem; + + if (Grub2 && Array->Items != NULL) { + return EFI_SUCCESS; + } + + NewItem = OcFlexArrayAddItem (Array); + if (NewItem == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + *NewItem = AllocateCopyPool (AsciiStrSize (Value), Value); + if (*NewItem == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +InternalProcessLoaderEntryFile ( + IN CONST CHAR16 *FileName, + IN OUT CHAR8 *Content, + OUT LOADER_ENTRY **Entry, + IN CONST BOOLEAN Grub2 + ) +{ + EFI_STATUS Status; + UINTN Pos; + CHAR8 *Key; + CHAR8 *Value; + + ASSERT (Content != NULL); + + *Entry = InternalAllocateLoaderEntry (); + if (*Entry == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_SUCCESS; + Pos = 0; + + // + // Grub2 blscfg module uses first only (even for options, + // which should allow more than one according to BL Spec). + // systemd-boot uses multiple (concatenated with a space) + // for options, and last only for everything which should + // have only one. + // Both use multiple for initrd. + // + while (TRUE) { + Status = GetLoaderEntryLine(FileName, Content, &Pos, &Key, &Value); + if (Status == EFI_NOT_FOUND) { + break; + } + + if (EFI_ERROR (Status)) { + InternalFreeLoaderEntry (Entry); + return Status; + } + + if (AsciiStrCmp (Key, "title") == 0) { + Status = EntryCopySingleValue (Grub2, &(*Entry)->Title, Value); + } else if (AsciiStrCmp (Key, "version") == 0) { + Status = EntryCopySingleValue (Grub2, &(*Entry)->Version, Value); + } else if (AsciiStrCmp (Key, "linux") == 0) { + Status = EntryCopySingleValue (Grub2, &(*Entry)->Linux, Value); + } else if (AsciiStrCmp (Key, "options") == 0) { + Status = EntryCopyMultipleValue (Grub2, (*Entry)->Options, Value); + } else if (AsciiStrCmp (Key, "initrd") == 0) { + Status = EntryCopyMultipleValue (FALSE, (*Entry)->Initrds, Value); + } + + if (EFI_ERROR (Status)) { + InternalFreeLoaderEntry (Entry); + return Status; + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +ExpandReplaceOptions ( + IN OUT LOADER_ENTRY *Entry + ) +{ + EFI_STATUS Status; + CHAR8 **Options; + CHAR8 *NewOptions; + GRUB_VAR *DefaultOptionsVar; + + if (Entry->Options->Count > 0) { + // + // Grub2 blscfg takes the first only. + // + ASSERT (Entry->Options->Count == 1); + Status = InternalExpandGrubVarsForArray (Entry->Options); + if (EFI_ERROR (Status)) { + return Status; + } + } else { + // + // This is what grub2 blscfg does if there are no options. + // + DefaultOptionsVar = InternalGetGrubVar ("default_kernelopts"); + if (DefaultOptionsVar != NULL) { + if (DefaultOptionsVar->Errors != 0) { + DEBUG ((DEBUG_WARN, "LNX: Unusable grub var $%a - 0x%x\n", "default_kernelopts", DefaultOptionsVar->Errors)); + return EFI_INVALID_PARAMETER; + } + + DEBUG ((DEBUG_INFO, "LNX: Using $%a\n", "default_kernelopts")); + + // + // Blscfg expands $default_kernelopts, and we expand it even + // if !HasVars since we need the string to be realloced. + // + Status = InternalExpandGrubVars (DefaultOptionsVar->Value, &NewOptions); + if (EFI_ERROR (Status)) { + return Status; + } + Options = OcFlexArrayAddItem (Entry->Options); + if (Options == NULL) { + FreePool (NewOptions); + return EFI_OUT_OF_RESOURCES; + } + *Options = NewOptions; + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +DoFilterLoaderEntry ( + EFI_FILE_HANDLE Directory, + EFI_FILE_INFO *FileInfo, + UINTN FileInfoSize, + VOID *Context OPTIONAL + ) +{ + if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0) { + return EFI_NOT_FOUND; + } + + // + // Skip ".*" and case sensitive "auto-*" files, + // follows systemd-boot logic. + // + if (FileInfo->FileName[0] == L'.' || + StrnCmp (FileInfo->FileName, BLSPEC_PREFIX_AUTO, L_STR_LEN (BLSPEC_PREFIX_AUTO)) == 0) { + return EFI_NOT_FOUND; + } + + if (!OcUnicodeEndsWith (FileInfo->FileName, BLSPEC_SUFFIX_CONF, TRUE)) { + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +// +// Works for vmlinuz-{version} and {machine-id}-{version}.conf. +// If blspec/blscfg style then we want .conf files with the same machine-id +// to count as alternate kernels of the same install; but there might be +// different installs, and the same kernel might be on different installs; +// so {machine-id} is good as the shared id (and it would not be sufficient +// to just use {version} when generating a non-shared id). +// If autodetect, we assume there is only one install in the directory, +// so {vmlinuz} is good as the shared id. +// +STATIC +CHAR8 * +OcIdFromFileName ( + CHAR16 *FileName + ) +{ + EFI_STATUS Status; + UINTN NumCopied; + + CHAR16 *IdEnd; + CHAR8 *OcId; + + ASSERT (FileName != NULL); + + IdEnd = NULL; + + // + // Shared id; intended to pick up machine-id from standard .conf file + // naming, or just the word vmlinuz from standard kernel file naming. + // + if ((gLinuxBootFlags & LINUX_BOOT_USE_LATEST) != 0) { + IdEnd = OcStrChr (FileName, L'-'); + } + + // + // Non-shared id, or no '-' found in filename above. + // + if (IdEnd == NULL) { + IdEnd = &FileName[StrLen (FileName)]; + if (OcUnicodeEndsWith (FileName, L".conf", TRUE)) { + IdEnd -= L_STR_LEN (L".conf"); + } + } + + OcId = AllocatePool (IdEnd - FileName + 1); + + if (OcId != NULL) { + Status = UnicodeStrnToAsciiStrS (FileName, IdEnd - FileName, OcId, IdEnd - FileName + 1, &NumCopied); + ASSERT (!EFI_ERROR (Status)); + ASSERT (NumCopied == (UINTN)(IdEnd - FileName)); + } + + return OcId; +} + +NAMED_LOADER_ENTRY * +InternalCreateNamedLoaderEntry ( + IN LOADER_ENTRY *Entry, + IN CHAR16 *FileName + ) +{ + NAMED_LOADER_ENTRY *NamedEntry; + CHAR8 *OcId; + + OcId = OcIdFromFileName (FileName); + if (OcId == NULL) { + return NULL; + } + + NamedEntry = OcFlexArrayAddItem (gNamedLoaderEntries); + if (NamedEntry == NULL) { + FreePool (OcId); + return NULL; + } + + Entry->OcId = OcId; + + NamedEntry->Entry = Entry; + NamedEntry->FileName = FileName; + + return NamedEntry; +} + +STATIC +EFI_STATUS +DoProcessLoaderEntry ( + EFI_FILE_HANDLE Directory, + EFI_FILE_INFO *FileInfo, + UINTN FileInfoSize, + VOID *Context OPTIONAL + ) +{ + EFI_STATUS Status; + CHAR8 *Content; + LOADER_ENTRY *Entry; + NAMED_LOADER_ENTRY *NamedEntry; + CHAR16 *FileName; + + // + // Doesn't really matter if we return EFI_SUCCESS or EFI_NOT_FOUND in these initial checks; + // anything else will terminate directory processing. + // + if (FileInfoSize > MAX_LOADER_ENTRY_FILE_INFO_SIZE) { + DEBUG ((DEBUG_WARN, "LNX: Entry %a %s overlong...\n", "filename", FileInfo->FileName)); + return EFI_SUCCESS; + } + + if (FileInfo->FileSize > MAX_LOADER_ENTRY_FILE_SIZE) { + DEBUG ((DEBUG_WARN, "LNX: Entry %a %s overlong...\n", "file size", FileInfo->FileName)); + return EFI_SUCCESS; + } + + DEBUG ((DEBUG_INFO, "LNX: Parsing %s...\n", FileInfo->FileName)); + + Content = OcReadFileFromDirectory (Directory, FileInfo->FileName, NULL, 0); + if (Content == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = InternalProcessLoaderEntryFile (FileInfo->FileName, Content, &Entry, mIsGrub2); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Entry->Linux == NULL) { + InternalFreeLoaderEntry (&Entry); + DEBUG ((DEBUG_INFO, "LNX: No linux line, ignoring\n")); + } else { + if (mIsGrub2) { + Status = ExpandReplaceOptions (Entry); + if (EFI_ERROR (Status)) { + InternalFreeLoaderEntry (&Entry); + return Status; + } + } + + FileName = AllocateCopyPool (StrSize (FileInfo->FileName), FileInfo->FileName); + if (FileName == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + NamedEntry = InternalCreateNamedLoaderEntry (Entry, FileName); + if (NamedEntry == NULL) { + FreePool (FileName); + InternalFreeLoaderEntry (&Entry); + return EFI_OUT_OF_RESOURCES; + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +ProcessLoaderEntry ( + EFI_FILE_HANDLE Directory, + EFI_FILE_INFO *FileInfo, + UINTN FileInfoSize, + VOID *Context OPTIONAL + ) +{ + EFI_STATUS Status; + + Status = DoFilterLoaderEntry (Directory, FileInfo, FileInfoSize, Context); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = DoProcessLoaderEntry (Directory, FileInfo, FileInfoSize, Context); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "LNX: Error processing %s - %r\n", FileInfo->FileName, Status)); + + // + // Continue to use earlier or later .conf files which we can process - + // more chance of a way to boot into Linux for end user. + // + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +ScanLoaderEntries ( + IN EFI_FILE_PROTOCOL *RootDirectory, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ) +{ + EFI_STATUS Status; + EFI_FILE_PROTOCOL *EntriesDirectory; + CHAR8 *GrubCfg; + CHAR8 *GrubEnv; + GRUB_VAR *EarlyInitrdVar; + + Status = OcSafeFileOpen (RootDirectory, &EntriesDirectory, LOADER_ENTRIES_DIR, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = EFI_SUCCESS; + GrubEnv = NULL; + mIsGrub2 = FALSE; + gNamedLoaderEntries = NULL; + + // + // Only treat as GRUB2 if grub2/grub.cfg exists. + // + GrubCfg = OcReadFileFromDirectory (RootDirectory, GRUB2_GRUB_CFG, NULL, 0); + if (GrubCfg == NULL) { + DEBUG ((DEBUG_INFO, "LNX: %s not found\n", GRUB2_GRUB_CFG)); + } else { + mIsGrub2 = TRUE; + Status = InternalInitGrubVars (); + if (!EFI_ERROR (Status)) { + // + // Read grubenv first, since vars in grub.cfg should overwrite it. + // + GrubEnv = OcReadFileFromDirectory (RootDirectory, GRUB2_GRUBENV, NULL, GRUB2_GRUBENV_SIZE); + if (GrubEnv == NULL) { + DEBUG ((DEBUG_WARN, "LNX: %s not found\n", GRUB2_GRUBENV)); + } else { + DEBUG ((DEBUG_INFO, "LNX: Reading %s\n", GRUB2_GRUBENV)); + Status = InternalProcessGrubEnv (GrubEnv, GRUB2_GRUBENV_SIZE); + } + if (!EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "LNX: Reading %s\n", GRUB2_GRUB_CFG)); + Status = InternalProcessGrubCfg (GrubCfg); + } + } + } + + // + // If we are grub2 and $early_initrd exists, then warn and halt (blscfg logic is to use it). + // Would not be hard to implement if required. This is a space separated list of filenames + // to use first as initrds. + // Note: they are filenames only, so (following how blscfg module does it) we need to prepend + // the path of either another (the first) initrd or the (first) vmlinuz. + // + if (!EFI_ERROR (Status)) { + if (mIsGrub2) { + EarlyInitrdVar = InternalGetGrubVar ("early_initrd"); + if (EarlyInitrdVar != NULL && + EarlyInitrdVar->Value != NULL && + EarlyInitrdVar->Value[0] != '\0' + ) { + DEBUG ((DEBUG_INFO, "LNX: grub var $%a is present but currently unsupported - aborting\n", "early_initrd")); + Status = EFI_INVALID_PARAMETER; + } + } + } + + if (!EFI_ERROR (Status)) { + gNamedLoaderEntries = OcFlexArrayInit (sizeof (NAMED_LOADER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM) InternalFreeNamedLoaderEntry); + if (gNamedLoaderEntries == NULL) { + Status = EFI_OUT_OF_RESOURCES; + } else { + Status = OcScanDirectory (EntriesDirectory, ProcessLoaderEntry, NULL); + } + + if (!EFI_ERROR (Status)) { + Status = InternalConvertNamedLoaderEntriesToBootEntries ( + RootDirectory, + Entries, + NumEntries + ); + } + + OcFlexArrayFree (&gNamedLoaderEntries); + } + + InternalFreeGrubVars (); + + if (GrubEnv != NULL) { + FreePool (GrubEnv); + } + if (GrubCfg != NULL) { + FreePool (GrubCfg); + } + + EntriesDirectory->Close (EntriesDirectory); + + return Status; +} + +VOID +InternalFreeNamedLoaderEntry ( + NAMED_LOADER_ENTRY *Entry + ) +{ + ASSERT (Entry != NULL); + ASSERT (Entry->Entry != NULL); + + if (Entry != NULL) { + if (Entry->FileName != NULL) { + FreePool (Entry->FileName); + Entry->FileName = NULL; + } + + if (Entry->Entry != NULL) { + InternalFreeLoaderEntry (&(Entry->Entry)); + } + } +} + +STATIC +EFI_STATUS +DoConvertNamedLoaderEntriesToBootEntries ( + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ) +{ + EFI_STATUS Status; + UINTN Index; + UINTN OptionsIndex; + NAMED_LOADER_ENTRY *NamedEntry; + LOADER_ENTRY *Entry; + OC_FLEX_ARRAY *PickerEntries; + OC_PICKER_ENTRY *PickerEntry; + OC_STRING_BUFFER *StringBuffer; + CHAR8 **Options; + UINTN OptionsLength; + + StringBuffer = NULL; + + PickerEntries = OcFlexArrayInit (sizeof (OC_PICKER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM) InternalFreePickerEntry); + if (PickerEntries == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_SUCCESS; + for (Index = 0; Index < gNamedLoaderEntries->Count; Index++) { + NamedEntry = OcFlexArrayItemAt (gNamedLoaderEntries, Index); + Entry = NamedEntry->Entry; + + PickerEntry = OcFlexArrayAddItem (PickerEntries); + if (PickerEntry == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + + PickerEntry->Path = AllocateCopyPool (AsciiStrSize (Entry->Linux), Entry->Linux); + if (PickerEntry->Path == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + + // + // Arguments. + // + StringBuffer = OcAsciiStringBufferInit (); + if (StringBuffer == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + + for (OptionsIndex = 0; OptionsIndex < Entry->Initrds->Count; OptionsIndex++) { + Options = OcFlexArrayItemAt (Entry->Initrds, OptionsIndex); + Status = OcAsciiStringBufferAppend (StringBuffer, "initrd="); + if (EFI_ERROR (Status)) { + break; + } + Status = OcAsciiStringBufferAppend (StringBuffer, *Options); + if (EFI_ERROR (Status)) { + break; + } + Status = OcAsciiStringBufferAppend (StringBuffer, " "); + if (EFI_ERROR (Status)) { + break; + } + } + + if (EFI_ERROR (Status)) { + break; + } + + for (OptionsIndex = 0; OptionsIndex < Entry->Options->Count; OptionsIndex++) { + Options = OcFlexArrayItemAt (Entry->Options, OptionsIndex); + OptionsLength = AsciiStrLen (*Options); + ASSERT (OptionsLength != 0); + if (OptionsLength == 0) { + continue; + } + if ((*Options)[OptionsLength - 1] == ' ') { + --OptionsLength; + } + Status = OcAsciiStringBufferAppendN (StringBuffer, *Options, OptionsLength); + if (EFI_ERROR (Status)) { + break; + } + if (OptionsIndex < Entry->Options->Count - 1) { + Status = OcAsciiStringBufferAppend (StringBuffer, " "); + if (EFI_ERROR (Status)) { + break; + } + } + } + + if (EFI_ERROR (Status)) { + break; + } + + PickerEntry->Arguments = OcAsciiStringBufferFreeContainer (&StringBuffer); + + // + // Name. + // + StringBuffer = OcAsciiStringBufferInit (); + if (StringBuffer == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + if ((gLinuxBootFlags & LINUX_BOOT_ADD_DEBUG_INFO) != 0) { + // + // Show file system type and enough of PARTUUID to help distinguish one partition from another. + // + Status = OcAsciiStringBufferSPrint (StringBuffer, "%a-%08x", gFileSystemType, gPartuuid.Data1); + if (EFI_ERROR (Status)) { + break; + } + + OcAsciiToLower (&StringBuffer->String[StringBuffer->StringLength] - sizeof (gPartuuid.Data1) * 2); + + Status = OcAsciiStringBufferAppend (StringBuffer, ": "); + if (EFI_ERROR (Status)) { + break; + } + } + Status = OcAsciiStringBufferAppend (StringBuffer, Entry->Title); + if (EFI_ERROR (Status)) { + break; + } + PickerEntry->Name = OcAsciiStringBufferFreeContainer (&StringBuffer); + + ASSERT (Entry->OcFlavour != NULL); + PickerEntry->Flavour = AllocateCopyPool (AsciiStrSize (Entry->OcFlavour), Entry->OcFlavour); + if (PickerEntry->Flavour == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + + ASSERT (Entry->OcId != NULL); + PickerEntry->Id = AllocateCopyPool (AsciiStrSize (Entry->OcId), Entry->OcId); + if (PickerEntry->Id == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + + PickerEntry->Auxiliary = Entry->OcAuxiliary; + + PickerEntry->RealPath = TRUE; + PickerEntry->TextMode = FALSE; + PickerEntry->Tool = FALSE; + } + + if (EFI_ERROR (Status)) { + if (StringBuffer != NULL) { + OcAsciiStringBufferFree (&StringBuffer); + } + OcFlexArrayFree (&PickerEntries); + } else { + ASSERT (StringBuffer == NULL); + OcFlexArrayFreeContainer (&PickerEntries, (VOID **) Entries, NumEntries); + } + + return Status; +} + +STATIC +EFI_STATUS +MakeUnique ( + LOADER_ENTRY *Entry + ) +{ + EFI_STATUS Status; + OC_STRING_BUFFER *StringBuffer; + + ASSERT (Entry->Title != NULL); + + if (Entry->Version == NULL || AsciiStrStr (Entry->Title, Entry->Version) != NULL) { + return EFI_SUCCESS; + } + + StringBuffer = OcAsciiStringBufferInit (); + + Status = OcAsciiStringBufferSPrint (StringBuffer, "%a (%a)", Entry->Title, Entry->Version); + + if (!EFI_ERROR (Status)) { + FreePool (Entry->Title); + Entry->Title = OcAsciiStringBufferFreeContainer (&StringBuffer); + } + + return Status; +} + +STATIC +EFI_STATUS +MakeAllUnique ( + VOID + ) +{ + EFI_STATUS Status; + UINTN Index; + NAMED_LOADER_ENTRY *NamedEntry; + + if (gPickerContext->HideAuxiliary) { + return EFI_SUCCESS; + } + + for (Index = 0; Index < gNamedLoaderEntries->Count; Index++) { + NamedEntry = OcFlexArrayItemAt (gNamedLoaderEntries, Index); + + Status = MakeUnique (NamedEntry->Entry); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +DisambiguateDuplicates ( + VOID + ) +{ + UINTN Index; + UINTN MatchIndex; + NAMED_LOADER_ENTRY *NamedEntry; + NAMED_LOADER_ENTRY *MatchEntry; + + // + // This check should not be strictly necessary, since there shouldn't + // (normally) be any duplicate ids if this flag is not set. + // + if ((gLinuxBootFlags & LINUX_BOOT_USE_LATEST) == 0) { + return EFI_SUCCESS; + } + + for (Index = 0; Index < gNamedLoaderEntries->Count - 1; Index++) { + NamedEntry = OcFlexArrayItemAt (gNamedLoaderEntries, Index); + + if ((NamedEntry->DuplicateFlags & DUPLICATE_ID_SCANNED) != 0) { + continue; + } + + for (MatchIndex = Index + 1; MatchIndex < gNamedLoaderEntries->Count; MatchIndex++) { + MatchEntry = OcFlexArrayItemAt (gNamedLoaderEntries, MatchIndex); + + if ((MatchEntry->DuplicateFlags & DUPLICATE_ID_SCANNED) == 0) { + if (AsciiStrCmp (NamedEntry->Entry->OcId, MatchEntry->Entry->OcId) == 0) { + // + // Everything but the first id duplicate becomes auxiliary. + // + MatchEntry->Entry->OcAuxiliary = TRUE; + MatchEntry->DuplicateFlags |= DUPLICATE_ID_SCANNED; + } + } + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EntryApplyDefaults ( + IN EFI_FILE_PROTOCOL *RootDirectory, + IN LOADER_ENTRY *Entry + ) +{ + CHAR8 *Variant; + UINTN Length; + UINTN NumPrinted; + + Variant = NULL; + + if (Entry->Title != NULL) { + Variant = ExtractVariantFrom (Entry->Title); + } + + ASSERT (Entry->OcFlavour == NULL); + + if (Variant != NULL) { + Length = AsciiStrLen (Variant) + L_STR_LEN ("Linux") + 1; + + Entry->OcFlavour = AllocatePool (Length + 1); + if (Entry->OcFlavour == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + NumPrinted = AsciiSPrint (Entry->OcFlavour, Length + 1, "%a:%a", Variant, "Linux"); + ASSERT (NumPrinted == Length); + } else { + Variant = "Linux"; + Entry->OcFlavour = AllocateCopyPool (AsciiStrSize (Variant), Variant); + if (Entry->OcFlavour == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + + if (Entry->Title == NULL) { + Entry->Title = AllocateCopyPool (AsciiStrSize (Variant), Variant); + if (Entry->Title == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +ApplyDefaults ( + IN EFI_FILE_PROTOCOL *RootDirectory + ) +{ + EFI_STATUS Status; + UINTN Index; + NAMED_LOADER_ENTRY *NamedEntry; + + Status = EFI_SUCCESS; + + for (Index = 0; Index < gNamedLoaderEntries->Count; Index++) { + NamedEntry = OcFlexArrayItemAt (gNamedLoaderEntries, Index); + + Status = EntryApplyDefaults (RootDirectory, NamedEntry->Entry); + if (EFI_ERROR (Status)) { + break; + } + } + + return Status; +} + +// +// Also use for loader entries generated by autodetect. +// +EFI_STATUS +InternalConvertNamedLoaderEntriesToBootEntries ( + IN EFI_FILE_PROTOCOL *RootDirectory, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ) +{ + EFI_STATUS Status; + + Status = EFI_SUCCESS; + + // + // Sort entries by filename descending. + // + PerformQuickSort (gNamedLoaderEntries->Items, gNamedLoaderEntries->Count, gNamedLoaderEntries->ItemSize, OcReverseStringCompare); + + // + // Fill out any missing data, including from kernel file version string if needed. + // + Status = ApplyDefaults (RootDirectory); + + // + // Append version to titles when !HideAuxiliary. + // We previously implemented a pure-BLSpec process of disambiguating only if the + // title is ambiguous with others on the same partition, but this works better. + // + if (!EFI_ERROR (Status)) { + Status = MakeAllUnique (); + } + + // + // Make duplicates by id after the first into auxiliary entries. + // + if (!EFI_ERROR (Status)) { + Status = DisambiguateDuplicates (); + } + + if (!EFI_ERROR (Status)) { + Status = DoConvertNamedLoaderEntriesToBootEntries ( + Entries, + NumEntries + ); + } + + return Status; +} diff --git a/Platform/OpenLinuxBoot/OpenLinuxBoot.c b/Platform/OpenLinuxBoot/OpenLinuxBoot.c new file mode 100644 index 00000000..09ef863f --- /dev/null +++ b/Platform/OpenLinuxBoot/OpenLinuxBoot.c @@ -0,0 +1,307 @@ +/** @file + Linux boot driver, supporting Boot Loader Specification, GRUB2 blscfg, and autodetect. + + Copyright (c) 2021, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "LinuxBootInternal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +UINTN gLinuxBootFlags = LINUX_BOOT_ALL & ~LINUX_BOOT_ADD_DEBUG_INFO; + +OC_PICKER_CONTEXT *gPickerContext; +OC_FLEX_ARRAY *gParsedLoadOptions; +OC_FLEX_ARRAY *gNamedLoaderEntries; +EFI_GUID gPartuuid; +CHAR8 *gFileSystemType; + +VOID +InternalFreePickerEntry ( + IN OC_PICKER_ENTRY *Entry + ) +{ + ASSERT (Entry != NULL); + + if (Entry == NULL) { + return; + } + + // + // TODO: Is this un-CONST casting okay? + // (Are they CONST because they are not supposed to be freed when used as before?) + // + if (Entry->Id != NULL) { + FreePool ((CHAR8 *)Entry->Id); + } + if (Entry->Name != NULL) { + FreePool ((CHAR8 *)Entry->Name); + } + if (Entry->Path != NULL) { + FreePool ((CHAR8 *)Entry->Path); + } + if (Entry->Arguments != NULL) { + FreePool ((CHAR8 *)Entry->Arguments); + } + if (Entry->Flavour != NULL) { + FreePool ((CHAR8 *)Entry->Flavour); + } +} + +STATIC +VOID +EFIAPI +OcFreeLinuxBootEntries ( + IN OC_PICKER_ENTRY **Entries, + IN UINTN NumEntries + ) +{ + UINTN Index; + + ASSERT (Entries != NULL); + ASSERT (*Entries != NULL); + if (Entries == NULL || *Entries == NULL) { + return; + } + + for (Index = 0; Index < NumEntries; Index++) { + InternalFreePickerEntry (&(*Entries)[Index]); + } + + FreePool (*Entries); + *Entries = NULL; +} + +STATIC +EFI_STATUS +EFIAPI +OcGetLinuxBootEntries ( + IN OC_PICKER_CONTEXT *PickerContext, + IN CONST EFI_HANDLE Device, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ) +{ + EFI_STATUS Status; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem; + EFI_FILE_PROTOCOL *RootDirectory; + UINT32 FileSystemPolicy; + CONST EFI_PARTITION_ENTRY *PartitionEntry; + + ASSERT (PickerContext != NULL); + ASSERT (Entries != NULL); + ASSERT (NumEntries != NULL); + + gPickerContext = PickerContext; + *Entries = NULL; + *NumEntries = 0; + + // + // No custom entries. + // + if (Device == NULL) { + return EFI_NOT_FOUND; + } + + // + // Open partition file system. + // + Status = gBS->HandleProtocol ( + Device, + &gEfiSimpleFileSystemProtocolGuid, + (VOID **) &FileSystem + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "LNX: Missing filesystem - %r\n", Status)); + return Status; + } + + // + // Get handle to partiton root directory. + // + Status = FileSystem->OpenVolume ( + FileSystem, + &RootDirectory + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "LNX: Invalid root volume - %r\n", Status)); + return Status; + } + + gFileSystemType = NULL; + + FileSystemPolicy = OcGetFileSystemPolicyType (Device); + + // + // Disallow Apple filesystems, mainly to avoid needlessly + // scanning multiple APFS partitions. + // + if ((FileSystemPolicy & OC_SCAN_ALLOW_FS_APFS) != 0) { + gFileSystemType = "APFS"; + DEBUG ((DEBUG_INFO, "LNX: %a - not scanning\n", gFileSystemType)); + Status = EFI_NOT_FOUND; + } else if ((FileSystemPolicy & OC_SCAN_ALLOW_FS_HFS) != 0) { + gFileSystemType = "HFS"; + DEBUG ((DEBUG_INFO, "LNX: %a - not scanning\n", gFileSystemType)); + Status = EFI_NOT_FOUND; + } else if ((FileSystemPolicy & OC_SCAN_ALLOW_FS_ESP) != 0) { + gFileSystemType = "ESP"; + if ((gLinuxBootFlags & LINUX_BOOT_SCAN_ESP) == 0) { + DEBUG ((DEBUG_INFO, "LNX: %a - requested not to scan\n", gFileSystemType)); + Status = EFI_NOT_FOUND; + } + } else if ((FileSystemPolicy & OC_SCAN_ALLOW_FS_XBOOTLDR) != 0) { + gFileSystemType = "XBOOTLDR"; + if ((gLinuxBootFlags & LINUX_BOOT_SCAN_XBOOTLDR) == 0) { + DEBUG ((DEBUG_INFO, "LNX: %a - requested not to scan\n", gFileSystemType)); + Status = EFI_NOT_FOUND; + } + } else if ((FileSystemPolicy & OC_SCAN_ALLOW_FS_LINUX_ROOT) != 0) { + gFileSystemType = "ROOT"; + if ((gLinuxBootFlags & LINUX_BOOT_SCAN_LINUX_ROOT) == 0) { + DEBUG ((DEBUG_INFO, "LNX: %a - requested not to scan\n", gFileSystemType)); + Status = EFI_NOT_FOUND; + } + } else if ((FileSystemPolicy & OC_SCAN_ALLOW_FS_LINUX_DATA) != 0) { + gFileSystemType = "DATA"; + if ((gLinuxBootFlags & LINUX_BOOT_SCAN_LINUX_DATA) == 0) { + DEBUG ((DEBUG_INFO, "LNX: %a - requested not to scan\n", gFileSystemType)); + Status = EFI_NOT_FOUND; + } + } else { + if ((FileSystemPolicy & OC_SCAN_ALLOW_FS_NTFS) != 0) { + // + // This is not just NTFS, Msft Basic Data part type GUID is used for non-ESP FAT too. + // + gFileSystemType = "NTFS/FAT"; + } else { + gFileSystemType = "OTHER"; + } + + if ((gLinuxBootFlags & LINUX_BOOT_SCAN_OTHER) == 0) { + DEBUG ((DEBUG_INFO, "LNX: %a - requested not to scan\n", gFileSystemType)); + Status = EFI_NOT_FOUND; + } + } + + if (EFI_ERROR (Status)) { + RootDirectory->Close (RootDirectory); + return Status; + } + + // + // Save PARTUUID for autodetect. + // + PartitionEntry = OcGetGptPartitionEntry (Device); + if (PartitionEntry == NULL) { + gPartuuid = gEfiPartTypeUnusedGuid; + } else { + gPartuuid = PartitionEntry->UniquePartitionGUID; + } + + // + // Log TypeGUID and PARTUUID of the drive we're in. + // + DEBUG (( + DEBUG_INFO, + "LNX: TypeGUID: %g (%a) PARTUUID: %g\n", + PartitionEntry->PartitionTypeGUID, + gFileSystemType, + PartitionEntry->UniquePartitionGUID + )); + + // + // Scan for boot loader spec & blscfg entries (Fedora-like). + // + Status = ScanLoaderEntries ( + RootDirectory, + Entries, + NumEntries + ); + + // + // Note: As currently structured, will fall through to autodetect + // if no /loader/entries/*.conf files are present, but also if there + // are only unusable files in there. + // + if (EFI_ERROR (Status)) { + if (Status != EFI_NOT_FOUND) { + DEBUG ((DEBUG_WARN, "LNX: ScanLoaderEntries - %r\n", Status)); + } + + // + // Auto-detect vmlinuz and initrd files on own root filesystem (Debian-like). + // + if ((gLinuxBootFlags & LINUX_BOOT_ALLOW_AUTODETECT) != 0) { + Status = AutodetectLinux ( + RootDirectory, + Entries, + NumEntries + ); + + if (EFI_ERROR (Status) && Status != EFI_NOT_FOUND) { + DEBUG ((DEBUG_WARN, "LNX: AutodetectLinux - %r\n", Status)); + } + } + } + + if (Status == EFI_NOT_FOUND) { + DEBUG ((DEBUG_INFO, "LNX: Nothing found\n")); + } + + RootDirectory->Close (RootDirectory); + + return Status; +} + +STATIC +OC_BOOT_ENTRY_PROTOCOL +mLinuxBootEntryProtocol = { + OC_BOOT_ENTRY_PROTOCOL_REVISION, + OcGetLinuxBootEntries, + OcFreeLinuxBootEntries +}; + +EFI_STATUS +EFIAPI +UefiMain ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + + Status = OcParseLoadOptions (ImageHandle, &gParsedLoadOptions); + if (!EFI_ERROR (Status)) { + OcParsedVarsGetInt (gParsedLoadOptions, L"flags", &gLinuxBootFlags, TRUE); + } else if (Status != EFI_NOT_FOUND) { + return Status; + } + + Status = gBS->InstallMultipleProtocolInterfaces ( + &ImageHandle, + &gOcBootEntryProtocolGuid, + &mLinuxBootEntryProtocol, + NULL + ); + + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} diff --git a/Platform/OpenLinuxBoot/OpenLinuxBoot.inf b/Platform/OpenLinuxBoot/OpenLinuxBoot.inf new file mode 100644 index 00000000..623c66ba --- /dev/null +++ b/Platform/OpenLinuxBoot/OpenLinuxBoot.inf @@ -0,0 +1,44 @@ +## @file +# Linux boot entry protocol implementation. +# +# Copyright (C) 2021, Mike Beaton. All rights reserved.
+# SPDX-License-Identifier: BSD-3-Clause +## + + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = OpenLinuxBoot + ENTRY_POINT = UefiMain + FILE_GUID = B2EC4DF3-FC2C-4D24-B1D6-B2D2DE8D8172 + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + +[Packages] + OpenCorePkg/OpenCorePkg.dec + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + +[Guids] + gEfiPartTypeUnusedGuid ## SOMETIMES_CONSUMES + +[LibraryClasses] + OcBootManagementLib + OcDebugLogLib + OcFileLib + OcFlexArrayLib + SortLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + +[Protocols] + gOcBootEntryProtocolGuid # PRODUCES + +[Sources] + Autodetect.c + GrubCfg.c + GrubEnv.c + GrubVars.c + LoaderEntry.c + OpenLinuxBoot.c diff --git a/User/Include/UserGlobalVar.h b/User/Include/UserGlobalVar.h index a94c8898..6ff35efa 100644 --- a/User/Include/UserGlobalVar.h +++ b/User/Include/UserGlobalVar.h @@ -71,6 +71,7 @@ extern EFI_GUID gEfiLegacyRegionProtocolGuid; extern EFI_GUID gEfiLegacyRegion2ProtocolGuid; extern EFI_GUID gEfiPciRootBridgeIoProtocolGuid; extern EFI_GUID gEfiSmbiosTableGuid; +extern EFI_GUID gEfiUnicodeCollation2ProtocolGuid; extern EFI_GUID gOcBootstrapProtocolGuid; extern EFI_GUID gOcVendorVariableGuid; diff --git a/User/Library/UserGlobalVar.c b/User/Library/UserGlobalVar.c index f8336b9f..9ac1ebc6 100644 --- a/User/Library/UserGlobalVar.c +++ b/User/Library/UserGlobalVar.c @@ -56,6 +56,7 @@ EFI_GUID gEfiLegacyRegionProtocolGuid = { 0x0fc9013a, 0x0568, 0x4ba9, { 0 EFI_GUID gEfiLegacyRegion2ProtocolGuid = { 0x70101eaf, 0x85, 0x440c, { 0xb3, 0x56, 0x8e, 0xe3, 0x6f, 0xef, 0x24, 0xf0 }}; EFI_GUID gEfiPciRootBridgeIoProtocolGuid = { 0x2F707EBB, 0x4A1A, 0x11D4, { 0x9A, 0x38, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }}; EFI_GUID gEfiSmbiosTableGuid = { 0xEB9D2D31, 0x2D88, 0x11D3, { 0x9A, 0x16, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }}; +EFI_GUID gEfiUnicodeCollation2ProtocolGuid = { 0xa4c751fc, 0x23ae, 0x4c3e, { 0x92, 0xe9, 0x49, 0x64, 0xcf, 0x63, 0xf3, 0x49 }}; EFI_GUID gOcBootstrapProtocolGuid = { 0xBA1EB455, 0xB182, 0x4F14, { 0x85, 0x21, 0xE4, 0x22, 0xC3, 0x25, 0xDE, 0xF6 }}; EFI_GUID gOcVendorVariableGuid = { 0x4D1FDA02, 0x38C7, 0x4A6A, { 0x9C, 0xC6, 0x4B, 0xCC, 0xA8, 0xB3, 0x01, 0x02 }}; diff --git a/User/Makefile b/User/Makefile index f512dcc5..eed11fa2 100644 --- a/User/Makefile +++ b/User/Makefile @@ -136,7 +136,7 @@ ifneq ($(STANDALONE),1) # # UDK implementations. # - OBJS += UefiLib.o UefiLibPrint.o CpuDeadLoop.o BaseDebugPrintErrorLevelLib.o DebugLib.o PrintLib.o PrintLibInternal.o String.o SafeString.o SwapBytes16.o SwapBytes32.o LinkedList.o HighBitSet32.o HighBitSet64.o MtrrLib.o GetPowerOfTwo32.o GetPowerOfTwo64.o Cpu.o BmpSupportLib.o SafeIntLib.o X86GetInterruptState.o PciLib.o PciExpressLib.o DevicePathUtilities.o UefiDevicePathLib.o DevicePathToText.o DevicePathFromText.o BitField.o CheckSum.o + OBJS += UefiLib.o UefiLibPrint.o CpuDeadLoop.o BaseDebugPrintErrorLevelLib.o DebugLib.o PrintLib.o PrintLibInternal.o String.o SafeString.o SwapBytes16.o SwapBytes32.o LinkedList.o HighBitSet32.o HighBitSet64.o MtrrLib.o GetPowerOfTwo32.o GetPowerOfTwo64.o Cpu.o BmpSupportLib.o SafeIntLib.o X86GetInterruptState.o PciLib.o PciExpressLib.o DevicePathUtilities.o UefiDevicePathLib.o DevicePathToText.o DevicePathFromText.o BitField.o CheckSum.o UefiSortLib.o # # Customised/Simplified implementations at userspace level. # @@ -201,6 +201,7 @@ ifneq ($(STANDALONE),1) $(UDK_PATH)/MdePkg/Library/BasePciExpressLib:$\ $(UDK_PATH)/MdePkg/Library/UefiDevicePathLib:$\ $(UDK_PATH)/MdeModulePkg/Library/BaseBmpSupportLib:$\ + $(UDK_PATH)/MdeModulePkg/Library/UefiSortLib:$\ $(UDK_PATH)/UefiCpuPkg/Library/MtrrLib:$\ $(OC_USER)/Library/OcGuardLib:$\ $(OC_USER)/Library/OcSerializeLib:$\ diff --git a/Utilities/ocvalidate/OcValidateLib.c b/Utilities/ocvalidate/OcValidateLib.c index f34261b6..d6d441e6 100644 --- a/Utilities/ocvalidate/OcValidateLib.c +++ b/Utilities/ocvalidate/OcValidateLib.c @@ -203,21 +203,27 @@ AsciiPropertyIsLegal ( BOOLEAN AsciiUefiDriverIsLegal ( - IN CONST CHAR8 *Driver + IN CONST CHAR8 *Driver, + IN CONST UINTN DriverIndex ) { UINTN Index; UINTN DriverLength; - // - // If an EFI driver does not contain .efi suffix, - // then it must be illegal. - // - if (!OcAsciiEndsWith (Driver, ".efi", TRUE)) { + DriverLength = AsciiStrLen (Driver); + if (DriverLength == 0) { + DEBUG ((DEBUG_WARN, "UEFI->Drivers[%u].Path value is missing!\n", DriverIndex)); return FALSE; } - DriverLength = AsciiStrLen (Driver); + // + // If an EFI driver does not have .efi suffix, + // then it must be illegal. + // + if (!OcAsciiEndsWith (Driver, ".efi", TRUE)) { + DEBUG ((DEBUG_WARN, "UEFI->Drivers[%u].Path does not end with \"%a\"!\n", DriverIndex, ".efi")); + return FALSE; + } for (Index = 0; Index < DriverLength; ++Index) { // @@ -235,6 +241,7 @@ AsciiUefiDriverIsLegal ( // // Disallowed characters matched. // + DEBUG ((DEBUG_WARN, "UEFI->Drivers[%u].Path contains illegal character!\n", DriverIndex)); return FALSE; } diff --git a/Utilities/ocvalidate/OcValidateLib.h b/Utilities/ocvalidate/OcValidateLib.h index a0854576..afc8ae31 100644 --- a/Utilities/ocvalidate/OcValidateLib.h +++ b/Utilities/ocvalidate/OcValidateLib.h @@ -99,13 +99,15 @@ AsciiPropertyIsLegal ( /** Check if a UEFI Driver matches specific conventions. - @param[in] Driver Driver to be checked. + @param[in] Driver Driver path name to be checked. + @param[in] DriverIndex Index of driver being checked. @retval TRUE If path of Driver contains .efi suffix, and only contains 0-9, A-Z, a-z, '_', '-', '.', and '/'. **/ BOOLEAN AsciiUefiDriverIsLegal ( - IN CONST CHAR8 *Driver + IN CONST CHAR8 *Driver, + IN CONST UINTN DriverIndex ); /** diff --git a/Utilities/ocvalidate/ValidateBooter.c b/Utilities/ocvalidate/ValidateBooter.c index efef777d..d12477a6 100644 --- a/Utilities/ocvalidate/ValidateBooter.c +++ b/Utilities/ocvalidate/ValidateBooter.c @@ -140,18 +140,19 @@ CheckBooterQuirks ( IN OC_GLOBAL_CONFIG *Config ) { - UINT32 ErrorCount; - UINT32 Index; - OC_BOOTER_CONFIG *UserBooter; - OC_UEFI_CONFIG *UserUefi; - CONST CHAR8 *Driver; - UINT8 MaxSlide; - BOOLEAN IsAllowRelocationBlockEnabled; - BOOLEAN IsProvideCustomSlideEnabled; - BOOLEAN IsEnableSafeModeSlideEnabled; - BOOLEAN IsDisableVariableWriteEnabled; - BOOLEAN IsEnableWriteUnprotectorEnabled; - BOOLEAN HasOpenRuntimeEfiDriver; + UINT32 ErrorCount; + UINT32 Index; + OC_BOOTER_CONFIG *UserBooter; + OC_UEFI_CONFIG *UserUefi; + OC_UEFI_DRIVER_ENTRY *DriverEntry; + CONST CHAR8 *Driver; + UINT8 MaxSlide; + BOOLEAN IsAllowRelocationBlockEnabled; + BOOLEAN IsProvideCustomSlideEnabled; + BOOLEAN IsEnableSafeModeSlideEnabled; + BOOLEAN IsDisableVariableWriteEnabled; + BOOLEAN IsEnableWriteUnprotectorEnabled; + BOOLEAN HasOpenRuntimeEfiDriver; ErrorCount = 0; UserBooter = &Config->Booter; @@ -165,13 +166,14 @@ CheckBooterQuirks ( MaxSlide = UserBooter->Quirks.ProvideMaxSlide; for (Index = 0; Index < UserUefi->Drivers.Count; ++Index) { - Driver = OC_BLOB_GET (UserUefi->Drivers.Values[Index]); + DriverEntry = UserUefi->Drivers.Values[Index]; + Driver = OC_BLOB_GET (&DriverEntry->Path); // // Skip sanitising UEFI->Drivers as it will be performed when checking UEFI section. // - if (AsciiStrCmp (Driver, "OpenRuntime.efi") == 0) { + if (DriverEntry->Enabled && AsciiStrCmp (Driver, "OpenRuntime.efi") == 0) { HasOpenRuntimeEfiDriver = TRUE; } } diff --git a/Utilities/ocvalidate/ValidateMisc.c b/Utilities/ocvalidate/ValidateMisc.c index 7dc666b8..188cf9ce 100644 --- a/Utilities/ocvalidate/ValidateMisc.c +++ b/Utilities/ocvalidate/ValidateMisc.c @@ -184,21 +184,22 @@ CheckMiscBoot ( IN OC_GLOBAL_CONFIG *Config ) { - UINT32 ErrorCount; - OC_MISC_CONFIG *UserMisc; - OC_UEFI_CONFIG *UserUefi; - UINT32 ConsoleAttributes; - CONST CHAR8 *HibernateMode; - UINT32 PickerAttributes; - UINT32 Index; - CONST CHAR8 *Driver; - BOOLEAN HasOpenCanopyEfiDriver; - CONST CHAR8 *PickerMode; - CONST CHAR8 *PickerVariant; - BOOLEAN IsPickerAudioAssistEnabled; - BOOLEAN IsAudioSupportEnabled; - CONST CHAR8 *LauncherOption; - CONST CHAR8 *LauncherPath; + UINT32 ErrorCount; + OC_MISC_CONFIG *UserMisc; + OC_UEFI_CONFIG *UserUefi; + UINT32 ConsoleAttributes; + CONST CHAR8 *HibernateMode; + UINT32 PickerAttributes; + UINT32 Index; + OC_UEFI_DRIVER_ENTRY *DriverEntry; + CONST CHAR8 *Driver; + BOOLEAN HasOpenCanopyEfiDriver; + CONST CHAR8 *PickerMode; + CONST CHAR8 *PickerVariant; + BOOLEAN IsPickerAudioAssistEnabled; + BOOLEAN IsAudioSupportEnabled; + CONST CHAR8 *LauncherOption; + CONST CHAR8 *LauncherPath; ErrorCount = 0; UserMisc = &Config->Misc; @@ -221,15 +222,16 @@ CheckMiscBoot ( PickerAttributes = UserMisc->Boot.PickerAttributes; if ((PickerAttributes & ~OC_ATTR_ALL_BITS) != 0) { - DEBUG ((DEBUG_WARN, "Misc->Boot->PickerAttributes is has unknown bits set!\n")); + DEBUG ((DEBUG_WARN, "Misc->Boot->PickerAttributes has unknown bits set!\n")); ++ErrorCount; } HasOpenCanopyEfiDriver = FALSE; for (Index = 0; Index < UserUefi->Drivers.Count; ++Index) { - Driver = OC_BLOB_GET (UserUefi->Drivers.Values[Index]); + DriverEntry = UserUefi->Drivers.Values[Index]; + Driver = OC_BLOB_GET (&DriverEntry->Path); - if (AsciiStrCmp (Driver, "OpenCanopy.efi") == 0) { + if (DriverEntry->Enabled && AsciiStrCmp (Driver, "OpenCanopy.efi") == 0) { HasOpenCanopyEfiDriver = TRUE; } } diff --git a/Utilities/ocvalidate/ValidateUEFI.c b/Utilities/ocvalidate/ValidateUEFI.c index 6fa53e9b..71276f74 100644 --- a/Utilities/ocvalidate/ValidateUEFI.c +++ b/Utilities/ocvalidate/ValidateUEFI.c @@ -231,6 +231,7 @@ CheckUEFIDrivers ( UINT32 ErrorCount; OC_UEFI_CONFIG *UserUefi; UINT32 Index; + OC_UEFI_DRIVER_ENTRY *DriverEntry; CONST CHAR8 *Driver; BOOLEAN HasOpenRuntimeEfiDriver; BOOLEAN HasOpenUsbKbDxeEfiDriver; @@ -258,7 +259,8 @@ CheckUEFIDrivers ( HasAudioDxeEfiDriver = FALSE; IndexAudioDxeEfiDriver = 0; for (Index = 0; Index < UserUefi->Drivers.Count; ++Index) { - Driver = OC_BLOB_GET (UserUefi->Drivers.Values[Index]); + DriverEntry = UserUefi->Drivers.Values[Index]; + Driver = OC_BLOB_GET (&DriverEntry->Path); // // Check the length of path relative to OC directory. @@ -268,19 +270,18 @@ CheckUEFIDrivers ( ++ErrorCount; } - if (Driver[0] == '#') { - continue; - } - // // Sanitise strings. // - if (!AsciiUefiDriverIsLegal (Driver)) { - DEBUG ((DEBUG_WARN, "UEFI->Drivers[%u] contains illegal character!\n", Index)); + if (!AsciiUefiDriverIsLegal (Driver, Index)) { ++ErrorCount; continue; } + if (!DriverEntry->Enabled) { + continue; + } + if (AsciiStrCmp (Driver, "OpenRuntime.efi") == 0) { HasOpenRuntimeEfiDriver = TRUE; } diff --git a/build_oc.tool b/build_oc.tool index e2ff331f..bdc2a7f7 100755 --- a/build_oc.tool +++ b/build_oc.tool @@ -157,6 +157,7 @@ package() { "AudioDxe.efi" "CrScreenshotDxe.efi" "OpenCanopy.efi" + "OpenLinuxBoot.efi" "OpenPartitionDxe.efi" "OpenRuntime.efi" "OpenUsbKbDxe.efi"