1 /** @file
2   Provides interface to shell MAN file parser.
3 
4   Copyright (c) 2009 - 2016, 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
10 
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.
13 
14 **/
15 
16 #include "Shell.h"
17 
18 #define SHELL_MAN_HII_GUID \
19 { \
20   0xf62ccd0c, 0x2449, 0x453c, { 0x8a, 0xcb, 0x8c, 0xc5, 0x7c, 0xf0, 0x2a, 0x97 } \
21 }
22 
23 EFI_HII_HANDLE  mShellManHiiHandle    = NULL;
24 EFI_HANDLE      mShellManDriverHandle = NULL;
25 
26 
27 SHELL_MAN_HII_VENDOR_DEVICE_PATH  mShellManHiiDevicePath = {
28   {
29     {
30       HARDWARE_DEVICE_PATH,
31       HW_VENDOR_DP,
32       {
33         (UINT8) (sizeof (VENDOR_DEVICE_PATH)),
34         (UINT8) ((sizeof (VENDOR_DEVICE_PATH)) >> 8)
35       }
36     },
37     SHELL_MAN_HII_GUID
38   },
39   {
40     END_DEVICE_PATH_TYPE,
41     END_ENTIRE_DEVICE_PATH_SUBTYPE,
42     {
43       (UINT8) (END_DEVICE_PATH_LENGTH),
44       (UINT8) ((END_DEVICE_PATH_LENGTH) >> 8)
45     }
46   }
47 };
48 
49 
50 /**
51   Convert a Unicode character to upper case only if
52   it maps to a valid small-case ASCII character.
53 
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
57   is returned directly.
58 
59   @param  Char  The character to convert.
60 
61   @retval LowerCharacter   If the Char is with range L'a' to L'z'.
62   @retval Unchanged        Otherwise.
63 
64 **/
65 CHAR16
66 InternalShellCharToUpper (
67   IN CHAR16  Char
68   );
69 
70 /**
71   Verifies that the filename has .EFI on the end.
72 
73   allocates a new buffer and copies the name (appending .EFI if necessary).
74   Caller to free the buffer.
75 
76   @param[in] NameString            original name string
77 
78   @return                          the new filename with .efi as the extension.
79 **/
80 CHAR16 *
GetExecuatableFileName(IN CONST CHAR16 * NameString)81 GetExecuatableFileName (
82   IN CONST CHAR16    *NameString
83   )
84 {
85   CHAR16  *Buffer;
86   CHAR16  *SuffixStr;
87   if (NameString == NULL) {
88     return (NULL);
89   }
90 
91   //
92   // Fix the file name
93   //
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);
98     if (Buffer != NULL) {
99       SuffixStr = Buffer+StrLen(Buffer)-StrLen(L".man");
100       StrnCpyS (SuffixStr, StrSize(L".man")/sizeof(CHAR16), L".efi", StrLen(L".efi"));
101     }
102   } else {
103     Buffer = AllocateZeroPool(StrSize(NameString) + StrLen(L".efi")*sizeof(CHAR16));
104     if (Buffer != NULL) {
105       StrnCpyS( Buffer,
106                 (StrSize(NameString) + StrLen(L".efi")*sizeof(CHAR16))/sizeof(CHAR16),
107                 NameString,
108                 StrLen(NameString)
109                 );
110       StrnCatS( Buffer,
111                 (StrSize(NameString) + StrLen(L".efi")*sizeof(CHAR16))/sizeof(CHAR16),
112                 L".efi",
113                 StrLen(L".efi")
114                 );
115     }
116   }
117   return (Buffer);
118 
119 }
120 
121 /**
122   Verifies that the filename has .MAN on the end.
123 
124   allocates a new buffer and copies the name (appending .MAN if necessary)
125 
126   ASSERT if ManFileName is NULL
127 
128   @param[in] ManFileName            original filename
129 
130   @return the new filename with .man as the extension.
131 **/
132 CHAR16 *
GetManFileName(IN CONST CHAR16 * ManFileName)133 GetManFileName(
134   IN CONST CHAR16 *ManFileName
135   )
136 {
137   CHAR16 *Buffer;
138   if (ManFileName == NULL) {
139     return (NULL);
140   }
141   //
142   // Fix the file name
143   //
144   if (StrnCmp(ManFileName+StrLen(ManFileName)-4, L".man", 4)==0) {
145     Buffer = AllocateCopyPool(StrSize(ManFileName), ManFileName);
146   } else {
147     Buffer = AllocateZeroPool(StrSize(ManFileName) + 4*sizeof(CHAR16));
148     if (Buffer != NULL) {
149       StrnCpyS( Buffer,
150                 (StrSize(ManFileName) + 4*sizeof(CHAR16))/sizeof(CHAR16),
151                 ManFileName,
152                 StrLen(ManFileName)
153                 );
154       StrnCatS( Buffer,
155                 (StrSize(ManFileName) + 4*sizeof(CHAR16))/sizeof(CHAR16),
156                 L".man",
157                 4
158                 );
159     }
160   }
161   return (Buffer);
162 }
163 
164 /**
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.
168 
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
172                                 except EFI_SUCCESS.
173 
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.
177 **/
178 EFI_STATUS
SearchPathForFile(IN CONST CHAR16 * FileName,OUT SHELL_FILE_HANDLE * Handle)179 SearchPathForFile(
180   IN CONST CHAR16             *FileName,
181   OUT SHELL_FILE_HANDLE       *Handle
182   )
183 {
184   CHAR16          *FullFileName;
185   EFI_STATUS      Status;
186 
187   if ( FileName     == NULL
188     || Handle       == NULL
189     || StrLen(FileName) == 0
190    ){
191     return (EFI_INVALID_PARAMETER);
192   }
193 
194   FullFileName = ShellFindFilePath(FileName);
195   if (FullFileName == NULL) {
196     return (EFI_NOT_FOUND);
197   }
198 
199   //
200   // now open that file
201   //
202   Status = EfiShellOpenFileByName(FullFileName, Handle, EFI_FILE_MODE_READ);
203   FreePool(FullFileName);
204 
205   return (Status);
206 }
207 
208 /**
209   parses through Buffer (which is MAN file formatted) 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
212   return.
213 
214   Upon a sucessful return the caller is responsible to free the memory in *HelpText
215 
216   @param[in] Buffer             Buffer to read from
217   @param[in] Sections           name of command's sub sections to find
218   @param[in] HelpText           pointer to pointer to string where text goes.
219   @param[in] HelpSize           pointer to size of allocated HelpText (may be updated)
220 
221   @retval EFI_OUT_OF_RESOURCES  a memory allocation failed.
222   @retval EFI_SUCCESS           the section was found and its description sotred in
223                                 an alloceted buffer.
224 **/
225 EFI_STATUS
ManBufferFindSections(IN CONST CHAR16 * Buffer,IN CONST CHAR16 * Sections,IN CHAR16 ** HelpText,IN UINTN * HelpSize)226 ManBufferFindSections(
227   IN CONST CHAR16 *Buffer,
228   IN CONST CHAR16 *Sections,
229   IN CHAR16       **HelpText,
230   IN UINTN        *HelpSize
231   )
232 {
233   EFI_STATUS          Status;
234   CONST CHAR16        *CurrentLocation;
235   BOOLEAN             CurrentlyReading;
236   CHAR16              *SectionName;
237   UINTN               SectionLen;
238   BOOLEAN             Found;
239   CHAR16              *TempString;
240   CHAR16              *TempString2;
241 
242   if ( Buffer     == NULL
243     || HelpText   == NULL
244     || HelpSize   == NULL
245    ){
246     return (EFI_INVALID_PARAMETER);
247   }
248 
249   Status            = EFI_SUCCESS;
250   CurrentlyReading  = FALSE;
251   Found             = FALSE;
252 
253   for (CurrentLocation = Buffer,TempString = NULL
254     ;  CurrentLocation != NULL && *CurrentLocation != CHAR_NULL
255     ;  CurrentLocation=StrStr(CurrentLocation, L"\r\n"),TempString = NULL
256    ){
257     while(CurrentLocation[0] == L'\r' || CurrentLocation[0] == L'\n') {
258       CurrentLocation++;
259     }
260     if (CurrentLocation[0] == L'#') {
261       //
262       // Skip comment lines
263       //
264       continue;
265     }
266     if (StrnCmp(CurrentLocation, L".TH", 3) == 0) {
267       //
268       // we hit the end of this commands section so stop.
269       //
270       break;
271     }
272     if (StrnCmp(CurrentLocation, L".SH ", 4) == 0) {
273       if (Sections == NULL) {
274         CurrentlyReading = TRUE;
275         continue;
276       } else if (CurrentlyReading) {
277         CurrentlyReading = FALSE;
278       }
279       CurrentLocation += 4;
280       //
281       // is this a section we want to read in?
282       //
283       if (StrLen(CurrentLocation)!=0) {
284         TempString2 = StrStr(CurrentLocation, L" ");
285         TempString2 = MIN(TempString2, StrStr(CurrentLocation, L"\r"));
286         TempString2 = MIN(TempString2, StrStr(CurrentLocation, L"\n"));
287         ASSERT(TempString == NULL);
288         TempString = StrnCatGrow(&TempString, NULL, CurrentLocation, TempString2==NULL?0:TempString2 - CurrentLocation);
289         if (TempString == NULL) {
290           Status = EFI_OUT_OF_RESOURCES;
291           break;
292         }
293         SectionName = TempString;
294         SectionLen = StrLen(SectionName);
295         SectionName = StrStr(Sections, SectionName);
296         if (SectionName == NULL) {
297           SHELL_FREE_NON_NULL(TempString);
298           continue;
299         }
300         if (*(SectionName + SectionLen) == CHAR_NULL || *(SectionName + SectionLen) == L',') {
301           CurrentlyReading = TRUE;
302         }
303       }
304     } else if (CurrentlyReading) {
305       Found = TRUE;
306       if (StrLen(CurrentLocation)!=0) {
307         TempString2 = StrStr(CurrentLocation, L"\r");
308         TempString2 = MIN(TempString2, StrStr(CurrentLocation, L"\n"));
309         ASSERT(TempString == NULL);
310         TempString = StrnCatGrow(&TempString, NULL, CurrentLocation, TempString2==NULL?0:TempString2 - CurrentLocation);
311         if (TempString == NULL) {
312           Status = EFI_OUT_OF_RESOURCES;
313           break;
314         }
315         //
316         // copy and save the current line.
317         //
318         ASSERT((*HelpText == NULL && *HelpSize == 0) || (*HelpText != NULL));
319         StrnCatGrow (HelpText, HelpSize, TempString, 0);
320         if (HelpText == NULL) {
321           Status = EFI_OUT_OF_RESOURCES;
322           break;
323         }
324         StrnCatGrow (HelpText, HelpSize, L"\r\n", 0);
325         if (HelpText == NULL) {
326           Status = EFI_OUT_OF_RESOURCES;
327           break;
328         }
329       }
330     }
331     SHELL_FREE_NON_NULL(TempString);
332   }
333   SHELL_FREE_NON_NULL(TempString);
334   if (!Found && !EFI_ERROR(Status)) {
335     return (EFI_NOT_FOUND);
336   }
337   return (Status);
338 }
339 
340 /**
341   parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
342   detailed help for any sub section specified in the comma seperated list of
343   sections provided.  If the end of the file or a .TH section is found then
344   return.
345 
346   Upon a sucessful return the caller is responsible to free the memory in *HelpText
347 
348   @param[in] Handle             FileHandle to read from
349   @param[in] Sections           name of command's sub sections to find
350   @param[out] HelpText          pointer to pointer to string where text goes.
351   @param[out] HelpSize          pointer to size of allocated HelpText (may be updated)
352   @param[in] Ascii              TRUE if the file is ASCII, FALSE otherwise.
353 
354   @retval EFI_OUT_OF_RESOURCES  a memory allocation failed.
355   @retval EFI_SUCCESS           the section was found and its description sotred in
356                                 an alloceted buffer.
357 **/
358 EFI_STATUS
ManFileFindSections(IN SHELL_FILE_HANDLE Handle,IN CONST CHAR16 * Sections,OUT CHAR16 ** HelpText,OUT UINTN * HelpSize,IN BOOLEAN Ascii)359 ManFileFindSections(
360   IN SHELL_FILE_HANDLE  Handle,
361   IN CONST CHAR16       *Sections,
362   OUT CHAR16            **HelpText,
363   OUT UINTN             *HelpSize,
364   IN BOOLEAN            Ascii
365   )
366 {
367   EFI_STATUS          Status;
368   CHAR16              *ReadLine;
369   UINTN               Size;
370   BOOLEAN             CurrentlyReading;
371   CHAR16              *SectionName;
372   UINTN               SectionLen;
373   BOOLEAN             Found;
374 
375   if ( Handle     == NULL
376     || HelpText   == NULL
377     || HelpSize   == NULL
378    ){
379     return (EFI_INVALID_PARAMETER);
380   }
381 
382   Status            = EFI_SUCCESS;
383   CurrentlyReading  = FALSE;
384   Size              = 1024;
385   Found             = FALSE;
386 
387   ReadLine          = AllocateZeroPool(Size);
388   if (ReadLine == NULL) {
389     return (EFI_OUT_OF_RESOURCES);
390   }
391 
392   for (;!ShellFileHandleEof(Handle);Size = 1024) {
393     Status = ShellFileHandleReadLine(Handle, ReadLine, &Size, TRUE, &Ascii);
394     if (ReadLine[0] == L'#') {
395       //
396       // Skip comment lines
397       //
398       continue;
399     }
400     //
401     // ignore too small of buffer...
402     //
403     if (Status == EFI_BUFFER_TOO_SMALL) {
404       Status = EFI_SUCCESS;
405     }
406     if (EFI_ERROR(Status)) {
407       break;
408     } else if (StrnCmp(ReadLine, L".TH", 3) == 0) {
409       //
410       // we hit the end of this commands section so stop.
411       //
412       break;
413     } else if (StrnCmp(ReadLine, L".SH", 3) == 0) {
414       if (Sections == NULL) {
415         CurrentlyReading = TRUE;
416         continue;
417       }
418       //
419       // we found a section
420       //
421       if (CurrentlyReading) {
422         CurrentlyReading = FALSE;
423       }
424       //
425       // is this a section we want to read in?
426       //
427       for ( SectionName = ReadLine + 3
428           ; *SectionName == L' '
429           ; SectionName++);
430       SectionLen = StrLen(SectionName);
431       SectionName = StrStr(Sections, SectionName);
432       if (SectionName == NULL) {
433         continue;
434       }
435       if (*(SectionName + SectionLen) == CHAR_NULL || *(SectionName + SectionLen) == L',') {
436         CurrentlyReading = TRUE;
437       }
438     } else if (CurrentlyReading) {
439       Found = TRUE;
440       //
441       // copy and save the current line.
442       //
443       ASSERT((*HelpText == NULL && *HelpSize == 0) || (*HelpText != NULL));
444       StrnCatGrow (HelpText, HelpSize, ReadLine, 0);
445       StrnCatGrow (HelpText, HelpSize, L"\r\n", 0);
446     }
447   }
448   FreePool(ReadLine);
449   if (!Found && !EFI_ERROR(Status)) {
450     return (EFI_NOT_FOUND);
451   }
452   return (Status);
453 }
454 
455 /**
456   parses through the MAN file formatted Buffer and returns the
457   "Brief Description" for the .TH section as specified by Command.  If the
458   command section is not found return EFI_NOT_FOUND.
459 
460   Upon a sucessful return the caller is responsible to free the memory in *BriefDesc
461 
462   @param[in] Buffer             Buffer to read from
463   @param[in] Command            name of command's section to find
464   @param[in] BriefDesc          pointer to pointer to string where description goes.
465   @param[in] BriefSize          pointer to size of allocated BriefDesc
466 
467   @retval EFI_OUT_OF_RESOURCES  a memory allocation failed.
468   @retval EFI_SUCCESS           the section was found and its description sotred in
469                                 an alloceted buffer.
470 **/
471 EFI_STATUS
ManBufferFindTitleSection(IN CHAR16 ** Buffer,IN CONST CHAR16 * Command,IN CHAR16 ** BriefDesc,IN UINTN * BriefSize)472 ManBufferFindTitleSection(
473   IN CHAR16         **Buffer,
474   IN CONST CHAR16   *Command,
475   IN CHAR16         **BriefDesc,
476   IN UINTN          *BriefSize
477   )
478 {
479   EFI_STATUS    Status;
480   CHAR16        *TitleString;
481   CHAR16        *TitleEnd;
482   CHAR16        *CurrentLocation;
483   UINTN         TitleLength;
484   UINTN         Start;
485   CONST CHAR16  StartString[] = L".TH ";
486   CONST CHAR16  EndString[]   = L" 0 ";
487 
488   if ( Buffer     == NULL
489     || Command    == NULL
490     || (BriefDesc != NULL && BriefSize == NULL)
491    ){
492     return (EFI_INVALID_PARAMETER);
493   }
494 
495   Status    = EFI_SUCCESS;
496 
497   //
498   // Do not pass any leading path information that may be present to IsTitleHeader().
499   //
500   Start = StrLen(Command);
501   while ((Start != 0)
502          && (*(Command + Start - 1) != L'\\')
503          && (*(Command + Start - 1) != L'/')
504          && (*(Command + Start - 1) != L':')) {
505     --Start;
506   }
507 
508   //
509   // more characters for StartString and EndString
510   //
511   TitleLength = StrSize(Command + Start) + (StrLen(StartString) + StrLen(EndString)) * sizeof(CHAR16);
512   TitleString = AllocateZeroPool(TitleLength);
513   if (TitleString == NULL) {
514     return (EFI_OUT_OF_RESOURCES);
515   }
516   StrCpyS(TitleString, TitleLength/sizeof(CHAR16), StartString);
517   StrCatS(TitleString, TitleLength/sizeof(CHAR16), Command + Start);
518   StrCatS(TitleString, TitleLength/sizeof(CHAR16), EndString);
519 
520   CurrentLocation = StrStr(*Buffer, TitleString);
521   if (CurrentLocation == NULL){
522     Status = EFI_NOT_FOUND;
523   } else {
524     //
525     // we found it so copy out the rest of the line into BriefDesc
526     // After skipping any spaces or zeroes
527     //
528     for (CurrentLocation += StrLen(TitleString)
529       ;  *CurrentLocation == L' ' || *CurrentLocation == L'0' || *CurrentLocation == L'1' || *CurrentLocation == L'\"'
530       ;  CurrentLocation++);
531 
532     TitleEnd = StrStr(CurrentLocation, L"\"");
533     if (TitleEnd == NULL) {
534       Status = EFI_DEVICE_ERROR;
535     } else {
536       if (BriefDesc != NULL) {
537         *BriefSize = StrSize(TitleEnd);
538         *BriefDesc = AllocateZeroPool(*BriefSize);
539         if (*BriefDesc == NULL) {
540           Status = EFI_OUT_OF_RESOURCES;
541         } else {
542           StrnCpyS(*BriefDesc, (*BriefSize)/sizeof(CHAR16), CurrentLocation, TitleEnd-CurrentLocation);
543         }
544       }
545 
546       for (CurrentLocation = TitleEnd
547         ;  *CurrentLocation != L'\n'
548         ;  CurrentLocation++);
549       for (
550         ;  *CurrentLocation == L' ' || *CurrentLocation == L'\n' || *CurrentLocation == L'\r'
551         ;  CurrentLocation++);
552       *Buffer = CurrentLocation;
553     }
554   }
555 
556   FreePool(TitleString);
557   return (Status);
558 }
559 
560 /**
561   Parses a line from a MAN file to see if it is the Title Header. If it is, then
562   if the "Brief Description" is desired, allocate a buffer for it and return a
563   copy. Upon a sucessful return the caller is responsible to free the memory in
564   *BriefDesc
565 
566   Uses a simple state machine that allows "unlimited" whitespace before and after the
567   ".TH", compares Command and the MAN file commnd name without respect to case, and
568   allows "unlimited" whitespace and '0' and '1' characters before the Short Description.
569   The PCRE regex describing this functionality is: ^\s*\.TH\s+(\S)\s[\s01]*(.*)$
570   where group 1 is the Command Name and group 2 is the Short Description.
571 
572   @param[in] Command             name of command whose MAN file we think Line came from
573   @param[in] Line                Pointer to a line from the MAN file
574   @param[out] BriefDesc          pointer to pointer to string where description goes.
575   @param[out] BriefSize          pointer to size of allocated BriefDesc
576   @param[out] Found              TRUE if the Title Header was found and it belongs to Command
577 
578   @retval TRUE   Line contained the Title Header
579   @retval FALSE  Line did not contain the Title Header
580 **/
581 BOOLEAN
IsTitleHeader(IN CONST CHAR16 * Command,IN CHAR16 * Line,OUT CHAR16 ** BriefDesc OPTIONAL,OUT UINTN * BriefSize OPTIONAL,OUT BOOLEAN * Found)582 IsTitleHeader(
583   IN CONST CHAR16       *Command,
584   IN CHAR16             *Line,
585   OUT CHAR16            **BriefDesc OPTIONAL,
586   OUT UINTN             *BriefSize OPTIONAL,
587   OUT BOOLEAN           *Found
588   )
589 {
590   // The states of a simple state machine used to recognize a title header line
591   // and to extract the Short Description, if desired.
592   typedef enum {
593     LookForThMacro, LookForCommandName, CompareCommands, GetBriefDescription, Final
594   } STATEVALUES;
595 
596   STATEVALUES  State;
597   UINTN        CommandIndex; // Indexes Command as we compare its chars to the MAN file.
598   BOOLEAN      ReturnValue;  // TRUE if this the Title Header line of *some* MAN file.
599   BOOLEAN      ReturnFound;  // TRUE if this the Title Header line of *the desired* MAN file.
600 
601   ReturnValue = FALSE;
602   ReturnFound = FALSE;
603   CommandIndex = 0;
604   State = LookForThMacro;
605 
606   do {
607 
608     if (*Line == L'\0') {
609       break;
610     }
611 
612     switch (State) {
613 
614       // Handle "^\s*.TH\s"
615       // Go to state LookForCommandName if the title header macro is present; otherwise,
616       // eat white space. If we see something other than white space, this is not a
617       // title header line.
618       case LookForThMacro:
619         if (StrnCmp (L".TH ", Line, 4) == 0 || StrnCmp (L".TH\t", Line, 4) == 0) {
620           Line += 4;
621           State = LookForCommandName;
622         }
623         else if (*Line == L' ' || *Line == L'\t') {
624           Line++;
625         }
626         else {
627           State = Final;
628         }
629       break;
630 
631       // Handle "\s*"
632       // Eat any "extra" whitespace after the title header macro (we have already seen
633       // at least one white space character). Go to state CompareCommands when a
634       // non-white space is seen.
635       case LookForCommandName:
636         if (*Line == L' ' || *Line == L'\t') {
637           Line++;
638         }
639         else {
640           ReturnValue = TRUE;  // This is *some* command's title header line.
641           State = CompareCommands;
642           // Do not increment Line; it points to the first character of the command
643           // name on the title header line.
644         }
645       break;
646 
647       // Handle "(\S)\s"
648       // Compare Command to the title header command name, ignoring case. When we
649       // reach the end of the command (i.e. we see white space), the next state
650       // depends on whether the caller wants a copy of the Brief Description.
651       case CompareCommands:
652         if (*Line == L' ' || *Line == L'\t') {
653           ReturnFound = TRUE;  // This is the desired command's title header line.
654           State = (BriefDesc == NULL) ? Final : GetBriefDescription;
655         }
656         else if (InternalShellCharToUpper (*Line) != InternalShellCharToUpper (*(Command + CommandIndex++))) {
657           State = Final;
658         }
659         Line++;
660       break;
661 
662       // Handle "[\s01]*(.*)$"
663       // Skip whitespace, '0', and '1' characters, if any, prior to the brief description.
664       // Return the description to the caller.
665       case GetBriefDescription:
666         if (*Line != L' ' && *Line != L'\t' && *Line != L'0' && *Line != L'1') {
667           *BriefSize = StrSize(Line);
668           *BriefDesc = AllocateZeroPool(*BriefSize);
669           if (*BriefDesc != NULL) {
670             StrCpyS(*BriefDesc, (*BriefSize)/sizeof(CHAR16), Line);
671           }
672           State = Final;
673         }
674         Line++;
675       break;
676 
677       default:
678        break;
679     }
680 
681   } while (State < Final);
682 
683   *Found = ReturnFound;
684   return ReturnValue;
685 }
686 
687 /**
688   parses through the MAN file specified by SHELL_FILE_HANDLE and returns the
689   "Brief Description" for the .TH section as specified by Command.  If the
690   command section is not found return EFI_NOT_FOUND.
691 
692   Upon a sucessful return the caller is responsible to free the memory in *BriefDesc
693 
694   @param[in] Handle              FileHandle to read from
695   @param[in] Command             name of command's section to find as entered on the
696                                  command line (may be a relative or absolute path or
697                                  be in any case: upper, lower, or mixed in numerous ways!).
698   @param[out] BriefDesc          pointer to pointer to string where description goes.
699   @param[out] BriefSize          pointer to size of allocated BriefDesc
700   @param[in, out] Ascii          TRUE if the file is ASCII, FALSE otherwise, will be
701                                  set if the file handle is at the 0 position.
702 
703   @retval EFI_OUT_OF_RESOURCES  a memory allocation failed.
704   @retval EFI_SUCCESS           the section was found and its description stored in
705                                 an allocated buffer if requested.
706 **/
707 EFI_STATUS
ManFileFindTitleSection(IN SHELL_FILE_HANDLE Handle,IN CONST CHAR16 * Command,OUT CHAR16 ** BriefDesc OPTIONAL,OUT UINTN * BriefSize OPTIONAL,IN OUT BOOLEAN * Ascii)708 ManFileFindTitleSection(
709   IN SHELL_FILE_HANDLE  Handle,
710   IN CONST CHAR16       *Command,
711   OUT CHAR16            **BriefDesc OPTIONAL,
712   OUT UINTN             *BriefSize OPTIONAL,
713   IN OUT BOOLEAN        *Ascii
714   )
715 {
716   EFI_STATUS  Status;
717   CHAR16      *ReadLine;
718   UINTN       Size;
719   BOOLEAN     Found;
720   UINTN       Start;
721 
722   if ( Handle     == NULL
723     || Command    == NULL
724     || (BriefDesc != NULL && BriefSize == NULL)
725    ){
726     return (EFI_INVALID_PARAMETER);
727   }
728 
729   Status    = EFI_SUCCESS;
730   Size      = 1024;
731   Found     = FALSE;
732 
733   ReadLine  = AllocateZeroPool(Size);
734   if (ReadLine == NULL) {
735     return (EFI_OUT_OF_RESOURCES);
736   }
737 
738   //
739   // Do not pass any leading path information that may be present to IsTitleHeader().
740   //
741   Start = StrLen(Command);
742   while ((Start != 0)
743          && (*(Command + Start - 1) != L'\\')
744          && (*(Command + Start - 1) != L'/')
745          && (*(Command + Start - 1) != L':')) {
746     --Start;
747   }
748 
749   for (;!ShellFileHandleEof(Handle);Size = 1024) {
750    Status = ShellFileHandleReadLine(Handle, ReadLine, &Size, TRUE, Ascii);
751     //
752     // ignore too small of buffer...
753     //
754     if (EFI_ERROR(Status) && Status != EFI_BUFFER_TOO_SMALL) {
755       break;
756     }
757 
758     Status = EFI_NOT_FOUND;
759     if (IsTitleHeader (Command+Start, ReadLine, BriefDesc, BriefSize, &Found)) {
760       Status = Found ? EFI_SUCCESS : EFI_NOT_FOUND;
761       break;
762     }
763   }
764 
765   FreePool(ReadLine);
766   return (Status);
767 }
768 
769 /**
770   This function returns the help information for the specified command. The help text
771   will be parsed from a UEFI Shell manual page. (see UEFI Shell 2.0 Appendix B)
772 
773   If Sections is specified, then each section name listed will be compared in a casesensitive
774   manner, to the section names described in Appendix B. If the section exists,
775   it will be appended to the returned help text. If the section does not exist, no
776   information will be returned. If Sections is NULL, then all help text information
777   available will be returned.
778 
779   if BriefDesc is NULL, then the breif description will not be savedd seperatly,
780   but placed first in the main HelpText.
781 
782   @param[in] ManFileName        Points to the NULL-terminated UEFI Shell MAN file name.
783   @param[in] Command            Points to the NULL-terminated UEFI Shell command name.
784   @param[in] Sections           Points to the NULL-terminated comma-delimited
785                                 section names to return. If NULL, then all
786                                 sections will be returned.
787   @param[out] BriefDesc         On return, points to a callee-allocated buffer
788                                 containing brief description text.
789   @param[out] HelpText          On return, points to a callee-allocated buffer
790                                 containing all specified help text.
791 
792   @retval EFI_SUCCESS           The help text was returned.
793   @retval EFI_OUT_OF_RESOURCES  The necessary buffer could not be allocated to hold the
794                                 returned help text.
795   @retval EFI_INVALID_PARAMETER HelpText is NULL.
796   @retval EFI_INVALID_PARAMETER ManFileName is invalid.
797   @retval EFI_NOT_FOUND         There is no help text available for Command.
798 **/
799 EFI_STATUS
ProcessManFile(IN CONST CHAR16 * ManFileName,IN CONST CHAR16 * Command,IN CONST CHAR16 * Sections OPTIONAL,OUT CHAR16 ** BriefDesc OPTIONAL,OUT CHAR16 ** HelpText)800 ProcessManFile(
801   IN CONST CHAR16 *ManFileName,
802   IN CONST CHAR16 *Command,
803   IN CONST CHAR16 *Sections OPTIONAL,
804   OUT CHAR16      **BriefDesc OPTIONAL,
805   OUT CHAR16      **HelpText
806   )
807 {
808   CHAR16            *TempString;
809   SHELL_FILE_HANDLE FileHandle;
810   EFI_HANDLE        CmdFileImgHandle;
811   EFI_STATUS        Status;
812   UINTN             HelpSize;
813   UINTN             BriefSize;
814   UINTN             StringIdWalker;
815   BOOLEAN           Ascii;
816   CHAR16            *TempString2;
817   CHAR16            *CmdFileName;
818   CHAR16            *CmdFilePathName;
819   CHAR16            *StringBuff;
820   EFI_DEVICE_PATH_PROTOCOL      *FileDevPath;
821   EFI_DEVICE_PATH_PROTOCOL      *DevPath;
822   EFI_HII_PACKAGE_LIST_HEADER   *PackageListHeader;
823 
824   if ( ManFileName == NULL
825     || Command     == NULL
826     || HelpText    == NULL
827    ){
828     return (EFI_INVALID_PARAMETER);
829   }
830 
831   HelpSize          = 0;
832   BriefSize         = 0;
833   StringIdWalker    = 0;
834   TempString        = NULL;
835   Ascii             = FALSE;
836   CmdFileName       = NULL;
837   CmdFilePathName   = NULL;
838   CmdFileImgHandle  = NULL;
839   StringBuff        = NULL;
840   PackageListHeader = NULL;
841   FileDevPath       = NULL;
842   DevPath           = NULL;
843 
844   //
845   // See if it's in HII first
846   //
847   TempString = ShellCommandGetCommandHelp(Command);
848   if (TempString != NULL) {
849     TempString2 = TempString;
850     Status = ManBufferFindTitleSection(&TempString2, Command, BriefDesc, &BriefSize);
851     if (!EFI_ERROR(Status) && HelpText != NULL){
852       Status = ManBufferFindSections(TempString2, Sections, HelpText, &HelpSize);
853     }
854   } else {
855     //
856     // If the image is a external app, check .MAN file first.
857     //
858     FileHandle    = NULL;
859     TempString  = GetManFileName(ManFileName);
860     if (TempString == NULL) {
861       return (EFI_INVALID_PARAMETER);
862     }
863 
864     Status = SearchPathForFile(TempString, &FileHandle);
865     if (EFI_ERROR(Status)) {
866       FileDevPath = FileDevicePath(NULL, TempString);
867       DevPath = AppendDevicePath (ShellInfoObject.ImageDevPath, FileDevPath);
868       Status = InternalOpenFileDevicePath(DevPath, &FileHandle, EFI_FILE_MODE_READ, 0);
869       SHELL_FREE_NON_NULL(FileDevPath);
870       SHELL_FREE_NON_NULL(DevPath);
871     }
872 
873     if (!EFI_ERROR(Status)) {
874       HelpSize  = 0;
875       BriefSize = 0;
876       Status = ManFileFindTitleSection(FileHandle, Command, BriefDesc, &BriefSize, &Ascii);
877       if (!EFI_ERROR(Status) && HelpText != NULL){
878         Status = ManFileFindSections(FileHandle, Sections, HelpText, &HelpSize, Ascii);
879       }
880       ShellInfoObject.NewEfiShellProtocol->CloseFile(FileHandle);
881       if (!EFI_ERROR(Status)) {
882         //
883         // Get help text from .MAN file success.
884         //
885         goto Done;
886       }
887     }
888 
889     //
890     // Load the app image to check  EFI_HII_PACKAGE_LIST_PROTOCOL.
891     //
892     CmdFileName     = GetExecuatableFileName(TempString);
893     if (CmdFileName == NULL) {
894       Status = EFI_OUT_OF_RESOURCES;
895       goto Done;
896     }
897     //
898     // If the file in CWD then use the file name, else use the full
899     // path name.
900     //
901     CmdFilePathName = ShellFindFilePath(CmdFileName);
902     if (CmdFilePathName == NULL) {
903       Status = EFI_NOT_FOUND;
904       goto Done;
905     }
906     DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CmdFilePathName);
907     Status      = gBS->LoadImage(FALSE, gImageHandle, DevPath, NULL, 0, &CmdFileImgHandle);
908     if(EFI_ERROR(Status)) {
909       *HelpText = NULL;
910       goto Done;
911     }
912     Status = gBS->OpenProtocol(
913                     CmdFileImgHandle,
914                     &gEfiHiiPackageListProtocolGuid,
915                     (VOID**)&PackageListHeader,
916                     gImageHandle,
917                     NULL,
918                     EFI_OPEN_PROTOCOL_GET_PROTOCOL
919                     );
920     if(EFI_ERROR(Status)) {
921       *HelpText = NULL;
922       goto Done;
923     }
924 
925     //
926     // If get package list on image handle, install it on HiiDatabase.
927     //
928     Status = gBS->InstallProtocolInterface (
929                     &mShellManDriverHandle,
930                     &gEfiDevicePathProtocolGuid,
931                     EFI_NATIVE_INTERFACE,
932                     &mShellManHiiDevicePath
933                     );
934     if (EFI_ERROR(Status)) {
935       goto Done;
936     }
937 
938     Status = gHiiDatabase->NewPackageList (
939                             gHiiDatabase,
940                             PackageListHeader,
941                             mShellManDriverHandle,
942                             &mShellManHiiHandle
943                             );
944     if (EFI_ERROR (Status)) {
945       goto Done;
946     }
947 
948     StringIdWalker = 1;
949     do {
950         SHELL_FREE_NON_NULL(StringBuff);
951         if (BriefDesc != NULL) {
952           SHELL_FREE_NON_NULL(*BriefDesc);
953         }
954         StringBuff = HiiGetString (mShellManHiiHandle, (EFI_STRING_ID)StringIdWalker, NULL);
955         if (StringBuff == NULL) {
956           Status = EFI_NOT_FOUND;
957           goto Done;
958         }
959         TempString2 = StringBuff;
960         Status = ManBufferFindTitleSection(&TempString2, Command, BriefDesc, &BriefSize);
961         if (!EFI_ERROR(Status) && HelpText != NULL){
962           Status = ManBufferFindSections(TempString2, Sections, HelpText, &HelpSize);
963         }
964         if (!EFI_ERROR(Status)){
965           //
966           // Found what we need and return
967           //
968           goto Done;
969         }
970 
971         StringIdWalker += 1;
972     } while (StringIdWalker < 0xFFFF && StringBuff != NULL);
973 
974   }
975 
976 Done:
977   if (mShellManDriverHandle != NULL) {
978     gBS->UninstallProtocolInterface (
979             mShellManDriverHandle,
980             &gEfiDevicePathProtocolGuid,
981             &mShellManHiiDevicePath
982            );
983     mShellManDriverHandle = NULL;
984   }
985 
986   if (mShellManHiiHandle != NULL) {
987     HiiRemovePackages (mShellManHiiHandle);
988     mShellManHiiHandle = NULL;
989   }
990 
991   if (CmdFileImgHandle != NULL) {
992     Status = gBS->UnloadImage (CmdFileImgHandle);
993   }
994 
995   SHELL_FREE_NON_NULL(StringBuff);
996   SHELL_FREE_NON_NULL(TempString);
997   SHELL_FREE_NON_NULL(CmdFileName);
998   SHELL_FREE_NON_NULL(CmdFilePathName);
999   SHELL_FREE_NON_NULL(FileDevPath);
1000   SHELL_FREE_NON_NULL(DevPath);
1001 
1002   return (Status);
1003 }
1004 
1005