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 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)
50 Verifies that the filename has .EFI on the end.
52 allocates a new buffer and copies the name (appending .EFI if necessary).
53 Caller to free the buffer.
55 @param[in] NameString original name string
57 @return the new filename with .efi as the extension.
60 GetExecuatableFileName (
61 IN CONST CHAR16
*NameString
66 if (NameString
== NULL
) {
73 if (StrnCmp(NameString
+StrLen(NameString
)-StrLen(L
".efi"), L
".efi", StrLen(L
".efi"))==0) {
74 Buffer
= AllocateCopyPool(StrSize(NameString
), NameString
);
75 } else if (StrnCmp(NameString
+StrLen(NameString
)-StrLen(L
".man"), L
".man", StrLen(L
".man"))==0) {
76 Buffer
= AllocateCopyPool(StrSize(NameString
), NameString
);
78 SuffixStr
= Buffer
+StrLen(Buffer
)-StrLen(L
".man");
79 StrnCpyS (SuffixStr
, StrSize(L
".man")/sizeof(CHAR16
), L
".efi", StrLen(L
".efi"));
82 Buffer
= AllocateZeroPool(StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
));
85 (StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
))/sizeof(CHAR16
),
90 (StrSize(NameString
) + StrLen(L
".efi")*sizeof(CHAR16
))/sizeof(CHAR16
),
101 Verifies that the filename has .MAN on the end.
103 allocates a new buffer and copies the name (appending .MAN if necessary)
105 ASSERT if ManFileName is NULL
107 @param[in] ManFileName original filename
109 @return the new filename with .man as the extension.
113 IN CONST CHAR16
*ManFileName
117 if (ManFileName
== NULL
) {
123 if (StrnCmp(ManFileName
+StrLen(ManFileName
)-4, L
".man", 4)==0) {
124 Buffer
= AllocateCopyPool(StrSize(ManFileName
), ManFileName
);
126 Buffer
= AllocateZeroPool(StrSize(ManFileName
) + 4*sizeof(CHAR16
));
127 if (Buffer
!= NULL
) {
129 (StrSize(ManFileName
) + 4*sizeof(CHAR16
))/sizeof(CHAR16
),
134 (StrSize(ManFileName
) + 4*sizeof(CHAR16
))/sizeof(CHAR16
),
144 Search the path environment variable for possible locations and test for
145 which one contains a man file with the name specified. If a valid file is found
146 stop searching and return the (opened) SHELL_FILE_HANDLE for that file.
148 @param[in] FileName Name of the file to find and open.
149 @param[out] Handle Pointer to the handle of the found file. The
150 value of this is undefined for return values
153 @retval EFI_SUCCESS The file was found. Handle is a valid SHELL_FILE_HANDLE
154 @retval EFI_INVALID_PARAMETER A parameter had an invalid value.
155 @retval EFI_NOT_FOUND The file was not found.
159 IN CONST CHAR16
*FileName
,
160 OUT SHELL_FILE_HANDLE
*Handle
163 CHAR16
*FullFileName
;
166 if ( FileName
== NULL
168 || StrLen(FileName
) == 0
170 return (EFI_INVALID_PARAMETER
);
173 FullFileName
= ShellFindFilePath(FileName
);
174 if (FullFileName
== NULL
) {
175 return (EFI_NOT_FOUND
);
179 // now open that file
181 Status
= EfiShellOpenFileByName(FullFileName
, Handle
, EFI_FILE_MODE_READ
);
182 FreePool(FullFileName
);
188 parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
189 detailed help for any sub section specified in the comma seperated list of
190 sections provided. If the end of the file or a .TH section is found then
193 Upon a sucessful return the caller is responsible to free the memory in *HelpText
195 @param[in] Handle FileHandle to read from
196 @param[in] Sections name of command's sub sections to find
197 @param[out] HelpText pointer to pointer to string where text goes.
198 @param[out] HelpSize pointer to size of allocated HelpText (may be updated)
199 @param[in] Ascii TRUE if the file is ASCII, FALSE otherwise.
201 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
202 @retval EFI_SUCCESS the section was found and its description sotred in
207 IN SHELL_FILE_HANDLE Handle
,
208 IN CONST CHAR16
*Sections
,
209 OUT CHAR16
**HelpText
,
217 BOOLEAN CurrentlyReading
;
226 return (EFI_INVALID_PARAMETER
);
229 Status
= EFI_SUCCESS
;
230 CurrentlyReading
= FALSE
;
234 ReadLine
= AllocateZeroPool(Size
);
235 if (ReadLine
== NULL
) {
236 return (EFI_OUT_OF_RESOURCES
);
239 for (;!ShellFileHandleEof(Handle
);Size
= 1024) {
240 Status
= ShellFileHandleReadLine(Handle
, ReadLine
, &Size
, TRUE
, &Ascii
);
241 if (ReadLine
[0] == L
'#') {
243 // Skip comment lines
248 // ignore too small of buffer...
250 if (Status
== EFI_BUFFER_TOO_SMALL
) {
251 Status
= EFI_SUCCESS
;
253 if (EFI_ERROR(Status
)) {
255 } else if (StrnCmp(ReadLine
, L
".TH", 3) == 0) {
257 // we hit the end of this commands section so stop.
260 } else if (StrnCmp(ReadLine
, L
".SH", 3) == 0) {
261 if (Sections
== NULL
) {
262 CurrentlyReading
= TRUE
;
266 // we found a section
268 if (CurrentlyReading
) {
269 CurrentlyReading
= FALSE
;
272 // is this a section we want to read in?
274 for ( SectionName
= ReadLine
+ 3
275 ; *SectionName
== L
' '
277 SectionLen
= StrLen(SectionName
);
278 SectionName
= StrStr(Sections
, SectionName
);
279 if (SectionName
== NULL
) {
282 if (*(SectionName
+ SectionLen
) == CHAR_NULL
|| *(SectionName
+ SectionLen
) == L
',') {
283 CurrentlyReading
= TRUE
;
285 } else if (CurrentlyReading
) {
288 // copy and save the current line.
290 ASSERT((*HelpText
== NULL
&& *HelpSize
== 0) || (*HelpText
!= NULL
));
291 StrnCatGrow (HelpText
, HelpSize
, ReadLine
, 0);
292 StrnCatGrow (HelpText
, HelpSize
, L
"\r\n", 0);
296 if (!Found
&& !EFI_ERROR(Status
)) {
297 return (EFI_NOT_FOUND
);
303 Parses a line from a MAN file to see if it is the Title Header. If it is, then
304 if the "Brief Description" is desired, allocate a buffer for it and return a
305 copy. Upon a sucessful return the caller is responsible to free the memory in
308 Uses a simple state machine that allows "unlimited" whitespace before and after the
309 ".TH", compares Command and the MAN file commnd name without respect to case, and
310 allows "unlimited" whitespace and '0' and '1' characters before the Short Description.
311 The PCRE regex describing this functionality is: ^\s*\.TH\s+(\S)\s[\s01]*(.*)$
312 where group 1 is the Command Name and group 2 is the Short Description.
314 @param[in] Command name of command whose MAN file we think Line came from
315 @param[in] Line Pointer to a line from the MAN file
316 @param[out] BriefDesc pointer to pointer to string where description goes.
317 @param[out] BriefSize pointer to size of allocated BriefDesc
318 @param[out] Found TRUE if the Title Header was found and it belongs to Command
320 @retval TRUE Line contained the Title Header
321 @retval FALSE Line did not contain the Title Header
325 IN CONST CHAR16
*Command
,
327 OUT CHAR16
**BriefDesc OPTIONAL
,
328 OUT UINTN
*BriefSize OPTIONAL
,
332 // The states of a simple state machine used to recognize a title header line
333 // and to extract the Short Description, if desired.
335 LookForThMacro
, LookForCommandName
, CompareCommands
, GetBriefDescription
, Final
339 UINTN CommandIndex
; // Indexes Command as we compare its chars to the MAN file.
340 BOOLEAN ReturnValue
; // TRUE if this the Title Header line of *some* MAN file.
341 BOOLEAN ReturnFound
; // TRUE if this the Title Header line of *the desired* MAN file.
346 State
= LookForThMacro
;
350 if (*Line
== L
'\0') {
356 // Handle "^\s*.TH\s"
357 // Go to state LookForCommandName if the title header macro is present; otherwise,
358 // eat white space. If we see something other than white space, this is not a
359 // title header line.
361 if (StrnCmp (L
".TH ", Line
, 4) == 0 || StrnCmp (L
".TH\t", Line
, 4) == 0) {
363 State
= LookForCommandName
;
365 else if (*Line
== L
' ' || *Line
== L
'\t') {
374 // Eat any "extra" whitespace after the title header macro (we have already seen
375 // at least one white space character). Go to state CompareCommands when a
376 // non-white space is seen.
377 case LookForCommandName
:
378 if (*Line
== L
' ' || *Line
== L
'\t') {
382 ReturnValue
= TRUE
; // This is *some* command's title header line.
383 State
= CompareCommands
;
384 // Do not increment Line; it points to the first character of the command
385 // name on the title header line.
390 // Compare Command to the title header command name, ignoring case. When we
391 // reach the end of the command (i.e. we see white space), the next state
392 // depends on whether the caller wants a copy of the Brief Description.
393 case CompareCommands
:
394 if (*Line
== L
' ' || *Line
== L
'\t') {
395 ReturnFound
= TRUE
; // This is the desired command's title header line.
396 State
= (BriefDesc
== NULL
) ? Final
: GetBriefDescription
;
398 else if (CharToUpper (*Line
) != CharToUpper (*(Command
+ CommandIndex
++))) {
404 // Handle "[\s01]*(.*)$"
405 // Skip whitespace, '0', and '1' characters, if any, prior to the brief description.
406 // Return the description to the caller.
407 case GetBriefDescription
:
408 if (*Line
!= L
' ' && *Line
!= L
'\t' && *Line
!= L
'0' && *Line
!= L
'1') {
409 *BriefSize
= StrSize(Line
);
410 *BriefDesc
= AllocateZeroPool(*BriefSize
);
411 if (*BriefDesc
!= NULL
) {
412 StrCpyS(*BriefDesc
, (*BriefSize
)/sizeof(CHAR16
), Line
);
423 } while (State
< Final
);
425 *Found
= ReturnFound
;
430 parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
431 "Brief Description" for the .TH section as specified by Command. If the
432 command section is not found return EFI_NOT_FOUND.
434 Upon a sucessful return the caller is responsible to free the memory in *BriefDesc
436 @param[in] Handle FileHandle to read from
437 @param[in] Command name of command's section to find as entered on the
438 command line (may be a relative or absolute path or
439 be in any case: upper, lower, or mixed in numerous ways!).
440 @param[out] BriefDesc pointer to pointer to string where description goes.
441 @param[out] BriefSize pointer to size of allocated BriefDesc
442 @param[in, out] Ascii TRUE if the file is ASCII, FALSE otherwise, will be
443 set if the file handle is at the 0 position.
445 @retval EFI_OUT_OF_RESOURCES a memory allocation failed.
446 @retval EFI_SUCCESS the section was found and its description stored in
447 an allocated buffer if requested.
450 ManFileFindTitleSection(
451 IN SHELL_FILE_HANDLE Handle
,
452 IN CONST CHAR16
*Command
,
453 OUT CHAR16
**BriefDesc OPTIONAL
,
454 OUT UINTN
*BriefSize OPTIONAL
,
455 IN OUT BOOLEAN
*Ascii
466 || (BriefDesc
!= NULL
&& BriefSize
== NULL
)
468 return (EFI_INVALID_PARAMETER
);
471 Status
= EFI_SUCCESS
;
475 ReadLine
= AllocateZeroPool(Size
);
476 if (ReadLine
== NULL
) {
477 return (EFI_OUT_OF_RESOURCES
);
481 // Do not pass any leading path information that may be present to IsTitleHeader().
483 Start
= StrLen(Command
);
485 && (*(Command
+ Start
- 1) != L
'\\')
486 && (*(Command
+ Start
- 1) != L
'/')
487 && (*(Command
+ Start
- 1) != L
':')) {
491 for (;!ShellFileHandleEof(Handle
);Size
= 1024) {
492 Status
= ShellFileHandleReadLine(Handle
, ReadLine
, &Size
, TRUE
, Ascii
);
494 // ignore too small of buffer...
496 if (EFI_ERROR(Status
) && Status
!= EFI_BUFFER_TOO_SMALL
) {
500 Status
= EFI_NOT_FOUND
;
501 if (IsTitleHeader (Command
+Start
, ReadLine
, BriefDesc
, BriefSize
, &Found
)) {
502 Status
= Found
? EFI_SUCCESS
: EFI_NOT_FOUND
;
512 This function returns the help information for the specified command. The help text
513 will be parsed from a UEFI Shell manual page. (see UEFI Shell 2.0 Appendix B)
515 If Sections is specified, then each section name listed will be compared in a casesensitive
516 manner, to the section names described in Appendix B. If the section exists,
517 it will be appended to the returned help text. If the section does not exist, no
518 information will be returned. If Sections is NULL, then all help text information
519 available will be returned.
521 if BriefDesc is NULL, then the breif description will not be savedd seperatly,
522 but placed first in the main HelpText.
524 @param[in] ManFileName Points to the NULL-terminated UEFI Shell MAN file name.
525 @param[in] Command Points to the NULL-terminated UEFI Shell command name.
526 @param[in] Sections Points to the NULL-terminated comma-delimited
527 section names to return. If NULL, then all
528 sections will be returned.
529 @param[out] BriefDesc On return, points to a callee-allocated buffer
530 containing brief description text.
531 @param[out] HelpText On return, points to a callee-allocated buffer
532 containing all specified help text.
534 @retval EFI_SUCCESS The help text was returned.
535 @retval EFI_OUT_OF_RESOURCES The necessary buffer could not be allocated to hold the
537 @retval EFI_INVALID_PARAMETER HelpText is NULL.
538 @retval EFI_INVALID_PARAMETER ManFileName is invalid.
539 @retval EFI_NOT_FOUND There is no help text available for Command.
543 IN CONST CHAR16
*ManFileName
,
544 IN CONST CHAR16
*Command
,
545 IN CONST CHAR16
*Sections OPTIONAL
,
546 OUT CHAR16
**BriefDesc OPTIONAL
,
547 OUT CHAR16
**HelpText
551 SHELL_FILE_HANDLE FileHandle
;
552 EFI_HANDLE CmdFileImgHandle
;
556 UINTN StringIdWalker
;
559 CHAR16
*CmdFilePathName
;
560 EFI_DEVICE_PATH_PROTOCOL
*FileDevPath
;
561 EFI_DEVICE_PATH_PROTOCOL
*DevPath
;
562 EFI_HII_PACKAGE_LIST_HEADER
*PackageListHeader
;
564 if ( ManFileName
== NULL
568 return (EFI_INVALID_PARAMETER
);
577 CmdFilePathName
= NULL
;
578 CmdFileImgHandle
= NULL
;
579 PackageListHeader
= NULL
;
584 // See if it's in HII first
586 TempString
= ShellCommandGetCommandHelp(Command
);
587 if (TempString
!= NULL
) {
588 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
589 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
590 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
591 ShellSetFilePosition (FileHandle
, 0);
594 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
595 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
596 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
598 ShellCloseFile (&FileHandle
);
601 // If the image is a external app, check .MAN file first.
604 TempString
= GetManFileName(ManFileName
);
605 if (TempString
== NULL
) {
606 return (EFI_INVALID_PARAMETER
);
609 Status
= SearchPathForFile(TempString
, &FileHandle
);
610 if (EFI_ERROR(Status
)) {
611 FileDevPath
= FileDevicePath(NULL
, TempString
);
612 DevPath
= AppendDevicePath (ShellInfoObject
.ImageDevPath
, FileDevPath
);
613 Status
= InternalOpenFileDevicePath(DevPath
, &FileHandle
, EFI_FILE_MODE_READ
, 0);
614 SHELL_FREE_NON_NULL(FileDevPath
);
615 SHELL_FREE_NON_NULL(DevPath
);
618 if (!EFI_ERROR(Status
)) {
621 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
622 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
623 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
625 ShellInfoObject
.NewEfiShellProtocol
->CloseFile(FileHandle
);
626 if (!EFI_ERROR(Status
)) {
628 // Get help text from .MAN file success.
635 // Load the app image to check EFI_HII_PACKAGE_LIST_PROTOCOL.
637 CmdFileName
= GetExecuatableFileName(TempString
);
638 if (CmdFileName
== NULL
) {
639 Status
= EFI_OUT_OF_RESOURCES
;
643 // If the file in CWD then use the file name, else use the full
646 CmdFilePathName
= ShellFindFilePath(CmdFileName
);
647 if (CmdFilePathName
== NULL
) {
648 Status
= EFI_NOT_FOUND
;
651 DevPath
= ShellInfoObject
.NewEfiShellProtocol
->GetDevicePathFromFilePath(CmdFilePathName
);
652 Status
= gBS
->LoadImage(FALSE
, gImageHandle
, DevPath
, NULL
, 0, &CmdFileImgHandle
);
653 if(EFI_ERROR(Status
)) {
657 Status
= gBS
->OpenProtocol(
659 &gEfiHiiPackageListProtocolGuid
,
660 (VOID
**)&PackageListHeader
,
663 EFI_OPEN_PROTOCOL_GET_PROTOCOL
665 if(EFI_ERROR(Status
)) {
671 // If get package list on image handle, install it on HiiDatabase.
673 Status
= gBS
->InstallProtocolInterface (
674 &mShellManDriverHandle
,
675 &gEfiDevicePathProtocolGuid
,
676 EFI_NATIVE_INTERFACE
,
677 &mShellManHiiDevicePath
679 if (EFI_ERROR(Status
)) {
683 Status
= gHiiDatabase
->NewPackageList (
686 mShellManDriverHandle
,
689 if (EFI_ERROR (Status
)) {
695 SHELL_FREE_NON_NULL(TempString
);
696 if (BriefDesc
!= NULL
) {
697 SHELL_FREE_NON_NULL(*BriefDesc
);
699 TempString
= HiiGetString (mShellManHiiHandle
, (EFI_STRING_ID
)StringIdWalker
, NULL
);
700 if (TempString
== NULL
) {
701 Status
= EFI_NOT_FOUND
;
704 FileHandle
= ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (TRUE
), NULL
);
705 HelpSize
= StrLen (TempString
) * sizeof (CHAR16
);
706 ShellWriteFile (FileHandle
, &HelpSize
, TempString
);
707 ShellSetFilePosition (FileHandle
, 0);
710 Status
= ManFileFindTitleSection(FileHandle
, Command
, BriefDesc
, &BriefSize
, &Ascii
);
711 if (!EFI_ERROR(Status
) && HelpText
!= NULL
){
712 Status
= ManFileFindSections(FileHandle
, Sections
, HelpText
, &HelpSize
, Ascii
);
714 ShellCloseFile (&FileHandle
);
715 if (!EFI_ERROR(Status
)){
717 // Found what we need and return
723 } while (StringIdWalker
< 0xFFFF && TempString
!= NULL
);
728 if (mShellManDriverHandle
!= NULL
) {
729 gBS
->UninstallProtocolInterface (
730 mShellManDriverHandle
,
731 &gEfiDevicePathProtocolGuid
,
732 &mShellManHiiDevicePath
734 mShellManDriverHandle
= NULL
;
737 if (mShellManHiiHandle
!= NULL
) {
738 HiiRemovePackages (mShellManHiiHandle
);
739 mShellManHiiHandle
= NULL
;
742 if (CmdFileImgHandle
!= NULL
) {
743 Status
= gBS
->UnloadImage (CmdFileImgHandle
);
746 SHELL_FREE_NON_NULL(TempString
);
747 SHELL_FREE_NON_NULL(CmdFileName
);
748 SHELL_FREE_NON_NULL(CmdFilePathName
);
749 SHELL_FREE_NON_NULL(FileDevPath
);
750 SHELL_FREE_NON_NULL(DevPath
);