2 Provides interface to shell MAN file parser.
4 Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.<BR>
5 Copyright 2015 Dell Inc.
6 SPDX-License-Identifier: BSD-2-Clause-Patent
12 #define SHELL_MAN_HII_GUID \
14 0xf62ccd0c, 0x2449, 0x453c, { 0x8a, 0xcb, 0x8c, 0xc5, 0x7c, 0xf0, 0x2a, 0x97 } \
17 EFI_HII_HANDLE mShellManHiiHandle
= NULL
;
18 EFI_HANDLE mShellManDriverHandle
= NULL
;
20 SHELL_MAN_HII_VENDOR_DEVICE_PATH mShellManHiiDevicePath
= {
26 (UINT8
)(sizeof (VENDOR_DEVICE_PATH
)),
27 (UINT8
)((sizeof (VENDOR_DEVICE_PATH
)) >> 8)
34 END_ENTIRE_DEVICE_PATH_SUBTYPE
,
36 (UINT8
)(END_DEVICE_PATH_LENGTH
),
37 (UINT8
)((END_DEVICE_PATH_LENGTH
) >> 8)
43 Verifies that the filename has .EFI on the end.
45 allocates a new buffer and copies the name (appending .EFI if necessary).
46 Caller to free the buffer.
48 @param[in] NameString original name string
50 @return the new filename with .efi as the extension.
53 GetExecuatableFileName (
54 IN CONST CHAR16
*NameString
60 if (NameString
== NULL
) {
67 if (StrnCmp (NameString
+StrLen (NameString
)-StrLen (L
".efi"), L
".efi", StrLen (L
".efi")) == 0) {
68 Buffer
= AllocateCopyPool (StrSize (NameString
), NameString
);
69 } else if (StrnCmp (NameString
+StrLen (NameString
)-StrLen (L
".man"), L
".man", StrLen (L
".man")) == 0) {
70 Buffer
= AllocateCopyPool (StrSize (NameString
), NameString
);
72 SuffixStr
= Buffer
+StrLen (Buffer
)-StrLen (L
".man");
73 StrnCpyS (SuffixStr
, StrSize (L
".man")/sizeof (CHAR16
), L
".efi", StrLen (L
".efi"));
76 Buffer
= AllocateZeroPool (StrSize (NameString
) + StrLen (L
".efi")*sizeof (CHAR16
));
80 (StrSize (NameString
) + StrLen (L
".efi")*sizeof (CHAR16
))/sizeof (CHAR16
),
86 (StrSize (NameString
) + StrLen (L
".efi")*sizeof (CHAR16
))/sizeof (CHAR16
),
97 Verifies that the filename has .MAN on the end.
99 allocates a new buffer and copies the name (appending .MAN if necessary)
101 ASSERT if ManFileName is NULL
103 @param[in] ManFileName original filename
105 @return the new filename with .man as the extension.
109 IN CONST CHAR16
*ManFileName
114 if (ManFileName
== NULL
) {
121 if (StrnCmp (ManFileName
+StrLen (ManFileName
)-4, L
".man", 4) == 0) {
122 Buffer
= AllocateCopyPool (StrSize (ManFileName
), ManFileName
);
124 Buffer
= AllocateZeroPool (StrSize (ManFileName
) + 4*sizeof (CHAR16
));
125 if (Buffer
!= NULL
) {
128 (StrSize (ManFileName
) + 4*sizeof (CHAR16
))/sizeof (CHAR16
),
134 (StrSize (ManFileName
) + 4*sizeof (CHAR16
))/sizeof (CHAR16
),
145 Search the path environment variable for possible locations and test for
146 which one contains a man file with the name specified. If a valid file is found
147 stop searching and return the (opened) SHELL_FILE_HANDLE for that file.
149 @param[in] FileName Name of the file to find and open.
150 @param[out] Handle Pointer to the handle of the found file. The
151 value of this is undefined for return values
154 @retval EFI_SUCCESS The file was found. Handle is a valid SHELL_FILE_HANDLE
155 @retval EFI_INVALID_PARAMETER A parameter had an invalid value.
156 @retval EFI_NOT_FOUND The file was not found.
160 IN CONST CHAR16
*FileName
,
161 OUT SHELL_FILE_HANDLE
*Handle
164 CHAR16
*FullFileName
;
167 if ( (FileName
== NULL
)
169 || (StrLen (FileName
) == 0)
172 return (EFI_INVALID_PARAMETER
);
175 FullFileName
= ShellFindFilePath (FileName
);
176 if (FullFileName
== NULL
) {
177 return (EFI_NOT_FOUND
);
181 // now open that file
183 Status
= EfiShellOpenFileByName (FullFileName
, Handle
, EFI_FILE_MODE_READ
);
184 FreePool (FullFileName
);
190 Parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
191 detailed help for any sub section specified in the comma separated list of
192 sections provided. If the end of the file or a .TH section is found then
195 Upon a successful return the caller is responsible to free the memory in *HelpText
197 @param[in] Handle FileHandle to read from
198 @param[in] Sections name of command's sub sections to find
199 @param[out] HelpText pointer to pointer to string where text goes.
200 @param[out] HelpSize pointer to size of allocated HelpText (may be updated)
201 @param[in] Ascii TRUE if the file is ASCII, FALSE otherwise.
203 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
204 @retval EFI_SUCCESS the section was found and its description stored in
208 ManFileFindSections (
209 IN SHELL_FILE_HANDLE Handle
,
210 IN CONST CHAR16
*Sections
,
211 OUT CHAR16
**HelpText
,
219 BOOLEAN CurrentlyReading
;
224 if ( (Handle
== NULL
)
225 || (HelpText
== NULL
)
226 || (HelpSize
== NULL
)
229 return (EFI_INVALID_PARAMETER
);
232 Status
= EFI_SUCCESS
;
233 CurrentlyReading
= FALSE
;
237 ReadLine
= AllocateZeroPool (Size
);
238 if (ReadLine
== NULL
) {
239 return (EFI_OUT_OF_RESOURCES
);
242 for ( ; !ShellFileHandleEof (Handle
); Size
= 1024) {
243 Status
= ShellFileHandleReadLine (Handle
, ReadLine
, &Size
, TRUE
, &Ascii
);
244 if (ReadLine
[0] == L
'#') {
246 // Skip comment lines
252 // ignore too small of buffer...
254 if (Status
== EFI_BUFFER_TOO_SMALL
) {
255 Status
= EFI_SUCCESS
;
258 if (EFI_ERROR (Status
)) {
260 } else if (StrnCmp (ReadLine
, L
".TH", 3) == 0) {
262 // we hit the end of this commands section so stop.
265 } else if (StrnCmp (ReadLine
, L
".SH", 3) == 0) {
266 if (Sections
== NULL
) {
267 CurrentlyReading
= TRUE
;
272 // we found a section
274 if (CurrentlyReading
) {
275 CurrentlyReading
= FALSE
;
279 // is this a section we want to read in?
281 for ( SectionName
= ReadLine
+ 3
282 ; *SectionName
== L
' '
287 SectionLen
= StrLen (SectionName
);
288 SectionName
= StrStr (Sections
, SectionName
);
289 if (SectionName
== NULL
) {
293 if ((*(SectionName
+ SectionLen
) == CHAR_NULL
) || (*(SectionName
+ SectionLen
) == L
',')) {
294 CurrentlyReading
= TRUE
;
296 } else if (CurrentlyReading
) {
299 // copy and save the current line.
301 ASSERT ((*HelpText
== NULL
&& *HelpSize
== 0) || (*HelpText
!= NULL
));
302 StrnCatGrow (HelpText
, HelpSize
, ReadLine
, 0);
303 StrnCatGrow (HelpText
, HelpSize
, L
"\r\n", 0);
308 if (!Found
&& !EFI_ERROR (Status
)) {
309 return (EFI_NOT_FOUND
);
316 Parses a line from a MAN file to see if it is the Title Header. If it is, then
317 if the "Brief Description" is desired, allocate a buffer for it and return a
318 copy. Upon a successful return the caller is responsible to free the memory in
321 Uses a simple state machine that allows "unlimited" whitespace before and after the
322 ".TH", compares Command and the MAN file command name without respect to case, and
323 allows "unlimited" whitespace and '0' and '1' characters before the Short Description.
324 The PCRE regex describing this functionality is: ^\s*\.TH\s+(\S)\s[\s01]*(.*)$
325 where group 1 is the Command Name and group 2 is the Short Description.
327 @param[in] Command name of command whose MAN file we think Line came from
328 @param[in] Line Pointer to a line from the MAN file
329 @param[out] BriefDesc pointer to pointer to string where description goes.
330 @param[out] BriefSize pointer to size of allocated BriefDesc
331 @param[out] Found TRUE if the Title Header was found and it belongs to Command
333 @retval TRUE Line contained the Title Header
334 @retval FALSE Line did not contain the Title Header
338 IN CONST CHAR16
*Command
,
340 OUT CHAR16
**BriefDesc OPTIONAL
,
341 OUT UINTN
*BriefSize OPTIONAL
,
345 // The states of a simple state machine used to recognize a title header line
346 // and to extract the Short Description, if desired.
348 LookForThMacro
, LookForCommandName
, CompareCommands
, GetBriefDescription
, Final
352 UINTN CommandIndex
; // Indexes Command as we compare its chars to the MAN file.
353 BOOLEAN ReturnValue
; // TRUE if this the Title Header line of *some* MAN file.
354 BOOLEAN ReturnFound
; // TRUE if this the Title Header line of *the desired* MAN file.
359 State
= LookForThMacro
;
362 if (*Line
== L
'\0') {
367 // Handle "^\s*.TH\s"
368 // Go to state LookForCommandName if the title header macro is present; otherwise,
369 // eat white space. If we see something other than white space, this is not a
370 // title header line.
372 if ((StrnCmp (L
".TH ", Line
, 4) == 0) || (StrnCmp (L
".TH\t", Line
, 4) == 0)) {
374 State
= LookForCommandName
;
375 } else if ((*Line
== L
' ') || (*Line
== L
'\t')) {
384 // Eat any "extra" whitespace after the title header macro (we have already seen
385 // at least one white space character). Go to state CompareCommands when a
386 // non-white space is seen.
387 case LookForCommandName
:
388 if ((*Line
== L
' ') || (*Line
== L
'\t')) {
391 ReturnValue
= TRUE
; // This is *some* command's title header line.
392 State
= CompareCommands
;
393 // Do not increment Line; it points to the first character of the command
394 // name on the title header line.
400 // Compare Command to the title header command name, ignoring case. When we
401 // reach the end of the command (i.e. we see white space), the next state
402 // depends on whether the caller wants a copy of the Brief Description.
403 case CompareCommands
:
404 if ((*Line
== L
' ') || (*Line
== L
'\t')) {
405 ReturnFound
= TRUE
; // This is the desired command's title header line.
406 State
= (BriefDesc
== NULL
) ? Final
: GetBriefDescription
;
407 } else if (CharToUpper (*Line
) != CharToUpper (*(Command
+ CommandIndex
++))) {
414 // Handle "[\s01]*(.*)$"
415 // Skip whitespace, '0', and '1' characters, if any, prior to the brief description.
416 // Return the description to the caller.
417 case GetBriefDescription
:
418 if ((*Line
!= L
' ') && (*Line
!= L
'\t') && (*Line
!= L
'0') && (*Line
!= L
'1')) {
419 *BriefSize
= StrSize (Line
);
420 *BriefDesc
= AllocateZeroPool (*BriefSize
);
421 if (*BriefDesc
!= NULL
) {
422 StrCpyS (*BriefDesc
, (*BriefSize
)/sizeof (CHAR16
), Line
);
434 } while (State
< Final
);
436 *Found
= ReturnFound
;
441 Parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
442 "Brief Description" for the .TH section as specified by Command. If the
443 command section is not found return EFI_NOT_FOUND.
445 Upon a successful return the caller is responsible to free the memory in *BriefDesc
447 @param[in] Handle FileHandle to read from
448 @param[in] Command name of command's section to find as entered on the
449 command line (may be a relative or absolute path or
450 be in any case: upper, lower, or mixed in numerous ways!).
451 @param[out] BriefDesc pointer to pointer to string where description goes.
452 @param[out] BriefSize pointer to size of allocated BriefDesc
453 @param[in, out] Ascii TRUE if the file is ASCII, FALSE otherwise, will be
454 set if the file handle is at the 0 position.
456 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
457 @retval EFI_SUCCESS the section was found and its description stored in
458 an allocated buffer if requested.
461 ManFileFindTitleSection (
462 IN SHELL_FILE_HANDLE Handle
,
463 IN CONST CHAR16
*Command
,
464 OUT CHAR16
**BriefDesc OPTIONAL
,
465 OUT UINTN
*BriefSize OPTIONAL
,
466 IN OUT BOOLEAN
*Ascii
475 if ( (Handle
== NULL
)
477 || ((BriefDesc
!= NULL
) && (BriefSize
== NULL
))
480 return (EFI_INVALID_PARAMETER
);
483 Status
= EFI_SUCCESS
;
487 ReadLine
= AllocateZeroPool (Size
);
488 if (ReadLine
== NULL
) {
489 return (EFI_OUT_OF_RESOURCES
);
493 // Do not pass any leading path information that may be present to IsTitleHeader().
495 Start
= StrLen (Command
);
497 && (*(Command
+ Start
- 1) != L
'\\')
498 && (*(Command
+ Start
- 1) != L
'/')
499 && (*(Command
+ Start
- 1) != L
':'))
504 for ( ; !ShellFileHandleEof (Handle
); Size
= 1024) {
505 Status
= ShellFileHandleReadLine (Handle
, ReadLine
, &Size
, TRUE
, Ascii
);
507 // ignore too small of buffer...
509 if (EFI_ERROR (Status
) && (Status
!= EFI_BUFFER_TOO_SMALL
)) {
513 Status
= EFI_NOT_FOUND
;
514 if (IsTitleHeader (Command
+Start
, ReadLine
, BriefDesc
, BriefSize
, &Found
)) {
515 Status
= Found
? EFI_SUCCESS
: EFI_NOT_FOUND
;
525 This function returns the help information for the specified command. The help text
526 will be parsed from a UEFI Shell manual page. (see UEFI Shell 2.0 Appendix B)
528 If Sections is specified, then each section name listed will be compared in a casesensitive
529 manner, to the section names described in Appendix B. If the section exists,
530 it will be appended to the returned help text. If the section does not exist, no
531 information will be returned. If Sections is NULL, then all help text information
532 available will be returned.
534 if BriefDesc is NULL, then the breif description will not be saved separately,
535 but placed first in the main HelpText.
537 @param[in] ManFileName Points to the NULL-terminated UEFI Shell MAN file name.
538 @param[in] Command Points to the NULL-terminated UEFI Shell command name.
539 @param[in] Sections Points to the NULL-terminated comma-delimited
540 section names to return. If NULL, then all
541 sections will be returned.
542 @param[out] BriefDesc On return, points to a callee-allocated buffer
543 containing brief description text.
544 @param[out] HelpText On return, points to a callee-allocated buffer
545 containing all specified help text.
547 @retval EFI_SUCCESS The help text was returned.
548 @retval EFI_OUT_OF_RESOURCES The necessary buffer could not be allocated to hold the
550 @retval EFI_INVALID_PARAMETER HelpText is NULL.
551 @retval EFI_INVALID_PARAMETER ManFileName is invalid.
552 @retval EFI_NOT_FOUND There is no help text available for Command.
556 IN CONST CHAR16
*ManFileName
,
557 IN CONST CHAR16
*Command
,
558 IN CONST CHAR16
*Sections OPTIONAL
,
559 OUT CHAR16
**BriefDesc OPTIONAL
,
560 OUT CHAR16
**HelpText
564 SHELL_FILE_HANDLE FileHandle
;
565 EFI_HANDLE CmdFileImgHandle
;
569 UINTN StringIdWalker
;
572 CHAR16
*CmdFilePathName
;
573 EFI_DEVICE_PATH_PROTOCOL
*FileDevPath
;
574 EFI_DEVICE_PATH_PROTOCOL
*DevPath
;
575 EFI_HII_PACKAGE_LIST_HEADER
*PackageListHeader
;
577 if ( (ManFileName
== NULL
)
579 || (HelpText
== NULL
)
582 return (EFI_INVALID_PARAMETER
);
591 CmdFilePathName
= NULL
;
592 CmdFileImgHandle
= NULL
;
593 PackageListHeader
= NULL
;
598 // See if it's in HII first
600 TempString
= ShellCommandGetCommandHelp (Command
);
601 if (TempString
!= NULL
) {
602 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
603 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
604 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
605 ShellSetFilePosition (FileHandle
, 0);
608 Status
= ManFileFindTitleSection (FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
609 if (!EFI_ERROR (Status
) && (HelpText
!= NULL
)) {
610 Status
= ManFileFindSections (FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
613 ShellCloseFile (&FileHandle
);
616 // If the image is a external app, check .MAN file first.
619 TempString
= GetManFileName (ManFileName
);
620 if (TempString
== NULL
) {
621 return (EFI_INVALID_PARAMETER
);
624 Status
= SearchPathForFile (TempString
, &FileHandle
);
625 if (EFI_ERROR (Status
)) {
626 FileDevPath
= FileDevicePath (NULL
, TempString
);
627 DevPath
= AppendDevicePath (ShellInfoObject
.ImageDevPath
, FileDevPath
);
628 Status
= InternalOpenFileDevicePath (DevPath
, &FileHandle
, EFI_FILE_MODE_READ
, 0);
629 SHELL_FREE_NON_NULL (FileDevPath
);
630 SHELL_FREE_NON_NULL (DevPath
);
633 if (!EFI_ERROR (Status
)) {
636 Status
= ManFileFindTitleSection (FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
637 if (!EFI_ERROR (Status
) && (HelpText
!= NULL
)) {
638 Status
= ManFileFindSections (FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
641 ShellInfoObject
.NewEfiShellProtocol
->CloseFile (FileHandle
);
642 if (!EFI_ERROR (Status
)) {
644 // Get help text from .MAN file success.
651 // Load the app image to check EFI_HII_PACKAGE_LIST_PROTOCOL.
653 CmdFileName
= GetExecuatableFileName (TempString
);
654 if (CmdFileName
== NULL
) {
655 Status
= EFI_OUT_OF_RESOURCES
;
660 // If the file in CWD then use the file name, else use the full
663 CmdFilePathName
= ShellFindFilePath (CmdFileName
);
664 if (CmdFilePathName
== NULL
) {
665 Status
= EFI_NOT_FOUND
;
669 DevPath
= ShellInfoObject
.NewEfiShellProtocol
->GetDevicePathFromFilePath (CmdFilePathName
);
670 Status
= gBS
->LoadImage (FALSE
, gImageHandle
, DevPath
, NULL
, 0, &CmdFileImgHandle
);
671 if (EFI_ERROR (Status
)) {
673 // With EFI_SECURITY_VIOLATION retval, the Image was loaded and an ImageHandle was created
674 // with a valid EFI_LOADED_IMAGE_PROTOCOL, but the image can not be started right now.
675 // If the caller doesn't have the option to defer the execution of an image, we should
676 // unload image for the EFI_SECURITY_VIOLATION to avoid the resource leak.
678 if (Status
== EFI_SECURITY_VIOLATION
) {
679 gBS
->UnloadImage (CmdFileImgHandle
);
686 Status
= gBS
->OpenProtocol (
688 &gEfiHiiPackageListProtocolGuid
,
689 (VOID
**)&PackageListHeader
,
692 EFI_OPEN_PROTOCOL_GET_PROTOCOL
694 if (EFI_ERROR (Status
)) {
700 // If get package list on image handle, install it on HiiDatabase.
702 Status
= gBS
->InstallProtocolInterface (
703 &mShellManDriverHandle
,
704 &gEfiDevicePathProtocolGuid
,
705 EFI_NATIVE_INTERFACE
,
706 &mShellManHiiDevicePath
708 if (EFI_ERROR (Status
)) {
712 Status
= gHiiDatabase
->NewPackageList (
715 mShellManDriverHandle
,
718 if (EFI_ERROR (Status
)) {
724 SHELL_FREE_NON_NULL (TempString
);
725 if (BriefDesc
!= NULL
) {
726 SHELL_FREE_NON_NULL (*BriefDesc
);
729 TempString
= HiiGetString (mShellManHiiHandle
, (EFI_STRING_ID
)StringIdWalker
, NULL
);
730 if (TempString
== NULL
) {
731 Status
= EFI_NOT_FOUND
;
735 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
736 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
737 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
738 ShellSetFilePosition (FileHandle
, 0);
741 Status
= ManFileFindTitleSection (FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
742 if (!EFI_ERROR (Status
) && (HelpText
!= NULL
)) {
743 Status
= ManFileFindSections (FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
746 ShellCloseFile (&FileHandle
);
747 if (!EFI_ERROR (Status
)) {
749 // Found what we need and return
755 } while (StringIdWalker
< 0xFFFF && TempString
!= NULL
);
759 if (mShellManDriverHandle
!= NULL
) {
760 gBS
->UninstallProtocolInterface (
761 mShellManDriverHandle
,
762 &gEfiDevicePathProtocolGuid
,
763 &mShellManHiiDevicePath
765 mShellManDriverHandle
= NULL
;
768 if (mShellManHiiHandle
!= NULL
) {
769 HiiRemovePackages (mShellManHiiHandle
);
770 mShellManHiiHandle
= NULL
;
773 if (CmdFileImgHandle
!= NULL
) {
774 Status
= gBS
->UnloadImage (CmdFileImgHandle
);
777 SHELL_FREE_NON_NULL (TempString
);
778 SHELL_FREE_NON_NULL (CmdFileName
);
779 SHELL_FREE_NON_NULL (CmdFilePathName
);
780 SHELL_FREE_NON_NULL (FileDevPath
);
781 SHELL_FREE_NON_NULL (DevPath
);