]> git.proxmox.com Git - mirror_edk2.git/blob - ShellPkg/Library/UefiShellLevel2CommandsLib/Mv.c
ShellPkg: Refine code style to avoid potential NullPointer dereference.
[mirror_edk2.git] / ShellPkg / Library / UefiShellLevel2CommandsLib / Mv.c
1 /** @file
2 Main file for mv shell level 2 function.
3
4 (C) Copyright 2013-2014, Hewlett-Packard Development Company, L.P.
5 Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.<BR>
6 This program and the accompanying materials
7 are licensed and made available under the terms and conditions of the BSD License
8 which accompanies this distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
10
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13
14 **/
15
16 #include "UefiShellLevel2CommandsLib.h"
17
18 /**
19 Function to validate that moving a specific file (FileName) to a specific
20 location (DestPath) is valid.
21
22 This function will verify that the destination is not a subdirectory of
23 FullName, that the Current working Directory is not being moved, and that
24 the directory is not read only.
25
26 if the move is invalid this function will report the error to StdOut.
27
28 @param FullName [in] The name of the file to move.
29 @param Cwd [in] The current working directory
30 @param DestPath [in] The target location to move to
31 @param Attribute[in] The Attribute of the file
32 @param FileStatus[in] The Status of the file when opened
33
34 @retval TRUE The move is valid
35 @retval FALSE The move is not
36 **/
37 BOOLEAN
38 EFIAPI
39 IsValidMove(
40 IN CONST CHAR16 *FullName,
41 IN CONST CHAR16 *Cwd,
42 IN CONST CHAR16 *DestPath,
43 IN CONST UINT64 Attribute,
44 IN CONST EFI_STATUS FileStatus
45 )
46 {
47 CHAR16 *Test;
48 CHAR16 *Test1;
49 CHAR16 *TestWalker;
50 INTN Result;
51 UINTN TempLen;
52 if (Cwd != NULL && StrCmp(FullName, Cwd) == 0) {
53 //
54 // Invalid move
55 //
56 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_MV_INV_CWD), gShellLevel2HiiHandle);
57 return (FALSE);
58 }
59 Test = NULL;
60 Test = StrnCatGrow(&Test, NULL, DestPath, 0);
61 TestWalker = Test;
62 ASSERT(TestWalker != NULL);
63 while(*TestWalker == L'\\') {
64 TestWalker++;
65 }
66 while(TestWalker != NULL && TestWalker[StrLen(TestWalker)-1] == L'\\') {
67 TestWalker[StrLen(TestWalker)-1] = CHAR_NULL;
68 }
69 ASSERT(TestWalker != NULL);
70 ASSERT(FullName != NULL);
71 if (StrStr(FullName, TestWalker) != 0) {
72 TempLen = StrLen(FullName);
73 if (StrStr(FullName, TestWalker) != FullName // not the first items... (could below it)
74 && TempLen <= (StrLen(TestWalker) + 1)
75 && StrStr(FullName+StrLen(TestWalker) + 1, L"\\") == NULL) {
76 //
77 // Invalid move
78 //
79 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_MV_INV_SUB), gShellLevel2HiiHandle);
80 FreePool(Test);
81 return (FALSE);
82 }
83 }
84 FreePool(Test);
85 if (StrStr(DestPath, FullName) != 0 && StrStr(DestPath, FullName) != DestPath) {
86 //
87 // Invalid move
88 //
89 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_MV_INV_SUB), gShellLevel2HiiHandle);
90 return (FALSE);
91 }
92 if (((Attribute & EFI_FILE_READ_ONLY) != 0) || (FileStatus == EFI_WRITE_PROTECTED)) {
93 //
94 // invalid to move read only
95 //
96 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_MV_INV_RO), gShellLevel2HiiHandle, FullName);
97 return (FALSE);
98 }
99 Test = StrStr(FullName, L":");
100 Test1 = StrStr(DestPath, L":");
101 if (Test1 != NULL && Test != NULL) {
102 *Test = CHAR_NULL;
103 *Test1 = CHAR_NULL;
104 Result = StringNoCaseCompare(&FullName, &DestPath);
105 *Test = L':';
106 *Test1 = L':';
107 if (Result != 0) {
108 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_MV_INV_FS), gShellLevel2HiiHandle);
109 return (FALSE);
110 }
111 }
112 return (TRUE);
113 }
114
115 /**
116 Function to take a destination path that might contain wildcards and verify
117 that there is only a single possible target (IE we cant have wildcards that
118 have 2 possible destination).
119
120 if the result is sucessful the caller must free *DestPathPointer.
121
122 @param[in] DestDir The original path to the destination.
123 @param[in, out] DestPathPointer A pointer to the callee allocated final path.
124 @param[in] Cwd A pointer to the current working directory.
125
126 @retval SHELL_INVALID_PARAMETER The DestDir could not be resolved to a location.
127 @retval SHELL_INVALID_PARAMETER The DestDir could be resolved to more than 1 location.
128 @retval SHELL_INVALID_PARAMETER Cwd is required and is NULL.
129 @retval SHELL_SUCCESS The operation was sucessful.
130 **/
131 SHELL_STATUS
132 EFIAPI
133 GetDestinationLocation(
134 IN CONST CHAR16 *DestDir,
135 IN OUT CHAR16 **DestPathPointer,
136 IN CONST CHAR16 *Cwd
137 )
138 {
139 EFI_SHELL_FILE_INFO *DestList;
140 EFI_SHELL_FILE_INFO *Node;
141 CHAR16 *DestPath;
142 UINTN NewSize;
143 UINTN CurrentSize;
144
145 DestList = NULL;
146 DestPath = NULL;
147
148 if (StrStr(DestDir, L"\\") == DestDir) {
149 if (Cwd == NULL) {
150 return SHELL_INVALID_PARAMETER;
151 }
152 DestPath = AllocateZeroPool(StrSize(Cwd));
153 if (DestPath == NULL) {
154 return (SHELL_OUT_OF_RESOURCES);
155 }
156 StrCpy(DestPath, Cwd);
157 while (PathRemoveLastItem(DestPath)) ;
158
159 //
160 // Append DestDir beyond '\' which may be present
161 //
162 CurrentSize = StrSize(DestPath);
163 StrnCatGrow(&DestPath, &CurrentSize, &DestDir[1], 0);
164
165 *DestPathPointer = DestPath;
166 return (SHELL_SUCCESS);
167 }
168 //
169 // get the destination path
170 //
171 ShellOpenFileMetaArg((CHAR16*)DestDir, EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ|EFI_FILE_MODE_CREATE, &DestList);
172 if (DestList == NULL || IsListEmpty(&DestList->Link)) {
173 //
174 // Not existing... must be renaming
175 //
176 if (StrStr(DestDir, L":") == NULL) {
177 if (Cwd == NULL) {
178 ShellCloseFileMetaArg(&DestList);
179 return (SHELL_INVALID_PARAMETER);
180 }
181 NewSize = StrSize(Cwd);
182 NewSize += StrSize(DestDir);
183 DestPath = AllocateZeroPool(NewSize);
184 if (DestPath == NULL) {
185 ShellCloseFileMetaArg(&DestList);
186 return (SHELL_OUT_OF_RESOURCES);
187 }
188 StrCpy(DestPath, Cwd);
189 if (DestPath[StrLen(DestPath)-1] != L'\\' && DestDir[0] != L'\\') {
190 StrCat(DestPath, L"\\");
191 } else if (DestPath[StrLen(DestPath)-1] == L'\\' && DestDir[0] == L'\\') {
192 ((CHAR16*)DestPath)[StrLen(DestPath)-1] = CHAR_NULL;
193 }
194 StrCat(DestPath, DestDir);
195 } else {
196 ASSERT(DestPath == NULL);
197 DestPath = StrnCatGrow(&DestPath, NULL, DestDir, 0);
198 if (DestPath == NULL) {
199 ShellCloseFileMetaArg(&DestList);
200 return (SHELL_OUT_OF_RESOURCES);
201 }
202 }
203 } else {
204 Node = (EFI_SHELL_FILE_INFO*)GetFirstNode(&DestList->Link);
205 //
206 // Make sure there is only 1 node in the list.
207 //
208 if (!IsNodeAtEnd(&DestList->Link, &Node->Link)) {
209 ShellCloseFileMetaArg(&DestList);
210 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_MARG_ERROR), gShellLevel2HiiHandle, DestDir);
211 return (SHELL_INVALID_PARAMETER);
212 }
213 if (ShellIsDirectory(Node->FullName)==EFI_SUCCESS) {
214 DestPath = AllocateZeroPool(StrSize(Node->FullName)+sizeof(CHAR16));
215 if (DestPath == NULL) {
216 ShellCloseFileMetaArg(&DestList);
217 return (SHELL_OUT_OF_RESOURCES);
218 }
219 StrCpy(DestPath, Node->FullName);
220 StrCat(DestPath, L"\\");
221 } else {
222 //
223 // cant move onto another file.
224 //
225 ShellCloseFileMetaArg(&DestList);
226 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_FILE_ERROR), gShellLevel2HiiHandle, DestDir);
227 return (SHELL_INVALID_PARAMETER);
228 }
229 }
230
231 *DestPathPointer = DestPath;
232 ShellCloseFileMetaArg(&DestList);
233
234 return (SHELL_SUCCESS);
235 }
236
237 /**
238 function to take a list of files to move and a destination location and do
239 the verification and moving of those files to that location. This function
240 will report any errors to the user and continue to move the rest of the files.
241
242 @param[in] FileList A LIST_ENTRY* based list of files to move
243 @param[out] Resp pointer to response from question. Pass back on looped calling
244 @param[in] DestDir the destination location
245
246 @retval SHELL_SUCCESS the files were all moved.
247 @retval SHELL_INVALID_PARAMETER a parameter was invalid
248 @retval SHELL_SECURITY_VIOLATION a security violation ocurred
249 @retval SHELL_WRITE_PROTECTED the destination was write protected
250 @retval SHELL_OUT_OF_RESOURCES a memory allocation failed
251 **/
252 SHELL_STATUS
253 EFIAPI
254 ValidateAndMoveFiles(
255 IN CONST EFI_SHELL_FILE_INFO *FileList,
256 OUT VOID **Resp,
257 IN CONST CHAR16 *DestDir
258 )
259 {
260 EFI_STATUS Status;
261 CHAR16 *HiiOutput;
262 CHAR16 *HiiResultOk;
263 CHAR16 *DestPath;
264 CONST CHAR16 *Cwd;
265 SHELL_STATUS ShellStatus;
266 CONST EFI_SHELL_FILE_INFO *Node;
267 EFI_FILE_INFO *NewFileInfo;
268 CHAR16 *TempLocation;
269 UINTN NewSize;
270 UINTN Length;
271 VOID *Response;
272 SHELL_FILE_HANDLE DestHandle;
273 CHAR16 *CleanFilePathStr;
274
275 ASSERT(FileList != NULL);
276 ASSERT(DestDir != NULL);
277
278 DestPath = NULL;
279 Cwd = ShellGetCurrentDir(NULL);
280 Response = *Resp;
281 CleanFilePathStr = NULL;
282
283 Status = ShellLevel2StripQuotes (DestDir, &CleanFilePathStr);
284 if (EFI_ERROR (Status)) {
285 if (Status == EFI_OUT_OF_RESOURCES) {
286 return SHELL_OUT_OF_RESOURCES;
287 } else {
288 return SHELL_INVALID_PARAMETER;
289 }
290 }
291
292 ASSERT (CleanFilePathStr != NULL);
293
294 //
295 // Get and validate the destination location
296 //
297 ShellStatus = GetDestinationLocation(CleanFilePathStr, &DestPath, Cwd);
298 FreePool (CleanFilePathStr);
299 if (ShellStatus != SHELL_SUCCESS) {
300 return (ShellStatus);
301 }
302 DestPath = PathCleanUpDirectories(DestPath);
303
304 HiiOutput = HiiGetString (gShellLevel2HiiHandle, STRING_TOKEN (STR_MV_OUTPUT), NULL);
305 HiiResultOk = HiiGetString (gShellLevel2HiiHandle, STRING_TOKEN (STR_GEN_RES_OK), NULL);
306 ASSERT (DestPath != NULL);
307 ASSERT (HiiResultOk != NULL);
308 ASSERT (HiiOutput != NULL);
309
310 //
311 // Go through the list of files and directories to move...
312 //
313 for (Node = (EFI_SHELL_FILE_INFO *)GetFirstNode(&FileList->Link)
314 ; !IsNull(&FileList->Link, &Node->Link)
315 ; Node = (EFI_SHELL_FILE_INFO *)GetNextNode(&FileList->Link, &Node->Link)
316 ){
317 if (ShellGetExecutionBreakFlag()) {
318 break;
319 }
320
321 //
322 // These should never be NULL
323 //
324 ASSERT(Node->FileName != NULL);
325 ASSERT(Node->FullName != NULL);
326 ASSERT(Node->Info != NULL);
327
328 //
329 // skip the directory traversing stuff...
330 //
331 if (StrCmp(Node->FileName, L".") == 0 || StrCmp(Node->FileName, L"..") == 0) {
332 continue;
333 }
334
335 //
336 // Validate that the move is valid
337 //
338 if (!IsValidMove(Node->FullName, Cwd, DestPath, Node->Info->Attribute, Node->Status)) {
339 ShellStatus = SHELL_INVALID_PARAMETER;
340 continue;
341 }
342
343 //
344 // Chop off map info from "DestPath"
345 //
346 if ((TempLocation = StrStr(DestPath, L":")) != NULL) {
347 CopyMem(DestPath, TempLocation+1, StrSize(TempLocation+1));
348 }
349
350 //
351 // construct the new file info block
352 //
353 NewSize = StrSize(DestPath);
354 NewSize += StrSize(Node->FileName) + SIZE_OF_EFI_FILE_INFO + sizeof(CHAR16);
355 NewFileInfo = AllocateZeroPool(NewSize);
356 if (NewFileInfo == NULL) {
357 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_NO_MEM), gShellLevel2HiiHandle);
358 ShellStatus = SHELL_OUT_OF_RESOURCES;
359 } else {
360 CopyMem(NewFileInfo, Node->Info, SIZE_OF_EFI_FILE_INFO);
361 if (DestPath[0] != L'\\') {
362 StrCpy(NewFileInfo->FileName, L"\\");
363 StrCat(NewFileInfo->FileName, DestPath);
364 } else {
365 StrCpy(NewFileInfo->FileName, DestPath);
366 }
367 Length = StrLen(NewFileInfo->FileName);
368 if (Length > 0) {
369 Length--;
370 }
371 if (NewFileInfo->FileName[Length] == L'\\') {
372 if (Node->FileName[0] == L'\\') {
373 //
374 // Don't allow for double slashes. Eliminate one of them.
375 //
376 NewFileInfo->FileName[Length] = CHAR_NULL;
377 }
378 StrCat(NewFileInfo->FileName, Node->FileName);
379 }
380 NewFileInfo->Size = SIZE_OF_EFI_FILE_INFO + StrSize(NewFileInfo->FileName);
381 ShellPrintEx(-1, -1, HiiOutput, Node->FullName, NewFileInfo->FileName);
382
383 if (!EFI_ERROR(ShellFileExists(NewFileInfo->FileName))) {
384 if (Response == NULL) {
385 ShellPromptForResponseHii(ShellPromptResponseTypeYesNoAllCancel, STRING_TOKEN (STR_GEN_DEST_EXIST_OVR), gShellLevel2HiiHandle, &Response);
386 }
387 switch (*(SHELL_PROMPT_RESPONSE*)Response) {
388 case ShellPromptResponseNo:
389 FreePool(NewFileInfo);
390 continue;
391 case ShellPromptResponseCancel:
392 *Resp = Response;
393 //
394 // indicate to stop everything
395 //
396 FreePool(NewFileInfo);
397 FreePool(DestPath);
398 FreePool(HiiOutput);
399 FreePool(HiiResultOk);
400 return (SHELL_ABORTED);
401 case ShellPromptResponseAll:
402 *Resp = Response;
403 break;
404 case ShellPromptResponseYes:
405 FreePool(Response);
406 break;
407 default:
408 FreePool(Response);
409 FreePool(NewFileInfo);
410 FreePool(DestPath);
411 FreePool(HiiOutput);
412 FreePool(HiiResultOk);
413 return SHELL_ABORTED;
414 }
415 Status = ShellOpenFileByName(NewFileInfo->FileName, &DestHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0);
416 ShellDeleteFile(&DestHandle);
417 }
418
419
420 //
421 // Perform the move operation
422 //
423 Status = ShellSetFileInfo(Node->Handle, NewFileInfo);
424
425 //
426 // Free the info object we used...
427 //
428 FreePool(NewFileInfo);
429
430 //
431 // Check our result
432 //
433 if (EFI_ERROR(Status)) {
434 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_UK), gShellLevel2HiiHandle, Status);
435 ShellStatus = SHELL_INVALID_PARAMETER;
436 if (Status == EFI_SECURITY_VIOLATION) {
437 ShellStatus = SHELL_SECURITY_VIOLATION;
438 } else if (Status == EFI_WRITE_PROTECTED) {
439 ShellStatus = SHELL_WRITE_PROTECTED;
440 } else if (Status == EFI_OUT_OF_RESOURCES) {
441 ShellStatus = SHELL_OUT_OF_RESOURCES;
442 } else if (Status == EFI_DEVICE_ERROR) {
443 ShellStatus = SHELL_DEVICE_ERROR;
444 } else if (Status == EFI_ACCESS_DENIED) {
445 ShellStatus = SHELL_ACCESS_DENIED;
446 }
447 } else {
448 ShellPrintEx(-1, -1, L"%s", HiiResultOk);
449 }
450 }
451 } // for loop
452
453 FreePool(DestPath);
454 FreePool(HiiOutput);
455 FreePool(HiiResultOk);
456 return (ShellStatus);
457 }
458
459 /**
460 Function for 'mv' command.
461
462 @param[in] ImageHandle Handle to the Image (NULL if Internal).
463 @param[in] SystemTable Pointer to the System Table (NULL if Internal).
464 **/
465 SHELL_STATUS
466 EFIAPI
467 ShellCommandRunMv (
468 IN EFI_HANDLE ImageHandle,
469 IN EFI_SYSTEM_TABLE *SystemTable
470 )
471 {
472 EFI_STATUS Status;
473 LIST_ENTRY *Package;
474 CHAR16 *ProblemParam;
475 SHELL_STATUS ShellStatus;
476 UINTN ParamCount;
477 UINTN LoopCounter;
478 EFI_SHELL_FILE_INFO *FileList;
479 VOID *Response;
480
481 ProblemParam = NULL;
482 ShellStatus = SHELL_SUCCESS;
483 ParamCount = 0;
484 FileList = NULL;
485 Response = NULL;
486
487 //
488 // initialize the shell lib (we must be in non-auto-init...)
489 //
490 Status = ShellInitialize();
491 ASSERT_EFI_ERROR(Status);
492
493 //
494 // parse the command line
495 //
496 Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
497 if (EFI_ERROR(Status)) {
498 if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
499 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PROBLEM), gShellLevel2HiiHandle, ProblemParam);
500 FreePool(ProblemParam);
501 ShellStatus = SHELL_INVALID_PARAMETER;
502 } else {
503 ASSERT(FALSE);
504 }
505 } else {
506 //
507 // check for "-?"
508 //
509 if (ShellCommandLineGetFlag(Package, L"-?")) {
510 ASSERT(FALSE);
511 }
512
513 switch (ParamCount = ShellCommandLineGetCount(Package)) {
514 case 0:
515 case 1:
516 //
517 // we have insufficient parameters
518 //
519 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_FEW), gShellLevel2HiiHandle);
520 ShellStatus = SHELL_INVALID_PARAMETER;
521 break;
522 case 2:
523 //
524 // must have valid CWD for single parameter...
525 //
526 if (ShellGetCurrentDir(NULL) == NULL){
527 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_NO_CWD), gShellLevel2HiiHandle);
528 ShellStatus = SHELL_INVALID_PARAMETER;
529 } else {
530 Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, 1), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
531 if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
532 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_FILE_NF), gShellLevel2HiiHandle, ShellCommandLineGetRawValue(Package, 1));
533 ShellStatus = SHELL_NOT_FOUND;
534 } else {
535 //
536 // ValidateAndMoveFiles will report errors to the screen itself
537 //
538 ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellGetCurrentDir(NULL));
539 }
540 }
541
542 break;
543 default:
544 ///@todo make sure this works with error half way through and continues...
545 for (ParamCount--, LoopCounter = 1 ; LoopCounter < ParamCount ; LoopCounter++) {
546 if (ShellGetExecutionBreakFlag()) {
547 break;
548 }
549 Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, LoopCounter), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
550 if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
551 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_FILE_NF), gShellLevel2HiiHandle, ShellCommandLineGetRawValue(Package, LoopCounter));
552 ShellStatus = SHELL_NOT_FOUND;
553 } else {
554 //
555 // ValidateAndMoveFiles will report errors to the screen itself
556 // Only change ShellStatus if it's sucessful
557 //
558 if (ShellStatus == SHELL_SUCCESS) {
559 ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
560 } else {
561 ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
562 }
563 }
564 if (FileList != NULL && !IsListEmpty(&FileList->Link)) {
565 Status = ShellCloseFileMetaArg(&FileList);
566 if (EFI_ERROR(Status) && ShellStatus == SHELL_SUCCESS) {
567 ShellStatus = SHELL_ACCESS_DENIED;
568 ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_FILE), gShellLevel2HiiHandle, ShellCommandLineGetRawValue(Package, 1), ShellStatus|MAX_BIT);
569 }
570 }
571 }
572 break;
573 } // switch on parameter count
574
575 if (FileList != NULL) {
576 ShellCloseFileMetaArg(&FileList);
577 }
578
579 //
580 // free the command line package
581 //
582 ShellCommandLineFreeVarList (Package);
583 }
584
585 SHELL_FREE_NON_NULL(Response);
586
587 if (ShellGetExecutionBreakFlag()) {
588 return (SHELL_ABORTED);
589 }
590
591 return (ShellStatus);
592 }