1 /** @file
2   Main file for Help shell level 3 function.
3 
4   Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved. <BR>
5   Copyright (c) 2014, ARM Limited. All rights reserved. <BR>
6   (C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR>
7 
8   SPDX-License-Identifier: BSD-2-Clause-Patent
9 
10 **/
11 
12 #include "UefiShellLevel3CommandsLib.h"
13 
14 #include <Library/ShellLib.h>
15 #include <Library/HandleParsingLib.h>
16 
17 #include <Protocol/ShellDynamicCommand.h>
18 
19 /**
20    function to insert string items into a list in the correct alphabetical place
21 
22    the resultant list is a double NULL terminated list of NULL terminated strings.
23 
24    upon successful return the memory must be caller freed (unless passed back in
25    via a loop where it will get reallocated).
26 
27    @param[in,out] DestList    double pointer to the list. may be NULL.
28    @param[in,out] DestSize    pointer to the size of list. may be 0, if DestList is NULL.
29    @param[in]     Item        the item to insert.
30 
31    @retval EFI_SUCCESS        the operation was successful.
32 **/
33 EFI_STATUS
LexicalInsertIntoList(IN OUT CHAR16 ** DestList,IN OUT UINTN * DestSize,IN CONST CHAR16 * Item)34 LexicalInsertIntoList(
35   IN OUT   CHAR16 **DestList,
36   IN OUT   UINTN  *DestSize,
37   IN CONST CHAR16 *Item
38   )
39 {
40   CHAR16                              *NewList;
41   INTN                                LexicalMatchValue;
42   CHAR16                              *LexicalSpot;
43   UINTN                               SizeOfAddedNameInBytes;
44 
45   //
46   // If there are none, then just return with success
47   //
48   if (Item == NULL || *Item == CHAR_NULL || StrLen(Item)==0) {
49     return (EFI_SUCCESS);
50   }
51 
52   NewList = *DestList;
53 
54   SizeOfAddedNameInBytes = StrSize(Item);
55   NewList = ReallocatePool(*DestSize, (*DestSize) + SizeOfAddedNameInBytes, NewList);
56   (*DestSize) = (*DestSize) + SizeOfAddedNameInBytes;
57 
58   //
59   // Find the correct spot in the list
60   //
61   for (LexicalSpot = NewList
62     ; LexicalSpot != NULL && LexicalSpot < NewList + (*DestSize)
63     ; LexicalSpot += StrLen(LexicalSpot) + 1
64     ) {
65     //
66     // Get Lexical Comparison Value between PrevCommand and Command list entry
67     //
68     LexicalMatchValue = gUnicodeCollation->StriColl (
69                                               gUnicodeCollation,
70                                               (CHAR16 *)LexicalSpot,
71                                               (CHAR16 *)Item
72                                               );
73     //
74     // The new item goes before this one.
75     //
76     if (LexicalMatchValue > 0 || StrLen(LexicalSpot) == 0) {
77       if (StrLen(LexicalSpot) != 0) {
78         //
79         // Move this and all other items out of the way
80         //
81         CopyMem(
82           LexicalSpot + (SizeOfAddedNameInBytes/sizeof(CHAR16)),
83           LexicalSpot,
84           (*DestSize) - SizeOfAddedNameInBytes - ((LexicalSpot - NewList) * sizeof(CHAR16))
85           );
86       }
87 
88       //
89       // Stick this one in place
90       //
91       StrCpyS(LexicalSpot, SizeOfAddedNameInBytes/sizeof(CHAR16), Item);
92       break;
93     }
94   }
95 
96   *DestList = NewList;
97   return (EFI_SUCCESS);
98 }
99 
100 /**
101    function to add each command name from the linked list to the string list.
102 
103    the resultant list is a double NULL terminated list of NULL terminated strings.
104 
105    @param[in,out] DestList    double pointer to the list. may be NULL.
106    @param[in,out] DestSize    pointer to the size of list. may be 0, if DestList is NULL.
107    @param[in]     SourceList  the double linked list of commands.
108 
109    @retval EFI_SUCCESS        the operation was successful.
110 **/
111 EFI_STATUS
CopyListOfCommandNames(IN OUT CHAR16 ** DestList,IN OUT UINTN * DestSize,IN CONST COMMAND_LIST * SourceList)112 CopyListOfCommandNames(
113   IN OUT   CHAR16       **DestList,
114   IN OUT   UINTN        *DestSize,
115   IN CONST COMMAND_LIST *SourceList
116   )
117 {
118   CONST COMMAND_LIST  *Node;
119 
120   for ( Node = (COMMAND_LIST*)GetFirstNode(&SourceList->Link)
121       ; SourceList != NULL && !IsListEmpty(&SourceList->Link) && !IsNull(&SourceList->Link, &Node->Link)
122       ; Node = (COMMAND_LIST*)GetNextNode(&SourceList->Link, &Node->Link)
123     ) {
124     LexicalInsertIntoList(DestList, DestSize, Node->CommandString);
125   }
126   return (EFI_SUCCESS);
127 }
128 
129 /**
130    function to add each dynamic command name to the string list.
131 
132    the resultant list is a double NULL terminated list of NULL terminated strings.
133 
134    @param[in,out] DestList    double pointer to the list. may be NULL.
135    @param[in,out] DestSize    pointer to the size of list. may be 0, if DestList is NULL.
136 
137    @retval EFI_SUCCESS        the operation was successful.
138    @return an error from HandleProtocol
139 **/
140 STATIC
141 EFI_STATUS
CopyListOfCommandNamesWithDynamic(IN OUT CHAR16 ** DestList,IN OUT UINTN * DestSize)142 CopyListOfCommandNamesWithDynamic(
143   IN OUT  CHAR16** DestList,
144   IN OUT  UINTN    *DestSize
145   )
146 {
147   EFI_HANDLE                          *CommandHandleList;
148   CONST EFI_HANDLE                    *NextCommand;
149   EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL  *DynamicCommand;
150   EFI_STATUS                          Status;
151 
152   CommandHandleList = GetHandleListByProtocol(&gEfiShellDynamicCommandProtocolGuid);
153 
154   //
155   // If there are none, then just return with success
156   //
157   if (CommandHandleList == NULL) {
158     return (EFI_SUCCESS);
159   }
160 
161   Status = EFI_SUCCESS;
162 
163   //
164   // Append those to the list.
165   //
166   for (NextCommand = CommandHandleList ; *NextCommand != NULL && !EFI_ERROR(Status) ; NextCommand++) {
167     Status = gBS->HandleProtocol(
168       *NextCommand,
169       &gEfiShellDynamicCommandProtocolGuid,
170       (VOID **)&DynamicCommand
171       );
172 
173     if (EFI_ERROR(Status)) {
174       continue;
175     }
176 
177     Status = LexicalInsertIntoList(DestList, DestSize, DynamicCommand->CommandName);
178   }
179 
180   SHELL_FREE_NON_NULL(CommandHandleList);
181   return (Status);
182 }
183 
184 
185 /**
186   Attempt to print help from a dynamically added command.
187 
188   @param[in]  CommandToGetHelpOn  The unicode name of the command that help is
189                                   requested on.
190   @param[in]  SectionToGetHelpOn  Pointer to the section specifier(s).
191   @param[in]  PrintCommandText    Print the command followed by the help content
192                                   or just help.
193 
194   @retval EFI_SUCCESS             The help was displayed
195   @retval EFI_NOT_FOUND           The command name could not be found
196   @retval EFI_DEVICE_ERROR        The help data format was incorrect.
197 **/
198 EFI_STATUS
PrintDynamicCommandHelp(IN CONST CHAR16 * CommandToGetHelpOn,IN CONST CHAR16 * SectionToGetHelpOn,IN BOOLEAN PrintCommandText)199 PrintDynamicCommandHelp(
200   IN CONST CHAR16  *CommandToGetHelpOn,
201   IN CONST CHAR16  *SectionToGetHelpOn,
202   IN BOOLEAN       PrintCommandText
203  )
204 {
205   EFI_STATUS                          Status;
206   BOOLEAN                             Found;
207   EFI_HANDLE                          *CommandHandleList;
208   EFI_HANDLE                          *NextCommand;
209   EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL  *DynamicCommand;
210 
211   Status = EFI_NOT_FOUND;
212   Found = FALSE;
213   CommandHandleList = NULL;
214 
215   CommandHandleList = GetHandleListByProtocol(&gEfiShellDynamicCommandProtocolGuid);
216 
217   if (CommandHandleList == NULL) {
218     //
219     // not found or out of resources
220     //
221     return Status;
222   }
223 
224   for (NextCommand = CommandHandleList; *NextCommand != NULL; NextCommand++) {
225     Status = gBS->HandleProtocol(
226       *NextCommand,
227       &gEfiShellDynamicCommandProtocolGuid,
228       (VOID **)&DynamicCommand
229       );
230 
231     if (EFI_ERROR(Status)) {
232       continue;
233     }
234 
235     //
236     // Check execution break flag when printing multiple command help information.
237     //
238     if (ShellGetExecutionBreakFlag ()) {
239       break;
240     }
241 
242     if ((gUnicodeCollation->MetaiMatch (gUnicodeCollation, (CHAR16 *)DynamicCommand->CommandName, (CHAR16*)CommandToGetHelpOn)) ||
243       (gEfiShellProtocol->GetAlias (CommandToGetHelpOn, NULL) != NULL && (gUnicodeCollation->MetaiMatch (gUnicodeCollation, (CHAR16 *)DynamicCommand->CommandName, (CHAR16*)(gEfiShellProtocol->GetAlias(CommandToGetHelpOn, NULL)))))) {
244       // Print as Shell Help if in ManPage format.
245       Status = ShellPrintHelp (DynamicCommand->CommandName, SectionToGetHelpOn,
246                               PrintCommandText);
247       if (Status == EFI_DEVICE_ERROR) {
248         ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_HELP_INV),
249                         gShellLevel3HiiHandle, DynamicCommand->CommandName);
250       } else if (EFI_ERROR(Status)) {
251         ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_HELP_NF),
252                         gShellLevel3HiiHandle, DynamicCommand->CommandName);
253       } else {
254         Found = TRUE;
255       }
256     }
257   }
258 
259   SHELL_FREE_NON_NULL(CommandHandleList);
260 
261   return (Found ? EFI_SUCCESS : Status);
262 
263 }
264 
265 STATIC CONST SHELL_PARAM_ITEM ParamList[] = {
266   {L"-usage", TypeFlag},
267   {L"-section", TypeMaxValue},
268   {L"-verbose", TypeFlag},
269   {L"-v", TypeFlag},
270   {NULL, TypeMax}
271   };
272 
273 /**
274   Function for 'help' command.
275 
276   @param[in] ImageHandle  Handle to the Image (NULL if Internal).
277   @param[in] SystemTable  Pointer to the System Table (NULL if Internal).
278 **/
279 SHELL_STATUS
280 EFIAPI
ShellCommandRunHelp(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)281 ShellCommandRunHelp (
282   IN EFI_HANDLE        ImageHandle,
283   IN EFI_SYSTEM_TABLE  *SystemTable
284   )
285 {
286   EFI_STATUS          Status;
287   LIST_ENTRY          *Package;
288   CHAR16              *ProblemParam;
289   SHELL_STATUS        ShellStatus;
290   CHAR16              *SortedCommandList;
291   CONST CHAR16        *CurrentCommand;
292   CHAR16              *CommandToGetHelpOn;
293   CHAR16              *SectionToGetHelpOn;
294   CHAR16              *HiiString;
295   BOOLEAN             Found;
296   BOOLEAN             PrintCommandText;
297   UINTN               SortedCommandListSize;
298 
299   PrintCommandText    = TRUE;
300   ProblemParam        = NULL;
301   ShellStatus         = SHELL_SUCCESS;
302   CommandToGetHelpOn  = NULL;
303   SectionToGetHelpOn  = NULL;
304   SortedCommandList   = NULL;
305   Found               = FALSE;
306 
307   //
308   // initialize the shell lib (we must be in non-auto-init...)
309   //
310   Status = ShellInitialize();
311   ASSERT_EFI_ERROR(Status);
312 
313   Status = CommandInit();
314   ASSERT_EFI_ERROR(Status);
315 
316   //
317   // parse the command line
318   //
319   Status = ShellCommandLineParse (ParamList, &Package, &ProblemParam, TRUE);
320   if (EFI_ERROR(Status)) {
321     if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
322       ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PROBLEM), gShellLevel3HiiHandle, L"help", ProblemParam);
323       FreePool(ProblemParam);
324       ShellStatus = SHELL_INVALID_PARAMETER;
325     } else {
326       ASSERT(FALSE);
327     }
328   } else {
329     //
330     // Check for conflicting parameters.
331     //
332     if (ShellCommandLineGetFlag(Package, L"-usage")
333       &&ShellCommandLineGetFlag(Package, L"-section")
334       &&(ShellCommandLineGetFlag(Package, L"-verbose") || ShellCommandLineGetFlag(Package, L"-v"))
335      ){
336       ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_CON), gShellLevel3HiiHandle, L"help");
337       ShellStatus = SHELL_INVALID_PARAMETER;
338     } else if (ShellCommandLineGetRawValue(Package, 2) != NULL) {
339       ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_MANY), gShellLevel3HiiHandle, L"help");
340       ShellStatus = SHELL_INVALID_PARAMETER;
341     } else {
342       //
343       // Get the command name we are getting help on
344       //
345       ASSERT(CommandToGetHelpOn == NULL);
346       StrnCatGrow(&CommandToGetHelpOn, NULL, ShellCommandLineGetRawValue(Package, 1), 0);
347       if (CommandToGetHelpOn == NULL && ShellCommandLineGetFlag(Package, L"-?")) {
348         //
349         // If we dont have a command and we got a simple -?
350         // we are looking for help on help command.
351         //
352         StrnCatGrow(&CommandToGetHelpOn, NULL, L"help", 0);
353       }
354 
355       if (CommandToGetHelpOn == NULL) {
356         StrnCatGrow(&CommandToGetHelpOn, NULL, L"*", 0);
357         ASSERT(SectionToGetHelpOn == NULL);
358         StrnCatGrow(&SectionToGetHelpOn, NULL, L"NAME", 0);
359       } else {
360         PrintCommandText = FALSE;
361         ASSERT(SectionToGetHelpOn == NULL);
362         //
363         // Get the section name for the given command name
364         //
365         if (ShellCommandLineGetFlag(Package, L"-section")) {
366           StrnCatGrow(&SectionToGetHelpOn, NULL, ShellCommandLineGetValue(Package, L"-section"), 0);
367         } else if (ShellCommandLineGetFlag(Package, L"-usage")) {
368           StrnCatGrow(&SectionToGetHelpOn, NULL, L"NAME,SYNOPSIS", 0);
369         } else if (ShellCommandLineGetFlag(Package, L"-verbose") || ShellCommandLineGetFlag(Package, L"-v")) {
370         } else {
371           //
372           // The output of help <command> will display NAME, SYNOPSIS, OPTIONS, DESCRIPTION, and EXAMPLES sections.
373           //
374           StrnCatGrow (&SectionToGetHelpOn, NULL, L"NAME,SYNOPSIS,OPTIONS,DESCRIPTION,EXAMPLES", 0);
375         }
376       }
377 
378       if (gUnicodeCollation->StriColl(gUnicodeCollation, CommandToGetHelpOn, L"special") == 0) {
379         //
380         // we need info on the special characters
381         //
382         ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_HELP_SC_HEADER), gShellLevel3HiiHandle);
383         HiiString = HiiGetString(gShellLevel3HiiHandle, STRING_TOKEN(STR_HELP_SC_DATA), NULL);
384         ShellPrintEx(-1, -1, L"%s", HiiString);
385         FreePool(HiiString);
386         Found = TRUE;
387       } else {
388         SortedCommandList = NULL;
389         SortedCommandListSize = 0;
390         CopyListOfCommandNames(&SortedCommandList, &SortedCommandListSize, ShellCommandGetCommandList(TRUE));
391         CopyListOfCommandNamesWithDynamic(&SortedCommandList, &SortedCommandListSize);
392 
393         for (CurrentCommand = SortedCommandList
394           ; CurrentCommand != NULL && CurrentCommand < SortedCommandList + SortedCommandListSize/sizeof(CHAR16) && *CurrentCommand != CHAR_NULL
395           ; CurrentCommand += StrLen(CurrentCommand) + 1
396           ) {
397           //
398           // Checking execution break flag when print multiple command help information.
399           //
400           if (ShellGetExecutionBreakFlag ()) {
401             break;
402           }
403 
404           if ((gUnicodeCollation->MetaiMatch(gUnicodeCollation, (CHAR16*)CurrentCommand, CommandToGetHelpOn)) ||
405              (gEfiShellProtocol->GetAlias(CommandToGetHelpOn, NULL) != NULL && (gUnicodeCollation->MetaiMatch(gUnicodeCollation, (CHAR16*)CurrentCommand, (CHAR16*)(gEfiShellProtocol->GetAlias(CommandToGetHelpOn, NULL)))))) {
406             //
407             // We have a command to look for help on.
408             //
409             Status = ShellPrintHelp(CurrentCommand, SectionToGetHelpOn, PrintCommandText);
410             if (EFI_ERROR(Status)) {
411               //
412               // now try to match against the dynamic command list and print help
413               //
414               Status = PrintDynamicCommandHelp (CurrentCommand, SectionToGetHelpOn, PrintCommandText);
415             }
416             if (Status == EFI_DEVICE_ERROR) {
417                 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_HELP_INV), gShellLevel3HiiHandle, CurrentCommand);
418             } else if (EFI_ERROR(Status)) {
419                 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_HELP_NF), gShellLevel3HiiHandle, CurrentCommand);
420             } else {
421                 Found = TRUE;
422             }
423           }
424         }
425 
426         //
427         // Search the .man file for Shell applications (Shell external commands).
428         //
429         if (!Found) {
430           Status = ShellPrintHelp(CommandToGetHelpOn, SectionToGetHelpOn, FALSE);
431           if (Status == EFI_DEVICE_ERROR) {
432               ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_HELP_INV), gShellLevel3HiiHandle, CommandToGetHelpOn);
433           } else if (EFI_ERROR(Status)) {
434               ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_HELP_NF), gShellLevel3HiiHandle, CommandToGetHelpOn);
435           } else {
436             Found = TRUE;
437           }
438         }
439       }
440 
441       if (!Found) {
442         ShellStatus = SHELL_NOT_FOUND;
443       }
444 
445       //
446       // free the command line package
447       //
448       ShellCommandLineFreeVarList (Package);
449     }
450   }
451 
452   if (CommandToGetHelpOn != NULL && StrCmp(CommandToGetHelpOn, L"*") == 0){
453     //
454     // If '*' then the command entered was 'Help' without qualifiers, This footer
455     // provides additional info on help switches
456     //
457     ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_HELP_FOOTER), gShellLevel3HiiHandle);
458   }
459   if (CommandToGetHelpOn != NULL) {
460     FreePool(CommandToGetHelpOn);
461   }
462   if (SectionToGetHelpOn != NULL) {
463     FreePool(SectionToGetHelpOn);
464   }
465   SHELL_FREE_NON_NULL(SortedCommandList);
466 
467   return (ShellStatus);
468 }
469