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
;
21 SHELL_MAN_HII_VENDOR_DEVICE_PATH mShellManHiiDevicePath
= {
27 (UINT8
) (sizeof (VENDOR_DEVICE_PATH
)),
28 (UINT8
) ((sizeof (VENDOR_DEVICE_PATH
)) >> 8)
35 END_ENTIRE_DEVICE_PATH_SUBTYPE
,
37 (UINT8
) (END_DEVICE_PATH_LENGTH
),
38 (UINT8
) ((END_DEVICE_PATH_LENGTH
) >> 8)
44 Verifies that the filename has .EFI on the end.
46 allocates a new buffer and copies the name (appending .EFI if necessary).
47 Caller to free the buffer.
49 @param[in] NameString original name string
51 @return the new filename with .efi as the extension.
54 GetExecuatableFileName (
55 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
));
79 (StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
))/sizeof(CHAR16
),
84 (StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
))/sizeof(CHAR16
),
95 Verifies that the filename has .MAN on the end.
97 allocates a new buffer and copies the name (appending .MAN if necessary)
99 ASSERT if ManFileName is NULL
101 @param[in] ManFileName original filename
103 @return the new filename with .man as the extension.
107 IN CONST CHAR16
*ManFileName
111 if (ManFileName
== NULL
) {
117 if (StrnCmp(ManFileName
+StrLen(ManFileName
)-4, L
".man", 4)==0) {
118 Buffer
= AllocateCopyPool(StrSize(ManFileName
), ManFileName
);
120 Buffer
= AllocateZeroPool(StrSize(ManFileName
) + 4*sizeof(CHAR16
));
121 if (Buffer
!= NULL
) {
123 (StrSize(ManFileName
) + 4*sizeof(CHAR16
))/sizeof(CHAR16
),
128 (StrSize(ManFileName
) + 4*sizeof(CHAR16
))/sizeof(CHAR16
),
138 Search the path environment variable for possible locations and test for
139 which one contains a man file with the name specified. If a valid file is found
140 stop searching and return the (opened) SHELL_FILE_HANDLE for that file.
142 @param[in] FileName Name of the file to find and open.
143 @param[out] Handle Pointer to the handle of the found file. The
144 value of this is undefined for return values
147 @retval EFI_SUCCESS The file was found. Handle is a valid SHELL_FILE_HANDLE
148 @retval EFI_INVALID_PARAMETER A parameter had an invalid value.
149 @retval EFI_NOT_FOUND The file was not found.
153 IN CONST CHAR16
*FileName
,
154 OUT SHELL_FILE_HANDLE
*Handle
157 CHAR16
*FullFileName
;
160 if ( FileName
== NULL
162 || StrLen(FileName
) == 0
164 return (EFI_INVALID_PARAMETER
);
167 FullFileName
= ShellFindFilePath(FileName
);
168 if (FullFileName
== NULL
) {
169 return (EFI_NOT_FOUND
);
173 // now open that file
175 Status
= EfiShellOpenFileByName(FullFileName
, Handle
, EFI_FILE_MODE_READ
);
176 FreePool(FullFileName
);
182 parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
183 detailed help for any sub section specified in the comma seperated list of
184 sections provided. If the end of the file or a .TH section is found then
187 Upon a sucessful return the caller is responsible to free the memory in *HelpText
189 @param[in] Handle FileHandle to read from
190 @param[in] Sections name of command's sub sections to find
191 @param[out] HelpText pointer to pointer to string where text goes.
192 @param[out] HelpSize pointer to size of allocated HelpText (may be updated)
193 @param[in] Ascii TRUE if the file is ASCII, FALSE otherwise.
195 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
196 @retval EFI_SUCCESS the section was found and its description sotred in
201 IN SHELL_FILE_HANDLE Handle
,
202 IN CONST CHAR16
*Sections
,
203 OUT CHAR16
**HelpText
,
211 BOOLEAN CurrentlyReading
;
220 return (EFI_INVALID_PARAMETER
);
223 Status
= EFI_SUCCESS
;
224 CurrentlyReading
= FALSE
;
228 ReadLine
= AllocateZeroPool(Size
);
229 if (ReadLine
== NULL
) {
230 return (EFI_OUT_OF_RESOURCES
);
233 for (;!ShellFileHandleEof(Handle
);Size
= 1024) {
234 Status
= ShellFileHandleReadLine(Handle
, ReadLine
, &Size
, TRUE
, &Ascii
);
235 if (ReadLine
[0] == L
'#') {
237 // Skip comment lines
242 // ignore too small of buffer...
244 if (Status
== EFI_BUFFER_TOO_SMALL
) {
245 Status
= EFI_SUCCESS
;
247 if (EFI_ERROR(Status
)) {
249 } else if (StrnCmp(ReadLine
, L
".TH", 3) == 0) {
251 // we hit the end of this commands section so stop.
254 } else if (StrnCmp(ReadLine
, L
".SH", 3) == 0) {
255 if (Sections
== NULL
) {
256 CurrentlyReading
= TRUE
;
260 // we found a section
262 if (CurrentlyReading
) {
263 CurrentlyReading
= FALSE
;
266 // is this a section we want to read in?
268 for ( SectionName
= ReadLine
+ 3
269 ; *SectionName
== L
' '
271 SectionLen
= StrLen(SectionName
);
272 SectionName
= StrStr(Sections
, SectionName
);
273 if (SectionName
== NULL
) {
276 if (*(SectionName
+ SectionLen
) == CHAR_NULL
|| *(SectionName
+ SectionLen
) == L
',') {
277 CurrentlyReading
= TRUE
;
279 } else if (CurrentlyReading
) {
282 // copy and save the current line.
284 ASSERT((*HelpText
== NULL
&& *HelpSize
== 0) || (*HelpText
!= NULL
));
285 StrnCatGrow (HelpText
, HelpSize
, ReadLine
, 0);
286 StrnCatGrow (HelpText
, HelpSize
, L
"\r\n", 0);
290 if (!Found
&& !EFI_ERROR(Status
)) {
291 return (EFI_NOT_FOUND
);
297 Parses a line from a MAN file to see if it is the Title Header. If it is, then
298 if the "Brief Description" is desired, allocate a buffer for it and return a
299 copy. Upon a sucessful return the caller is responsible to free the memory in
302 Uses a simple state machine that allows "unlimited" whitespace before and after the
303 ".TH", compares Command and the MAN file commnd name without respect to case, and
304 allows "unlimited" whitespace and '0' and '1' characters before the Short Description.
305 The PCRE regex describing this functionality is: ^\s*\.TH\s+(\S)\s[\s01]*(.*)$
306 where group 1 is the Command Name and group 2 is the Short Description.
308 @param[in] Command name of command whose MAN file we think Line came from
309 @param[in] Line Pointer to a line from the MAN file
310 @param[out] BriefDesc pointer to pointer to string where description goes.
311 @param[out] BriefSize pointer to size of allocated BriefDesc
312 @param[out] Found TRUE if the Title Header was found and it belongs to Command
314 @retval TRUE Line contained the Title Header
315 @retval FALSE Line did not contain the Title Header
319 IN CONST CHAR16
*Command
,
321 OUT CHAR16
**BriefDesc OPTIONAL
,
322 OUT UINTN
*BriefSize OPTIONAL
,
326 // The states of a simple state machine used to recognize a title header line
327 // and to extract the Short Description, if desired.
329 LookForThMacro
, LookForCommandName
, CompareCommands
, GetBriefDescription
, Final
333 UINTN CommandIndex
; // Indexes Command as we compare its chars to the MAN file.
334 BOOLEAN ReturnValue
; // TRUE if this the Title Header line of *some* MAN file.
335 BOOLEAN ReturnFound
; // TRUE if this the Title Header line of *the desired* MAN file.
340 State
= LookForThMacro
;
344 if (*Line
== L
'\0') {
350 // Handle "^\s*.TH\s"
351 // Go to state LookForCommandName if the title header macro is present; otherwise,
352 // eat white space. If we see something other than white space, this is not a
353 // title header line.
355 if (StrnCmp (L
".TH ", Line
, 4) == 0 || StrnCmp (L
".TH\t", Line
, 4) == 0) {
357 State
= LookForCommandName
;
359 else if (*Line
== L
' ' || *Line
== L
'\t') {
368 // Eat any "extra" whitespace after the title header macro (we have already seen
369 // at least one white space character). Go to state CompareCommands when a
370 // non-white space is seen.
371 case LookForCommandName
:
372 if (*Line
== L
' ' || *Line
== L
'\t') {
376 ReturnValue
= TRUE
; // This is *some* command's title header line.
377 State
= CompareCommands
;
378 // Do not increment Line; it points to the first character of the command
379 // name on the title header line.
384 // Compare Command to the title header command name, ignoring case. When we
385 // reach the end of the command (i.e. we see white space), the next state
386 // depends on whether the caller wants a copy of the Brief Description.
387 case CompareCommands
:
388 if (*Line
== L
' ' || *Line
== L
'\t') {
389 ReturnFound
= TRUE
; // This is the desired command's title header line.
390 State
= (BriefDesc
== NULL
) ? Final
: GetBriefDescription
;
392 else if (CharToUpper (*Line
) != CharToUpper (*(Command
+ CommandIndex
++))) {
398 // Handle "[\s01]*(.*)$"
399 // Skip whitespace, '0', and '1' characters, if any, prior to the brief description.
400 // Return the description to the caller.
401 case GetBriefDescription
:
402 if (*Line
!= L
' ' && *Line
!= L
'\t' && *Line
!= L
'0' && *Line
!= L
'1') {
403 *BriefSize
= StrSize(Line
);
404 *BriefDesc
= AllocateZeroPool(*BriefSize
);
405 if (*BriefDesc
!= NULL
) {
406 StrCpyS(*BriefDesc
, (*BriefSize
)/sizeof(CHAR16
), Line
);
417 } while (State
< Final
);
419 *Found
= ReturnFound
;
424 parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
425 "Brief Description" for the .TH section as specified by Command. If the
426 command section is not found return EFI_NOT_FOUND.
428 Upon a sucessful return the caller is responsible to free the memory in *BriefDesc
430 @param[in] Handle FileHandle to read from
431 @param[in] Command name of command's section to find as entered on the
432 command line (may be a relative or absolute path or
433 be in any case: upper, lower, or mixed in numerous ways!).
434 @param[out] BriefDesc pointer to pointer to string where description goes.
435 @param[out] BriefSize pointer to size of allocated BriefDesc
436 @param[in, out] Ascii TRUE if the file is ASCII, FALSE otherwise, will be
437 set if the file handle is at the 0 position.
439 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
440 @retval EFI_SUCCESS the section was found and its description stored in
441 an allocated buffer if requested.
444 ManFileFindTitleSection(
445 IN SHELL_FILE_HANDLE Handle
,
446 IN CONST CHAR16
*Command
,
447 OUT CHAR16
**BriefDesc OPTIONAL
,
448 OUT UINTN
*BriefSize OPTIONAL
,
449 IN OUT BOOLEAN
*Ascii
460 || (BriefDesc
!= NULL
&& BriefSize
== NULL
)
462 return (EFI_INVALID_PARAMETER
);
465 Status
= EFI_SUCCESS
;
469 ReadLine
= AllocateZeroPool(Size
);
470 if (ReadLine
== NULL
) {
471 return (EFI_OUT_OF_RESOURCES
);
475 // Do not pass any leading path information that may be present to IsTitleHeader().
477 Start
= StrLen(Command
);
479 && (*(Command
+ Start
- 1) != L
'\\')
480 && (*(Command
+ Start
- 1) != L
'/')
481 && (*(Command
+ Start
- 1) != L
':')) {
485 for (;!ShellFileHandleEof(Handle
);Size
= 1024) {
486 Status
= ShellFileHandleReadLine(Handle
, ReadLine
, &Size
, TRUE
, Ascii
);
488 // ignore too small of buffer...
490 if (EFI_ERROR(Status
) && Status
!= EFI_BUFFER_TOO_SMALL
) {
494 Status
= EFI_NOT_FOUND
;
495 if (IsTitleHeader (Command
+Start
, ReadLine
, BriefDesc
, BriefSize
, &Found
)) {
496 Status
= Found
? EFI_SUCCESS
: EFI_NOT_FOUND
;
506 This function returns the help information for the specified command. The help text
507 will be parsed from a UEFI Shell manual page. (see UEFI Shell 2.0 Appendix B)
509 If Sections is specified, then each section name listed will be compared in a casesensitive
510 manner, to the section names described in Appendix B. If the section exists,
511 it will be appended to the returned help text. If the section does not exist, no
512 information will be returned. If Sections is NULL, then all help text information
513 available will be returned.
515 if BriefDesc is NULL, then the breif description will not be savedd seperatly,
516 but placed first in the main HelpText.
518 @param[in] ManFileName Points to the NULL-terminated UEFI Shell MAN file name.
519 @param[in] Command Points to the NULL-terminated UEFI Shell command name.
520 @param[in] Sections Points to the NULL-terminated comma-delimited
521 section names to return. If NULL, then all
522 sections will be returned.
523 @param[out] BriefDesc On return, points to a callee-allocated buffer
524 containing brief description text.
525 @param[out] HelpText On return, points to a callee-allocated buffer
526 containing all specified help text.
528 @retval EFI_SUCCESS The help text was returned.
529 @retval EFI_OUT_OF_RESOURCES The necessary buffer could not be allocated to hold the
531 @retval EFI_INVALID_PARAMETER HelpText is NULL.
532 @retval EFI_INVALID_PARAMETER ManFileName is invalid.
533 @retval EFI_NOT_FOUND There is no help text available for Command.
537 IN CONST CHAR16
*ManFileName
,
538 IN CONST CHAR16
*Command
,
539 IN CONST CHAR16
*Sections OPTIONAL
,
540 OUT CHAR16
**BriefDesc OPTIONAL
,
541 OUT CHAR16
**HelpText
545 SHELL_FILE_HANDLE FileHandle
;
546 EFI_HANDLE CmdFileImgHandle
;
550 UINTN StringIdWalker
;
553 CHAR16
*CmdFilePathName
;
554 EFI_DEVICE_PATH_PROTOCOL
*FileDevPath
;
555 EFI_DEVICE_PATH_PROTOCOL
*DevPath
;
556 EFI_HII_PACKAGE_LIST_HEADER
*PackageListHeader
;
558 if ( ManFileName
== NULL
562 return (EFI_INVALID_PARAMETER
);
571 CmdFilePathName
= NULL
;
572 CmdFileImgHandle
= NULL
;
573 PackageListHeader
= NULL
;
578 // See if it's in HII first
580 TempString
= ShellCommandGetCommandHelp(Command
);
581 if (TempString
!= NULL
) {
582 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
583 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
584 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
585 ShellSetFilePosition (FileHandle
, 0);
588 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
589 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
590 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
592 ShellCloseFile (&FileHandle
);
595 // If the image is a external app, check .MAN file first.
598 TempString
= GetManFileName(ManFileName
);
599 if (TempString
== NULL
) {
600 return (EFI_INVALID_PARAMETER
);
603 Status
= SearchPathForFile(TempString
, &FileHandle
);
604 if (EFI_ERROR(Status
)) {
605 FileDevPath
= FileDevicePath(NULL
, TempString
);
606 DevPath
= AppendDevicePath (ShellInfoObject
.ImageDevPath
, FileDevPath
);
607 Status
= InternalOpenFileDevicePath(DevPath
, &FileHandle
, EFI_FILE_MODE_READ
, 0);
608 SHELL_FREE_NON_NULL(FileDevPath
);
609 SHELL_FREE_NON_NULL(DevPath
);
612 if (!EFI_ERROR(Status
)) {
615 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
616 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
617 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
619 ShellInfoObject
.NewEfiShellProtocol
->CloseFile(FileHandle
);
620 if (!EFI_ERROR(Status
)) {
622 // Get help text from .MAN file success.
629 // Load the app image to check EFI_HII_PACKAGE_LIST_PROTOCOL.
631 CmdFileName
= GetExecuatableFileName(TempString
);
632 if (CmdFileName
== NULL
) {
633 Status
= EFI_OUT_OF_RESOURCES
;
637 // If the file in CWD then use the file name, else use the full
640 CmdFilePathName
= ShellFindFilePath(CmdFileName
);
641 if (CmdFilePathName
== NULL
) {
642 Status
= EFI_NOT_FOUND
;
645 DevPath
= ShellInfoObject
.NewEfiShellProtocol
->GetDevicePathFromFilePath(CmdFilePathName
);
646 Status
= gBS
->LoadImage(FALSE
, gImageHandle
, DevPath
, NULL
, 0, &CmdFileImgHandle
);
647 if(EFI_ERROR(Status
)) {
651 Status
= gBS
->OpenProtocol(
653 &gEfiHiiPackageListProtocolGuid
,
654 (VOID
**)&PackageListHeader
,
657 EFI_OPEN_PROTOCOL_GET_PROTOCOL
659 if(EFI_ERROR(Status
)) {
665 // If get package list on image handle, install it on HiiDatabase.
667 Status
= gBS
->InstallProtocolInterface (
668 &mShellManDriverHandle
,
669 &gEfiDevicePathProtocolGuid
,
670 EFI_NATIVE_INTERFACE
,
671 &mShellManHiiDevicePath
673 if (EFI_ERROR(Status
)) {
677 Status
= gHiiDatabase
->NewPackageList (
680 mShellManDriverHandle
,
683 if (EFI_ERROR (Status
)) {
689 SHELL_FREE_NON_NULL(TempString
);
690 if (BriefDesc
!= NULL
) {
691 SHELL_FREE_NON_NULL(*BriefDesc
);
693 TempString
= HiiGetString (mShellManHiiHandle
, (EFI_STRING_ID
)StringIdWalker
, NULL
);
694 if (TempString
== NULL
) {
695 Status
= EFI_NOT_FOUND
;
698 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
699 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
700 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
701 ShellSetFilePosition (FileHandle
, 0);
704 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
705 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
706 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
708 ShellCloseFile (&FileHandle
);
709 if (!EFI_ERROR(Status
)){
711 // Found what we need and return
717 } while (StringIdWalker
< 0xFFFF && TempString
!= NULL
);
722 if (mShellManDriverHandle
!= NULL
) {
723 gBS
->UninstallProtocolInterface (
724 mShellManDriverHandle
,
725 &gEfiDevicePathProtocolGuid
,
726 &mShellManHiiDevicePath
728 mShellManDriverHandle
= NULL
;
731 if (mShellManHiiHandle
!= NULL
) {
732 HiiRemovePackages (mShellManHiiHandle
);
733 mShellManHiiHandle
= NULL
;
736 if (CmdFileImgHandle
!= NULL
) {
737 Status
= gBS
->UnloadImage (CmdFileImgHandle
);
740 SHELL_FREE_NON_NULL(TempString
);
741 SHELL_FREE_NON_NULL(CmdFileName
);
742 SHELL_FREE_NON_NULL(CmdFilePathName
);
743 SHELL_FREE_NON_NULL(FileDevPath
);
744 SHELL_FREE_NON_NULL(DevPath
);