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