]> git.proxmox.com Git - mirror_edk2.git/blame - ShellPkg/Library/UefiShellNetwork2CommandsLib/Ping6.c
ShellPkg: Merge Ping6 and Ifconfig6 tools to Shell command.
[mirror_edk2.git] / ShellPkg / Library / UefiShellNetwork2CommandsLib / Ping6.c
CommitLineData
43ca1753
ZL
1/** @file\r
2 The implementation for Ping6 application.\r
3\r
4 Copyright (c) 2016, Intel Corporation. All rights reserved.<BR>\r
5\r
6 This program and the accompanying materials\r
7 are licensed and made available under the terms and conditions of the BSD License\r
8 which accompanies this distribution. The full text of the license may be found at\r
9 http://opensource.org/licenses/bsd-license.php.\r
10\r
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
13\r
14**/\r
15\r
16#include "UefiShellNetwork2CommandsLib.h"\r
17\r
18#define PING6_DEFAULT_TIMEOUT 5000\r
19#define PING6_MAX_SEND_NUMBER 10000\r
20#define PING6_MAX_BUFFER_SIZE 32768\r
21#define PING6_ONE_SECOND 10000000\r
22\r
23//\r
24// A similar amount of time that passes in femtoseconds\r
25// for each increment of TimerValue. It is for NT32 only.\r
26//\r
27#define NTTIMERPERIOD 358049\r
28\r
29#pragma pack(1)\r
30\r
31typedef struct _ICMP6_ECHO_REQUEST_REPLY {\r
32 UINT8 Type;\r
33 UINT8 Code;\r
34 UINT16 Checksum;\r
35 UINT16 Identifier;\r
36 UINT16 SequenceNum;\r
37 UINT64 TimeStamp;\r
38 UINT8 Data[1];\r
39} ICMP6_ECHO_REQUEST_REPLY;\r
40\r
41#pragma pack()\r
42\r
43typedef struct _PING6_ICMP6_TX_INFO {\r
44 LIST_ENTRY Link;\r
45 UINT16 SequenceNum;\r
46 UINT64 TimeStamp;\r
47 EFI_IP6_COMPLETION_TOKEN *Token;\r
48} PING6_ICMP6_TX_INFO;\r
49\r
50typedef struct _PING6_PRIVATE_DATA {\r
51 EFI_HANDLE ImageHandle;\r
52 EFI_HANDLE NicHandle;\r
53 EFI_HANDLE Ip6ChildHandle;\r
54 EFI_IP6_PROTOCOL *Ip6;\r
55 EFI_EVENT Timer;\r
56\r
57 EFI_STATUS Status;\r
58 LIST_ENTRY TxList;\r
59 EFI_IP6_COMPLETION_TOKEN RxToken;\r
60 UINT16 RxCount;\r
61 UINT16 TxCount;\r
62 UINT64 RttSum;\r
63 UINT64 RttMin;\r
64 UINT64 RttMax;\r
65 UINT32 SequenceNum;\r
66\r
67 EFI_IPv6_ADDRESS SrcAddress;\r
68 EFI_IPv6_ADDRESS DstAddress;\r
69 UINT32 SendNum;\r
70 UINT32 BufferSize;\r
71} PING6_PRIVATE_DATA;\r
72\r
73\r
74SHELL_PARAM_ITEM Ping6ParamList[] = {\r
75 {\r
76 L"-l",\r
77 TypeValue\r
78 },\r
79 {\r
80 L"-n",\r
81 TypeValue\r
82 },\r
83 {\r
84 L"-s",\r
85 TypeValue\r
86 },\r
87 {\r
88 L"-?",\r
89 TypeFlag\r
90 },\r
91 {\r
92 NULL,\r
93 TypeMax\r
94 },\r
95};\r
96\r
97//\r
98// Global Variables in Ping6 application.\r
99//\r
100CONST CHAR16 *mIp6DstString;\r
101CONST CHAR16 *mIp6SrcString;\r
102UINT64 mFrequency = 0;\r
103UINT64 mIp6CurrentTick = 0;\r
104EFI_CPU_ARCH_PROTOCOL *Cpu = NULL;\r
105\r
106\r
107\r
108/**\r
109 Reads and returns the current value of the Time.\r
110\r
111 @return The current tick value.\r
112\r
113**/\r
114UINT64\r
115Ping6ReadTime ()\r
116{\r
117 UINT64 TimerPeriod;\r
118 EFI_STATUS Status;\r
119\r
120 ASSERT (Cpu != NULL);\r
121\r
122 Status = Cpu->GetTimerValue (Cpu, 0, &mIp6CurrentTick, &TimerPeriod);\r
123 if (EFI_ERROR (Status)) {\r
124 //\r
125 // The WinntGetTimerValue will return EFI_UNSUPPORTED. Set the\r
126 // TimerPeriod by ourselves.\r
127 //\r
128 mIp6CurrentTick += 1000000;\r
129 }\r
130\r
131 return mIp6CurrentTick;\r
132}\r
133\r
134/**\r
135 Get and calculate the frequency in tick/ms.\r
136 The result is saved in the globle variable mFrequency\r
137\r
138 @retval EFI_SUCCESS Calculated the frequency successfully.\r
139 @retval Others Failed to calculate the frequency.\r
140\r
141**/\r
142EFI_STATUS\r
143Ping6GetFrequency (\r
144 VOID\r
145 )\r
146{\r
147 EFI_STATUS Status;\r
148 UINT64 CurrentTick;\r
149 UINT64 TimerPeriod;\r
150\r
151 Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &Cpu);\r
152\r
153 if (EFI_ERROR (Status)) {\r
154 return Status;\r
155 }\r
156\r
157 Status = Cpu->GetTimerValue (Cpu, 0, &CurrentTick, &TimerPeriod);\r
158\r
159 if (EFI_ERROR (Status)) {\r
160 //\r
161 // For NT32 Simulator only. 358049 is a similar value to keep timer granularity.\r
162 // Set the timer period by ourselves.\r
163 //\r
164 TimerPeriod = (UINT64) NTTIMERPERIOD;\r
165 }\r
166 //\r
167 // The timer period is in femtosecond (1 femtosecond is 1e-15 second).\r
168 // So 1e+12 is divided by timer period to produce the freq in tick/ms.\r
169 //\r
170 mFrequency = DivU64x64Remainder (1000000000000ULL, TimerPeriod, NULL);\r
171\r
172 return EFI_SUCCESS;\r
173}\r
174\r
175/**\r
176 Get and calculate the duration in ms.\r
177\r
178 @param[in] Begin The start point of time.\r
179 @param[in] End The end point of time.\r
180\r
181 @return The duration in ms.\r
182\r
183**/\r
184UINT64\r
185Ping6CalculateTick (\r
186 IN UINT64 Begin,\r
187 IN UINT64 End\r
188 )\r
189{\r
190 ASSERT (End > Begin);\r
191 return DivU64x64Remainder (End - Begin, mFrequency, NULL);\r
192}\r
193\r
194/**\r
195 Destroy IPING6_ICMP6_TX_INFO, and recollect the memory.\r
196\r
197 @param[in] TxInfo The pointer to PING6_ICMP6_TX_INFO.\r
198\r
199**/\r
200VOID\r
201Ping6DestroyTxInfo (\r
202 IN PING6_ICMP6_TX_INFO *TxInfo\r
203 )\r
204{\r
205 EFI_IP6_TRANSMIT_DATA *TxData;\r
206 EFI_IP6_FRAGMENT_DATA *FragData;\r
207 UINTN Index;\r
208\r
209 ASSERT (TxInfo != NULL);\r
210\r
211 if (TxInfo->Token != NULL) {\r
212\r
213 if (TxInfo->Token->Event != NULL) {\r
214 gBS->CloseEvent (TxInfo->Token->Event);\r
215 }\r
216\r
217 TxData = TxInfo->Token->Packet.TxData;\r
218 if (TxData != NULL) {\r
219\r
220 if (TxData->OverrideData != NULL) {\r
221 FreePool (TxData->OverrideData);\r
222 }\r
223\r
224 if (TxData->ExtHdrs != NULL) {\r
225 FreePool (TxData->ExtHdrs);\r
226 }\r
227\r
228 for (Index = 0; Index < TxData->FragmentCount; Index++) {\r
229 FragData = TxData->FragmentTable[Index].FragmentBuffer;\r
230 if (FragData != NULL) {\r
231 FreePool (FragData);\r
232 }\r
233 }\r
234 }\r
235\r
236 FreePool (TxInfo->Token);\r
237 }\r
238\r
239 FreePool (TxInfo);\r
240}\r
241\r
242/**\r
243 Match the request, and reply with SequenceNum/TimeStamp.\r
244\r
245 @param[in] Private The pointer to PING6_PRIVATE_DATA.\r
246 @param[in] Packet The pointer to ICMP6_ECHO_REQUEST_REPLY.\r
247\r
248 @retval EFI_SUCCESS The match is successful.\r
249 @retval EFI_NOT_FOUND The reply can't be matched with any request.\r
250\r
251**/\r
252EFI_STATUS\r
253Ping6OnMatchEchoReply (\r
254 IN PING6_PRIVATE_DATA *Private,\r
255 IN ICMP6_ECHO_REQUEST_REPLY *Packet\r
256 )\r
257{\r
258 PING6_ICMP6_TX_INFO *TxInfo;\r
259 LIST_ENTRY *Entry;\r
260 LIST_ENTRY *NextEntry;\r
261\r
262 NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->TxList) {\r
263 TxInfo = BASE_CR (Entry, PING6_ICMP6_TX_INFO, Link);\r
264\r
265 if ((TxInfo->SequenceNum == Packet->SequenceNum) && (TxInfo->TimeStamp == Packet->TimeStamp)) {\r
266 Private->RxCount++;\r
267 RemoveEntryList (&TxInfo->Link);\r
268 Ping6DestroyTxInfo (TxInfo);\r
269 return EFI_SUCCESS;\r
270 }\r
271 }\r
272\r
273 return EFI_NOT_FOUND;\r
274}\r
275\r
276/**\r
277 The original intention is to send a request.\r
278 Currently, the application retransmits an icmp6 echo request packet\r
279 per second in sendnumber times that is specified by the user.\r
280 Because nothing can be done here, all things move to the timer rountine.\r
281\r
282 @param[in] Event A EFI_EVENT type event.\r
283 @param[in] Context The pointer to Context.\r
284\r
285**/\r
286VOID\r
287EFIAPI\r
288Ping6OnEchoRequestSent6 (\r
289 IN EFI_EVENT Event,\r
290 IN VOID *Context\r
291 )\r
292{\r
293}\r
294\r
295/**\r
296 receive reply, match and print reply infomation.\r
297\r
298 @param[in] Event A EFI_EVENT type event.\r
299 @param[in] Context The pointer to context.\r
300\r
301**/\r
302VOID\r
303EFIAPI\r
304Ping6OnEchoReplyReceived6 (\r
305 IN EFI_EVENT Event,\r
306 IN VOID *Context\r
307 )\r
308{\r
309 EFI_STATUS Status;\r
310 PING6_PRIVATE_DATA *Private;\r
311 EFI_IP6_COMPLETION_TOKEN *RxToken;\r
312 EFI_IP6_RECEIVE_DATA *RxData;\r
313 ICMP6_ECHO_REQUEST_REPLY *Reply;\r
314 UINT32 PayLoad;\r
315 UINT64 Rtt;\r
316 CHAR8 Near;\r
317\r
318 Private = (PING6_PRIVATE_DATA *) Context;\r
319\r
320 if (Private->Status == EFI_ABORTED) {\r
321 return;\r
322 }\r
323\r
324 RxToken = &Private->RxToken;\r
325 RxData = RxToken->Packet.RxData;\r
326 Reply = RxData->FragmentTable[0].FragmentBuffer;\r
327 PayLoad = RxData->DataLength;\r
328\r
329 if (RxData->Header->NextHeader != IP6_ICMP) {\r
330 goto ON_EXIT;\r
331 }\r
332\r
333 if (!IP6_IS_MULTICAST (&Private->DstAddress) &&\r
334 !EFI_IP6_EQUAL (&RxData->Header->SourceAddress, &Private->DstAddress)) {\r
335 goto ON_EXIT;\r
336 }\r
337\r
338 if ((Reply->Type != ICMP_V6_ECHO_REPLY) || (Reply->Code != 0)) {\r
339 goto ON_EXIT;\r
340 }\r
341\r
342 if (PayLoad != Private->BufferSize) {\r
343 goto ON_EXIT;\r
344 }\r
345 //\r
346 // Check whether the reply matches the sent request before.\r
347 //\r
348 Status = Ping6OnMatchEchoReply (Private, Reply);\r
349 if (EFI_ERROR(Status)) {\r
350 goto ON_EXIT;\r
351 }\r
352 //\r
353 // Display statistics on this icmp6 echo reply packet.\r
354 //\r
355 Rtt = Ping6CalculateTick (Reply->TimeStamp, Ping6ReadTime ());\r
356 if (Rtt != 0) {\r
357 Near = (CHAR8) '=';\r
358 } else {\r
359 Near = (CHAR8) '<';\r
360 }\r
361\r
362 Private->RttSum += Rtt;\r
363 Private->RttMin = Private->RttMin > Rtt ? Rtt : Private->RttMin;\r
364 Private->RttMax = Private->RttMax < Rtt ? Rtt : Private->RttMax;\r
365\r
366 ShellPrintHiiEx (\r
367 -1,\r
368 -1,\r
369 NULL,\r
370 STRING_TOKEN (STR_PING6_REPLY_INFO),\r
371 gShellNetwork2HiiHandle,\r
372 PayLoad,\r
373 mIp6DstString,\r
374 Reply->SequenceNum,\r
375 RxData->Header->HopLimit,\r
376 Near,\r
377 Rtt\r
378 );\r
379\r
380ON_EXIT:\r
381\r
382 if (Private->RxCount < Private->SendNum) {\r
383 //\r
384 // Continue to receive icmp6 echo reply packets.\r
385 //\r
386 RxToken->Status = EFI_ABORTED;\r
387\r
388 Status = Private->Ip6->Receive (Private->Ip6, RxToken);\r
389\r
390 if (EFI_ERROR (Status)) {\r
391 Private->Status = EFI_ABORTED;\r
392 }\r
393 } else {\r
394 //\r
395 // All reply have already been received from the dest host.\r
396 //\r
397 Private->Status = EFI_SUCCESS;\r
398 }\r
399 //\r
400 // Singal to recycle the each rxdata here, not at the end of process.\r
401 //\r
402 gBS->SignalEvent (RxData->RecycleSignal);\r
403}\r
404\r
405/**\r
406 Initial EFI_IP6_COMPLETION_TOKEN.\r
407\r
408 @param[in] Private The pointer of PING6_PRIVATE_DATA.\r
409 @param[in] TimeStamp The TimeStamp of request.\r
410 @param[in] SequenceNum The SequenceNum of request.\r
411\r
412 @return The pointer of EFI_IP6_COMPLETION_TOKEN.\r
413\r
414**/\r
415EFI_IP6_COMPLETION_TOKEN *\r
416Ping6GenerateToken (\r
417 IN PING6_PRIVATE_DATA *Private,\r
418 IN UINT64 TimeStamp,\r
419 IN UINT16 SequenceNum\r
420 )\r
421{\r
422 EFI_STATUS Status;\r
423 EFI_IP6_COMPLETION_TOKEN *Token;\r
424 EFI_IP6_TRANSMIT_DATA *TxData;\r
425 ICMP6_ECHO_REQUEST_REPLY *Request;\r
426\r
427 Request = AllocateZeroPool (Private->BufferSize);\r
428\r
429 if (Request == NULL) {\r
430 return NULL;\r
431 }\r
432 //\r
433 // Assembly icmp6 echo request packet.\r
434 //\r
435 Request->Type = ICMP_V6_ECHO_REQUEST;\r
436 Request->Code = 0;\r
437 Request->SequenceNum = SequenceNum;\r
438 Request->TimeStamp = TimeStamp;\r
439 Request->Identifier = 0;\r
440 //\r
441 // Leave check sum to ip6 layer, since it has no idea of source address\r
442 // selection.\r
443 //\r
444 Request->Checksum = 0;\r
445\r
446 TxData = AllocateZeroPool (sizeof (EFI_IP6_TRANSMIT_DATA));\r
447\r
448 if (TxData == NULL) {\r
449 FreePool (Request);\r
450 return NULL;\r
451 }\r
452 //\r
453 // Assembly ipv6 token for transmit.\r
454 //\r
455 TxData->OverrideData = 0;\r
456 TxData->ExtHdrsLength = 0;\r
457 TxData->ExtHdrs = NULL;\r
458 TxData->DataLength = Private->BufferSize;\r
459 TxData->FragmentCount = 1;\r
460 TxData->FragmentTable[0].FragmentBuffer = (VOID *) Request;\r
461 TxData->FragmentTable[0].FragmentLength = Private->BufferSize;\r
462\r
463 Token = AllocateZeroPool (sizeof (EFI_IP6_COMPLETION_TOKEN));\r
464\r
465 if (Token == NULL) {\r
466 FreePool (Request);\r
467 FreePool (TxData);\r
468 return NULL;\r
469 }\r
470\r
471 Token->Status = EFI_ABORTED;\r
472 Token->Packet.TxData = TxData;\r
473\r
474 Status = gBS->CreateEvent (\r
475 EVT_NOTIFY_SIGNAL,\r
476 TPL_CALLBACK,\r
477 Ping6OnEchoRequestSent6,\r
478 Private,\r
479 &Token->Event\r
480 );\r
481\r
482 if (EFI_ERROR (Status)) {\r
483 FreePool (Request);\r
484 FreePool (TxData);\r
485 FreePool (Token);\r
486 return NULL;\r
487 }\r
488\r
489 return Token;\r
490}\r
491\r
492/**\r
493 Transmit the EFI_IP6_COMPLETION_TOKEN.\r
494\r
495 @param[in] Private The pointer of PING6_PRIVATE_DATA.\r
496\r
497 @retval EFI_SUCCESS Transmitted successfully.\r
498 @retval EFI_OUT_OF_RESOURCES No memory is available on the platform.\r
499 @retval others Transmitted unsuccessfully.\r
500\r
501**/\r
502EFI_STATUS\r
503Ping6SendEchoRequest (\r
504 IN PING6_PRIVATE_DATA *Private\r
505 )\r
506{\r
507 EFI_STATUS Status;\r
508 PING6_ICMP6_TX_INFO *TxInfo;\r
509\r
510 TxInfo = AllocateZeroPool (sizeof (PING6_ICMP6_TX_INFO));\r
511\r
512 if (TxInfo == NULL) {\r
513 return EFI_OUT_OF_RESOURCES;\r
514 }\r
515\r
516 TxInfo->TimeStamp = Ping6ReadTime ();\r
517 TxInfo->SequenceNum = (UINT16) (Private->TxCount + 1);\r
518\r
519 TxInfo->Token = Ping6GenerateToken (\r
520 Private,\r
521 TxInfo->TimeStamp,\r
522 TxInfo->SequenceNum\r
523 );\r
524\r
525 if (TxInfo->Token == NULL) {\r
526 Ping6DestroyTxInfo (TxInfo);\r
527 return EFI_OUT_OF_RESOURCES;\r
528 }\r
529\r
530 Status = Private->Ip6->Transmit (Private->Ip6, TxInfo->Token);\r
531\r
532 if (EFI_ERROR (Status)) {\r
533 Ping6DestroyTxInfo (TxInfo);\r
534 return Status;\r
535 }\r
536\r
537 InsertTailList (&Private->TxList, &TxInfo->Link);\r
538 Private->TxCount++;\r
539\r
540 return EFI_SUCCESS;\r
541}\r
542\r
543/**\r
544 Place a completion token into the receive packet queue to receive the echo reply.\r
545\r
546 @param[in] Private The pointer of PING6_PRIVATE_DATA.\r
547\r
548 @retval EFI_SUCCESS Put the token into the receive packet queue successfully.\r
549 @retval others Put the token into the receive packet queue unsuccessfully.\r
550\r
551**/\r
552EFI_STATUS\r
553Ping6OnReceiveEchoReply (\r
554 IN PING6_PRIVATE_DATA *Private\r
555 )\r
556{\r
557 EFI_STATUS Status;\r
558\r
559 ZeroMem (&Private->RxToken, sizeof (EFI_IP6_COMPLETION_TOKEN));\r
560\r
561 Status = gBS->CreateEvent (\r
562 EVT_NOTIFY_SIGNAL,\r
563 TPL_CALLBACK,\r
564 Ping6OnEchoReplyReceived6,\r
565 Private,\r
566 &Private->RxToken.Event\r
567 );\r
568\r
569 if (EFI_ERROR (Status)) {\r
570 return Status;\r
571 }\r
572\r
573 Private->RxToken.Status = EFI_NOT_READY;\r
574\r
575 return Private->Ip6->Receive (Private->Ip6, &Private->RxToken);\r
576}\r
577\r
578/**\r
579 Remove the timeout request from the list.\r
580\r
581 @param[in] Event A EFI_EVENT type event.\r
582 @param[in] Context The pointer to Context.\r
583\r
584**/\r
585VOID\r
586EFIAPI\r
587Ping6OnTimerRoutine6 (\r
588 IN EFI_EVENT Event,\r
589 IN VOID *Context\r
590 )\r
591{\r
592 EFI_STATUS Status;\r
593 PING6_PRIVATE_DATA *Private;\r
594 PING6_ICMP6_TX_INFO *TxInfo;\r
595 LIST_ENTRY *Entry;\r
596 LIST_ENTRY *NextEntry;\r
597 UINT64 Time;\r
598\r
599 Private = (PING6_PRIVATE_DATA *) Context;\r
600\r
601 //\r
602 // Retransmit icmp6 echo request packets per second in sendnumber times.\r
603 //\r
604 if (Private->TxCount < Private->SendNum) {\r
605\r
606 Status = Ping6SendEchoRequest (Private);\r
607 if (Private->TxCount != 0){\r
608 if (EFI_ERROR (Status)) {\r
609 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_SEND_REQUEST), gShellNetwork2HiiHandle, Private->TxCount + 1);\r
610 }\r
611 }\r
612 }\r
613 //\r
614 // Check whether any icmp6 echo request in the list timeout.\r
615 //\r
616 NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->TxList) {\r
617 TxInfo = BASE_CR (Entry, PING6_ICMP6_TX_INFO, Link);\r
618 Time = Ping6CalculateTick (TxInfo->TimeStamp, Ping6ReadTime ());\r
619\r
620 //\r
621 // Remove the timeout echo request from txlist.\r
622 //\r
623 if (Time > PING6_DEFAULT_TIMEOUT) {\r
624\r
625 if (EFI_ERROR (TxInfo->Token->Status)) {\r
626 Private->Ip6->Cancel (Private->Ip6, TxInfo->Token);\r
627 }\r
628 //\r
629 // Remove the timeout icmp6 echo request from list.\r
630 //\r
631 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_TIMEOUT), gShellNetwork2HiiHandle, TxInfo->SequenceNum);\r
632\r
633 RemoveEntryList (&TxInfo->Link);\r
634 Ping6DestroyTxInfo (TxInfo);\r
635\r
636 if (IsListEmpty (&Private->TxList) && (Private->TxCount == Private->SendNum)) {\r
637 //\r
638 // All the left icmp6 echo request in the list timeout.\r
639 //\r
640 Private->Status = EFI_TIMEOUT;\r
641 }\r
642 }\r
643 }\r
644}\r
645\r
646/**\r
647 Create a valid IP6 instance.\r
648\r
649 @param[in] Private The pointer of PING6_PRIVATE_DATA.\r
650\r
651 @retval EFI_SUCCESS Create a valid IP6 instance successfully.\r
652 @retval EFI_ABORTED Locate handle with ip6 service binding protocol unsuccessfully.\r
653 @retval EFI_INVALID_PARAMETER The source address is unspecified when the destination address is a link -ocal address.\r
654 @retval EFI_OUT_OF_RESOURCES No memory is available on the platform.\r
655 @retval EFI_NOT_FOUND The source address is not found.\r
656**/\r
657EFI_STATUS\r
658Ping6CreateIpInstance (\r
659 IN PING6_PRIVATE_DATA *Private\r
660 )\r
661{\r
662 EFI_STATUS Status;\r
663 UINTN HandleIndex;\r
664 UINTN HandleNum;\r
665 EFI_HANDLE *HandleBuffer;\r
666 EFI_SERVICE_BINDING_PROTOCOL *Ip6Sb;\r
667 EFI_IP6_CONFIG_PROTOCOL *Ip6Cfg;\r
668 EFI_IP6_CONFIG_DATA Ip6Config;\r
669 EFI_IP6_CONFIG_INTERFACE_INFO *IfInfo;\r
670 UINTN IfInfoSize;\r
671 EFI_IPv6_ADDRESS *Addr;\r
672 UINTN AddrIndex;\r
673\r
674 HandleBuffer = NULL;\r
675 Ip6Sb = NULL;\r
676 IfInfo = NULL;\r
677 IfInfoSize = 0;\r
678\r
679 //\r
680 // Locate all the handles with ip6 service binding protocol.\r
681 //\r
682 Status = gBS->LocateHandleBuffer (\r
683 ByProtocol,\r
684 &gEfiIp6ServiceBindingProtocolGuid,\r
685 NULL,\r
686 &HandleNum,\r
687 &HandleBuffer\r
688 );\r
689 if (EFI_ERROR (Status) || (HandleNum == 0)) {\r
690 return EFI_ABORTED;\r
691 }\r
692 //\r
693 // Source address is required when pinging a link-local address on multi-\r
694 // interfaces host.\r
695 //\r
696 if (NetIp6IsLinkLocalAddr (&Private->DstAddress) &&\r
697 NetIp6IsUnspecifiedAddr (&Private->SrcAddress) &&\r
698 (HandleNum > 1)) {\r
699 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_INVALID_SOURCE), gShellNetwork2HiiHandle);\r
700 Status = EFI_INVALID_PARAMETER;\r
701 goto ON_ERROR;\r
702 }\r
703 //\r
704 // For each ip6 protocol, check interface addresses list.\r
705 //\r
706 for (HandleIndex = 0; HandleIndex < HandleNum; HandleIndex++) {\r
707\r
708 Ip6Sb = NULL;\r
709 IfInfo = NULL;\r
710 IfInfoSize = 0;\r
711\r
712 Status = gBS->HandleProtocol (\r
713 HandleBuffer[HandleIndex],\r
714 &gEfiIp6ServiceBindingProtocolGuid,\r
715 (VOID **) &Ip6Sb\r
716 );\r
717 if (EFI_ERROR (Status)) {\r
718 goto ON_ERROR;\r
719 }\r
720\r
721 if (NetIp6IsUnspecifiedAddr (&Private->SrcAddress)) {\r
722 //\r
723 // No need to match interface address.\r
724 //\r
725 break;\r
726 } else {\r
727 //\r
728 // Ip6config protocol and ip6 service binding protocol are installed\r
729 // on the same handle.\r
730 //\r
731 Status = gBS->HandleProtocol (\r
732 HandleBuffer[HandleIndex],\r
733 &gEfiIp6ConfigProtocolGuid,\r
734 (VOID **) &Ip6Cfg\r
735 );\r
736\r
737 if (EFI_ERROR (Status)) {\r
738 goto ON_ERROR;\r
739 }\r
740 //\r
741 // Get the interface information size.\r
742 //\r
743 Status = Ip6Cfg->GetData (\r
744 Ip6Cfg,\r
745 Ip6ConfigDataTypeInterfaceInfo,\r
746 &IfInfoSize,\r
747 NULL\r
748 );\r
749\r
750 if (Status != EFI_BUFFER_TOO_SMALL) {\r
751 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_IP6CFG_GETDATA), gShellNetwork2HiiHandle, Status);\r
752 goto ON_ERROR;\r
753 }\r
754\r
755 IfInfo = AllocateZeroPool (IfInfoSize);\r
756\r
757 if (IfInfo == NULL) {\r
758 Status = EFI_OUT_OF_RESOURCES;\r
759 goto ON_ERROR;\r
760 }\r
761 //\r
762 // Get the interface info.\r
763 //\r
764 Status = Ip6Cfg->GetData (\r
765 Ip6Cfg,\r
766 Ip6ConfigDataTypeInterfaceInfo,\r
767 &IfInfoSize,\r
768 IfInfo\r
769 );\r
770\r
771 if (EFI_ERROR (Status)) {\r
772 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_IP6CFG_GETDATA), gShellNetwork2HiiHandle, Status);\r
773 goto ON_ERROR;\r
774 }\r
775 //\r
776 // Check whether the source address is one of the interface addresses.\r
777 //\r
778 for (AddrIndex = 0; AddrIndex < IfInfo->AddressInfoCount; AddrIndex++) {\r
779\r
780 Addr = &(IfInfo->AddressInfo[AddrIndex].Address);\r
781 if (EFI_IP6_EQUAL (&Private->SrcAddress, Addr)) {\r
782 //\r
783 // Match a certain interface address.\r
784 //\r
785 break;\r
786 }\r
787 }\r
788\r
789 if (AddrIndex < IfInfo->AddressInfoCount) {\r
790 //\r
791 // Found a nic handle with right interface address.\r
792 //\r
793 break;\r
794 }\r
795 }\r
796\r
797 FreePool (IfInfo);\r
798 IfInfo = NULL;\r
799 }\r
800 //\r
801 // No exact interface address matched.\r
802 //\r
803\r
804 if (HandleIndex == HandleNum) {\r
805 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_SOURCE_NOT_FOUND), gShellNetwork2HiiHandle, mIp6SrcString);\r
806 Status = EFI_NOT_FOUND;\r
807 goto ON_ERROR;\r
808 }\r
809\r
810 Private->NicHandle = HandleBuffer[HandleIndex];\r
811\r
812 ASSERT (Ip6Sb != NULL);\r
813 Status = Ip6Sb->CreateChild (Ip6Sb, &Private->Ip6ChildHandle);\r
814\r
815 if (EFI_ERROR (Status)) {\r
816 goto ON_ERROR;\r
817 }\r
818\r
819 Status = gBS->OpenProtocol (\r
820 Private->Ip6ChildHandle,\r
821 &gEfiIp6ProtocolGuid,\r
822 (VOID **) &Private->Ip6,\r
823 Private->ImageHandle,\r
824 Private->Ip6ChildHandle,\r
825 EFI_OPEN_PROTOCOL_GET_PROTOCOL\r
826 );\r
827 if (EFI_ERROR (Status)) {\r
828 goto ON_ERROR;\r
829 }\r
830\r
831 ZeroMem (&Ip6Config, sizeof (EFI_IP6_CONFIG_DATA));\r
832\r
833 //\r
834 // Configure the ip6 instance for icmp6 packet exchange.\r
835 //\r
836 Ip6Config.DefaultProtocol = 58;\r
837 Ip6Config.AcceptAnyProtocol = FALSE;\r
838 Ip6Config.AcceptIcmpErrors = TRUE;\r
839 Ip6Config.AcceptPromiscuous = FALSE;\r
840 Ip6Config.TrafficClass = 0;\r
841 Ip6Config.HopLimit = 128;\r
842 Ip6Config.FlowLabel = 0;\r
843 Ip6Config.ReceiveTimeout = 0;\r
844 Ip6Config.TransmitTimeout = 0;\r
845\r
846 IP6_COPY_ADDRESS (&Ip6Config.StationAddress, &Private->SrcAddress);\r
847\r
848 IP6_COPY_ADDRESS (&Ip6Config.DestinationAddress, &Private->DstAddress);\r
849\r
850 Status = Private->Ip6->Configure (Private->Ip6, &Ip6Config);\r
851\r
852 if (EFI_ERROR (Status)) {\r
853 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_IP6_CONFIG), gShellNetwork2HiiHandle, Status);\r
854 goto ON_ERROR;\r
855 }\r
856\r
857 return EFI_SUCCESS;\r
858\r
859ON_ERROR:\r
860 if (HandleBuffer != NULL) {\r
861 FreePool (HandleBuffer);\r
862 }\r
863\r
864 if (IfInfo != NULL) {\r
865 FreePool (IfInfo);\r
866 }\r
867\r
868 if ((Ip6Sb != NULL) && (Private->Ip6ChildHandle != NULL)) {\r
869 Ip6Sb->DestroyChild (Ip6Sb, Private->Ip6ChildHandle);\r
870 }\r
871\r
872 return Status;\r
873}\r
874\r
875/**\r
876 Destroy the IP6 instance.\r
877\r
878 @param[in] Private The pointer of PING6_PRIVATE_DATA.\r
879\r
880**/\r
881VOID\r
882Ping6DestroyIpInstance (\r
883 IN PING6_PRIVATE_DATA *Private\r
884 )\r
885{\r
886 EFI_STATUS Status;\r
887 EFI_SERVICE_BINDING_PROTOCOL *Ip6Sb;\r
888\r
889 gBS->CloseProtocol (\r
890 Private->Ip6ChildHandle,\r
891 &gEfiIp6ProtocolGuid,\r
892 Private->ImageHandle,\r
893 Private->Ip6ChildHandle\r
894 );\r
895\r
896 Status = gBS->HandleProtocol (\r
897 Private->NicHandle,\r
898 &gEfiIp6ServiceBindingProtocolGuid,\r
899 (VOID **) &Ip6Sb\r
900 );\r
901\r
902 if (!EFI_ERROR(Status)) {\r
903 Ip6Sb->DestroyChild (Ip6Sb, Private->Ip6ChildHandle);\r
904 }\r
905}\r
906\r
907/**\r
908 The Ping6 Process.\r
909\r
910 @param[in] ImageHandle The firmware allocated handle for the UEFI image.\r
911 @param[in] SendNumber The send request count.\r
912 @param[in] BufferSize The send buffer size.\r
913 @param[in] SrcAddress The source IPv6 address.\r
914 @param[in] DstAddress The destination IPv6 address.\r
915\r
916 @retval SHELL_SUCCESS The ping6 processed successfullly.\r
917 @retval others The ping6 processed unsuccessfully.\r
918\r
919**/\r
920SHELL_STATUS\r
921ShellPing6 (\r
922 IN EFI_HANDLE ImageHandle,\r
923 IN UINT32 SendNumber,\r
924 IN UINT32 BufferSize,\r
925 IN EFI_IPv6_ADDRESS *SrcAddress,\r
926 IN EFI_IPv6_ADDRESS *DstAddress\r
927 )\r
928{\r
929 EFI_STATUS Status;\r
930 EFI_INPUT_KEY Key;\r
931 PING6_PRIVATE_DATA *Private;\r
932 PING6_ICMP6_TX_INFO *TxInfo;\r
933 LIST_ENTRY *Entry;\r
934 LIST_ENTRY *NextEntry;\r
935 SHELL_STATUS ShellStatus;\r
936\r
937 ShellStatus = SHELL_SUCCESS;\r
938 Private = AllocateZeroPool (sizeof (PING6_PRIVATE_DATA));\r
939\r
940 ASSERT (Private != NULL);\r
941\r
942 Private->ImageHandle = ImageHandle;\r
943 Private->SendNum = SendNumber;\r
944 Private->BufferSize = BufferSize;\r
945 Private->RttMin = ~((UINT64 )(0x0));\r
946 Private->Status = EFI_NOT_READY;\r
947\r
948 InitializeListHead (&Private->TxList);\r
949\r
950 IP6_COPY_ADDRESS (&Private->SrcAddress, SrcAddress);\r
951 IP6_COPY_ADDRESS (&Private->DstAddress, DstAddress);\r
952\r
953 //\r
954 // Open and configure a ip6 instance for ping6.\r
955 //\r
956 Status = Ping6CreateIpInstance (Private);\r
957\r
958 if (EFI_ERROR (Status)) {\r
959 ShellStatus = SHELL_ACCESS_DENIED;\r
960 goto ON_EXIT;\r
961 }\r
962 //\r
963 // Print the command line itself.\r
964 //\r
965 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_START), gShellNetwork2HiiHandle, mIp6DstString, Private->BufferSize);\r
966 //\r
967 // Create a ipv6 token to receive the first icmp6 echo reply packet.\r
968 //\r
969 Status = Ping6OnReceiveEchoReply (Private);\r
970\r
971 if (EFI_ERROR (Status)) {\r
972 ShellStatus = SHELL_ACCESS_DENIED;\r
973 goto ON_EXIT;\r
974 }\r
975 //\r
976 // Create and start timer to send icmp6 echo request packet per second.\r
977 //\r
978 Status = gBS->CreateEvent (\r
979 EVT_TIMER | EVT_NOTIFY_SIGNAL,\r
980 TPL_CALLBACK,\r
981 Ping6OnTimerRoutine6,\r
982 Private,\r
983 &Private->Timer\r
984 );\r
985\r
986 if (EFI_ERROR (Status)) {\r
987 ShellStatus = SHELL_ACCESS_DENIED;\r
988 goto ON_EXIT;\r
989 }\r
990 //\r
991 // Create a ipv6 token to send the first icmp6 echo request packet.\r
992 //\r
993 Status = Ping6SendEchoRequest (Private);\r
994 //\r
995 // EFI_NOT_READY for IPsec is enable and IKE is not established.\r
996 //\r
997 if (EFI_ERROR (Status) && (Status != EFI_NOT_READY)) {\r
998 ShellStatus = SHELL_ACCESS_DENIED;\r
999 if(Status == EFI_NOT_FOUND) {\r
1000 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_NOSOURCE_INDOMAIN), gShellNetwork2HiiHandle, mIp6DstString);\r
1001 }\r
1002\r
1003 goto ON_EXIT;\r
1004 }\r
1005\r
1006 Status = gBS->SetTimer (\r
1007 Private->Timer,\r
1008 TimerPeriodic,\r
1009 PING6_ONE_SECOND\r
1010 );\r
1011\r
1012 if (EFI_ERROR (Status)) {\r
1013 ShellStatus = SHELL_ACCESS_DENIED;\r
1014 goto ON_EXIT;\r
1015 }\r
1016 //\r
1017 // Control the ping6 process by two factors:\r
1018 // 1. Hot key\r
1019 // 2. Private->Status\r
1020 // 2.1. success means all icmp6 echo request packets get reply packets.\r
1021 // 2.2. timeout means the last icmp6 echo reply request timeout to get reply.\r
1022 // 2.3. noready means ping6 process is on-the-go.\r
1023 //\r
1024 while (Private->Status == EFI_NOT_READY) {\r
1025 Private->Ip6->Poll (Private->Ip6);\r
1026\r
1027 //\r
1028 // Terminate the ping6 process by 'esc' or 'ctl-c'.\r
1029 //\r
1030 Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);\r
1031\r
1032 if (!EFI_ERROR(Status)) {\r
1033 if ((Key.UnicodeChar == 0x1b) || (Key.UnicodeChar == 0x03) ||\r
1034 ((Key.UnicodeChar == 0) && (Key.ScanCode == SCAN_ESC))) {\r
1035 goto ON_STAT;\r
1036 }\r
1037 }\r
1038 }\r
1039\r
1040ON_STAT:\r
1041 //\r
1042 // Display the statistics in all.\r
1043 //\r
1044 gBS->SetTimer (Private->Timer, TimerCancel, 0);\r
1045\r
1046 if (Private->TxCount != 0) {\r
1047 ShellPrintHiiEx (\r
1048 -1,\r
1049 -1,\r
1050 NULL,\r
1051 STRING_TOKEN (STR_PING6_STAT),\r
1052 gShellNetwork2HiiHandle,\r
1053 Private->TxCount,\r
1054 Private->RxCount,\r
1055 (100 * (Private->TxCount - Private->RxCount)) / Private->TxCount,\r
1056 Private->RttSum\r
1057 );\r
1058 }\r
1059\r
1060 if (Private->RxCount != 0) {\r
1061 ShellPrintHiiEx (\r
1062 -1,\r
1063 -1,\r
1064 NULL,\r
1065 STRING_TOKEN (STR_PING6_RTT),\r
1066 gShellNetwork2HiiHandle,\r
1067 Private->RttMin,\r
1068 Private->RttMax,\r
1069 DivU64x64Remainder (Private->RttSum, Private->RxCount, NULL)\r
1070 );\r
1071 }\r
1072\r
1073ON_EXIT:\r
1074\r
1075 if (Private != NULL) {\r
1076 Private->Status = EFI_ABORTED;\r
1077\r
1078 NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->TxList) {\r
1079 TxInfo = BASE_CR (Entry, PING6_ICMP6_TX_INFO, Link);\r
1080\r
1081 Status = Private->Ip6->Cancel (Private->Ip6, TxInfo->Token);\r
1082\r
1083 RemoveEntryList (&TxInfo->Link);\r
1084 Ping6DestroyTxInfo (TxInfo);\r
1085 }\r
1086\r
1087 if (Private->Timer != NULL) {\r
1088 gBS->CloseEvent (Private->Timer);\r
1089 }\r
1090\r
1091 if (Private->Ip6 != NULL) {\r
1092 Status = Private->Ip6->Cancel (Private->Ip6, &Private->RxToken);\r
1093 }\r
1094\r
1095 if (Private->RxToken.Event != NULL) {\r
1096 gBS->CloseEvent (Private->RxToken.Event);\r
1097 }\r
1098\r
1099 if (Private->Ip6ChildHandle != NULL) {\r
1100 Ping6DestroyIpInstance (Private);\r
1101 }\r
1102\r
1103 FreePool (Private);\r
1104 }\r
1105\r
1106 return ShellStatus;\r
1107}\r
1108\r
1109/**\r
1110 Function for 'ping6' command.\r
1111\r
1112 @param[in] ImageHandle Handle to the Image (NULL if Internal).\r
1113 @param[in] SystemTable Pointer to the System Table (NULL if Internal).\r
1114\r
1115 @retval SHELL_SUCCESS The ping6 processed successfullly.\r
1116 @retval others The ping6 processed unsuccessfully.\r
1117\r
1118**/\r
1119SHELL_STATUS\r
1120EFIAPI\r
1121ShellCommandRunPing6 (\r
1122 IN EFI_HANDLE ImageHandle,\r
1123 IN EFI_SYSTEM_TABLE *SystemTable\r
1124 )\r
1125{\r
1126 EFI_STATUS Status;\r
1127 SHELL_STATUS ShellStatus;\r
1128 EFI_IPv6_ADDRESS DstAddress;\r
1129 EFI_IPv6_ADDRESS SrcAddress;\r
1130 UINT64 BufferSize;\r
1131 UINTN SendNumber;\r
1132 LIST_ENTRY *ParamPackage;\r
1133 CONST CHAR16 *ValueStr;\r
1134 CONST CHAR16 *ValueStrPtr;\r
1135 UINTN NonOptionCount;\r
1136 CHAR16 *ProblemParam;\r
1137\r
1138 ProblemParam = NULL;\r
1139 ShellStatus = SHELL_SUCCESS;\r
1140\r
1141 Status = ShellCommandLineParseEx (Ping6ParamList, &ParamPackage, &ProblemParam, TRUE, FALSE);\r
1142 if (EFI_ERROR(Status)) {\r
1143 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_INVALID_INPUT), gShellNetwork2HiiHandle);\r
1144 ShellStatus = SHELL_INVALID_PARAMETER;\r
1145 goto ON_EXIT;\r
1146 }\r
1147\r
1148 SendNumber = 10;\r
1149 BufferSize = 16;\r
1150\r
1151 //\r
1152 // Parse the paramter of count number.\r
1153 //\r
1154 ValueStr = ShellCommandLineGetValue (ParamPackage, L"-n");\r
1155 ValueStrPtr = ValueStr;\r
1156 if (ValueStr != NULL) {\r
1157 SendNumber = ShellStrToUintn (ValueStrPtr);\r
1158\r
1159 //\r
1160 // ShellStrToUintn will return 0 when input is 0 or an invalid input string.\r
1161 //\r
1162 if ((SendNumber == 0) || (SendNumber > PING6_MAX_SEND_NUMBER)) {\r
1163 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_INVALID_SEND_NUMBER), gShellNetwork2HiiHandle, ValueStr);\r
1164 ShellStatus = SHELL_INVALID_PARAMETER;\r
1165 goto ON_EXIT;\r
1166 }\r
1167 }\r
1168 //\r
1169 // Parse the paramter of buffer size.\r
1170 //\r
1171 ValueStr = ShellCommandLineGetValue (ParamPackage, L"-l");\r
1172 ValueStrPtr = ValueStr;\r
1173 if (ValueStr != NULL) {\r
1174 BufferSize = ShellStrToUintn (ValueStrPtr);\r
1175\r
1176 //\r
1177 // ShellStrToUintn will return 0 when input is 0 or an invalid input string.\r
1178 //\r
1179 if ((BufferSize < 16) || (BufferSize > PING6_MAX_BUFFER_SIZE)) {\r
1180 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_INVALID_BUFFER_SIZE), gShellNetwork2HiiHandle, ValueStr);\r
1181 ShellStatus = SHELL_INVALID_PARAMETER;\r
1182 goto ON_EXIT;\r
1183 }\r
1184 }\r
1185\r
1186 ZeroMem (&SrcAddress, sizeof (EFI_IPv6_ADDRESS));\r
1187 ZeroMem (&DstAddress, sizeof (EFI_IPv6_ADDRESS));\r
1188\r
1189 //\r
1190 // Parse the paramter of source ip address.\r
1191 //\r
1192 ValueStr = ShellCommandLineGetValue (ParamPackage, L"-s");\r
1193 ValueStrPtr = ValueStr;\r
1194 if (ValueStr != NULL) {\r
1195 mIp6SrcString = ValueStr;\r
1196 Status = NetLibStrToIp6 (ValueStrPtr, &SrcAddress);\r
1197 if (EFI_ERROR (Status)) {\r
1198 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_INVALID_IP), gShellNetwork2HiiHandle, ValueStr);\r
1199 ShellStatus = SHELL_INVALID_PARAMETER;\r
1200 goto ON_EXIT;\r
1201 }\r
1202 }\r
1203 //\r
1204 // Parse the paramter of destination ip address.\r
1205 //\r
1206 NonOptionCount = ShellCommandLineGetCount(ParamPackage);\r
1207 ValueStr = ShellCommandLineGetRawValue (ParamPackage, (UINT32)(NonOptionCount-1));\r
1208 if (NonOptionCount != 2) {\r
1209 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_INVALID_INPUT), gShellNetwork2HiiHandle);\r
1210 ShellStatus = SHELL_INVALID_PARAMETER;\r
1211 goto ON_EXIT;\r
1212 }\r
1213 ValueStrPtr = ValueStr;\r
1214 if (ValueStr != NULL) {\r
1215 mIp6DstString = ValueStr;\r
1216 Status = NetLibStrToIp6 (ValueStrPtr, &DstAddress);\r
1217 if (EFI_ERROR (Status)) {\r
1218 ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING6_INVALID_IP), gShellNetwork2HiiHandle, ValueStr);\r
1219 ShellStatus = SHELL_INVALID_PARAMETER;\r
1220 goto ON_EXIT;\r
1221 }\r
1222 }\r
1223 //\r
1224 // Get frequency to calculate the time from ticks.\r
1225 //\r
1226 Status = Ping6GetFrequency ();\r
1227\r
1228 if (EFI_ERROR(Status)) {\r
1229 ShellStatus = SHELL_ACCESS_DENIED;\r
1230 goto ON_EXIT;\r
1231 }\r
1232 //\r
1233 // Enter into ping6 process.\r
1234 //\r
1235 ShellStatus = ShellPing6 (\r
1236 ImageHandle,\r
1237 (UINT32)SendNumber,\r
1238 (UINT32)BufferSize,\r
1239 &SrcAddress,\r
1240 &DstAddress\r
1241 );\r
1242\r
1243ON_EXIT:\r
1244 ShellCommandLineFreeVarList (ParamPackage);\r
1245 return ShellStatus;\r
1246}\r
1247\r