1 /** @file
2   This is THE shell (application)
3 
4   Copyright (c) 2009 - 2016, Intel Corporation. All rights reserved.<BR>
5   (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P.<BR>
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 //
19 // Initialize the global structure
20 //
21 SHELL_INFO ShellInfoObject = {
22   NULL,
23   NULL,
24   FALSE,
25   FALSE,
26   {
27     {{
28       0,
29       0,
30       0,
31       0,
32       0,
33       0,
34       0,
35       0,
36       0,
37       0
38     }},
39     0,
40     NULL,
41     NULL
42   },
43   {{NULL, NULL}, NULL},
44   {
45     {{NULL, NULL}, NULL},
46     0,
47     0,
48     TRUE
49   },
50   NULL,
51   0,
52   NULL,
53   NULL,
54   NULL,
55   NULL,
56   NULL,
57   {{NULL, NULL}, NULL, NULL},
58   {{NULL, NULL}, NULL, NULL},
59   NULL,
60   NULL,
61   NULL,
62   NULL,
63   NULL,
64   NULL,
65   NULL,
66   NULL,
67   FALSE
68 };
69 
70 STATIC CONST CHAR16 mScriptExtension[]      = L".NSH";
71 STATIC CONST CHAR16 mExecutableExtensions[] = L".NSH;.EFI";
72 STATIC CONST CHAR16 mStartupScript[]        = L"startup.nsh";
73 CONST CHAR16 mNoNestingEnvVarName[]         = L"nonesting";
74 CONST CHAR16 mNoNestingTrue[]               = L"True";
75 CONST CHAR16 mNoNestingFalse[]              = L"False";
76 
77 /**
78   Cleans off leading and trailing spaces and tabs.
79 
80   @param[in] String pointer to the string to trim them off.
81 **/
82 EFI_STATUS
TrimSpaces(IN CHAR16 ** String)83 TrimSpaces(
84   IN CHAR16 **String
85   )
86 {
87   ASSERT(String != NULL);
88   ASSERT(*String!= NULL);
89   //
90   // Remove any spaces and tabs at the beginning of the (*String).
91   //
92   while (((*String)[0] == L' ') || ((*String)[0] == L'\t')) {
93     CopyMem((*String), (*String)+1, StrSize((*String)) - sizeof((*String)[0]));
94   }
95 
96   //
97   // Remove any spaces and tabs at the end of the (*String).
98   //
99   while ((StrLen (*String) > 0) && (((*String)[StrLen((*String))-1] == L' ') || ((*String)[StrLen((*String))-1] == L'\t'))) {
100     (*String)[StrLen((*String))-1] = CHAR_NULL;
101   }
102 
103   return (EFI_SUCCESS);
104 }
105 
106 /**
107   Parse for the next instance of one string within another string. Can optionally make sure that
108   the string was not escaped (^ character) per the shell specification.
109 
110   @param[in] SourceString             The string to search within
111   @param[in] FindString               The string to look for
112   @param[in] CheckForEscapeCharacter  TRUE to skip escaped instances of FinfString, otherwise will return even escaped instances
113 **/
114 CHAR16*
FindNextInstance(IN CONST CHAR16 * SourceString,IN CONST CHAR16 * FindString,IN CONST BOOLEAN CheckForEscapeCharacter)115 FindNextInstance(
116   IN CONST CHAR16   *SourceString,
117   IN CONST CHAR16   *FindString,
118   IN CONST BOOLEAN  CheckForEscapeCharacter
119   )
120 {
121   CHAR16 *Temp;
122   if (SourceString == NULL) {
123     return (NULL);
124   }
125   Temp = StrStr(SourceString, FindString);
126 
127   //
128   // If nothing found, or we don't care about escape characters
129   //
130   if (Temp == NULL || !CheckForEscapeCharacter) {
131     return (Temp);
132   }
133 
134   //
135   // If we found an escaped character, try again on the remainder of the string
136   //
137   if ((Temp > (SourceString)) && *(Temp-1) == L'^') {
138     return FindNextInstance(Temp+1, FindString, CheckForEscapeCharacter);
139   }
140 
141   //
142   // we found the right character
143   //
144   return (Temp);
145 }
146 
147 /**
148   Check whether the string between a pair of % is a valid environment variable name.
149 
150   @param[in] BeginPercent       pointer to the first percent.
151   @param[in] EndPercent          pointer to the last percent.
152 
153   @retval TRUE                          is a valid environment variable name.
154   @retval FALSE                         is NOT a valid environment variable name.
155 **/
156 BOOLEAN
IsValidEnvironmentVariableName(IN CONST CHAR16 * BeginPercent,IN CONST CHAR16 * EndPercent)157 IsValidEnvironmentVariableName(
158   IN CONST CHAR16     *BeginPercent,
159   IN CONST CHAR16     *EndPercent
160   )
161 {
162   CONST CHAR16    *Walker;
163 
164   Walker = NULL;
165 
166   ASSERT (BeginPercent != NULL);
167   ASSERT (EndPercent != NULL);
168   ASSERT (BeginPercent < EndPercent);
169 
170   if ((BeginPercent + 1) == EndPercent) {
171     return FALSE;
172   }
173 
174   for (Walker = BeginPercent + 1; Walker < EndPercent; Walker++) {
175     if (
176         (*Walker >= L'0' && *Walker <= L'9') ||
177         (*Walker >= L'A' && *Walker <= L'Z') ||
178         (*Walker >= L'a' && *Walker <= L'z') ||
179         (*Walker == L'_')
180       ) {
181       if (Walker == BeginPercent + 1 && (*Walker >= L'0' && *Walker <= L'9')) {
182         return FALSE;
183       } else {
184         continue;
185       }
186     } else {
187       return FALSE;
188     }
189   }
190 
191   return TRUE;
192 }
193 
194 /**
195   Determine if a command line contains a split operation
196 
197   @param[in] CmdLine      The command line to parse.
198 
199   @retval TRUE            CmdLine has a valid split.
200   @retval FALSE           CmdLine does not have a valid split.
201 **/
202 BOOLEAN
ContainsSplit(IN CONST CHAR16 * CmdLine)203 ContainsSplit(
204   IN CONST CHAR16 *CmdLine
205   )
206 {
207   CONST CHAR16 *TempSpot;
208   CONST CHAR16 *FirstQuote;
209   CONST CHAR16 *SecondQuote;
210 
211   FirstQuote    = FindNextInstance (CmdLine, L"\"", TRUE);
212   SecondQuote   = NULL;
213   TempSpot      = FindFirstCharacter(CmdLine, L"|", L'^');
214 
215   if (FirstQuote == NULL    ||
216       TempSpot == NULL      ||
217       TempSpot == CHAR_NULL ||
218       FirstQuote > TempSpot
219       ) {
220     return (BOOLEAN) ((TempSpot != NULL) && (*TempSpot != CHAR_NULL));
221   }
222 
223   while ((TempSpot != NULL) && (*TempSpot != CHAR_NULL)) {
224     if (FirstQuote == NULL || FirstQuote > TempSpot) {
225       break;
226     }
227     SecondQuote = FindNextInstance (FirstQuote + 1, L"\"", TRUE);
228     if (SecondQuote == NULL) {
229       break;
230     }
231     if (SecondQuote < TempSpot) {
232       FirstQuote = FindNextInstance (SecondQuote + 1, L"\"", TRUE);
233       continue;
234     } else {
235       FirstQuote = FindNextInstance (SecondQuote + 1, L"\"", TRUE);
236       TempSpot = FindFirstCharacter(TempSpot + 1, L"|", L'^');
237       continue;
238     }
239   }
240 
241   return (BOOLEAN) ((TempSpot != NULL) && (*TempSpot != CHAR_NULL));
242 }
243 
244 /**
245   Function to start monitoring for CTRL-S using SimpleTextInputEx.  This
246   feature's enabled state was not known when the shell initially launched.
247 
248   @retval EFI_SUCCESS           The feature is enabled.
249   @retval EFI_OUT_OF_RESOURCES  There is not enough memory available.
250 **/
251 EFI_STATUS
InternalEfiShellStartCtrlSMonitor(VOID)252 InternalEfiShellStartCtrlSMonitor(
253   VOID
254   )
255 {
256   EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleEx;
257   EFI_KEY_DATA                      KeyData;
258   EFI_STATUS                        Status;
259 
260   Status = gBS->OpenProtocol(
261     gST->ConsoleInHandle,
262     &gEfiSimpleTextInputExProtocolGuid,
263     (VOID**)&SimpleEx,
264     gImageHandle,
265     NULL,
266     EFI_OPEN_PROTOCOL_GET_PROTOCOL);
267   if (EFI_ERROR(Status)) {
268     ShellPrintHiiEx(
269       -1,
270       -1,
271       NULL,
272       STRING_TOKEN (STR_SHELL_NO_IN_EX),
273       ShellInfoObject.HiiHandle);
274     return (EFI_SUCCESS);
275   }
276 
277   KeyData.KeyState.KeyToggleState = 0;
278   KeyData.Key.ScanCode            = 0;
279   KeyData.KeyState.KeyShiftState  = EFI_SHIFT_STATE_VALID|EFI_LEFT_CONTROL_PRESSED;
280   KeyData.Key.UnicodeChar         = L's';
281 
282   Status = SimpleEx->RegisterKeyNotify(
283     SimpleEx,
284     &KeyData,
285     NotificationFunction,
286     &ShellInfoObject.CtrlSNotifyHandle1);
287 
288   KeyData.KeyState.KeyShiftState  = EFI_SHIFT_STATE_VALID|EFI_RIGHT_CONTROL_PRESSED;
289   if (!EFI_ERROR(Status)) {
290     Status = SimpleEx->RegisterKeyNotify(
291       SimpleEx,
292       &KeyData,
293       NotificationFunction,
294       &ShellInfoObject.CtrlSNotifyHandle2);
295   }
296   KeyData.KeyState.KeyShiftState  = EFI_SHIFT_STATE_VALID|EFI_LEFT_CONTROL_PRESSED;
297   KeyData.Key.UnicodeChar         = 19;
298 
299   if (!EFI_ERROR(Status)) {
300     Status = SimpleEx->RegisterKeyNotify(
301       SimpleEx,
302       &KeyData,
303       NotificationFunction,
304       &ShellInfoObject.CtrlSNotifyHandle3);
305   }
306   KeyData.KeyState.KeyShiftState  = EFI_SHIFT_STATE_VALID|EFI_RIGHT_CONTROL_PRESSED;
307   if (!EFI_ERROR(Status)) {
308     Status = SimpleEx->RegisterKeyNotify(
309       SimpleEx,
310       &KeyData,
311       NotificationFunction,
312       &ShellInfoObject.CtrlSNotifyHandle4);
313   }
314   return (Status);
315 }
316 
317 
318 
319 /**
320   The entry point for the application.
321 
322   @param[in] ImageHandle    The firmware allocated handle for the EFI image.
323   @param[in] SystemTable    A pointer to the EFI System Table.
324 
325   @retval EFI_SUCCESS       The entry point is executed successfully.
326   @retval other             Some error occurs when executing this entry point.
327 
328 **/
329 EFI_STATUS
330 EFIAPI
UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)331 UefiMain (
332   IN EFI_HANDLE        ImageHandle,
333   IN EFI_SYSTEM_TABLE  *SystemTable
334   )
335 {
336   EFI_STATUS                      Status;
337   CHAR16                          *TempString;
338   UINTN                           Size;
339   EFI_HANDLE                      ConInHandle;
340   EFI_SIMPLE_TEXT_INPUT_PROTOCOL  *OldConIn;
341   SPLIT_LIST                      *Split;
342 
343   if (PcdGet8(PcdShellSupportLevel) > 3) {
344     return (EFI_UNSUPPORTED);
345   }
346 
347   //
348   // Clear the screen
349   //
350   Status = gST->ConOut->ClearScreen(gST->ConOut);
351   if (EFI_ERROR(Status)) {
352     return (Status);
353   }
354 
355   //
356   // Populate the global structure from PCDs
357   //
358   ShellInfoObject.ImageDevPath                = NULL;
359   ShellInfoObject.FileDevPath                 = NULL;
360   ShellInfoObject.PageBreakEnabled            = PcdGetBool(PcdShellPageBreakDefault);
361   ShellInfoObject.ViewingSettings.InsertMode  = PcdGetBool(PcdShellInsertModeDefault);
362   ShellInfoObject.LogScreenCount              = PcdGet8   (PcdShellScreenLogCount  );
363 
364   //
365   // verify we dont allow for spec violation
366   //
367   ASSERT(ShellInfoObject.LogScreenCount >= 3);
368 
369   //
370   // Initialize the LIST ENTRY objects...
371   //
372   InitializeListHead(&ShellInfoObject.BufferToFreeList.Link);
373   InitializeListHead(&ShellInfoObject.ViewingSettings.CommandHistory.Link);
374   InitializeListHead(&ShellInfoObject.SplitList.Link);
375 
376   //
377   // Check PCDs for optional features that are not implemented yet.
378   //
379   if (   PcdGetBool(PcdShellSupportOldProtocols)
380       || !FeaturePcdGet(PcdShellRequireHiiPlatform)
381       || FeaturePcdGet(PcdShellSupportFrameworkHii)
382    ) {
383     return (EFI_UNSUPPORTED);
384   }
385 
386   //
387   // turn off the watchdog timer
388   //
389   gBS->SetWatchdogTimer (0, 0, 0, NULL);
390 
391   //
392   // install our console logger.  This will keep a log of the output for back-browsing
393   //
394   Status = ConsoleLoggerInstall(ShellInfoObject.LogScreenCount, &ShellInfoObject.ConsoleInfo);
395   if (!EFI_ERROR(Status)) {
396     //
397     // Enable the cursor to be visible
398     //
399     gST->ConOut->EnableCursor (gST->ConOut, TRUE);
400 
401     //
402     // If supporting EFI 1.1 we need to install HII protocol
403     // only do this if PcdShellRequireHiiPlatform == FALSE
404     //
405     // remove EFI_UNSUPPORTED check above when complete.
406     ///@todo add support for Framework HII
407 
408     //
409     // install our (solitary) HII package
410     //
411     ShellInfoObject.HiiHandle = HiiAddPackages (&gEfiCallerIdGuid, gImageHandle, ShellStrings, NULL);
412     if (ShellInfoObject.HiiHandle == NULL) {
413       if (PcdGetBool(PcdShellSupportFrameworkHii)) {
414         ///@todo Add our package into Framework HII
415       }
416       if (ShellInfoObject.HiiHandle == NULL) {
417         Status = EFI_NOT_STARTED;
418         goto FreeResources;
419       }
420     }
421 
422     //
423     // create and install the EfiShellParametersProtocol
424     //
425     Status = CreatePopulateInstallShellParametersProtocol(&ShellInfoObject.NewShellParametersProtocol, &ShellInfoObject.RootShellInstance);
426     ASSERT_EFI_ERROR(Status);
427     ASSERT(ShellInfoObject.NewShellParametersProtocol != NULL);
428 
429     //
430     // create and install the EfiShellProtocol
431     //
432     Status = CreatePopulateInstallShellProtocol(&ShellInfoObject.NewEfiShellProtocol);
433     ASSERT_EFI_ERROR(Status);
434     ASSERT(ShellInfoObject.NewEfiShellProtocol != NULL);
435 
436     //
437     // Now initialize the shell library (it requires Shell Parameters protocol)
438     //
439     Status = ShellInitialize();
440     ASSERT_EFI_ERROR(Status);
441 
442     Status = CommandInit();
443     ASSERT_EFI_ERROR(Status);
444 
445     Status = ShellInitEnvVarList ();
446 
447     //
448     // Check the command line
449     //
450     Status = ProcessCommandLine ();
451     if (EFI_ERROR (Status)) {
452       goto FreeResources;
453     }
454 
455     //
456     // If shell support level is >= 1 create the mappings and paths
457     //
458     if (PcdGet8(PcdShellSupportLevel) >= 1) {
459       Status = ShellCommandCreateInitialMappingsAndPaths();
460     }
461 
462     //
463     // Set the environment variable for nesting support
464     //
465     Size = 0;
466     TempString = NULL;
467     if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoNest) {
468       //
469       // No change.  require nesting in Shell Protocol Execute()
470       //
471       StrnCatGrow(&TempString,
472                   &Size,
473                   L"False",
474                   0);
475     } else {
476       StrnCatGrow(&TempString,
477                   &Size,
478                   mNoNestingTrue,
479                   0);
480     }
481     Status = InternalEfiShellSetEnv(mNoNestingEnvVarName, TempString, TRUE);
482     SHELL_FREE_NON_NULL(TempString);
483     Size = 0;
484 
485     //
486     // save the device path for the loaded image and the device path for the filepath (under loaded image)
487     // These are where to look for the startup.nsh file
488     //
489     Status = GetDevicePathsForImageAndFile(&ShellInfoObject.ImageDevPath, &ShellInfoObject.FileDevPath);
490     ASSERT_EFI_ERROR(Status);
491 
492     //
493     // Display the version
494     //
495     if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion) {
496       ShellPrintHiiEx (
497         0,
498         gST->ConOut->Mode->CursorRow,
499         NULL,
500         STRING_TOKEN (STR_VER_OUTPUT_MAIN_SHELL),
501         ShellInfoObject.HiiHandle,
502         SupportLevel[PcdGet8(PcdShellSupportLevel)],
503         gEfiShellProtocol->MajorVersion,
504         gEfiShellProtocol->MinorVersion
505        );
506 
507       ShellPrintHiiEx (
508         -1,
509         -1,
510         NULL,
511         STRING_TOKEN (STR_VER_OUTPUT_MAIN_SUPPLIER),
512         ShellInfoObject.HiiHandle,
513         (CHAR16 *) PcdGetPtr (PcdShellSupplier)
514        );
515 
516       ShellPrintHiiEx (
517         -1,
518         -1,
519         NULL,
520         STRING_TOKEN (STR_VER_OUTPUT_MAIN_UEFI),
521         ShellInfoObject.HiiHandle,
522         (gST->Hdr.Revision&0xffff0000)>>16,
523         (gST->Hdr.Revision&0x0000ffff),
524         gST->FirmwareVendor,
525         gST->FirmwareRevision
526        );
527     }
528 
529     //
530     // Display the mapping
531     //
532     if (PcdGet8(PcdShellSupportLevel) >= 2 && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap) {
533       Status = RunCommand(L"map");
534       ASSERT_EFI_ERROR(Status);
535     }
536 
537     //
538     // init all the built in alias'
539     //
540     Status = SetBuiltInAlias();
541     ASSERT_EFI_ERROR(Status);
542 
543     //
544     // Initialize environment variables
545     //
546     if (ShellCommandGetProfileList() != NULL) {
547       Status = InternalEfiShellSetEnv(L"profiles", ShellCommandGetProfileList(), TRUE);
548       ASSERT_EFI_ERROR(Status);
549     }
550 
551     Size        = 100;
552     TempString  = AllocateZeroPool(Size);
553 
554     UnicodeSPrint(TempString, Size, L"%d", PcdGet8(PcdShellSupportLevel));
555     Status = InternalEfiShellSetEnv(L"uefishellsupport", TempString, TRUE);
556     ASSERT_EFI_ERROR(Status);
557 
558     UnicodeSPrint(TempString, Size, L"%d.%d", ShellInfoObject.NewEfiShellProtocol->MajorVersion, ShellInfoObject.NewEfiShellProtocol->MinorVersion);
559     Status = InternalEfiShellSetEnv(L"uefishellversion", TempString, TRUE);
560     ASSERT_EFI_ERROR(Status);
561 
562     UnicodeSPrint(TempString, Size, L"%d.%d", (gST->Hdr.Revision & 0xFFFF0000) >> 16, gST->Hdr.Revision & 0x0000FFFF);
563     Status = InternalEfiShellSetEnv(L"uefiversion", TempString, TRUE);
564     ASSERT_EFI_ERROR(Status);
565 
566     FreePool(TempString);
567 
568     if (!EFI_ERROR(Status)) {
569       if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt) {
570         //
571         // Set up the event for CTRL-C monitoring...
572         //
573         Status = InernalEfiShellStartMonitor();
574       }
575 
576       if (!EFI_ERROR(Status) && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) {
577         //
578         // Set up the event for CTRL-S monitoring...
579         //
580         Status = InternalEfiShellStartCtrlSMonitor();
581       }
582 
583       if (!EFI_ERROR(Status) && ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) {
584         //
585         // close off the gST->ConIn
586         //
587         OldConIn      = gST->ConIn;
588         ConInHandle   = gST->ConsoleInHandle;
589         gST->ConIn = CreateSimpleTextInOnFile((SHELL_FILE_HANDLE)&FileInterfaceNulFile, &gST->ConsoleInHandle);
590       } else {
591         OldConIn      = NULL;
592         ConInHandle   = NULL;
593       }
594 
595       if (!EFI_ERROR(Status) && PcdGet8(PcdShellSupportLevel) >= 1) {
596         //
597         // process the startup script or launch the called app.
598         //
599         Status = DoStartupScript(ShellInfoObject.ImageDevPath, ShellInfoObject.FileDevPath);
600       }
601 
602       if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit && !ShellCommandGetExit() && (PcdGet8(PcdShellSupportLevel) >= 3 || PcdGetBool(PcdShellForceConsole)) && !EFI_ERROR(Status) && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) {
603         //
604         // begin the UI waiting loop
605         //
606         do {
607           //
608           // clean out all the memory allocated for CONST <something> * return values
609           // between each shell prompt presentation
610           //
611           if (!IsListEmpty(&ShellInfoObject.BufferToFreeList.Link)){
612             FreeBufferList(&ShellInfoObject.BufferToFreeList);
613           }
614 
615           //
616           // Reset page break back to default.
617           //
618           ShellInfoObject.PageBreakEnabled        = PcdGetBool(PcdShellPageBreakDefault);
619           ASSERT (ShellInfoObject.ConsoleInfo != NULL);
620           ShellInfoObject.ConsoleInfo->Enabled    = TRUE;
621           ShellInfoObject.ConsoleInfo->RowCounter = 0;
622 
623           //
624           // Reset the CTRL-C event (yes we ignore the return values)
625           //
626           Status = gBS->CheckEvent (ShellInfoObject.NewEfiShellProtocol->ExecutionBreak);
627 
628           //
629           // Display Prompt
630           //
631           Status = DoShellPrompt();
632         } while (!ShellCommandGetExit());
633       }
634       if (OldConIn != NULL && ConInHandle != NULL) {
635         CloseSimpleTextInOnFile (gST->ConIn);
636         gST->ConIn            = OldConIn;
637         gST->ConsoleInHandle  = ConInHandle;
638       }
639     }
640   }
641 
642 FreeResources:
643   //
644   // uninstall protocols / free memory / etc...
645   //
646   if (ShellInfoObject.UserBreakTimer != NULL) {
647     gBS->CloseEvent(ShellInfoObject.UserBreakTimer);
648     DEBUG_CODE(ShellInfoObject.UserBreakTimer = NULL;);
649   }
650   if (ShellInfoObject.ImageDevPath != NULL) {
651     FreePool(ShellInfoObject.ImageDevPath);
652     DEBUG_CODE(ShellInfoObject.ImageDevPath = NULL;);
653   }
654   if (ShellInfoObject.FileDevPath != NULL) {
655     FreePool(ShellInfoObject.FileDevPath);
656     DEBUG_CODE(ShellInfoObject.FileDevPath = NULL;);
657   }
658   if (ShellInfoObject.NewShellParametersProtocol != NULL) {
659     CleanUpShellParametersProtocol(ShellInfoObject.NewShellParametersProtocol);
660     DEBUG_CODE(ShellInfoObject.NewShellParametersProtocol = NULL;);
661   }
662   if (ShellInfoObject.NewEfiShellProtocol != NULL){
663     if (ShellInfoObject.NewEfiShellProtocol->IsRootShell()){
664       InternalEfiShellSetEnv(L"cwd", NULL, TRUE);
665     }
666     CleanUpShellEnvironment (ShellInfoObject.NewEfiShellProtocol);
667     DEBUG_CODE(ShellInfoObject.NewEfiShellProtocol = NULL;);
668   }
669 
670   if (!IsListEmpty(&ShellInfoObject.BufferToFreeList.Link)){
671     FreeBufferList(&ShellInfoObject.BufferToFreeList);
672   }
673 
674   if (!IsListEmpty(&ShellInfoObject.SplitList.Link)){
675     ASSERT(FALSE); ///@todo finish this de-allocation (free SplitStdIn/Out when needed).
676 
677     for ( Split = (SPLIT_LIST*)GetFirstNode (&ShellInfoObject.SplitList.Link)
678         ; !IsNull (&ShellInfoObject.SplitList.Link, &Split->Link)
679         ; Split = (SPLIT_LIST *)GetNextNode (&ShellInfoObject.SplitList.Link, &Split->Link)
680      ) {
681       RemoveEntryList (&Split->Link);
682       FreePool (Split);
683     }
684 
685     DEBUG_CODE (InitializeListHead (&ShellInfoObject.SplitList.Link););
686   }
687 
688   if (ShellInfoObject.ShellInitSettings.FileName != NULL) {
689     FreePool(ShellInfoObject.ShellInitSettings.FileName);
690     DEBUG_CODE(ShellInfoObject.ShellInitSettings.FileName = NULL;);
691   }
692 
693   if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) {
694     FreePool(ShellInfoObject.ShellInitSettings.FileOptions);
695     DEBUG_CODE(ShellInfoObject.ShellInitSettings.FileOptions = NULL;);
696   }
697 
698   if (ShellInfoObject.HiiHandle != NULL) {
699     HiiRemovePackages(ShellInfoObject.HiiHandle);
700     DEBUG_CODE(ShellInfoObject.HiiHandle = NULL;);
701   }
702 
703   if (!IsListEmpty(&ShellInfoObject.ViewingSettings.CommandHistory.Link)){
704     FreeBufferList(&ShellInfoObject.ViewingSettings.CommandHistory);
705   }
706 
707   ASSERT(ShellInfoObject.ConsoleInfo != NULL);
708   if (ShellInfoObject.ConsoleInfo != NULL) {
709     ConsoleLoggerUninstall(ShellInfoObject.ConsoleInfo);
710     FreePool(ShellInfoObject.ConsoleInfo);
711     DEBUG_CODE(ShellInfoObject.ConsoleInfo = NULL;);
712   }
713 
714   ShellFreeEnvVarList ();
715 
716   if (ShellCommandGetExit()) {
717     return ((EFI_STATUS)ShellCommandGetExitCode());
718   }
719   return (Status);
720 }
721 
722 /**
723   Sets all the alias' that were registered with the ShellCommandLib library.
724 
725   @retval EFI_SUCCESS           all init commands were run successfully.
726 **/
727 EFI_STATUS
SetBuiltInAlias()728 SetBuiltInAlias(
729   )
730 {
731   EFI_STATUS          Status;
732   CONST ALIAS_LIST    *List;
733   ALIAS_LIST          *Node;
734 
735   //
736   // Get all the commands we want to run
737   //
738   List = ShellCommandGetInitAliasList();
739 
740   //
741   // for each command in the List
742   //
743   for ( Node = (ALIAS_LIST*)GetFirstNode(&List->Link)
744       ; !IsNull (&List->Link, &Node->Link)
745       ; Node = (ALIAS_LIST *)GetNextNode(&List->Link, &Node->Link)
746    ){
747     //
748     // install the alias'
749     //
750     Status = InternalSetAlias(Node->CommandString, Node->Alias, TRUE);
751     ASSERT_EFI_ERROR(Status);
752   }
753   return (EFI_SUCCESS);
754 }
755 
756 /**
757   Internal function to determine if 2 command names are really the same.
758 
759   @param[in] Command1       The pointer to the first command name.
760   @param[in] Command2       The pointer to the second command name.
761 
762   @retval TRUE              The 2 command names are the same.
763   @retval FALSE             The 2 command names are not the same.
764 **/
765 BOOLEAN
IsCommand(IN CONST CHAR16 * Command1,IN CONST CHAR16 * Command2)766 IsCommand(
767   IN CONST CHAR16 *Command1,
768   IN CONST CHAR16 *Command2
769   )
770 {
771   if (StringNoCaseCompare(&Command1, &Command2) == 0) {
772     return (TRUE);
773   }
774   return (FALSE);
775 }
776 
777 /**
778   Internal function to determine if a command is a script only command.
779 
780   @param[in] CommandName    The pointer to the command name.
781 
782   @retval TRUE              The command is a script only command.
783   @retval FALSE             The command is not a script only command.
784 **/
785 BOOLEAN
IsScriptOnlyCommand(IN CONST CHAR16 * CommandName)786 IsScriptOnlyCommand(
787   IN CONST CHAR16 *CommandName
788   )
789 {
790   if (IsCommand(CommandName, L"for")
791     ||IsCommand(CommandName, L"endfor")
792     ||IsCommand(CommandName, L"if")
793     ||IsCommand(CommandName, L"else")
794     ||IsCommand(CommandName, L"endif")
795     ||IsCommand(CommandName, L"goto")) {
796     return (TRUE);
797   }
798   return (FALSE);
799 }
800 
801 /**
802   This function will populate the 2 device path protocol parameters based on the
803   global gImageHandle.  The DevPath will point to the device path for the handle that has
804   loaded image protocol installed on it.  The FilePath will point to the device path
805   for the file that was loaded.
806 
807   @param[in, out] DevPath       On a successful return the device path to the loaded image.
808   @param[in, out] FilePath      On a successful return the device path to the file.
809 
810   @retval EFI_SUCCESS           The 2 device paths were successfully returned.
811   @retval other                 A error from gBS->HandleProtocol.
812 
813   @sa HandleProtocol
814 **/
815 EFI_STATUS
GetDevicePathsForImageAndFile(IN OUT EFI_DEVICE_PATH_PROTOCOL ** DevPath,IN OUT EFI_DEVICE_PATH_PROTOCOL ** FilePath)816 GetDevicePathsForImageAndFile (
817   IN OUT EFI_DEVICE_PATH_PROTOCOL **DevPath,
818   IN OUT EFI_DEVICE_PATH_PROTOCOL **FilePath
819   )
820 {
821   EFI_STATUS                Status;
822   EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
823   EFI_DEVICE_PATH_PROTOCOL  *ImageDevicePath;
824 
825   ASSERT(DevPath  != NULL);
826   ASSERT(FilePath != NULL);
827 
828   Status = gBS->OpenProtocol (
829                 gImageHandle,
830                 &gEfiLoadedImageProtocolGuid,
831                 (VOID**)&LoadedImage,
832                 gImageHandle,
833                 NULL,
834                 EFI_OPEN_PROTOCOL_GET_PROTOCOL
835                );
836   if (!EFI_ERROR (Status)) {
837     Status = gBS->OpenProtocol (
838                   LoadedImage->DeviceHandle,
839                   &gEfiDevicePathProtocolGuid,
840                   (VOID**)&ImageDevicePath,
841                   gImageHandle,
842                   NULL,
843                   EFI_OPEN_PROTOCOL_GET_PROTOCOL
844                  );
845     if (!EFI_ERROR (Status)) {
846       *DevPath  = DuplicateDevicePath (ImageDevicePath);
847       *FilePath = DuplicateDevicePath (LoadedImage->FilePath);
848       gBS->CloseProtocol(
849                   LoadedImage->DeviceHandle,
850                   &gEfiDevicePathProtocolGuid,
851                   gImageHandle,
852                   NULL);
853     }
854     gBS->CloseProtocol(
855                 gImageHandle,
856                 &gEfiLoadedImageProtocolGuid,
857                 gImageHandle,
858                 NULL);
859   }
860   return (Status);
861 }
862 
863 /**
864   Process all Uefi Shell 2.0 command line options.
865 
866   see Uefi Shell 2.0 section 3.2 for full details.
867 
868   the command line must resemble the following:
869 
870   shell.efi [ShellOpt-options] [options] [file-name [file-name-options]]
871 
872   ShellOpt-options  Options which control the initialization behavior of the shell.
873                     These options are read from the EFI global variable "ShellOpt"
874                     and are processed before options or file-name.
875 
876   options           Options which control the initialization behavior of the shell.
877 
878   file-name         The name of a UEFI shell application or script to be executed
879                     after initialization is complete. By default, if file-name is
880                     specified, then -nostartup is implied. Scripts are not supported
881                     by level 0.
882 
883   file-name-options The command-line options that are passed to file-name when it
884                     is invoked.
885 
886   This will initialize the ShellInfoObject.ShellInitSettings global variable.
887 
888   @retval EFI_SUCCESS           The variable is initialized.
889 **/
890 EFI_STATUS
ProcessCommandLine(VOID)891 ProcessCommandLine(
892   VOID
893   )
894 {
895   UINTN                           Size;
896   UINTN                           LoopVar;
897   CHAR16                          *CurrentArg;
898   CHAR16                          *DelayValueStr;
899   UINT64                          DelayValue;
900   EFI_STATUS                      Status;
901   EFI_UNICODE_COLLATION_PROTOCOL  *UnicodeCollation;
902 
903   // `file-name-options` will contain arguments to `file-name` that we don't
904   // know about. This would cause ShellCommandLineParse to error, so we parse
905   // arguments manually, ignoring those after the first thing that doesn't look
906   // like a shell option (which is assumed to be `file-name`).
907 
908   Status = gBS->LocateProtocol (
909                   &gEfiUnicodeCollation2ProtocolGuid,
910                   NULL,
911                   (VOID **) &UnicodeCollation
912                   );
913   if (EFI_ERROR (Status)) {
914     Status = gBS->LocateProtocol (
915                     &gEfiUnicodeCollationProtocolGuid,
916                     NULL,
917                     (VOID **) &UnicodeCollation
918                     );
919     if (EFI_ERROR (Status)) {
920       return Status;
921     }
922   }
923 
924   // Set default options
925   ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup      = FALSE;
926   ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup    = FALSE;
927   ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleOut = FALSE;
928   ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn  = FALSE;
929   ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt  = FALSE;
930   ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap        = FALSE;
931   ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion    = FALSE;
932   ShellInfoObject.ShellInitSettings.BitUnion.Bits.Delay        = FALSE;
933   ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit         = FALSE;
934   ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoNest       = FALSE;
935   ShellInfoObject.ShellInitSettings.Delay = 5;
936 
937   //
938   // Start LoopVar at 0 to parse only optional arguments at Argv[0]
939   // and parse other parameters from Argv[1].  This is for use case that
940   // UEFI Shell boot option is created, and OptionalData is provided
941   // that starts with shell command-line options.
942   //
943   for (LoopVar = 0 ; LoopVar < gEfiShellParametersProtocol->Argc ; LoopVar++) {
944     CurrentArg = gEfiShellParametersProtocol->Argv[LoopVar];
945     if (UnicodeCollation->StriColl (
946                             UnicodeCollation,
947                             L"-startup",
948                             CurrentArg
949                             ) == 0) {
950       ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup      = TRUE;
951     }
952     else if (UnicodeCollation->StriColl (
953                                  UnicodeCollation,
954                                  L"-nostartup",
955                                  CurrentArg
956                                  ) == 0) {
957       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup    = TRUE;
958     }
959     else if (UnicodeCollation->StriColl (
960                                  UnicodeCollation,
961                                  L"-noconsoleout",
962                                  CurrentArg
963                                  ) == 0) {
964       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleOut = TRUE;
965     }
966     else if (UnicodeCollation->StriColl (
967                                  UnicodeCollation,
968                                  L"-noconsolein",
969                                  CurrentArg
970                                  ) == 0) {
971       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn  = TRUE;
972     }
973     else if (UnicodeCollation->StriColl (
974                                  UnicodeCollation,
975                                  L"-nointerrupt",
976                                  CurrentArg
977                                  ) == 0) {
978       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt  = TRUE;
979     }
980     else if (UnicodeCollation->StriColl (
981                                  UnicodeCollation,
982                                  L"-nomap",
983                                  CurrentArg
984                                  ) == 0) {
985       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap        = TRUE;
986     }
987     else if (UnicodeCollation->StriColl (
988                                  UnicodeCollation,
989                                  L"-noversion",
990                                  CurrentArg
991                                  ) == 0) {
992       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion    = TRUE;
993     }
994     else if (UnicodeCollation->StriColl (
995                                  UnicodeCollation,
996                                  L"-nonest",
997                                  CurrentArg
998                                  ) == 0) {
999       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoNest       = TRUE;
1000     }
1001     else if (UnicodeCollation->StriColl (
1002                                  UnicodeCollation,
1003                                  L"-delay",
1004                                  CurrentArg
1005                                  ) == 0) {
1006       ShellInfoObject.ShellInitSettings.BitUnion.Bits.Delay        = TRUE;
1007       // Check for optional delay value following "-delay"
1008       DelayValueStr = gEfiShellParametersProtocol->Argv[LoopVar + 1];
1009       if (DelayValueStr != NULL){
1010         if (*DelayValueStr == L':') {
1011           DelayValueStr++;
1012         }
1013         if (!EFI_ERROR(ShellConvertStringToUint64 (
1014                         DelayValueStr,
1015                         &DelayValue,
1016                         FALSE,
1017                         FALSE
1018                         ))) {
1019           ShellInfoObject.ShellInitSettings.Delay = (UINTN)DelayValue;
1020           LoopVar++;
1021         }
1022       }
1023     } else if (UnicodeCollation->StriColl (
1024                                    UnicodeCollation,
1025                                    L"-_exit",
1026                                    CurrentArg
1027                                    ) == 0) {
1028       ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit         = TRUE;
1029     } else if (StrnCmp (L"-", CurrentArg, 1) == 0) {
1030       // Unrecognized option
1031       ShellPrintHiiEx(-1, -1, NULL,
1032         STRING_TOKEN (STR_GEN_PROBLEM),
1033         ShellInfoObject.HiiHandle,
1034         CurrentArg
1035         );
1036       return EFI_INVALID_PARAMETER;
1037     } else {
1038       //
1039       // First argument should be Shell.efi image name
1040       //
1041       if (LoopVar == 0) {
1042         continue;
1043       }
1044 
1045       ShellInfoObject.ShellInitSettings.FileName = AllocateCopyPool(StrSize(CurrentArg), CurrentArg);
1046       if (ShellInfoObject.ShellInitSettings.FileName == NULL) {
1047         return (EFI_OUT_OF_RESOURCES);
1048       }
1049       //
1050       // We found `file-name`.
1051       //
1052       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = 1;
1053       LoopVar++;
1054 
1055       // Add `file-name-options`
1056       for (Size = 0 ; LoopVar < gEfiShellParametersProtocol->Argc ; LoopVar++) {
1057         ASSERT((ShellInfoObject.ShellInitSettings.FileOptions == NULL && Size == 0) || (ShellInfoObject.ShellInitSettings.FileOptions != NULL));
1058         StrnCatGrow(&ShellInfoObject.ShellInitSettings.FileOptions,
1059                     &Size,
1060                     L" ",
1061                     0);
1062         if (ShellInfoObject.ShellInitSettings.FileOptions == NULL) {
1063           SHELL_FREE_NON_NULL(ShellInfoObject.ShellInitSettings.FileName);
1064           return (EFI_OUT_OF_RESOURCES);
1065         }
1066         StrnCatGrow(&ShellInfoObject.ShellInitSettings.FileOptions,
1067                     &Size,
1068                     gEfiShellParametersProtocol->Argv[LoopVar],
1069                     0);
1070         if (ShellInfoObject.ShellInitSettings.FileOptions == NULL) {
1071           SHELL_FREE_NON_NULL(ShellInfoObject.ShellInitSettings.FileName);
1072           return (EFI_OUT_OF_RESOURCES);
1073         }
1074       }
1075     }
1076   }
1077 
1078   // "-nointerrupt" overrides "-delay"
1079   if (ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt) {
1080     ShellInfoObject.ShellInitSettings.Delay = 0;
1081   }
1082 
1083   return EFI_SUCCESS;
1084 }
1085 
1086 /**
1087   Handles all interaction with the default startup script.
1088 
1089   this will check that the correct command line parameters were passed, handle the delay, and then start running the script.
1090 
1091   @param ImagePath              the path to the image for shell.  first place to look for the startup script
1092   @param FilePath               the path to the file for shell.  second place to look for the startup script.
1093 
1094   @retval EFI_SUCCESS           the variable is initialized.
1095 **/
1096 EFI_STATUS
DoStartupScript(IN EFI_DEVICE_PATH_PROTOCOL * ImagePath,IN EFI_DEVICE_PATH_PROTOCOL * FilePath)1097 DoStartupScript(
1098   IN EFI_DEVICE_PATH_PROTOCOL *ImagePath,
1099   IN EFI_DEVICE_PATH_PROTOCOL *FilePath
1100   )
1101 {
1102   EFI_STATUS                    Status;
1103   EFI_STATUS                    CalleeStatus;
1104   UINTN                         Delay;
1105   EFI_INPUT_KEY                 Key;
1106   SHELL_FILE_HANDLE             FileHandle;
1107   EFI_DEVICE_PATH_PROTOCOL      *NewPath;
1108   EFI_DEVICE_PATH_PROTOCOL      *NamePath;
1109   CHAR16                        *FileStringPath;
1110   CHAR16                        *TempSpot;
1111   UINTN                         NewSize;
1112   CONST CHAR16                  *MapName;
1113 
1114   Key.UnicodeChar = CHAR_NULL;
1115   Key.ScanCode    = 0;
1116   FileHandle      = NULL;
1117 
1118   if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup && ShellInfoObject.ShellInitSettings.FileName != NULL) {
1119     //
1120     // launch something else instead
1121     //
1122     NewSize = StrSize(ShellInfoObject.ShellInitSettings.FileName);
1123     if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) {
1124       NewSize += StrSize(ShellInfoObject.ShellInitSettings.FileOptions) + sizeof(CHAR16);
1125     }
1126     FileStringPath = AllocateZeroPool(NewSize);
1127     if (FileStringPath == NULL) {
1128       return (EFI_OUT_OF_RESOURCES);
1129     }
1130     StrCpyS(FileStringPath, NewSize/sizeof(CHAR16), ShellInfoObject.ShellInitSettings.FileName);
1131     if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) {
1132       StrnCatS(FileStringPath, NewSize/sizeof(CHAR16), L" ", NewSize/sizeof(CHAR16) - StrLen(FileStringPath) -1);
1133       StrnCatS(FileStringPath, NewSize/sizeof(CHAR16), ShellInfoObject.ShellInitSettings.FileOptions, NewSize/sizeof(CHAR16) - StrLen(FileStringPath) -1);
1134     }
1135     Status = RunShellCommand(FileStringPath, &CalleeStatus);
1136     if (ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit == TRUE) {
1137       ShellCommandRegisterExit(gEfiShellProtocol->BatchIsActive(), (UINT64)CalleeStatus);
1138     }
1139     FreePool(FileStringPath);
1140     return (Status);
1141 
1142   }
1143 
1144   //
1145   // for shell level 0 we do no scripts
1146   // Without the Startup bit overriding we allow for nostartup to prevent scripts
1147   //
1148   if ( (PcdGet8(PcdShellSupportLevel) < 1)
1149     || (ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup)
1150    ){
1151     return (EFI_SUCCESS);
1152   }
1153 
1154   gST->ConOut->EnableCursor(gST->ConOut, FALSE);
1155   //
1156   // print out our warning and see if they press a key
1157   //
1158   for ( Status = EFI_UNSUPPORTED, Delay = ShellInfoObject.ShellInitSettings.Delay
1159       ; Delay != 0 && EFI_ERROR(Status)
1160       ; Delay--
1161      ){
1162     ShellPrintHiiEx(0, gST->ConOut->Mode->CursorRow, NULL, STRING_TOKEN (STR_SHELL_STARTUP_QUESTION), ShellInfoObject.HiiHandle, Delay);
1163     gBS->Stall (1000000);
1164     if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) {
1165       Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
1166     }
1167   }
1168   ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_CRLF), ShellInfoObject.HiiHandle);
1169   gST->ConOut->EnableCursor(gST->ConOut, TRUE);
1170 
1171   //
1172   // ESC was pressed
1173   //
1174   if (Status == EFI_SUCCESS && Key.UnicodeChar == 0 && Key.ScanCode == SCAN_ESC) {
1175     return (EFI_SUCCESS);
1176   }
1177 
1178   //
1179   // Try the first location (must be file system)
1180   //
1181   MapName = ShellInfoObject.NewEfiShellProtocol->GetMapFromDevicePath(&ImagePath);
1182   if (MapName != NULL) {
1183     FileStringPath = NULL;
1184     NewSize = 0;
1185     FileStringPath = StrnCatGrow(&FileStringPath, &NewSize, MapName, 0);
1186     if (FileStringPath == NULL) {
1187       Status = EFI_OUT_OF_RESOURCES;
1188     } else {
1189       TempSpot = StrStr(FileStringPath, L";");
1190       if (TempSpot != NULL) {
1191         *TempSpot = CHAR_NULL;
1192       }
1193       FileStringPath = StrnCatGrow(&FileStringPath, &NewSize, ((FILEPATH_DEVICE_PATH*)FilePath)->PathName, 0);
1194       PathRemoveLastItem(FileStringPath);
1195       FileStringPath = StrnCatGrow(&FileStringPath, &NewSize, mStartupScript, 0);
1196       Status = ShellInfoObject.NewEfiShellProtocol->OpenFileByName(FileStringPath, &FileHandle, EFI_FILE_MODE_READ);
1197       FreePool(FileStringPath);
1198     }
1199   }
1200   if (EFI_ERROR(Status)) {
1201     NamePath = FileDevicePath (NULL, mStartupScript);
1202     NewPath = AppendDevicePathNode (ImagePath, NamePath);
1203     FreePool(NamePath);
1204 
1205     //
1206     // Try the location
1207     //
1208     Status = InternalOpenFileDevicePath(NewPath, &FileHandle, EFI_FILE_MODE_READ, 0);
1209     FreePool(NewPath);
1210   }
1211   //
1212   // If we got a file, run it
1213   //
1214   if (!EFI_ERROR(Status) && FileHandle != NULL) {
1215     Status = RunScriptFile (mStartupScript, FileHandle, L"", ShellInfoObject.NewShellParametersProtocol);
1216     ShellInfoObject.NewEfiShellProtocol->CloseFile(FileHandle);
1217   } else {
1218     FileStringPath = ShellFindFilePath(mStartupScript);
1219     if (FileStringPath == NULL) {
1220       //
1221       // we return success since we don't need to have a startup script
1222       //
1223       Status = EFI_SUCCESS;
1224       ASSERT(FileHandle == NULL);
1225     } else {
1226       Status = RunScriptFile(FileStringPath, NULL, L"", ShellInfoObject.NewShellParametersProtocol);
1227       FreePool(FileStringPath);
1228     }
1229   }
1230 
1231 
1232   return (Status);
1233 }
1234 
1235 /**
1236   Function to perform the shell prompt looping.  It will do a single prompt,
1237   dispatch the result, and then return.  It is expected that the caller will
1238   call this function in a loop many times.
1239 
1240   @retval EFI_SUCCESS
1241   @retval RETURN_ABORTED
1242 **/
1243 EFI_STATUS
DoShellPrompt(VOID)1244 DoShellPrompt (
1245   VOID
1246   )
1247 {
1248   UINTN         Column;
1249   UINTN         Row;
1250   CHAR16        *CmdLine;
1251   CONST CHAR16  *CurDir;
1252   UINTN         BufferSize;
1253   EFI_STATUS    Status;
1254   LIST_ENTRY    OldBufferList;
1255 
1256   CurDir  = NULL;
1257 
1258   //
1259   // Get screen setting to decide size of the command line buffer
1260   //
1261   gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &Column, &Row);
1262   BufferSize  = Column * Row * sizeof (CHAR16);
1263   CmdLine     = AllocateZeroPool (BufferSize);
1264   if (CmdLine == NULL) {
1265     return EFI_OUT_OF_RESOURCES;
1266   }
1267 
1268   SaveBufferList(&OldBufferList);
1269   CurDir = ShellInfoObject.NewEfiShellProtocol->GetEnv(L"cwd");
1270 
1271   //
1272   // Prompt for input
1273   //
1274   gST->ConOut->SetCursorPosition (gST->ConOut, 0, gST->ConOut->Mode->CursorRow);
1275 
1276   if (CurDir != NULL && StrLen(CurDir) > 1) {
1277     ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_CURDIR), ShellInfoObject.HiiHandle, CurDir);
1278   } else {
1279     ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_SHELL), ShellInfoObject.HiiHandle);
1280   }
1281 
1282   //
1283   // Read a line from the console
1284   //
1285   Status = ShellInfoObject.NewEfiShellProtocol->ReadFile(ShellInfoObject.NewShellParametersProtocol->StdIn, &BufferSize, CmdLine);
1286 
1287   //
1288   // Null terminate the string and parse it
1289   //
1290   if (!EFI_ERROR (Status)) {
1291     CmdLine[BufferSize / sizeof (CHAR16)] = CHAR_NULL;
1292     Status = RunCommand(CmdLine);
1293     }
1294 
1295   //
1296   // Done with this command
1297   //
1298   RestoreBufferList(&OldBufferList);
1299   FreePool (CmdLine);
1300   return Status;
1301 }
1302 
1303 /**
1304   Add a buffer to the Buffer To Free List for safely returning buffers to other
1305   places without risking letting them modify internal shell information.
1306 
1307   @param Buffer   Something to pass to FreePool when the shell is exiting.
1308 **/
1309 VOID*
AddBufferToFreeList(VOID * Buffer)1310 AddBufferToFreeList (
1311   VOID *Buffer
1312   )
1313 {
1314   BUFFER_LIST   *BufferListEntry;
1315 
1316   if (Buffer == NULL) {
1317     return (NULL);
1318   }
1319 
1320   BufferListEntry = AllocateZeroPool (sizeof (BUFFER_LIST));
1321   if (BufferListEntry == NULL) {
1322     return NULL;
1323   }
1324 
1325   BufferListEntry->Buffer = Buffer;
1326   InsertTailList (&ShellInfoObject.BufferToFreeList.Link, &BufferListEntry->Link);
1327   return (Buffer);
1328 }
1329 
1330 
1331 /**
1332   Create a new buffer list and stores the old one to OldBufferList
1333 
1334   @param OldBufferList   The temporary list head used to store the nodes in BufferToFreeList.
1335 **/
1336 VOID
SaveBufferList(OUT LIST_ENTRY * OldBufferList)1337 SaveBufferList (
1338   OUT LIST_ENTRY     *OldBufferList
1339   )
1340 {
1341   CopyMem (OldBufferList, &ShellInfoObject.BufferToFreeList.Link, sizeof (LIST_ENTRY));
1342   InitializeListHead (&ShellInfoObject.BufferToFreeList.Link);
1343 }
1344 
1345 /**
1346   Restore previous nodes into BufferToFreeList .
1347 
1348   @param OldBufferList   The temporary list head used to store the nodes in BufferToFreeList.
1349 **/
1350 VOID
RestoreBufferList(IN OUT LIST_ENTRY * OldBufferList)1351 RestoreBufferList (
1352   IN OUT LIST_ENTRY     *OldBufferList
1353   )
1354 {
1355   FreeBufferList (&ShellInfoObject.BufferToFreeList);
1356   CopyMem (&ShellInfoObject.BufferToFreeList.Link, OldBufferList, sizeof (LIST_ENTRY));
1357 }
1358 
1359 
1360 /**
1361   Add a buffer to the Line History List
1362 
1363   @param Buffer     The line buffer to add.
1364 **/
1365 VOID
AddLineToCommandHistory(IN CONST CHAR16 * Buffer)1366 AddLineToCommandHistory(
1367   IN CONST CHAR16 *Buffer
1368   )
1369 {
1370   BUFFER_LIST *Node;
1371   BUFFER_LIST *Walker;
1372   UINT16       MaxHistoryCmdCount;
1373   UINT16       Count;
1374 
1375   Count = 0;
1376   MaxHistoryCmdCount = PcdGet16(PcdShellMaxHistoryCommandCount);
1377 
1378   if (MaxHistoryCmdCount == 0) {
1379     return ;
1380   }
1381 
1382 
1383   Node = AllocateZeroPool(sizeof(BUFFER_LIST));
1384   if (Node == NULL) {
1385     return;
1386   }
1387 
1388   Node->Buffer = AllocateCopyPool (StrSize (Buffer), Buffer);
1389   if (Node->Buffer == NULL) {
1390     FreePool (Node);
1391     return;
1392   }
1393 
1394   for ( Walker = (BUFFER_LIST*)GetFirstNode(&ShellInfoObject.ViewingSettings.CommandHistory.Link)
1395       ; !IsNull(&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Walker->Link)
1396       ; Walker = (BUFFER_LIST*)GetNextNode(&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Walker->Link)
1397    ){
1398     Count++;
1399   }
1400   if (Count < MaxHistoryCmdCount){
1401     InsertTailList(&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Node->Link);
1402   } else {
1403     Walker = (BUFFER_LIST*)GetFirstNode(&ShellInfoObject.ViewingSettings.CommandHistory.Link);
1404     RemoveEntryList(&Walker->Link);
1405     if (Walker->Buffer != NULL) {
1406       FreePool(Walker->Buffer);
1407     }
1408     FreePool(Walker);
1409     InsertTailList(&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Node->Link);
1410   }
1411 }
1412 
1413 /**
1414   Checks if a string is an alias for another command.  If yes, then it replaces the alias name
1415   with the correct command name.
1416 
1417   @param[in, out] CommandString    Upon entry the potential alias.  Upon return the
1418                                    command name if it was an alias.  If it was not
1419                                    an alias it will be unchanged.  This function may
1420                                    change the buffer to fit the command name.
1421 
1422   @retval EFI_SUCCESS             The name was changed.
1423   @retval EFI_SUCCESS             The name was not an alias.
1424   @retval EFI_OUT_OF_RESOURCES    A memory allocation failed.
1425 **/
1426 EFI_STATUS
ShellConvertAlias(IN OUT CHAR16 ** CommandString)1427 ShellConvertAlias(
1428   IN OUT CHAR16 **CommandString
1429   )
1430 {
1431   CONST CHAR16  *NewString;
1432 
1433   NewString = ShellInfoObject.NewEfiShellProtocol->GetAlias(*CommandString, NULL);
1434   if (NewString == NULL) {
1435     return (EFI_SUCCESS);
1436   }
1437   FreePool(*CommandString);
1438   *CommandString = AllocateCopyPool(StrSize(NewString), NewString);
1439   if (*CommandString == NULL) {
1440     return (EFI_OUT_OF_RESOURCES);
1441   }
1442   return (EFI_SUCCESS);
1443 }
1444 
1445 /**
1446   This function will eliminate unreplaced (and therefore non-found) environment variables.
1447 
1448   @param[in,out] CmdLine   The command line to update.
1449 **/
1450 EFI_STATUS
StripUnreplacedEnvironmentVariables(IN OUT CHAR16 * CmdLine)1451 StripUnreplacedEnvironmentVariables(
1452   IN OUT CHAR16 *CmdLine
1453   )
1454 {
1455   CHAR16 *FirstPercent;
1456   CHAR16 *FirstQuote;
1457   CHAR16 *SecondPercent;
1458   CHAR16 *SecondQuote;
1459   CHAR16 *CurrentLocator;
1460 
1461   for (CurrentLocator = CmdLine ; CurrentLocator != NULL ; ) {
1462     FirstQuote = FindNextInstance(CurrentLocator, L"\"", TRUE);
1463     FirstPercent = FindNextInstance(CurrentLocator, L"%", TRUE);
1464     SecondPercent = FirstPercent!=NULL?FindNextInstance(FirstPercent+1, L"%", TRUE):NULL;
1465     if (FirstPercent == NULL || SecondPercent == NULL) {
1466       //
1467       // If we ever don't have 2 % we are done.
1468       //
1469       break;
1470     }
1471 
1472     if (FirstQuote!= NULL && FirstQuote < FirstPercent) {
1473       SecondQuote = FindNextInstance(FirstQuote+1, L"\"", TRUE);
1474       //
1475       // Quote is first found
1476       //
1477 
1478       if (SecondQuote < FirstPercent) {
1479         //
1480         // restart after the pair of "
1481         //
1482         CurrentLocator = SecondQuote + 1;
1483       } else /* FirstPercent < SecondQuote */{
1484         //
1485         // Restart on the first percent
1486         //
1487         CurrentLocator = FirstPercent;
1488       }
1489       continue;
1490     }
1491 
1492     if (FirstQuote == NULL || SecondPercent < FirstQuote) {
1493       if (IsValidEnvironmentVariableName(FirstPercent, SecondPercent)) {
1494         //
1495         // We need to remove from FirstPercent to SecondPercent
1496         //
1497         CopyMem(FirstPercent, SecondPercent + 1, StrSize(SecondPercent + 1));
1498         //
1499         // don't need to update the locator.  both % characters are gone.
1500         //
1501       } else {
1502         CurrentLocator = SecondPercent + 1;
1503       }
1504       continue;
1505     }
1506     CurrentLocator = FirstQuote;
1507   }
1508   return (EFI_SUCCESS);
1509 }
1510 
1511 /**
1512   Function allocates a new command line and replaces all instances of environment
1513   variable names that are correctly preset to their values.
1514 
1515   If the return value is not NULL the memory must be caller freed.
1516 
1517   @param[in] OriginalCommandLine    The original command line
1518 
1519   @retval NULL                      An error occurred.
1520   @return                           The new command line with no environment variables present.
1521 **/
1522 CHAR16*
ShellConvertVariables(IN CONST CHAR16 * OriginalCommandLine)1523 ShellConvertVariables (
1524   IN CONST CHAR16 *OriginalCommandLine
1525   )
1526 {
1527   CONST CHAR16        *MasterEnvList;
1528   UINTN               NewSize;
1529   CHAR16              *NewCommandLine1;
1530   CHAR16              *NewCommandLine2;
1531   CHAR16              *Temp;
1532   UINTN               ItemSize;
1533   CHAR16              *ItemTemp;
1534   SCRIPT_FILE         *CurrentScriptFile;
1535   ALIAS_LIST          *AliasListNode;
1536 
1537   ASSERT(OriginalCommandLine != NULL);
1538 
1539   ItemSize          = 0;
1540   NewSize           = StrSize(OriginalCommandLine);
1541   CurrentScriptFile = ShellCommandGetCurrentScriptFile();
1542   Temp              = NULL;
1543 
1544   ///@todo update this to handle the %0 - %9 for scripting only (borrow from line 1256 area) ? ? ?
1545 
1546   //
1547   // calculate the size required for the post-conversion string...
1548   //
1549   if (CurrentScriptFile != NULL) {
1550     for (AliasListNode = (ALIAS_LIST*)GetFirstNode(&CurrentScriptFile->SubstList)
1551       ;  !IsNull(&CurrentScriptFile->SubstList, &AliasListNode->Link)
1552       ;  AliasListNode = (ALIAS_LIST*)GetNextNode(&CurrentScriptFile->SubstList, &AliasListNode->Link)
1553    ){
1554       for (Temp = StrStr(OriginalCommandLine, AliasListNode->Alias)
1555         ;  Temp != NULL
1556         ;  Temp = StrStr(Temp+1, AliasListNode->Alias)
1557        ){
1558         //
1559         // we need a preceding and if there is space no ^ preceding (if no space ignore)
1560         //
1561         if ((((Temp-OriginalCommandLine)>2) && *(Temp-2) != L'^') || ((Temp-OriginalCommandLine)<=2)) {
1562           NewSize += StrSize(AliasListNode->CommandString);
1563         }
1564       }
1565     }
1566   }
1567 
1568   for (MasterEnvList = EfiShellGetEnv(NULL)
1569     ;  MasterEnvList != NULL && *MasterEnvList != CHAR_NULL //&& *(MasterEnvList+1) != CHAR_NULL
1570     ;  MasterEnvList += StrLen(MasterEnvList) + 1
1571    ){
1572     if (StrSize(MasterEnvList) > ItemSize) {
1573       ItemSize = StrSize(MasterEnvList);
1574     }
1575     for (Temp = StrStr(OriginalCommandLine, MasterEnvList)
1576       ;  Temp != NULL
1577       ;  Temp = StrStr(Temp+1, MasterEnvList)
1578      ){
1579       //
1580       // we need a preceding and following % and if there is space no ^ preceding (if no space ignore)
1581       //
1582       if (*(Temp-1) == L'%' && *(Temp+StrLen(MasterEnvList)) == L'%' &&
1583         ((((Temp-OriginalCommandLine)>2) && *(Temp-2) != L'^') || ((Temp-OriginalCommandLine)<=2))) {
1584         NewSize+=StrSize(EfiShellGetEnv(MasterEnvList));
1585       }
1586     }
1587   }
1588 
1589   //
1590   // now do the replacements...
1591   //
1592   NewCommandLine1 = AllocateCopyPool(NewSize, OriginalCommandLine);
1593   NewCommandLine2 = AllocateZeroPool(NewSize);
1594   ItemTemp        = AllocateZeroPool(ItemSize+(2*sizeof(CHAR16)));
1595   if (NewCommandLine1 == NULL || NewCommandLine2 == NULL || ItemTemp == NULL) {
1596     SHELL_FREE_NON_NULL(NewCommandLine1);
1597     SHELL_FREE_NON_NULL(NewCommandLine2);
1598     SHELL_FREE_NON_NULL(ItemTemp);
1599     return (NULL);
1600   }
1601   for (MasterEnvList = EfiShellGetEnv(NULL)
1602     ;  MasterEnvList != NULL && *MasterEnvList != CHAR_NULL
1603     ;  MasterEnvList += StrLen(MasterEnvList) + 1
1604    ){
1605     StrCpyS( ItemTemp,
1606               ((ItemSize+(2*sizeof(CHAR16)))/sizeof(CHAR16)),
1607               L"%"
1608               );
1609     StrCatS( ItemTemp,
1610               ((ItemSize+(2*sizeof(CHAR16)))/sizeof(CHAR16)),
1611               MasterEnvList
1612               );
1613     StrCatS( ItemTemp,
1614               ((ItemSize+(2*sizeof(CHAR16)))/sizeof(CHAR16)),
1615               L"%"
1616               );
1617     ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, ItemTemp, EfiShellGetEnv(MasterEnvList), TRUE, FALSE);
1618     StrCpyS(NewCommandLine1, NewSize/sizeof(CHAR16), NewCommandLine2);
1619   }
1620   if (CurrentScriptFile != NULL) {
1621     for (AliasListNode = (ALIAS_LIST*)GetFirstNode(&CurrentScriptFile->SubstList)
1622       ;  !IsNull(&CurrentScriptFile->SubstList, &AliasListNode->Link)
1623       ;  AliasListNode = (ALIAS_LIST*)GetNextNode(&CurrentScriptFile->SubstList, &AliasListNode->Link)
1624    ){
1625     ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, AliasListNode->Alias, AliasListNode->CommandString, TRUE, FALSE);
1626     StrCpyS(NewCommandLine1, NewSize/sizeof(CHAR16), NewCommandLine2);
1627     }
1628   }
1629 
1630   //
1631   // Remove non-existent environment variables
1632   //
1633   StripUnreplacedEnvironmentVariables(NewCommandLine1);
1634 
1635   //
1636   // Now cleanup any straggler intentionally ignored "%" characters
1637   //
1638   ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, L"^%", L"%", TRUE, FALSE);
1639   StrCpyS(NewCommandLine1, NewSize/sizeof(CHAR16), NewCommandLine2);
1640 
1641   FreePool(NewCommandLine2);
1642   FreePool(ItemTemp);
1643 
1644   return (NewCommandLine1);
1645 }
1646 
1647 /**
1648   Internal function to run a command line with pipe usage.
1649 
1650   @param[in] CmdLine        The pointer to the command line.
1651   @param[in] StdIn          The pointer to the Standard input.
1652   @param[in] StdOut         The pointer to the Standard output.
1653 
1654   @retval EFI_SUCCESS       The split command is executed successfully.
1655   @retval other             Some error occurs when executing the split command.
1656 **/
1657 EFI_STATUS
RunSplitCommand(IN CONST CHAR16 * CmdLine,IN SHELL_FILE_HANDLE * StdIn,IN SHELL_FILE_HANDLE * StdOut)1658 RunSplitCommand(
1659   IN CONST CHAR16             *CmdLine,
1660   IN       SHELL_FILE_HANDLE  *StdIn,
1661   IN       SHELL_FILE_HANDLE  *StdOut
1662   )
1663 {
1664   EFI_STATUS        Status;
1665   CHAR16            *NextCommandLine;
1666   CHAR16            *OurCommandLine;
1667   UINTN             Size1;
1668   UINTN             Size2;
1669   SPLIT_LIST        *Split;
1670   SHELL_FILE_HANDLE *TempFileHandle;
1671   BOOLEAN           Unicode;
1672 
1673   ASSERT(StdOut == NULL);
1674 
1675   ASSERT(StrStr(CmdLine, L"|") != NULL);
1676 
1677   Status          = EFI_SUCCESS;
1678   NextCommandLine = NULL;
1679   OurCommandLine  = NULL;
1680   Size1           = 0;
1681   Size2           = 0;
1682 
1683   NextCommandLine = StrnCatGrow(&NextCommandLine, &Size1, StrStr(CmdLine, L"|")+1, 0);
1684   OurCommandLine  = StrnCatGrow(&OurCommandLine , &Size2, CmdLine                , StrStr(CmdLine, L"|") - CmdLine);
1685 
1686   if (NextCommandLine == NULL || OurCommandLine == NULL) {
1687     SHELL_FREE_NON_NULL(OurCommandLine);
1688     SHELL_FREE_NON_NULL(NextCommandLine);
1689     return (EFI_OUT_OF_RESOURCES);
1690   } else if (StrStr(OurCommandLine, L"|") != NULL || Size1 == 0 || Size2 == 0) {
1691     SHELL_FREE_NON_NULL(OurCommandLine);
1692     SHELL_FREE_NON_NULL(NextCommandLine);
1693     return (EFI_INVALID_PARAMETER);
1694   } else if (NextCommandLine[0] == L'a' &&
1695              (NextCommandLine[1] == L' ' || NextCommandLine[1] == CHAR_NULL)
1696             ){
1697     CopyMem(NextCommandLine, NextCommandLine+1, StrSize(NextCommandLine) - sizeof(NextCommandLine[0]));
1698     while (NextCommandLine[0] == L' ') {
1699       CopyMem(NextCommandLine, NextCommandLine+1, StrSize(NextCommandLine) - sizeof(NextCommandLine[0]));
1700     }
1701     if (NextCommandLine[0] == CHAR_NULL) {
1702       SHELL_FREE_NON_NULL(OurCommandLine);
1703       SHELL_FREE_NON_NULL(NextCommandLine);
1704       return (EFI_INVALID_PARAMETER);
1705     }
1706     Unicode = FALSE;
1707   } else {
1708     Unicode = TRUE;
1709   }
1710 
1711 
1712   //
1713   // make a SPLIT_LIST item and add to list
1714   //
1715   Split = AllocateZeroPool(sizeof(SPLIT_LIST));
1716   if (Split == NULL) {
1717     return EFI_OUT_OF_RESOURCES;
1718   }
1719   Split->SplitStdIn   = StdIn;
1720   Split->SplitStdOut  = ConvertEfiFileProtocolToShellHandle(CreateFileInterfaceMem(Unicode), NULL);
1721   ASSERT(Split->SplitStdOut != NULL);
1722   InsertHeadList(&ShellInfoObject.SplitList.Link, &Split->Link);
1723 
1724   Status = RunCommand(OurCommandLine);
1725 
1726   //
1727   // move the output from the first to the in to the second.
1728   //
1729   TempFileHandle      = Split->SplitStdOut;
1730   if (Split->SplitStdIn == StdIn) {
1731     Split->SplitStdOut = NULL;
1732   } else {
1733     Split->SplitStdOut  = Split->SplitStdIn;
1734   }
1735   Split->SplitStdIn   = TempFileHandle;
1736   ShellInfoObject.NewEfiShellProtocol->SetFilePosition(ConvertShellHandleToEfiFileProtocol(Split->SplitStdIn), 0);
1737 
1738   if (!EFI_ERROR(Status)) {
1739     Status = RunCommand(NextCommandLine);
1740   }
1741 
1742   //
1743   // remove the top level from the ScriptList
1744   //
1745   ASSERT((SPLIT_LIST*)GetFirstNode(&ShellInfoObject.SplitList.Link) == Split);
1746   RemoveEntryList(&Split->Link);
1747 
1748   //
1749   // Note that the original StdIn is now the StdOut...
1750   //
1751   if (Split->SplitStdOut != NULL) {
1752     ShellInfoObject.NewEfiShellProtocol->CloseFile(ConvertShellHandleToEfiFileProtocol(Split->SplitStdOut));
1753   }
1754   if (Split->SplitStdIn != NULL) {
1755     ShellInfoObject.NewEfiShellProtocol->CloseFile(ConvertShellHandleToEfiFileProtocol(Split->SplitStdIn));
1756     FreePool (Split->SplitStdIn);
1757   }
1758 
1759   FreePool(Split);
1760   FreePool(NextCommandLine);
1761   FreePool(OurCommandLine);
1762 
1763   return (Status);
1764 }
1765 
1766 /**
1767   Take the original command line, substitute any variables, free
1768   the original string, return the modified copy.
1769 
1770   @param[in] CmdLine  pointer to the command line to update.
1771 
1772   @retval EFI_SUCCESS           the function was successful.
1773   @retval EFI_OUT_OF_RESOURCES  a memory allocation failed.
1774 **/
1775 EFI_STATUS
ShellSubstituteVariables(IN CHAR16 ** CmdLine)1776 ShellSubstituteVariables(
1777   IN CHAR16 **CmdLine
1778   )
1779 {
1780   CHAR16      *NewCmdLine;
1781   NewCmdLine = ShellConvertVariables(*CmdLine);
1782   SHELL_FREE_NON_NULL(*CmdLine);
1783   if (NewCmdLine == NULL) {
1784     return (EFI_OUT_OF_RESOURCES);
1785   }
1786   *CmdLine = NewCmdLine;
1787   return (EFI_SUCCESS);
1788 }
1789 
1790 /**
1791   Take the original command line, substitute any alias in the first group of space delimited characters, free
1792   the original string, return the modified copy.
1793 
1794   @param[in] CmdLine  pointer to the command line to update.
1795 
1796   @retval EFI_SUCCESS           the function was successful.
1797   @retval EFI_OUT_OF_RESOURCES  a memory allocation failed.
1798 **/
1799 EFI_STATUS
ShellSubstituteAliases(IN CHAR16 ** CmdLine)1800 ShellSubstituteAliases(
1801   IN CHAR16 **CmdLine
1802   )
1803 {
1804   CHAR16      *NewCmdLine;
1805   CHAR16      *CommandName;
1806   EFI_STATUS  Status;
1807   UINTN       PostAliasSize;
1808   ASSERT(CmdLine != NULL);
1809   ASSERT(*CmdLine!= NULL);
1810 
1811 
1812   CommandName = NULL;
1813   if (StrStr((*CmdLine), L" ") == NULL){
1814     StrnCatGrow(&CommandName, NULL, (*CmdLine), 0);
1815   } else {
1816     StrnCatGrow(&CommandName, NULL, (*CmdLine), StrStr((*CmdLine), L" ") - (*CmdLine));
1817   }
1818 
1819   //
1820   // This cannot happen 'inline' since the CmdLine can need extra space.
1821   //
1822   NewCmdLine = NULL;
1823   if (!ShellCommandIsCommandOnList(CommandName)) {
1824     //
1825     // Convert via alias
1826     //
1827     Status = ShellConvertAlias(&CommandName);
1828     if (EFI_ERROR(Status)){
1829       return (Status);
1830     }
1831     PostAliasSize = 0;
1832     NewCmdLine = StrnCatGrow(&NewCmdLine, &PostAliasSize, CommandName, 0);
1833     if (NewCmdLine == NULL) {
1834       SHELL_FREE_NON_NULL(CommandName);
1835       SHELL_FREE_NON_NULL(*CmdLine);
1836       return (EFI_OUT_OF_RESOURCES);
1837     }
1838     NewCmdLine = StrnCatGrow(&NewCmdLine, &PostAliasSize, StrStr((*CmdLine), L" "), 0);
1839     if (NewCmdLine == NULL) {
1840       SHELL_FREE_NON_NULL(CommandName);
1841       SHELL_FREE_NON_NULL(*CmdLine);
1842       return (EFI_OUT_OF_RESOURCES);
1843     }
1844   } else {
1845     NewCmdLine = StrnCatGrow(&NewCmdLine, NULL, (*CmdLine), 0);
1846   }
1847 
1848   SHELL_FREE_NON_NULL(*CmdLine);
1849   SHELL_FREE_NON_NULL(CommandName);
1850 
1851   //
1852   // re-assign the passed in double pointer to point to our newly allocated buffer
1853   //
1854   *CmdLine = NewCmdLine;
1855 
1856   return (EFI_SUCCESS);
1857 }
1858 
1859 /**
1860   Takes the Argv[0] part of the command line and determine the meaning of it.
1861 
1862   @param[in] CmdName  pointer to the command line to update.
1863 
1864   @retval Internal_Command    The name is an internal command.
1865   @retval File_Sys_Change     the name is a file system change.
1866   @retval Script_File_Name    the name is a NSH script file.
1867   @retval Unknown_Invalid     the name is unknown.
1868   @retval Efi_Application     the name is an application (.EFI).
1869 **/
1870 SHELL_OPERATION_TYPES
GetOperationType(IN CONST CHAR16 * CmdName)1871 GetOperationType(
1872   IN CONST CHAR16 *CmdName
1873   )
1874 {
1875         CHAR16* FileWithPath;
1876   CONST CHAR16* TempLocation;
1877   CONST CHAR16* TempLocation2;
1878 
1879   FileWithPath = NULL;
1880   //
1881   // test for an internal command.
1882   //
1883   if (ShellCommandIsCommandOnList(CmdName)) {
1884     return (Internal_Command);
1885   }
1886 
1887   //
1888   // Test for file system change request.  anything ending with first : and cant have spaces.
1889   //
1890   if (CmdName[(StrLen(CmdName)-1)] == L':') {
1891     if ( StrStr(CmdName, L" ") != NULL
1892       || StrLen(StrStr(CmdName, L":")) > 1
1893       ) {
1894       return (Unknown_Invalid);
1895     }
1896     return (File_Sys_Change);
1897   }
1898 
1899   //
1900   // Test for a file
1901   //
1902   if ((FileWithPath = ShellFindFilePathEx(CmdName, mExecutableExtensions)) != NULL) {
1903     //
1904     // See if that file has a script file extension
1905     //
1906     if (StrLen(FileWithPath) > 4) {
1907       TempLocation = FileWithPath+StrLen(FileWithPath)-4;
1908       TempLocation2 = mScriptExtension;
1909       if (StringNoCaseCompare((VOID*)(&TempLocation), (VOID*)(&TempLocation2)) == 0) {
1910         SHELL_FREE_NON_NULL(FileWithPath);
1911         return (Script_File_Name);
1912       }
1913     }
1914 
1915     //
1916     // Was a file, but not a script.  we treat this as an application.
1917     //
1918     SHELL_FREE_NON_NULL(FileWithPath);
1919     return (Efi_Application);
1920   }
1921 
1922   SHELL_FREE_NON_NULL(FileWithPath);
1923   //
1924   // No clue what this is... return invalid flag...
1925   //
1926   return (Unknown_Invalid);
1927 }
1928 
1929 /**
1930   Determine if the first item in a command line is valid.
1931 
1932   @param[in] CmdLine            The command line to parse.
1933 
1934   @retval EFI_SUCCESS           The item is valid.
1935   @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
1936   @retval EFI_NOT_FOUND         The operation type is unknown or invalid.
1937 **/
1938 EFI_STATUS
IsValidSplit(IN CONST CHAR16 * CmdLine)1939 IsValidSplit(
1940   IN CONST CHAR16 *CmdLine
1941   )
1942 {
1943   CHAR16        *Temp;
1944   CHAR16        *FirstParameter;
1945   CHAR16        *TempWalker;
1946   EFI_STATUS    Status;
1947 
1948   Temp           = NULL;
1949 
1950   Temp = StrnCatGrow(&Temp, NULL, CmdLine, 0);
1951   if (Temp == NULL) {
1952     return (EFI_OUT_OF_RESOURCES);
1953   }
1954 
1955   FirstParameter = StrStr(Temp, L"|");
1956   if (FirstParameter != NULL) {
1957     *FirstParameter = CHAR_NULL;
1958   }
1959 
1960   FirstParameter = NULL;
1961 
1962   //
1963   // Process the command line
1964   //
1965   Status = ProcessCommandLineToFinal(&Temp);
1966 
1967   if (!EFI_ERROR(Status)) {
1968     FirstParameter = AllocateZeroPool(StrSize(CmdLine));
1969     if (FirstParameter == NULL) {
1970       SHELL_FREE_NON_NULL(Temp);
1971       return (EFI_OUT_OF_RESOURCES);
1972     }
1973     TempWalker = (CHAR16*)Temp;
1974     if (!EFI_ERROR(GetNextParameter(&TempWalker, &FirstParameter, StrSize(CmdLine), TRUE))) {
1975       if (GetOperationType(FirstParameter) == Unknown_Invalid) {
1976         ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter);
1977         SetLastError(SHELL_NOT_FOUND);
1978         Status = EFI_NOT_FOUND;
1979       }
1980     }
1981   }
1982 
1983   SHELL_FREE_NON_NULL(Temp);
1984   SHELL_FREE_NON_NULL(FirstParameter);
1985   return Status;
1986 }
1987 
1988 /**
1989   Determine if a command line contains with a split contains only valid commands.
1990 
1991   @param[in] CmdLine      The command line to parse.
1992 
1993   @retval EFI_SUCCESS     CmdLine has only valid commands, application, or has no split.
1994   @retval EFI_ABORTED     CmdLine has at least one invalid command or application.
1995 **/
1996 EFI_STATUS
VerifySplit(IN CONST CHAR16 * CmdLine)1997 VerifySplit(
1998   IN CONST CHAR16 *CmdLine
1999   )
2000 {
2001   CONST CHAR16  *TempSpot;
2002   EFI_STATUS    Status;
2003 
2004   //
2005   // If this was the only item, then get out
2006   //
2007   if (!ContainsSplit(CmdLine)) {
2008     return (EFI_SUCCESS);
2009   }
2010 
2011   //
2012   // Verify up to the pipe or end character
2013   //
2014   Status = IsValidSplit(CmdLine);
2015   if (EFI_ERROR(Status)) {
2016     return (Status);
2017   }
2018 
2019   //
2020   // recurse to verify the next item
2021   //
2022   TempSpot = FindFirstCharacter(CmdLine, L"|", L'^') + 1;
2023   if (*TempSpot == L'a' &&
2024       (*(TempSpot + 1) == L' ' || *(TempSpot + 1) == CHAR_NULL)
2025      ) {
2026     // If it's an ASCII pipe '|a'
2027     TempSpot += 1;
2028   }
2029 
2030   return (VerifySplit(TempSpot));
2031 }
2032 
2033 /**
2034   Process a split based operation.
2035 
2036   @param[in] CmdLine    pointer to the command line to process
2037 
2038   @retval EFI_SUCCESS   The operation was successful
2039   @return               an error occurred.
2040 **/
2041 EFI_STATUS
ProcessNewSplitCommandLine(IN CONST CHAR16 * CmdLine)2042 ProcessNewSplitCommandLine(
2043   IN CONST CHAR16 *CmdLine
2044   )
2045 {
2046   SPLIT_LIST                *Split;
2047   EFI_STATUS                Status;
2048 
2049   Status = VerifySplit(CmdLine);
2050   if (EFI_ERROR(Status)) {
2051     return (Status);
2052   }
2053 
2054   Split = NULL;
2055 
2056   //
2057   // are we in an existing split???
2058   //
2059   if (!IsListEmpty(&ShellInfoObject.SplitList.Link)) {
2060     Split = (SPLIT_LIST*)GetFirstNode(&ShellInfoObject.SplitList.Link);
2061   }
2062 
2063   if (Split == NULL) {
2064     Status = RunSplitCommand(CmdLine, NULL, NULL);
2065   } else {
2066     Status = RunSplitCommand(CmdLine, Split->SplitStdIn, Split->SplitStdOut);
2067   }
2068   if (EFI_ERROR(Status)) {
2069     ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_SPLIT), ShellInfoObject.HiiHandle, CmdLine);
2070   }
2071   return (Status);
2072 }
2073 
2074 /**
2075   Handle a request to change the current file system.
2076 
2077   @param[in] CmdLine  The passed in command line.
2078 
2079   @retval EFI_SUCCESS The operation was successful.
2080 **/
2081 EFI_STATUS
ChangeMappedDrive(IN CONST CHAR16 * CmdLine)2082 ChangeMappedDrive(
2083   IN CONST CHAR16 *CmdLine
2084   )
2085 {
2086   EFI_STATUS Status;
2087   Status = EFI_SUCCESS;
2088 
2089   //
2090   // make sure we are the right operation
2091   //
2092   ASSERT(CmdLine[(StrLen(CmdLine)-1)] == L':' && StrStr(CmdLine, L" ") == NULL);
2093 
2094   //
2095   // Call the protocol API to do the work
2096   //
2097   Status = ShellInfoObject.NewEfiShellProtocol->SetCurDir(NULL, CmdLine);
2098 
2099   //
2100   // Report any errors
2101   //
2102   if (EFI_ERROR(Status)) {
2103     ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_MAPPING), ShellInfoObject.HiiHandle, CmdLine);
2104   }
2105 
2106   return (Status);
2107 }
2108 
2109 /**
2110   Reprocess the command line to direct all -? to the help command.
2111 
2112   if found, will add "help" as argv[0], and move the rest later.
2113 
2114   @param[in,out] CmdLine        pointer to the command line to update
2115 **/
2116 EFI_STATUS
DoHelpUpdate(IN OUT CHAR16 ** CmdLine)2117 DoHelpUpdate(
2118   IN OUT CHAR16 **CmdLine
2119   )
2120 {
2121   CHAR16 *CurrentParameter;
2122   CHAR16 *Walker;
2123   CHAR16 *NewCommandLine;
2124   EFI_STATUS Status;
2125   UINTN  NewCmdLineSize;
2126 
2127   Status = EFI_SUCCESS;
2128 
2129   CurrentParameter = AllocateZeroPool(StrSize(*CmdLine));
2130   if (CurrentParameter == NULL) {
2131     return (EFI_OUT_OF_RESOURCES);
2132   }
2133 
2134   Walker = *CmdLine;
2135   while(Walker != NULL && *Walker != CHAR_NULL) {
2136     if (!EFI_ERROR(GetNextParameter(&Walker, &CurrentParameter, StrSize(*CmdLine), TRUE))) {
2137       if (StrStr(CurrentParameter, L"-?") == CurrentParameter) {
2138         CurrentParameter[0] = L' ';
2139         CurrentParameter[1] = L' ';
2140         NewCmdLineSize = StrSize(L"help ") + StrSize(*CmdLine);
2141         NewCommandLine = AllocateZeroPool(NewCmdLineSize);
2142         if (NewCommandLine == NULL) {
2143           Status = EFI_OUT_OF_RESOURCES;
2144           break;
2145         }
2146 
2147         //
2148         // We know the space is sufficient since we just calculated it.
2149         //
2150         StrnCpyS(NewCommandLine, NewCmdLineSize/sizeof(CHAR16), L"help ", 5);
2151         StrnCatS(NewCommandLine, NewCmdLineSize/sizeof(CHAR16), *CmdLine, StrLen(*CmdLine));
2152         SHELL_FREE_NON_NULL(*CmdLine);
2153         *CmdLine = NewCommandLine;
2154         break;
2155       }
2156     }
2157   }
2158 
2159   SHELL_FREE_NON_NULL(CurrentParameter);
2160 
2161   return (Status);
2162 }
2163 
2164 /**
2165   Function to update the shell variable "lasterror".
2166 
2167   @param[in] ErrorCode      the error code to put into lasterror.
2168 **/
2169 EFI_STATUS
SetLastError(IN CONST SHELL_STATUS ErrorCode)2170 SetLastError(
2171   IN CONST SHELL_STATUS   ErrorCode
2172   )
2173 {
2174   CHAR16 LeString[19];
2175   if (sizeof(EFI_STATUS) == sizeof(UINT64)) {
2176     UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", ErrorCode);
2177   } else {
2178     UnicodeSPrint(LeString, sizeof(LeString), L"0x%x", ErrorCode);
2179   }
2180   DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE););
2181   InternalEfiShellSetEnv(L"lasterror", LeString, TRUE);
2182 
2183   return (EFI_SUCCESS);
2184 }
2185 
2186 /**
2187   Converts the command line to it's post-processed form.  this replaces variables and alias' per UEFI Shell spec.
2188 
2189   @param[in,out] CmdLine        pointer to the command line to update
2190 
2191   @retval EFI_SUCCESS           The operation was successful
2192   @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
2193   @return                       some other error occurred
2194 **/
2195 EFI_STATUS
ProcessCommandLineToFinal(IN OUT CHAR16 ** CmdLine)2196 ProcessCommandLineToFinal(
2197   IN OUT CHAR16 **CmdLine
2198   )
2199 {
2200   EFI_STATUS                Status;
2201   TrimSpaces(CmdLine);
2202 
2203   Status = ShellSubstituteAliases(CmdLine);
2204   if (EFI_ERROR(Status)) {
2205     return (Status);
2206   }
2207 
2208   TrimSpaces(CmdLine);
2209 
2210   Status = ShellSubstituteVariables(CmdLine);
2211   if (EFI_ERROR(Status)) {
2212     return (Status);
2213   }
2214   ASSERT (*CmdLine != NULL);
2215 
2216   TrimSpaces(CmdLine);
2217 
2218   //
2219   // update for help parsing
2220   //
2221   if (StrStr(*CmdLine, L"?") != NULL) {
2222     //
2223     // This may do nothing if the ? does not indicate help.
2224     // Save all the details for in the API below.
2225     //
2226     Status = DoHelpUpdate(CmdLine);
2227   }
2228 
2229   TrimSpaces(CmdLine);
2230 
2231   return (EFI_SUCCESS);
2232 }
2233 
2234 /**
2235   Run an internal shell command.
2236 
2237   This API will update the shell's environment since these commands are libraries.
2238 
2239   @param[in] CmdLine          the command line to run.
2240   @param[in] FirstParameter   the first parameter on the command line
2241   @param[in] ParamProtocol    the shell parameters protocol pointer
2242   @param[out] CommandStatus   the status from the command line.
2243 
2244   @retval EFI_SUCCESS     The command was completed.
2245   @retval EFI_ABORTED     The command's operation was aborted.
2246 **/
2247 EFI_STATUS
RunInternalCommand(IN CONST CHAR16 * CmdLine,IN CHAR16 * FirstParameter,IN EFI_SHELL_PARAMETERS_PROTOCOL * ParamProtocol,OUT EFI_STATUS * CommandStatus)2248 RunInternalCommand(
2249   IN CONST CHAR16                   *CmdLine,
2250   IN       CHAR16                   *FirstParameter,
2251   IN EFI_SHELL_PARAMETERS_PROTOCOL  *ParamProtocol,
2252   OUT EFI_STATUS                    *CommandStatus
2253 )
2254 {
2255   EFI_STATUS                Status;
2256   UINTN                     Argc;
2257   CHAR16                    **Argv;
2258   SHELL_STATUS              CommandReturnedStatus;
2259   BOOLEAN                   LastError;
2260   CHAR16                    *Walker;
2261   CHAR16                    *NewCmdLine;
2262 
2263   NewCmdLine = AllocateCopyPool (StrSize (CmdLine), CmdLine);
2264   if (NewCmdLine == NULL) {
2265     return EFI_OUT_OF_RESOURCES;
2266   }
2267 
2268   for (Walker = NewCmdLine; Walker != NULL && *Walker != CHAR_NULL ; Walker++) {
2269     if (*Walker == L'^' && *(Walker+1) == L'#') {
2270       CopyMem(Walker, Walker+1, StrSize(Walker) - sizeof(Walker[0]));
2271     }
2272   }
2273 
2274   //
2275   // get the argc and argv updated for internal commands
2276   //
2277   Status = UpdateArgcArgv(ParamProtocol, NewCmdLine, Internal_Command, &Argv, &Argc);
2278   if (!EFI_ERROR(Status)) {
2279     //
2280     // Run the internal command.
2281     //
2282     Status = ShellCommandRunCommandHandler(FirstParameter, &CommandReturnedStatus, &LastError);
2283 
2284     if (!EFI_ERROR(Status)) {
2285       if (CommandStatus != NULL) {
2286         if (CommandReturnedStatus != SHELL_SUCCESS) {
2287           *CommandStatus = (EFI_STATUS)(CommandReturnedStatus | MAX_BIT);
2288         } else {
2289           *CommandStatus = EFI_SUCCESS;
2290         }
2291       }
2292 
2293       //
2294       // Update last error status.
2295       // some commands do not update last error.
2296       //
2297       if (LastError) {
2298         SetLastError(CommandReturnedStatus);
2299       }
2300 
2301       //
2302       // Pass thru the exitcode from the app.
2303       //
2304       if (ShellCommandGetExit()) {
2305         //
2306         // An Exit was requested ("exit" command), pass its value up.
2307         //
2308         Status = CommandReturnedStatus;
2309       } else if (CommandReturnedStatus != SHELL_SUCCESS && IsScriptOnlyCommand(FirstParameter)) {
2310         //
2311         // Always abort when a script only command fails for any reason
2312         //
2313         Status = EFI_ABORTED;
2314       } else if (ShellCommandGetCurrentScriptFile() != NULL && CommandReturnedStatus == SHELL_ABORTED) {
2315         //
2316         // Abort when in a script and a command aborted
2317         //
2318         Status = EFI_ABORTED;
2319       }
2320     }
2321   }
2322 
2323   //
2324   // This is guaranteed to be called after UpdateArgcArgv no matter what else happened.
2325   // This is safe even if the update API failed.  In this case, it may be a no-op.
2326   //
2327   RestoreArgcArgv(ParamProtocol, &Argv, &Argc);
2328 
2329   //
2330   // If a script is running and the command is not a script only command, then
2331   // change return value to success so the script won't halt (unless aborted).
2332   //
2333   // Script only commands have to be able halt the script since the script will
2334   // not operate if they are failing.
2335   //
2336   if ( ShellCommandGetCurrentScriptFile() != NULL
2337     && !IsScriptOnlyCommand(FirstParameter)
2338     && Status != EFI_ABORTED
2339     ) {
2340     Status = EFI_SUCCESS;
2341   }
2342 
2343   FreePool (NewCmdLine);
2344   return (Status);
2345 }
2346 
2347 /**
2348   Function to run the command or file.
2349 
2350   @param[in] Type             the type of operation being run.
2351   @param[in] CmdLine          the command line to run.
2352   @param[in] FirstParameter   the first parameter on the command line
2353   @param[in] ParamProtocol    the shell parameters protocol pointer
2354   @param[out] CommandStatus   the status from the command line.
2355 
2356   @retval EFI_SUCCESS     The command was completed.
2357   @retval EFI_ABORTED     The command's operation was aborted.
2358 **/
2359 EFI_STATUS
RunCommandOrFile(IN SHELL_OPERATION_TYPES Type,IN CONST CHAR16 * CmdLine,IN CHAR16 * FirstParameter,IN EFI_SHELL_PARAMETERS_PROTOCOL * ParamProtocol,OUT EFI_STATUS * CommandStatus)2360 RunCommandOrFile(
2361   IN       SHELL_OPERATION_TYPES    Type,
2362   IN CONST CHAR16                   *CmdLine,
2363   IN       CHAR16                   *FirstParameter,
2364   IN EFI_SHELL_PARAMETERS_PROTOCOL  *ParamProtocol,
2365   OUT EFI_STATUS                    *CommandStatus
2366 )
2367 {
2368   EFI_STATUS                Status;
2369   EFI_STATUS                StartStatus;
2370   CHAR16                    *CommandWithPath;
2371   EFI_DEVICE_PATH_PROTOCOL  *DevPath;
2372   SHELL_STATUS              CalleeExitStatus;
2373 
2374   Status            = EFI_SUCCESS;
2375   CommandWithPath   = NULL;
2376   DevPath           = NULL;
2377   CalleeExitStatus  = SHELL_INVALID_PARAMETER;
2378 
2379   switch (Type) {
2380     case   Internal_Command:
2381       Status = RunInternalCommand(CmdLine, FirstParameter, ParamProtocol, CommandStatus);
2382       break;
2383     case   Script_File_Name:
2384     case   Efi_Application:
2385       //
2386       // Process a fully qualified path
2387       //
2388       if (StrStr(FirstParameter, L":") != NULL) {
2389         ASSERT (CommandWithPath == NULL);
2390         if (ShellIsFile(FirstParameter) == EFI_SUCCESS) {
2391           CommandWithPath = StrnCatGrow(&CommandWithPath, NULL, FirstParameter, 0);
2392         }
2393       }
2394 
2395       //
2396       // Process a relative path and also check in the path environment variable
2397       //
2398       if (CommandWithPath == NULL) {
2399         CommandWithPath = ShellFindFilePathEx(FirstParameter, mExecutableExtensions);
2400       }
2401 
2402       //
2403       // This should be impossible now.
2404       //
2405       ASSERT(CommandWithPath != NULL);
2406 
2407       //
2408       // Make sure that path is not just a directory (or not found)
2409       //
2410       if (!EFI_ERROR(ShellIsDirectory(CommandWithPath))) {
2411         ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter);
2412         SetLastError(SHELL_NOT_FOUND);
2413       }
2414       switch (Type) {
2415         case   Script_File_Name:
2416           Status = RunScriptFile (CommandWithPath, NULL, CmdLine, ParamProtocol);
2417           break;
2418         case   Efi_Application:
2419           //
2420           // Get the device path of the application image
2421           //
2422           DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CommandWithPath);
2423           if (DevPath == NULL){
2424             Status = EFI_OUT_OF_RESOURCES;
2425             break;
2426           }
2427 
2428           //
2429           // Execute the device path
2430           //
2431           Status = InternalShellExecuteDevicePath(
2432             &gImageHandle,
2433             DevPath,
2434             CmdLine,
2435             NULL,
2436             &StartStatus
2437            );
2438 
2439           SHELL_FREE_NON_NULL(DevPath);
2440 
2441           if(EFI_ERROR (Status)) {
2442             CalleeExitStatus = (SHELL_STATUS) (Status & (~MAX_BIT));
2443           } else {
2444             CalleeExitStatus = (SHELL_STATUS) StartStatus;
2445           }
2446 
2447           if (CommandStatus != NULL) {
2448             *CommandStatus = CalleeExitStatus;
2449           }
2450 
2451           //
2452           // Update last error status.
2453           //
2454           // Status is an EFI_STATUS. Clear top bit to convert to SHELL_STATUS
2455           SetLastError(CalleeExitStatus);
2456           break;
2457         default:
2458           //
2459           // Do nothing.
2460           //
2461           break;
2462       }
2463       break;
2464     default:
2465       //
2466       // Do nothing.
2467       //
2468       break;
2469   }
2470 
2471   SHELL_FREE_NON_NULL(CommandWithPath);
2472 
2473   return (Status);
2474 }
2475 
2476 /**
2477   Function to setup StdIn, StdErr, StdOut, and then run the command or file.
2478 
2479   @param[in] Type             the type of operation being run.
2480   @param[in] CmdLine          the command line to run.
2481   @param[in] FirstParameter   the first parameter on the command line.
2482   @param[in] ParamProtocol    the shell parameters protocol pointer
2483   @param[out] CommandStatus   the status from the command line.
2484 
2485   @retval EFI_SUCCESS     The command was completed.
2486   @retval EFI_ABORTED     The command's operation was aborted.
2487 **/
2488 EFI_STATUS
SetupAndRunCommandOrFile(IN SHELL_OPERATION_TYPES Type,IN CHAR16 * CmdLine,IN CHAR16 * FirstParameter,IN EFI_SHELL_PARAMETERS_PROTOCOL * ParamProtocol,OUT EFI_STATUS * CommandStatus)2489 SetupAndRunCommandOrFile(
2490   IN   SHELL_OPERATION_TYPES          Type,
2491   IN   CHAR16                         *CmdLine,
2492   IN   CHAR16                         *FirstParameter,
2493   IN   EFI_SHELL_PARAMETERS_PROTOCOL  *ParamProtocol,
2494   OUT EFI_STATUS                      *CommandStatus
2495 )
2496 {
2497   EFI_STATUS                Status;
2498   SHELL_FILE_HANDLE         OriginalStdIn;
2499   SHELL_FILE_HANDLE         OriginalStdOut;
2500   SHELL_FILE_HANDLE         OriginalStdErr;
2501   SYSTEM_TABLE_INFO         OriginalSystemTableInfo;
2502   CONST SCRIPT_FILE         *ConstScriptFile;
2503 
2504   //
2505   // Update the StdIn, StdOut, and StdErr for redirection to environment variables, files, etc... unicode and ASCII
2506   //
2507   Status = UpdateStdInStdOutStdErr(ParamProtocol, CmdLine, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo);
2508 
2509   //
2510   // The StdIn, StdOut, and StdErr are set up.
2511   // Now run the command, script, or application
2512   //
2513   if (!EFI_ERROR(Status)) {
2514     TrimSpaces(&CmdLine);
2515     Status = RunCommandOrFile(Type, CmdLine, FirstParameter, ParamProtocol, CommandStatus);
2516   }
2517 
2518   //
2519   // Now print errors
2520   //
2521   if (EFI_ERROR(Status)) {
2522     ConstScriptFile = ShellCommandGetCurrentScriptFile();
2523     if (ConstScriptFile == NULL || ConstScriptFile->CurrentCommand == NULL) {
2524       ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR), ShellInfoObject.HiiHandle, (VOID*)(Status));
2525     } else {
2526       ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR_SCRIPT), ShellInfoObject.HiiHandle, (VOID*)(Status), ConstScriptFile->CurrentCommand->Line);
2527     }
2528   }
2529 
2530   //
2531   // put back the original StdIn, StdOut, and StdErr
2532   //
2533   RestoreStdInStdOutStdErr(ParamProtocol, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo);
2534 
2535   return (Status);
2536 }
2537 
2538 /**
2539   Function will process and run a command line.
2540 
2541   This will determine if the command line represents an internal shell
2542   command or dispatch an external application.
2543 
2544   @param[in] CmdLine      The command line to parse.
2545   @param[out] CommandStatus   The status from the command line.
2546 
2547   @retval EFI_SUCCESS     The command was completed.
2548   @retval EFI_ABORTED     The command's operation was aborted.
2549 **/
2550 EFI_STATUS
RunShellCommand(IN CONST CHAR16 * CmdLine,OUT EFI_STATUS * CommandStatus)2551 RunShellCommand(
2552   IN CONST CHAR16   *CmdLine,
2553   OUT EFI_STATUS    *CommandStatus
2554   )
2555 {
2556   EFI_STATUS                Status;
2557   CHAR16                    *CleanOriginal;
2558   CHAR16                    *FirstParameter;
2559   CHAR16                    *TempWalker;
2560   SHELL_OPERATION_TYPES     Type;
2561 
2562   ASSERT(CmdLine != NULL);
2563   if (StrLen(CmdLine) == 0) {
2564     return (EFI_SUCCESS);
2565   }
2566 
2567   Status              = EFI_SUCCESS;
2568   CleanOriginal       = NULL;
2569 
2570   CleanOriginal = StrnCatGrow(&CleanOriginal, NULL, CmdLine, 0);
2571   if (CleanOriginal == NULL) {
2572     return (EFI_OUT_OF_RESOURCES);
2573   }
2574 
2575   TrimSpaces(&CleanOriginal);
2576 
2577   //
2578   // NULL out comments (leveraged from RunScriptFileHandle() ).
2579   // The # character on a line is used to denote that all characters on the same line
2580   // and to the right of the # are to be ignored by the shell.
2581   // Afterwards, again remove spaces, in case any were between the last command-parameter and '#'.
2582   //
2583   for (TempWalker = CleanOriginal; TempWalker != NULL && *TempWalker != CHAR_NULL; TempWalker++) {
2584     if (*TempWalker == L'^') {
2585       if (*(TempWalker + 1) == L'#') {
2586         TempWalker++;
2587       }
2588     } else if (*TempWalker == L'#') {
2589       *TempWalker = CHAR_NULL;
2590     }
2591   }
2592 
2593   TrimSpaces(&CleanOriginal);
2594 
2595   //
2596   // Handle case that passed in command line is just 1 or more " " characters.
2597   //
2598   if (StrLen (CleanOriginal) == 0) {
2599     SHELL_FREE_NON_NULL(CleanOriginal);
2600     return (EFI_SUCCESS);
2601   }
2602 
2603   Status = ProcessCommandLineToFinal(&CleanOriginal);
2604   if (EFI_ERROR(Status)) {
2605     SHELL_FREE_NON_NULL(CleanOriginal);
2606     return (Status);
2607   }
2608 
2609   //
2610   // We don't do normal processing with a split command line (output from one command input to another)
2611   //
2612   if (ContainsSplit(CleanOriginal)) {
2613     Status = ProcessNewSplitCommandLine(CleanOriginal);
2614     SHELL_FREE_NON_NULL(CleanOriginal);
2615     return (Status);
2616   }
2617 
2618   //
2619   // We need the first parameter information so we can determine the operation type
2620   //
2621   FirstParameter = AllocateZeroPool(StrSize(CleanOriginal));
2622   if (FirstParameter == NULL) {
2623     SHELL_FREE_NON_NULL(CleanOriginal);
2624     return (EFI_OUT_OF_RESOURCES);
2625   }
2626   TempWalker = CleanOriginal;
2627   if (!EFI_ERROR(GetNextParameter(&TempWalker, &FirstParameter, StrSize(CleanOriginal), TRUE))) {
2628     //
2629     // Depending on the first parameter we change the behavior
2630     //
2631     switch (Type = GetOperationType(FirstParameter)) {
2632       case   File_Sys_Change:
2633         Status = ChangeMappedDrive (FirstParameter);
2634         break;
2635       case   Internal_Command:
2636       case   Script_File_Name:
2637       case   Efi_Application:
2638         Status = SetupAndRunCommandOrFile(Type, CleanOriginal, FirstParameter, ShellInfoObject.NewShellParametersProtocol, CommandStatus);
2639         break;
2640       default:
2641         //
2642         // Whatever was typed, it was invalid.
2643         //
2644         ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter);
2645         SetLastError(SHELL_NOT_FOUND);
2646         break;
2647     }
2648   } else {
2649     ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter);
2650     SetLastError(SHELL_NOT_FOUND);
2651   }
2652 
2653   SHELL_FREE_NON_NULL(CleanOriginal);
2654   SHELL_FREE_NON_NULL(FirstParameter);
2655 
2656   return (Status);
2657 }
2658 
2659 /**
2660   Function will process and run a command line.
2661 
2662   This will determine if the command line represents an internal shell
2663   command or dispatch an external application.
2664 
2665   @param[in] CmdLine      The command line to parse.
2666 
2667   @retval EFI_SUCCESS     The command was completed.
2668   @retval EFI_ABORTED     The command's operation was aborted.
2669 **/
2670 EFI_STATUS
RunCommand(IN CONST CHAR16 * CmdLine)2671 RunCommand(
2672   IN CONST CHAR16   *CmdLine
2673   )
2674 {
2675   return (RunShellCommand(CmdLine, NULL));
2676 }
2677 
2678 
2679 STATIC CONST UINT16 InvalidChars[] = {L'*', L'?', L'<', L'>', L'\\', L'/', L'\"', 0x0001, 0x0002};
2680 /**
2681   Function determines if the CommandName COULD be a valid command.  It does not determine whether
2682   this is a valid command.  It only checks for invalid characters.
2683 
2684   @param[in] CommandName    The name to check
2685 
2686   @retval TRUE              CommandName could be a command name
2687   @retval FALSE             CommandName could not be a valid command name
2688 **/
2689 BOOLEAN
IsValidCommandName(IN CONST CHAR16 * CommandName)2690 IsValidCommandName(
2691   IN CONST CHAR16     *CommandName
2692   )
2693 {
2694   UINTN Count;
2695   if (CommandName == NULL) {
2696     ASSERT(FALSE);
2697     return (FALSE);
2698   }
2699   for ( Count = 0
2700       ; Count < sizeof(InvalidChars) / sizeof(InvalidChars[0])
2701       ; Count++
2702      ){
2703     if (ScanMem16(CommandName, StrSize(CommandName), InvalidChars[Count]) != NULL) {
2704       return (FALSE);
2705     }
2706   }
2707   return (TRUE);
2708 }
2709 
2710 /**
2711   Function to process a NSH script file via SHELL_FILE_HANDLE.
2712 
2713   @param[in] Handle             The handle to the already opened file.
2714   @param[in] Name               The name of the script file.
2715 
2716   @retval EFI_SUCCESS           the script completed successfully
2717 **/
2718 EFI_STATUS
RunScriptFileHandle(IN SHELL_FILE_HANDLE Handle,IN CONST CHAR16 * Name)2719 RunScriptFileHandle (
2720   IN SHELL_FILE_HANDLE  Handle,
2721   IN CONST CHAR16       *Name
2722   )
2723 {
2724   EFI_STATUS          Status;
2725   SCRIPT_FILE         *NewScriptFile;
2726   UINTN               LoopVar;
2727   UINTN               PrintBuffSize;
2728   CHAR16              *CommandLine;
2729   CHAR16              *CommandLine2;
2730   CHAR16              *CommandLine3;
2731   SCRIPT_COMMAND_LIST *LastCommand;
2732   BOOLEAN             Ascii;
2733   BOOLEAN             PreScriptEchoState;
2734   BOOLEAN             PreCommandEchoState;
2735   CONST CHAR16        *CurDir;
2736   UINTN               LineCount;
2737   CHAR16              LeString[50];
2738   LIST_ENTRY          OldBufferList;
2739 
2740   ASSERT(!ShellCommandGetScriptExit());
2741 
2742   PreScriptEchoState = ShellCommandGetEchoState();
2743   PrintBuffSize = PcdGet16(PcdShellPrintBufferSize);
2744 
2745   NewScriptFile = (SCRIPT_FILE*)AllocateZeroPool(sizeof(SCRIPT_FILE));
2746   if (NewScriptFile == NULL) {
2747     return (EFI_OUT_OF_RESOURCES);
2748   }
2749 
2750   //
2751   // Set up the name
2752   //
2753   ASSERT(NewScriptFile->ScriptName == NULL);
2754   NewScriptFile->ScriptName = StrnCatGrow(&NewScriptFile->ScriptName, NULL, Name, 0);
2755   if (NewScriptFile->ScriptName == NULL) {
2756     DeleteScriptFileStruct(NewScriptFile);
2757     return (EFI_OUT_OF_RESOURCES);
2758   }
2759 
2760   //
2761   // Save the parameters (used to replace %0 to %9 later on)
2762   //
2763   NewScriptFile->Argc = ShellInfoObject.NewShellParametersProtocol->Argc;
2764   if (NewScriptFile->Argc != 0) {
2765     NewScriptFile->Argv = (CHAR16**)AllocateZeroPool(NewScriptFile->Argc * sizeof(CHAR16*));
2766     if (NewScriptFile->Argv == NULL) {
2767       DeleteScriptFileStruct(NewScriptFile);
2768       return (EFI_OUT_OF_RESOURCES);
2769     }
2770     for (LoopVar = 0 ; LoopVar < 10 && LoopVar < NewScriptFile->Argc; LoopVar++) {
2771       ASSERT(NewScriptFile->Argv[LoopVar] == NULL);
2772       NewScriptFile->Argv[LoopVar] = StrnCatGrow(&NewScriptFile->Argv[LoopVar], NULL, ShellInfoObject.NewShellParametersProtocol->Argv[LoopVar], 0);
2773       if (NewScriptFile->Argv[LoopVar] == NULL) {
2774         DeleteScriptFileStruct(NewScriptFile);
2775         return (EFI_OUT_OF_RESOURCES);
2776       }
2777     }
2778   } else {
2779     NewScriptFile->Argv = NULL;
2780   }
2781 
2782   InitializeListHead(&NewScriptFile->CommandList);
2783   InitializeListHead(&NewScriptFile->SubstList);
2784 
2785   //
2786   // Now build the list of all script commands.
2787   //
2788   LineCount = 0;
2789   while(!ShellFileHandleEof(Handle)) {
2790     CommandLine = ShellFileHandleReturnLine(Handle, &Ascii);
2791     LineCount++;
2792     if (CommandLine == NULL || StrLen(CommandLine) == 0 || CommandLine[0] == '#') {
2793       SHELL_FREE_NON_NULL(CommandLine);
2794       continue;
2795     }
2796     NewScriptFile->CurrentCommand = AllocateZeroPool(sizeof(SCRIPT_COMMAND_LIST));
2797     if (NewScriptFile->CurrentCommand == NULL) {
2798       SHELL_FREE_NON_NULL(CommandLine);
2799       DeleteScriptFileStruct(NewScriptFile);
2800       return (EFI_OUT_OF_RESOURCES);
2801     }
2802 
2803     NewScriptFile->CurrentCommand->Cl   = CommandLine;
2804     NewScriptFile->CurrentCommand->Data = NULL;
2805     NewScriptFile->CurrentCommand->Line = LineCount;
2806 
2807     InsertTailList(&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link);
2808   }
2809 
2810   //
2811   // Add this as the topmost script file
2812   //
2813   ShellCommandSetNewScript (NewScriptFile);
2814 
2815   //
2816   // Now enumerate through the commands and run each one.
2817   //
2818   CommandLine = AllocateZeroPool(PrintBuffSize);
2819   if (CommandLine == NULL) {
2820     DeleteScriptFileStruct(NewScriptFile);
2821     return (EFI_OUT_OF_RESOURCES);
2822   }
2823   CommandLine2 = AllocateZeroPool(PrintBuffSize);
2824   if (CommandLine2 == NULL) {
2825     FreePool(CommandLine);
2826     DeleteScriptFileStruct(NewScriptFile);
2827     return (EFI_OUT_OF_RESOURCES);
2828   }
2829 
2830   for ( NewScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)GetFirstNode(&NewScriptFile->CommandList)
2831       ; !IsNull(&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link)
2832       ; // conditional increment in the body of the loop
2833   ){
2834     ASSERT(CommandLine2 != NULL);
2835     StrnCpyS( CommandLine2,
2836               PrintBuffSize/sizeof(CHAR16),
2837               NewScriptFile->CurrentCommand->Cl,
2838               PrintBuffSize/sizeof(CHAR16) - 1
2839               );
2840 
2841     SaveBufferList(&OldBufferList);
2842 
2843     //
2844     // NULL out comments
2845     //
2846     for (CommandLine3 = CommandLine2 ; CommandLine3 != NULL && *CommandLine3 != CHAR_NULL ; CommandLine3++) {
2847       if (*CommandLine3 == L'^') {
2848         if ( *(CommandLine3+1) == L':') {
2849           CopyMem(CommandLine3, CommandLine3+1, StrSize(CommandLine3) - sizeof(CommandLine3[0]));
2850         } else if (*(CommandLine3+1) == L'#') {
2851           CommandLine3++;
2852         }
2853       } else if (*CommandLine3 == L'#') {
2854         *CommandLine3 = CHAR_NULL;
2855       }
2856     }
2857 
2858     if (CommandLine2 != NULL && StrLen(CommandLine2) >= 1) {
2859       //
2860       // Due to variability in starting the find and replace action we need to have both buffers the same.
2861       //
2862       StrnCpyS( CommandLine,
2863                 PrintBuffSize/sizeof(CHAR16),
2864                 CommandLine2,
2865                 PrintBuffSize/sizeof(CHAR16) - 1
2866                 );
2867 
2868       //
2869       // Remove the %0 to %9 from the command line (if we have some arguments)
2870       //
2871       if (NewScriptFile->Argv != NULL) {
2872         switch (NewScriptFile->Argc) {
2873           default:
2874             Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%9", NewScriptFile->Argv[9], FALSE, FALSE);
2875             ASSERT_EFI_ERROR(Status);
2876           case 9:
2877             Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%8", NewScriptFile->Argv[8], FALSE, FALSE);
2878             ASSERT_EFI_ERROR(Status);
2879           case 8:
2880             Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%7", NewScriptFile->Argv[7], FALSE, FALSE);
2881             ASSERT_EFI_ERROR(Status);
2882           case 7:
2883             Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%6", NewScriptFile->Argv[6], FALSE, FALSE);
2884             ASSERT_EFI_ERROR(Status);
2885           case 6:
2886             Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%5", NewScriptFile->Argv[5], FALSE, FALSE);
2887             ASSERT_EFI_ERROR(Status);
2888           case 5:
2889             Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%4", NewScriptFile->Argv[4], FALSE, FALSE);
2890             ASSERT_EFI_ERROR(Status);
2891           case 4:
2892             Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%3", NewScriptFile->Argv[3], FALSE, FALSE);
2893             ASSERT_EFI_ERROR(Status);
2894           case 3:
2895             Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%2", NewScriptFile->Argv[2], FALSE, FALSE);
2896             ASSERT_EFI_ERROR(Status);
2897           case 2:
2898             Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%1", NewScriptFile->Argv[1], FALSE, FALSE);
2899             ASSERT_EFI_ERROR(Status);
2900           case 1:
2901             Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%0", NewScriptFile->Argv[0], FALSE, FALSE);
2902             ASSERT_EFI_ERROR(Status);
2903             break;
2904           case 0:
2905             break;
2906         }
2907       }
2908       Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%1", L"\"\"", FALSE, FALSE);
2909       Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%2", L"\"\"", FALSE, FALSE);
2910       Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%3", L"\"\"", FALSE, FALSE);
2911       Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%4", L"\"\"", FALSE, FALSE);
2912       Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%5", L"\"\"", FALSE, FALSE);
2913       Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%6", L"\"\"", FALSE, FALSE);
2914       Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%7", L"\"\"", FALSE, FALSE);
2915       Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PrintBuffSize, L"%8", L"\"\"", FALSE, FALSE);
2916       Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PrintBuffSize, L"%9", L"\"\"", FALSE, FALSE);
2917 
2918       StrnCpyS( CommandLine2,
2919                 PrintBuffSize/sizeof(CHAR16),
2920                 CommandLine,
2921                 PrintBuffSize/sizeof(CHAR16) - 1
2922                 );
2923 
2924       LastCommand = NewScriptFile->CurrentCommand;
2925 
2926       for (CommandLine3 = CommandLine2 ; CommandLine3[0] == L' ' ; CommandLine3++);
2927 
2928       if (CommandLine3 != NULL && CommandLine3[0] == L':' ) {
2929         //
2930         // This line is a goto target / label
2931         //
2932       } else {
2933         if (CommandLine3 != NULL && StrLen(CommandLine3) > 0) {
2934           if (CommandLine3[0] == L'@') {
2935             //
2936             // We need to save the current echo state
2937             // and disable echo for just this command.
2938             //
2939             PreCommandEchoState = ShellCommandGetEchoState();
2940             ShellCommandSetEchoState(FALSE);
2941             Status = RunCommand(CommandLine3+1);
2942 
2943             //
2944             // If command was "@echo -off" or "@echo -on" then don't restore echo state
2945             //
2946             if (StrCmp (L"@echo -off", CommandLine3) != 0 &&
2947                 StrCmp (L"@echo -on", CommandLine3) != 0) {
2948               //
2949               // Now restore the pre-'@' echo state.
2950               //
2951               ShellCommandSetEchoState(PreCommandEchoState);
2952             }
2953           } else {
2954             if (ShellCommandGetEchoState()) {
2955               CurDir = ShellInfoObject.NewEfiShellProtocol->GetEnv(L"cwd");
2956               if (CurDir != NULL && StrLen(CurDir) > 1) {
2957                 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_CURDIR), ShellInfoObject.HiiHandle, CurDir);
2958               } else {
2959                 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_SHELL), ShellInfoObject.HiiHandle);
2960               }
2961               ShellPrintEx(-1, -1, L"%s\r\n", CommandLine2);
2962             }
2963             Status = RunCommand(CommandLine3);
2964           }
2965         }
2966 
2967         if (ShellCommandGetScriptExit()) {
2968           //
2969           // ShellCommandGetExitCode() always returns a UINT64
2970           //
2971           UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", ShellCommandGetExitCode());
2972           DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE););
2973           InternalEfiShellSetEnv(L"lasterror", LeString, TRUE);
2974 
2975           ShellCommandRegisterExit(FALSE, 0);
2976           Status = EFI_SUCCESS;
2977           RestoreBufferList(&OldBufferList);
2978           break;
2979         }
2980         if (ShellGetExecutionBreakFlag()) {
2981           RestoreBufferList(&OldBufferList);
2982           break;
2983         }
2984         if (EFI_ERROR(Status)) {
2985           RestoreBufferList(&OldBufferList);
2986           break;
2987         }
2988         if (ShellCommandGetExit()) {
2989           RestoreBufferList(&OldBufferList);
2990           break;
2991         }
2992       }
2993       //
2994       // If that commend did not update the CurrentCommand then we need to advance it...
2995       //
2996       if (LastCommand == NewScriptFile->CurrentCommand) {
2997         NewScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)GetNextNode(&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link);
2998         if (!IsNull(&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link)) {
2999           NewScriptFile->CurrentCommand->Reset = TRUE;
3000         }
3001       }
3002     } else {
3003       NewScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)GetNextNode(&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link);
3004       if (!IsNull(&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link)) {
3005         NewScriptFile->CurrentCommand->Reset = TRUE;
3006       }
3007     }
3008     RestoreBufferList(&OldBufferList);
3009   }
3010 
3011 
3012   FreePool(CommandLine);
3013   FreePool(CommandLine2);
3014   ShellCommandSetNewScript (NULL);
3015 
3016   //
3017   // Only if this was the last script reset the state.
3018   //
3019   if (ShellCommandGetCurrentScriptFile()==NULL) {
3020     ShellCommandSetEchoState(PreScriptEchoState);
3021   }
3022   return (EFI_SUCCESS);
3023 }
3024 
3025 /**
3026   Function to process a NSH script file.
3027 
3028   @param[in] ScriptPath         Pointer to the script file name (including file system path).
3029   @param[in] Handle             the handle of the script file already opened.
3030   @param[in] CmdLine            the command line to run.
3031   @param[in] ParamProtocol      the shell parameters protocol pointer
3032 
3033   @retval EFI_SUCCESS           the script completed successfully
3034 **/
3035 EFI_STATUS
RunScriptFile(IN CONST CHAR16 * ScriptPath,IN SHELL_FILE_HANDLE Handle OPTIONAL,IN CONST CHAR16 * CmdLine,IN EFI_SHELL_PARAMETERS_PROTOCOL * ParamProtocol)3036 RunScriptFile (
3037   IN CONST CHAR16                   *ScriptPath,
3038   IN SHELL_FILE_HANDLE              Handle OPTIONAL,
3039   IN CONST CHAR16                   *CmdLine,
3040   IN EFI_SHELL_PARAMETERS_PROTOCOL  *ParamProtocol
3041   )
3042 {
3043   EFI_STATUS          Status;
3044   SHELL_FILE_HANDLE   FileHandle;
3045   UINTN                     Argc;
3046   CHAR16                    **Argv;
3047 
3048   if (ShellIsFile(ScriptPath) != EFI_SUCCESS) {
3049     return (EFI_INVALID_PARAMETER);
3050   }
3051 
3052   //
3053   // get the argc and argv updated for scripts
3054   //
3055   Status = UpdateArgcArgv(ParamProtocol, CmdLine, Script_File_Name, &Argv, &Argc);
3056   if (!EFI_ERROR(Status)) {
3057 
3058     if (Handle == NULL) {
3059       //
3060       // open the file
3061       //
3062       Status = ShellOpenFileByName(ScriptPath, &FileHandle, EFI_FILE_MODE_READ, 0);
3063       if (!EFI_ERROR(Status)) {
3064         //
3065         // run it
3066         //
3067         Status = RunScriptFileHandle(FileHandle, ScriptPath);
3068 
3069         //
3070         // now close the file
3071         //
3072         ShellCloseFile(&FileHandle);
3073       }
3074     } else {
3075       Status = RunScriptFileHandle(Handle, ScriptPath);
3076     }
3077   }
3078 
3079   //
3080   // This is guaranteed to be called after UpdateArgcArgv no matter what else happened.
3081   // This is safe even if the update API failed.  In this case, it may be a no-op.
3082   //
3083   RestoreArgcArgv(ParamProtocol, &Argv, &Argc);
3084 
3085   return (Status);
3086 }
3087 
3088 /**
3089   Return the pointer to the first occurrence of any character from a list of characters.
3090 
3091   @param[in] String           the string to parse
3092   @param[in] CharacterList    the list of character to look for
3093   @param[in] EscapeCharacter  An escape character to skip
3094 
3095   @return the location of the first character in the string
3096   @retval CHAR_NULL no instance of any character in CharacterList was found in String
3097 **/
3098 CONST CHAR16*
FindFirstCharacter(IN CONST CHAR16 * String,IN CONST CHAR16 * CharacterList,IN CONST CHAR16 EscapeCharacter)3099 FindFirstCharacter(
3100   IN CONST CHAR16 *String,
3101   IN CONST CHAR16 *CharacterList,
3102   IN CONST CHAR16 EscapeCharacter
3103   )
3104 {
3105   UINT32 WalkChar;
3106   UINT32 WalkStr;
3107 
3108   for (WalkStr = 0; WalkStr < StrLen(String); WalkStr++) {
3109     if (String[WalkStr] == EscapeCharacter) {
3110       WalkStr++;
3111       continue;
3112     }
3113     for (WalkChar = 0; WalkChar < StrLen(CharacterList); WalkChar++) {
3114       if (String[WalkStr] == CharacterList[WalkChar]) {
3115         return (&String[WalkStr]);
3116       }
3117     }
3118   }
3119   return (String + StrLen(String));
3120 }
3121