2 Provides interface to shell MAN file parser.
4 Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
5 Copyright 2015 Dell Inc.
6 This program and the accompanying materials
7 are licensed and made available under the terms and conditions of the BSD License
8 which accompanies this distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
18 #define SHELL_MAN_HII_GUID \
20 0xf62ccd0c, 0x2449, 0x453c, { 0x8a, 0xcb, 0x8c, 0xc5, 0x7c, 0xf0, 0x2a, 0x97 } \
23 EFI_HII_HANDLE mShellManHiiHandle
= NULL
;
24 EFI_HANDLE mShellManDriverHandle
= NULL
;
27 SHELL_MAN_HII_VENDOR_DEVICE_PATH mShellManHiiDevicePath
= {
33 (UINT8
) (sizeof (VENDOR_DEVICE_PATH
)),
34 (UINT8
) ((sizeof (VENDOR_DEVICE_PATH
)) >> 8)
41 END_ENTIRE_DEVICE_PATH_SUBTYPE
,
43 (UINT8
) (END_DEVICE_PATH_LENGTH
),
44 (UINT8
) ((END_DEVICE_PATH_LENGTH
) >> 8)
51 Convert a Unicode character to upper case only if
52 it maps to a valid small-case ASCII character.
54 This internal function only deal with Unicode character
55 which maps to a valid small-case ASCII character, i.e.
56 L'a' to L'z'. For other Unicode character, the input character
59 @param Char The character to convert.
61 @retval LowerCharacter If the Char is with range L'a' to L'z'.
62 @retval Unchanged Otherwise.
66 InternalShellCharToUpper (
71 Verifies that the filename has .EFI on the end.
73 allocates a new buffer and copies the name (appending .EFI if necessary).
74 Caller to free the buffer.
76 @param[in] NameString original name string
78 @return the new filename with .efi as the extension.
81 GetExecuatableFileName (
82 IN CONST CHAR16
*NameString
87 if (NameString
== NULL
) {
94 if (StrnCmp(NameString
+StrLen(NameString
)-StrLen(L
".efi"), L
".efi", StrLen(L
".efi"))==0) {
95 Buffer
= AllocateCopyPool(StrSize(NameString
), NameString
);
96 } else if (StrnCmp(NameString
+StrLen(NameString
)-StrLen(L
".man"), L
".man", StrLen(L
".man"))==0) {
97 Buffer
= AllocateCopyPool(StrSize(NameString
), NameString
);
99 SuffixStr
= Buffer
+StrLen(Buffer
)-StrLen(L
".man");
100 StrnCpyS (SuffixStr
, StrSize(L
".man")/sizeof(CHAR16
), L
".efi", StrLen(L
".efi"));
103 Buffer
= AllocateZeroPool(StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
));
104 if (Buffer
!= NULL
) {
106 (StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
))/sizeof(CHAR16
),
111 (StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
))/sizeof(CHAR16
),
122 Verifies that the filename has .MAN on the end.
124 allocates a new buffer and copies the name (appending .MAN if necessary)
126 ASSERT if ManFileName is NULL
128 @param[in] ManFileName original filename
130 @return the new filename with .man as the extension.
134 IN CONST CHAR16
*ManFileName
138 if (ManFileName
== NULL
) {
144 if (StrnCmp(ManFileName
+StrLen(ManFileName
)-4, L
".man", 4)==0) {
145 Buffer
= AllocateCopyPool(StrSize(ManFileName
), ManFileName
);
147 Buffer
= AllocateZeroPool(StrSize(ManFileName
) + 4*sizeof(CHAR16
));
148 if (Buffer
!= NULL
) {
150 (StrSize(ManFileName
) + 4*sizeof(CHAR16
))/sizeof(CHAR16
),
155 (StrSize(ManFileName
) + 4*sizeof(CHAR16
))/sizeof(CHAR16
),
165 Search the path environment variable for possible locations and test for
166 which one contains a man file with the name specified. If a valid file is found
167 stop searching and return the (opened) SHELL_FILE_HANDLE for that file.
169 @param[in] FileName Name of the file to find and open.
170 @param[out] Handle Pointer to the handle of the found file. The
171 value of this is undefined for return values
174 @retval EFI_SUCCESS The file was found. Handle is a valid SHELL_FILE_HANDLE
175 @retval EFI_INVALID_PARAMETER A parameter had an invalid value.
176 @retval EFI_NOT_FOUND The file was not found.
180 IN CONST CHAR16
*FileName
,
181 OUT SHELL_FILE_HANDLE
*Handle
184 CHAR16
*FullFileName
;
187 if ( FileName
== NULL
189 || StrLen(FileName
) == 0
191 return (EFI_INVALID_PARAMETER
);
194 FullFileName
= ShellFindFilePath(FileName
);
195 if (FullFileName
== NULL
) {
196 return (EFI_NOT_FOUND
);
200 // now open that file
202 Status
= EfiShellOpenFileByName(FullFileName
, Handle
, EFI_FILE_MODE_READ
);
203 FreePool(FullFileName
);
209 parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
210 detailed help for any sub section specified in the comma seperated list of
211 sections provided. If the end of the file or a .TH section is found then
214 Upon a sucessful return the caller is responsible to free the memory in *HelpText
216 @param[in] Handle FileHandle to read from
217 @param[in] Sections name of command's sub sections to find
218 @param[out] HelpText pointer to pointer to string where text goes.
219 @param[out] HelpSize pointer to size of allocated HelpText (may be updated)
220 @param[in] Ascii TRUE if the file is ASCII, FALSE otherwise.
222 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
223 @retval EFI_SUCCESS the section was found and its description sotred in
228 IN SHELL_FILE_HANDLE Handle
,
229 IN CONST CHAR16
*Sections
,
230 OUT CHAR16
**HelpText
,
238 BOOLEAN CurrentlyReading
;
247 return (EFI_INVALID_PARAMETER
);
250 Status
= EFI_SUCCESS
;
251 CurrentlyReading
= FALSE
;
255 ReadLine
= AllocateZeroPool(Size
);
256 if (ReadLine
== NULL
) {
257 return (EFI_OUT_OF_RESOURCES
);
260 for (;!ShellFileHandleEof(Handle
);Size
= 1024) {
261 Status
= ShellFileHandleReadLine(Handle
, ReadLine
, &Size
, TRUE
, &Ascii
);
262 if (ReadLine
[0] == L
'#') {
264 // Skip comment lines
269 // ignore too small of buffer...
271 if (Status
== EFI_BUFFER_TOO_SMALL
) {
272 Status
= EFI_SUCCESS
;
274 if (EFI_ERROR(Status
)) {
276 } else if (StrnCmp(ReadLine
, L
".TH", 3) == 0) {
278 // we hit the end of this commands section so stop.
281 } else if (StrnCmp(ReadLine
, L
".SH", 3) == 0) {
282 if (Sections
== NULL
) {
283 CurrentlyReading
= TRUE
;
287 // we found a section
289 if (CurrentlyReading
) {
290 CurrentlyReading
= FALSE
;
293 // is this a section we want to read in?
295 for ( SectionName
= ReadLine
+ 3
296 ; *SectionName
== L
' '
298 SectionLen
= StrLen(SectionName
);
299 SectionName
= StrStr(Sections
, SectionName
);
300 if (SectionName
== NULL
) {
303 if (*(SectionName
+ SectionLen
) == CHAR_NULL
|| *(SectionName
+ SectionLen
) == L
',') {
304 CurrentlyReading
= TRUE
;
306 } else if (CurrentlyReading
) {
309 // copy and save the current line.
311 ASSERT((*HelpText
== NULL
&& *HelpSize
== 0) || (*HelpText
!= NULL
));
312 StrnCatGrow (HelpText
, HelpSize
, ReadLine
, 0);
313 StrnCatGrow (HelpText
, HelpSize
, L
"\r\n", 0);
317 if (!Found
&& !EFI_ERROR(Status
)) {
318 return (EFI_NOT_FOUND
);
324 Parses a line from a MAN file to see if it is the Title Header. If it is, then
325 if the "Brief Description" is desired, allocate a buffer for it and return a
326 copy. Upon a sucessful return the caller is responsible to free the memory in
329 Uses a simple state machine that allows "unlimited" whitespace before and after the
330 ".TH", compares Command and the MAN file commnd name without respect to case, and
331 allows "unlimited" whitespace and '0' and '1' characters before the Short Description.
332 The PCRE regex describing this functionality is: ^\s*\.TH\s+(\S)\s[\s01]*(.*)$
333 where group 1 is the Command Name and group 2 is the Short Description.
335 @param[in] Command name of command whose MAN file we think Line came from
336 @param[in] Line Pointer to a line from the MAN file
337 @param[out] BriefDesc pointer to pointer to string where description goes.
338 @param[out] BriefSize pointer to size of allocated BriefDesc
339 @param[out] Found TRUE if the Title Header was found and it belongs to Command
341 @retval TRUE Line contained the Title Header
342 @retval FALSE Line did not contain the Title Header
346 IN CONST CHAR16
*Command
,
348 OUT CHAR16
**BriefDesc OPTIONAL
,
349 OUT UINTN
*BriefSize OPTIONAL
,
353 // The states of a simple state machine used to recognize a title header line
354 // and to extract the Short Description, if desired.
356 LookForThMacro
, LookForCommandName
, CompareCommands
, GetBriefDescription
, Final
360 UINTN CommandIndex
; // Indexes Command as we compare its chars to the MAN file.
361 BOOLEAN ReturnValue
; // TRUE if this the Title Header line of *some* MAN file.
362 BOOLEAN ReturnFound
; // TRUE if this the Title Header line of *the desired* MAN file.
367 State
= LookForThMacro
;
371 if (*Line
== L
'\0') {
377 // Handle "^\s*.TH\s"
378 // Go to state LookForCommandName if the title header macro is present; otherwise,
379 // eat white space. If we see something other than white space, this is not a
380 // title header line.
382 if (StrnCmp (L
".TH ", Line
, 4) == 0 || StrnCmp (L
".TH\t", Line
, 4) == 0) {
384 State
= LookForCommandName
;
386 else if (*Line
== L
' ' || *Line
== L
'\t') {
395 // Eat any "extra" whitespace after the title header macro (we have already seen
396 // at least one white space character). Go to state CompareCommands when a
397 // non-white space is seen.
398 case LookForCommandName
:
399 if (*Line
== L
' ' || *Line
== L
'\t') {
403 ReturnValue
= TRUE
; // This is *some* command's title header line.
404 State
= CompareCommands
;
405 // Do not increment Line; it points to the first character of the command
406 // name on the title header line.
411 // Compare Command to the title header command name, ignoring case. When we
412 // reach the end of the command (i.e. we see white space), the next state
413 // depends on whether the caller wants a copy of the Brief Description.
414 case CompareCommands
:
415 if (*Line
== L
' ' || *Line
== L
'\t') {
416 ReturnFound
= TRUE
; // This is the desired command's title header line.
417 State
= (BriefDesc
== NULL
) ? Final
: GetBriefDescription
;
419 else if (InternalShellCharToUpper (*Line
) != InternalShellCharToUpper (*(Command
+ CommandIndex
++))) {
425 // Handle "[\s01]*(.*)$"
426 // Skip whitespace, '0', and '1' characters, if any, prior to the brief description.
427 // Return the description to the caller.
428 case GetBriefDescription
:
429 if (*Line
!= L
' ' && *Line
!= L
'\t' && *Line
!= L
'0' && *Line
!= L
'1') {
430 *BriefSize
= StrSize(Line
);
431 *BriefDesc
= AllocateZeroPool(*BriefSize
);
432 if (*BriefDesc
!= NULL
) {
433 StrCpyS(*BriefDesc
, (*BriefSize
)/sizeof(CHAR16
), Line
);
444 } while (State
< Final
);
446 *Found
= ReturnFound
;
451 parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
452 "Brief Description" for the .TH section as specified by Command. If the
453 command section is not found return EFI_NOT_FOUND.
455 Upon a sucessful return the caller is responsible to free the memory in *BriefDesc
457 @param[in] Handle FileHandle to read from
458 @param[in] Command name of command's section to find as entered on the
459 command line (may be a relative or absolute path or
460 be in any case: upper, lower, or mixed in numerous ways!).
461 @param[out] BriefDesc pointer to pointer to string where description goes.
462 @param[out] BriefSize pointer to size of allocated BriefDesc
463 @param[in, out] Ascii TRUE if the file is ASCII, FALSE otherwise, will be
464 set if the file handle is at the 0 position.
466 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
467 @retval EFI_SUCCESS the section was found and its description stored in
468 an allocated buffer if requested.
471 ManFileFindTitleSection(
472 IN SHELL_FILE_HANDLE Handle
,
473 IN CONST CHAR16
*Command
,
474 OUT CHAR16
**BriefDesc OPTIONAL
,
475 OUT UINTN
*BriefSize OPTIONAL
,
476 IN OUT BOOLEAN
*Ascii
487 || (BriefDesc
!= NULL
&& BriefSize
== NULL
)
489 return (EFI_INVALID_PARAMETER
);
492 Status
= EFI_SUCCESS
;
496 ReadLine
= AllocateZeroPool(Size
);
497 if (ReadLine
== NULL
) {
498 return (EFI_OUT_OF_RESOURCES
);
502 // Do not pass any leading path information that may be present to IsTitleHeader().
504 Start
= StrLen(Command
);
506 && (*(Command
+ Start
- 1) != L
'\\')
507 && (*(Command
+ Start
- 1) != L
'/')
508 && (*(Command
+ Start
- 1) != L
':')) {
512 for (;!ShellFileHandleEof(Handle
);Size
= 1024) {
513 Status
= ShellFileHandleReadLine(Handle
, ReadLine
, &Size
, TRUE
, Ascii
);
515 // ignore too small of buffer...
517 if (EFI_ERROR(Status
) && Status
!= EFI_BUFFER_TOO_SMALL
) {
521 Status
= EFI_NOT_FOUND
;
522 if (IsTitleHeader (Command
+Start
, ReadLine
, BriefDesc
, BriefSize
, &Found
)) {
523 Status
= Found
? EFI_SUCCESS
: EFI_NOT_FOUND
;
533 This function returns the help information for the specified command. The help text
534 will be parsed from a UEFI Shell manual page. (see UEFI Shell 2.0 Appendix B)
536 If Sections is specified, then each section name listed will be compared in a casesensitive
537 manner, to the section names described in Appendix B. If the section exists,
538 it will be appended to the returned help text. If the section does not exist, no
539 information will be returned. If Sections is NULL, then all help text information
540 available will be returned.
542 if BriefDesc is NULL, then the breif description will not be savedd seperatly,
543 but placed first in the main HelpText.
545 @param[in] ManFileName Points to the NULL-terminated UEFI Shell MAN file name.
546 @param[in] Command Points to the NULL-terminated UEFI Shell command name.
547 @param[in] Sections Points to the NULL-terminated comma-delimited
548 section names to return. If NULL, then all
549 sections will be returned.
550 @param[out] BriefDesc On return, points to a callee-allocated buffer
551 containing brief description text.
552 @param[out] HelpText On return, points to a callee-allocated buffer
553 containing all specified help text.
555 @retval EFI_SUCCESS The help text was returned.
556 @retval EFI_OUT_OF_RESOURCES The necessary buffer could not be allocated to hold the
558 @retval EFI_INVALID_PARAMETER HelpText is NULL.
559 @retval EFI_INVALID_PARAMETER ManFileName is invalid.
560 @retval EFI_NOT_FOUND There is no help text available for Command.
564 IN CONST CHAR16
*ManFileName
,
565 IN CONST CHAR16
*Command
,
566 IN CONST CHAR16
*Sections OPTIONAL
,
567 OUT CHAR16
**BriefDesc OPTIONAL
,
568 OUT CHAR16
**HelpText
572 SHELL_FILE_HANDLE FileHandle
;
573 EFI_HANDLE CmdFileImgHandle
;
577 UINTN StringIdWalker
;
580 CHAR16
*CmdFilePathName
;
581 EFI_DEVICE_PATH_PROTOCOL
*FileDevPath
;
582 EFI_DEVICE_PATH_PROTOCOL
*DevPath
;
583 EFI_HII_PACKAGE_LIST_HEADER
*PackageListHeader
;
585 if ( ManFileName
== NULL
589 return (EFI_INVALID_PARAMETER
);
598 CmdFilePathName
= NULL
;
599 CmdFileImgHandle
= NULL
;
600 PackageListHeader
= NULL
;
605 // See if it's in HII first
607 TempString
= ShellCommandGetCommandHelp(Command
);
608 if (TempString
!= NULL
) {
609 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
610 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
611 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
612 ShellSetFilePosition (FileHandle
, 0);
615 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
616 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
617 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
619 ShellCloseFile (&FileHandle
);
622 // If the image is a external app, check .MAN file first.
625 TempString
= GetManFileName(ManFileName
);
626 if (TempString
== NULL
) {
627 return (EFI_INVALID_PARAMETER
);
630 Status
= SearchPathForFile(TempString
, &FileHandle
);
631 if (EFI_ERROR(Status
)) {
632 FileDevPath
= FileDevicePath(NULL
, TempString
);
633 DevPath
= AppendDevicePath (ShellInfoObject
.ImageDevPath
, FileDevPath
);
634 Status
= InternalOpenFileDevicePath(DevPath
, &FileHandle
, EFI_FILE_MODE_READ
, 0);
635 SHELL_FREE_NON_NULL(FileDevPath
);
636 SHELL_FREE_NON_NULL(DevPath
);
639 if (!EFI_ERROR(Status
)) {
642 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
643 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
644 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
646 ShellInfoObject
.NewEfiShellProtocol
->CloseFile(FileHandle
);
647 if (!EFI_ERROR(Status
)) {
649 // Get help text from .MAN file success.
656 // Load the app image to check EFI_HII_PACKAGE_LIST_PROTOCOL.
658 CmdFileName
= GetExecuatableFileName(TempString
);
659 if (CmdFileName
== NULL
) {
660 Status
= EFI_OUT_OF_RESOURCES
;
664 // If the file in CWD then use the file name, else use the full
667 CmdFilePathName
= ShellFindFilePath(CmdFileName
);
668 if (CmdFilePathName
== NULL
) {
669 Status
= EFI_NOT_FOUND
;
672 DevPath
= ShellInfoObject
.NewEfiShellProtocol
->GetDevicePathFromFilePath(CmdFilePathName
);
673 Status
= gBS
->LoadImage(FALSE
, gImageHandle
, DevPath
, NULL
, 0, &CmdFileImgHandle
);
674 if(EFI_ERROR(Status
)) {
678 Status
= gBS
->OpenProtocol(
680 &gEfiHiiPackageListProtocolGuid
,
681 (VOID
**)&PackageListHeader
,
684 EFI_OPEN_PROTOCOL_GET_PROTOCOL
686 if(EFI_ERROR(Status
)) {
692 // If get package list on image handle, install it on HiiDatabase.
694 Status
= gBS
->InstallProtocolInterface (
695 &mShellManDriverHandle
,
696 &gEfiDevicePathProtocolGuid
,
697 EFI_NATIVE_INTERFACE
,
698 &mShellManHiiDevicePath
700 if (EFI_ERROR(Status
)) {
704 Status
= gHiiDatabase
->NewPackageList (
707 mShellManDriverHandle
,
710 if (EFI_ERROR (Status
)) {
716 SHELL_FREE_NON_NULL(TempString
);
717 if (BriefDesc
!= NULL
) {
718 SHELL_FREE_NON_NULL(*BriefDesc
);
720 TempString
= HiiGetString (mShellManHiiHandle
, (EFI_STRING_ID
)StringIdWalker
, NULL
);
721 if (TempString
== NULL
) {
722 Status
= EFI_NOT_FOUND
;
725 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
726 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
727 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
728 ShellSetFilePosition (FileHandle
, 0);
731 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
732 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
733 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
735 ShellCloseFile (&FileHandle
);
736 if (!EFI_ERROR(Status
)){
738 // Found what we need and return
744 } while (StringIdWalker
< 0xFFFF && TempString
!= NULL
);
749 if (mShellManDriverHandle
!= NULL
) {
750 gBS
->UninstallProtocolInterface (
751 mShellManDriverHandle
,
752 &gEfiDevicePathProtocolGuid
,
753 &mShellManHiiDevicePath
755 mShellManDriverHandle
= NULL
;
758 if (mShellManHiiHandle
!= NULL
) {
759 HiiRemovePackages (mShellManHiiHandle
);
760 mShellManHiiHandle
= NULL
;
763 if (CmdFileImgHandle
!= NULL
) {
764 Status
= gBS
->UnloadImage (CmdFileImgHandle
);
767 SHELL_FREE_NON_NULL(TempString
);
768 SHELL_FREE_NON_NULL(CmdFileName
);
769 SHELL_FREE_NON_NULL(CmdFilePathName
);
770 SHELL_FREE_NON_NULL(FileDevPath
);
771 SHELL_FREE_NON_NULL(DevPath
);