Link Here
|
5 |
CmdExtract::Cmd=Cmd; |
5 |
CmdExtract::Cmd=Cmd; |
6 |
|
6 |
|
7 |
*ArcName=0; |
7 |
*ArcName=0; |
8 |
|
|
|
9 |
*DestFileName=0; |
8 |
*DestFileName=0; |
10 |
|
9 |
|
|
|
10 |
ArcAnalyzed=false; |
11 |
Analyze=new AnalyzeData; |
12 |
memset(Analyze,0,sizeof(*Analyze)); |
13 |
|
11 |
TotalFileCount=0; |
14 |
TotalFileCount=0; |
|
|
15 |
|
16 |
// Common for all archives involved. Set here instead of DoExtract() |
17 |
// to use in unrar.dll too. Allows to avoid LinksToDirs() calls |
18 |
// and save CPU time in no symlinks including ".." in target were extracted. |
19 |
#if defined(_WIN_ALL) |
20 |
// We can't expand symlink path components in another symlink target |
21 |
// in Windows. We can't create symlinks in Android now. Even though we do not |
22 |
// really need LinksToDirs() calls in these systems, we still call it |
23 |
// for extra safety, but only if symlink with ".." in target was extracted. |
24 |
ConvertSymlinkPaths=false; |
25 |
#else |
26 |
// We enable it by default in Unix to care about the case when several |
27 |
// archives are unpacked to same directory with several independent RAR runs. |
28 |
// Worst case performance penalty for a lot of small files seems to be ~3%. |
29 |
ConvertSymlinkPaths=true; |
30 |
#endif |
31 |
|
12 |
Unp=new Unpack(&DataIO); |
32 |
Unp=new Unpack(&DataIO); |
13 |
#ifdef RAR_SMP |
33 |
#ifdef RAR_SMP |
14 |
Unp->SetThreads(Cmd->Threads); |
34 |
Unp->SetThreads(Cmd->Threads); |
Link Here
|
18 |
|
38 |
|
19 |
CmdExtract::~CmdExtract() |
39 |
CmdExtract::~CmdExtract() |
20 |
{ |
40 |
{ |
|
|
41 |
FreeAnalyzeData(); |
21 |
delete Unp; |
42 |
delete Unp; |
|
|
43 |
delete Analyze; |
22 |
} |
44 |
} |
23 |
|
45 |
|
24 |
|
46 |
|
|
|
47 |
void CmdExtract::FreeAnalyzeData() |
48 |
{ |
49 |
for (size_t I=0;I<RefList.Size();I++) |
50 |
{ |
51 |
// We can have undeleted temporary reference source here if extraction |
52 |
// was interrupted early or if user refused to overwrite prompt. |
53 |
if (RefList[I].TmpName!=NULL) |
54 |
DelFile(RefList[I].TmpName); |
55 |
free(RefList[I].RefName); |
56 |
free(RefList[I].TmpName); |
57 |
} |
58 |
RefList.Reset(); |
59 |
|
60 |
memset(Analyze,0,sizeof(*Analyze)); |
61 |
} |
62 |
|
63 |
|
25 |
void CmdExtract::DoExtract() |
64 |
void CmdExtract::DoExtract() |
26 |
{ |
65 |
{ |
27 |
#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) |
66 |
#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) |
Link Here
|
30 |
PasswordCancelled=false; |
69 |
PasswordCancelled=false; |
31 |
DataIO.SetCurrentCommand(Cmd->Command[0]); |
70 |
DataIO.SetCurrentCommand(Cmd->Command[0]); |
32 |
|
71 |
|
33 |
FindData FD; |
72 |
if (*Cmd->UseStdin==0) |
34 |
while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) |
73 |
{ |
35 |
if (FindFile::FastFind(ArcName,&FD)) |
74 |
FindData FD; |
36 |
DataIO.TotalArcSize+=FD.Size; |
75 |
while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) |
|
|
76 |
if (FindFile::FastFind(ArcName,&FD)) |
77 |
DataIO.TotalArcSize+=FD.Size; |
78 |
} |
37 |
|
79 |
|
38 |
Cmd->ArcNames.Rewind(); |
80 |
Cmd->ArcNames.Rewind(); |
39 |
while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) |
81 |
while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) |
Link Here
|
97 |
AllMatchesExact=true; |
139 |
AllMatchesExact=true; |
98 |
AnySolidDataUnpackedWell=false; |
140 |
AnySolidDataUnpackedWell=false; |
99 |
|
141 |
|
|
|
142 |
ArcAnalyzed=false; |
143 |
|
100 |
StartTime.SetCurrentTime(); |
144 |
StartTime.SetCurrentTime(); |
|
|
145 |
|
146 |
LastCheckedSymlink.clear(); |
101 |
} |
147 |
} |
102 |
|
148 |
|
103 |
|
149 |
|
Link Here
|
168 |
} |
214 |
} |
169 |
#endif |
215 |
#endif |
170 |
|
216 |
|
|
|
217 |
Arc.ViewComment(); // Must be before possible EXTRACT_ARC_REPEAT. |
218 |
|
171 |
int64 VolumeSetSize=0; // Total size of volumes after the current volume. |
219 |
int64 VolumeSetSize=0; // Total size of volumes after the current volume. |
172 |
|
220 |
|
|
|
221 |
#ifndef SFX_MODULE |
222 |
if (!ArcAnalyzed && *Cmd->UseStdin==0) |
223 |
{ |
224 |
AnalyzeArchive(Arc.FileName,Arc.Volume,Arc.NewNumbering); |
225 |
ArcAnalyzed=true; // Avoid repeated analysis on EXTRACT_ARC_REPEAT. |
226 |
} |
227 |
#endif |
228 |
|
173 |
if (Arc.Volume) |
229 |
if (Arc.Volume) |
174 |
{ |
230 |
{ |
175 |
#ifndef SFX_MODULE |
231 |
#ifndef SFX_MODULE |
176 |
// Try to speed up extraction for independent solid volumes by starting |
232 |
// Try to speed up extraction for independent solid volumes by starting |
177 |
// extraction from non-first volume if we can. |
233 |
// extraction from non-first volume if we can. |
178 |
if (!UseExactVolName && Arc.Solid && DetectStartVolume(Arc.FileName,Arc.NewNumbering)) |
234 |
if (*Analyze->StartName!=0) |
179 |
{ |
235 |
{ |
|
|
236 |
wcsncpyz(ArcName,Analyze->StartName,ASIZE(ArcName)); |
237 |
*Analyze->StartName=0; |
238 |
|
180 |
UseExactVolName=true; |
239 |
UseExactVolName=true; |
181 |
return EXTRACT_ARC_REPEAT; |
240 |
return EXTRACT_ARC_REPEAT; |
182 |
} |
241 |
} |
183 |
#endif |
242 |
#endif |
184 |
|
243 |
|
185 |
// Calculate the total size of all accessible volumes. |
244 |
// Calculate the total size of all accessible volumes. |
186 |
// This size is necessary to display the correct total progress indicator. |
245 |
// This size is necessary to display the correct total progress indicator. |
187 |
|
246 |
|
Link Here
|
216 |
else |
275 |
else |
217 |
uiStartArchiveExtract(!Cmd->Test,ArcName); |
276 |
uiStartArchiveExtract(!Cmd->Test,ArcName); |
218 |
|
277 |
|
219 |
Arc.ViewComment(); |
278 |
#ifndef SFX_MODULE |
|
|
279 |
if (Analyze->StartPos!=0) |
280 |
{ |
281 |
Arc.Seek(Analyze->StartPos,SEEK_SET); |
282 |
Analyze->StartPos=0; |
283 |
} |
284 |
#endif |
220 |
|
285 |
|
221 |
|
286 |
|
222 |
while (1) |
287 |
while (1) |
Link Here
|
272 |
return false; |
337 |
return false; |
273 |
|
338 |
|
274 |
HEADER_TYPE HeaderType=Arc.GetHeaderType(); |
339 |
HEADER_TYPE HeaderType=Arc.GetHeaderType(); |
275 |
if (HeaderType!=HEAD_FILE) |
340 |
if (HeaderType==HEAD_FILE) |
276 |
{ |
341 |
{ |
|
|
342 |
// Unlike Arc.FileName, ArcName might store an old volume name here. |
343 |
if (Analyze->EndPos!=0 && Analyze->EndPos==Arc.CurBlockPos && |
344 |
(*Analyze->EndName==0 || wcscmp(Analyze->EndName,Arc.FileName)==0)) |
345 |
return false; |
346 |
} |
347 |
else |
348 |
{ |
277 |
#ifndef SFX_MODULE |
349 |
#ifndef SFX_MODULE |
278 |
if (Arc.Format==RARFMT15 && HeaderType==HEAD3_OLDSERVICE && PrevProcessed) |
350 |
if (Arc.Format==RARFMT15 && HeaderType==HEAD3_OLDSERVICE && PrevProcessed) |
279 |
SetExtraInfo20(Cmd,Arc,DestFileName); |
351 |
SetExtraInfo20(Cmd,Arc,DestFileName); |
Link Here
|
315 |
if (Arc.FileHead.UnpSize<0) |
387 |
if (Arc.FileHead.UnpSize<0) |
316 |
Arc.FileHead.UnpSize=0; |
388 |
Arc.FileHead.UnpSize=0; |
317 |
|
389 |
|
|
|
390 |
// 2022.03.20: We might remove this check in the future. |
391 |
// It duplicates Analyze->EndPos and Analyze->EndName in all cases except |
392 |
// volumes on removable media. |
318 |
if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact) |
393 |
if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact) |
319 |
return false; |
394 |
return false; |
320 |
|
395 |
|
Link Here
|
413 |
FirstFile=false; |
488 |
FirstFile=false; |
414 |
#endif |
489 |
#endif |
415 |
|
490 |
|
|
|
491 |
bool RefTarget=false; |
492 |
if (!MatchFound) |
493 |
for (size_t I=0;I<RefList.Size();I++) |
494 |
if (wcscmp(ArcFileName,RefList[I].RefName)==0) |
495 |
{ |
496 |
ExtractRef *MatchedRef=&RefList[I]; |
497 |
|
498 |
if (!Cmd->Test) // While harmless, it is useless for 't'. |
499 |
{ |
500 |
// If reference source isn't selected, but target is selected, |
501 |
// we unpack the source under the temporary name and then rename |
502 |
// or copy it to target name. We do not unpack it under the target |
503 |
// name immediately, because the same source can be used by multiple |
504 |
// targets and it is possible that first target isn't unpacked |
505 |
// for some reason. Also targets might have associated service blocks |
506 |
// like ACLs. All this would complicate processing a lot. |
507 |
wcsncpyz(DestFileName,*Cmd->TempPath!=0 ? Cmd->TempPath:Cmd->ExtrPath,ASIZE(DestFileName)); |
508 |
AddEndSlash(DestFileName,ASIZE(DestFileName)); |
509 |
wcsncatz(DestFileName,L"__tmp_reference_source_",ASIZE(DestFileName)); |
510 |
MkTemp(DestFileName,ASIZE(DestFileName)); |
511 |
MatchedRef->TmpName=wcsdup(DestFileName); |
512 |
} |
513 |
RefTarget=true; // Need it even for 't' to test the reference source. |
514 |
break; |
515 |
} |
516 |
|
416 |
if (Arc.FileHead.Encrypted && Cmd->SkipEncrypted) |
517 |
if (Arc.FileHead.Encrypted && Cmd->SkipEncrypted) |
417 |
if (Arc.Solid) |
518 |
if (Arc.Solid) |
418 |
return false; // Abort the entire extraction for solid archive. |
519 |
return false; // Abort the entire extraction for solid archive. |
419 |
else |
520 |
else |
420 |
MatchFound=false; // Skip only the current file for non-solid archive. |
521 |
MatchFound=false; // Skip only the current file for non-solid archive. |
421 |
|
522 |
|
422 |
if (MatchFound || (SkipSolid=Arc.Solid)!=0) |
523 |
if (MatchFound || RefTarget || (SkipSolid=Arc.Solid)!=0) |
423 |
{ |
524 |
{ |
424 |
// First common call of uiStartFileExtract. It is done before overwrite |
525 |
// First common call of uiStartFileExtract. It is done before overwrite |
425 |
// prompts, so if SkipSolid state is changed below, we'll need to make |
526 |
// prompts, so if SkipSolid state is changed below, we'll need to make |
Link Here
|
427 |
if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid)) |
528 |
if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid)) |
428 |
return false; |
529 |
return false; |
429 |
|
530 |
|
430 |
ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); |
531 |
if (!RefTarget) |
|
|
532 |
ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); |
431 |
|
533 |
|
432 |
// DestFileName can be set empty in case of excessive -ap switch. |
534 |
// DestFileName can be set empty in case of excessive -ap switch. |
433 |
ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore; |
535 |
ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore; |
Link Here
|
464 |
return !Arc.Solid; // Can try extracting next file only in non-solid archive. |
566 |
return !Arc.Solid; // Can try extracting next file only in non-solid archive. |
465 |
} |
567 |
} |
466 |
|
568 |
|
467 |
while (true) // Repeat the password prompt for wrong and empty passwords. |
569 |
if (Arc.FileHead.Encrypted) |
468 |
{ |
570 |
{ |
469 |
if (Arc.FileHead.Encrypted) |
571 |
RarCheckPassword CheckPwd; |
|
|
572 |
if (Arc.Format==RARFMT50 && Arc.FileHead.UsePswCheck && !Arc.BrokenHeader) |
573 |
CheckPwd.Set(Arc.FileHead.Salt,Arc.FileHead.InitV,Arc.FileHead.Lg2Count,Arc.FileHead.PswCheck); |
574 |
|
575 |
while (true) // Repeat the password prompt for wrong and empty passwords. |
470 |
{ |
576 |
{ |
471 |
// Stop archive extracting if user cancelled a password prompt. |
577 |
// Stop archive extracting if user cancelled a password prompt. |
472 |
#ifdef RARDLL |
578 |
#ifdef RARDLL |
Link Here
|
476 |
return false; |
582 |
return false; |
477 |
} |
583 |
} |
478 |
#else |
584 |
#else |
479 |
if (!ExtrGetPassword(Arc,ArcFileName)) |
585 |
if (!ExtrGetPassword(Arc,ArcFileName,CheckPwd.IsSet() ? &CheckPwd:NULL)) |
480 |
{ |
586 |
{ |
481 |
PasswordCancelled=true; |
587 |
PasswordCancelled=true; |
482 |
return false; |
588 |
return false; |
483 |
} |
589 |
} |
484 |
#endif |
590 |
#endif |
485 |
} |
|
|
486 |
|
591 |
|
487 |
// Set a password before creating the file, so we can skip creating |
592 |
// Set a password before creating the file, so we can skip creating |
488 |
// in case of wrong password. |
593 |
// in case of wrong password. |
489 |
SecPassword FilePassword=Cmd->Password; |
594 |
SecPassword FilePassword=Cmd->Password; |
490 |
#if defined(_WIN_ALL) && !defined(SFX_MODULE) |
595 |
#if defined(_WIN_ALL) && !defined(SFX_MODULE) |
491 |
ConvertDosPassword(Arc,FilePassword); |
596 |
ConvertDosPassword(Arc,FilePassword); |
492 |
#endif |
597 |
#endif |
493 |
|
598 |
|
494 |
byte PswCheck[SIZE_PSWCHECK]; |
599 |
byte PswCheck[SIZE_PSWCHECK]; |
495 |
DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, |
600 |
DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, |
496 |
Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, |
601 |
Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, |
497 |
Arc.FileHead.InitV,Arc.FileHead.Lg2Count, |
602 |
Arc.FileHead.InitV,Arc.FileHead.Lg2Count, |
498 |
Arc.FileHead.HashKey,PswCheck); |
603 |
Arc.FileHead.HashKey,PswCheck); |
499 |
|
604 |
|
500 |
// If header is damaged, we cannot rely on password check value, |
605 |
// If header is damaged, we cannot rely on password check value, |
501 |
// because it can be damaged too. |
606 |
// because it can be damaged too. |
502 |
if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck && |
607 |
if (Arc.FileHead.UsePswCheck && !Arc.BrokenHeader && |
503 |
memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 && |
608 |
memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0) |
504 |
!Arc.BrokenHeader) |
|
|
505 |
{ |
506 |
if (GlobalPassword) // For -p<pwd> or Ctrl+P to avoid the infinite loop. |
507 |
{ |
609 |
{ |
508 |
// This message is used by Android GUI to reset cached passwords. |
610 |
if (GlobalPassword) // For -p<pwd> or Ctrl+P to avoid the infinite loop. |
509 |
// Update appropriate code if changed. |
611 |
{ |
510 |
uiMsg(UIERROR_BADPSW,Arc.FileName,ArcFileName); |
612 |
// This message is used by Android GUI to reset cached passwords. |
511 |
} |
613 |
// Update appropriate code if changed. |
512 |
else // For passwords entered manually. |
614 |
uiMsg(UIERROR_BADPSW,Arc.FileName,ArcFileName); |
513 |
{ |
615 |
} |
514 |
// This message is used by Android GUI and Windows GUI and SFX to |
616 |
else // For passwords entered manually. |
515 |
// reset cached passwords. Update appropriate code if changed. |
617 |
{ |
516 |
uiMsg(UIWAIT_BADPSW,Arc.FileName,ArcFileName); |
618 |
// This message is used by Android GUI and Windows GUI and SFX to |
517 |
Cmd->Password.Clean(); |
619 |
// reset cached passwords. Update appropriate code if changed. |
|
|
620 |
uiMsg(UIWAIT_BADPSW,Arc.FileName,ArcFileName); |
621 |
Cmd->Password.Clean(); |
518 |
|
622 |
|
519 |
// Avoid new requests for unrar.dll to prevent the infinite loop |
623 |
// Avoid new requests for unrar.dll to prevent the infinite loop |
520 |
// if app always returns the same password. |
624 |
// if app always returns the same password. |
521 |
#ifndef RARDLL |
625 |
#ifndef RARDLL |
522 |
continue; // Request a password again. |
626 |
continue; // Request a password again. |
523 |
#endif |
627 |
#endif |
|
|
628 |
} |
629 |
#ifdef RARDLL |
630 |
// If we already have ERAR_EOPEN as result of missing volume, |
631 |
// we should not replace it with less precise ERAR_BAD_PASSWORD. |
632 |
if (Cmd->DllError!=ERAR_EOPEN) |
633 |
Cmd->DllError=ERAR_BAD_PASSWORD; |
634 |
#endif |
635 |
ErrHandler.SetErrorCode(RARX_BADPWD); |
636 |
ExtrFile=false; |
524 |
} |
637 |
} |
525 |
#ifdef RARDLL |
638 |
break; |
526 |
// If we already have ERAR_EOPEN as result of missing volume, |
|
|
527 |
// we should not replace it with less precise ERAR_BAD_PASSWORD. |
528 |
if (Cmd->DllError!=ERAR_EOPEN) |
529 |
Cmd->DllError=ERAR_BAD_PASSWORD; |
530 |
#endif |
531 |
ErrHandler.SetErrorCode(RARX_BADPWD); |
532 |
ExtrFile=false; |
533 |
} |
639 |
} |
534 |
break; |
|
|
535 |
} |
640 |
} |
|
|
641 |
else |
642 |
DataIO.SetEncryption(false,CRYPT_NONE,NULL,NULL,NULL,0,NULL,NULL); |
536 |
|
643 |
|
537 |
#ifdef RARDLL |
644 |
#ifdef RARDLL |
538 |
if (*Cmd->DllDestName!=0) |
645 |
if (*Cmd->DllDestName!=0) |
539 |
wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); |
646 |
wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); |
540 |
#endif |
647 |
#endif |
541 |
|
648 |
|
|
|
649 |
if (ExtrFile && Command!='P' && !Cmd->Test && !Cmd->AbsoluteLinks && |
650 |
ConvertSymlinkPaths) |
651 |
ExtrFile=LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); |
652 |
|
542 |
File CurFile; |
653 |
File CurFile; |
543 |
|
654 |
|
544 |
bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; |
655 |
bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; |
545 |
if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY) |
656 |
if (LinkEntry && (Arc.FileHead.RedirType!=FSREDIR_FILECOPY)) |
546 |
{ |
657 |
{ |
547 |
if (ExtrFile && Command!='P' && !Cmd->Test) |
658 |
if (ExtrFile && Command!='P' && !Cmd->Test) |
548 |
{ |
659 |
{ |
549 |
// Overwrite prompt for symbolic and hard links. |
660 |
// Overwrite prompt for symbolic and hard links and when we move |
|
|
661 |
// a temporary file to the file reference instead of copying it. |
550 |
bool UserReject=false; |
662 |
bool UserReject=false; |
551 |
if (FileExist(DestFileName) && !UserReject) |
663 |
if (FileExist(DestFileName) && !UserReject) |
552 |
FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); |
664 |
FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); |
Link Here
|
667 |
if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) |
779 |
if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) |
668 |
{ |
780 |
{ |
669 |
wchar RedirName[NM]; |
781 |
wchar RedirName[NM]; |
670 |
ConvertPath(Arc.FileHead.RedirName,RedirName,ASIZE(RedirName)); |
782 |
|
|
|
783 |
// 2022.11.15: Might be needed when unpacking WinRAR 5.0 links with |
784 |
// Unix RAR. WinRAR 5.0 used \ path separators here, when beginning |
785 |
// from 5.10 even Windows version uses / internally and converts |
786 |
// them to \ when reading FHEXTRA_REDIR. |
787 |
// We must perform this conversion before ConvertPath call, |
788 |
// so paths mixing different slashes like \dir1/dir2\file are |
789 |
// processed correctly. |
790 |
SlashToNative(Arc.FileHead.RedirName,RedirName,ASIZE(RedirName)); |
671 |
|
791 |
|
|
|
792 |
ConvertPath(RedirName,RedirName,ASIZE(RedirName)); |
793 |
|
672 |
wchar NameExisting[NM]; |
794 |
wchar NameExisting[NM]; |
673 |
ExtrPrepareName(Arc,RedirName,NameExisting,ASIZE(NameExisting)); |
795 |
ExtrPrepareName(Arc,RedirName,NameExisting,ASIZE(NameExisting)); |
674 |
if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch. |
796 |
if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch. |
675 |
if (Type==FSREDIR_HARDLINK) |
797 |
if (Type==FSREDIR_HARDLINK) |
676 |
LinkSuccess=ExtractHardlink(Cmd,DestFileName,NameExisting,ASIZE(NameExisting)); |
798 |
LinkSuccess=ExtractHardlink(Cmd,DestFileName,NameExisting,ASIZE(NameExisting)); |
677 |
else |
799 |
else |
678 |
LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting)); |
800 |
LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,RedirName,DestFileName,NameExisting,ASIZE(NameExisting),Arc.FileHead.UnpSize); |
679 |
} |
801 |
} |
680 |
else |
802 |
else |
681 |
if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION) |
803 |
if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION) |
682 |
{ |
804 |
{ |
683 |
if (FileCreateMode) |
805 |
if (FileCreateMode) |
684 |
LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName); |
806 |
{ |
|
|
807 |
bool UpLink; |
808 |
LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName,UpLink); |
809 |
ConvertSymlinkPaths|=LinkSuccess && UpLink; |
810 |
|
811 |
// We do not actually need to reset the cache here if we cache |
812 |
// only the single last checked path, because at this point |
813 |
// it will always contain the link own path and link can't |
814 |
// overwrite its parent folder. But if we ever decide to cache |
815 |
// several already checked paths, we'll need to reset them here. |
816 |
// Otherwise if no files were created in one of such paths, |
817 |
// let's say because of file create error, it might be possible |
818 |
// to overwrite the path with link and avoid checks. We keep this |
819 |
// code here as a reminder in case of possible modifications. |
820 |
LastCheckedSymlink.clear(); // Reset cache for safety reason. |
821 |
} |
685 |
} |
822 |
} |
686 |
else |
823 |
else |
687 |
{ |
824 |
{ |
688 |
uiMsg(UIERROR_UNKNOWNEXTRA,Arc.FileName,DestFileName); |
825 |
uiMsg(UIERROR_UNKNOWNEXTRA,Arc.FileName,ArcFileName); |
689 |
LinkSuccess=false; |
826 |
LinkSuccess=false; |
690 |
} |
827 |
} |
691 |
|
828 |
|
Link Here
|
709 |
Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); |
846 |
Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); |
710 |
Unp->SetDestSize(Arc.FileHead.UnpSize); |
847 |
Unp->SetDestSize(Arc.FileHead.UnpSize); |
711 |
#ifndef SFX_MODULE |
848 |
#ifndef SFX_MODULE |
|
|
849 |
// RAR 1.3 - 1.5 archives do not set per file solid flag. |
712 |
if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15) |
850 |
if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15) |
713 |
Unp->DoUnpack(15,FileCount>1 && Arc.Solid); |
851 |
Unp->DoUnpack(15,FileCount>1 && Arc.Solid); |
714 |
else |
852 |
else |
Link Here
|
866 |
} |
1004 |
} |
867 |
|
1005 |
|
868 |
|
1006 |
|
869 |
bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) |
1007 |
bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,const wchar *RedirName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize,int64 UnpSize) |
870 |
{ |
1008 |
{ |
871 |
SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. |
|
|
872 |
|
873 |
File Existing; |
1009 |
File Existing; |
874 |
if (!Existing.WOpen(NameExisting)) |
1010 |
if (!Existing.Open(NameExisting)) |
875 |
{ |
1011 |
{ |
876 |
uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew); |
1012 |
bool OpenFailed=true; |
877 |
uiMsg(UIERROR_FILECOPYHINT,ArcName); |
1013 |
// If we couldn't find the existing file, check if match is present |
|
|
1014 |
// in temporary reference sources list. |
1015 |
for (size_t I=0;I<RefList.Size();I++) |
1016 |
if (wcscmp(RedirName,RefList[I].RefName)==0 && RefList[I].TmpName!=NULL) |
1017 |
{ |
1018 |
// If only one reference left targeting to this temporary file, |
1019 |
// it is faster to move the file instead of copying and deleting it. |
1020 |
bool RefMove=RefList[I].RefCount-- == 1; |
1021 |
NameExisting=RefList[I].TmpName; |
1022 |
if (RefMove) // Only one reference left for this temporary file. |
1023 |
{ |
1024 |
New.Delete(); // Delete the previously opened destination file. |
1025 |
// Try moving the file first. |
1026 |
bool MoveFailed=!RenameFile(NameExisting,NameNew); |
1027 |
if (MoveFailed) |
1028 |
{ |
1029 |
// If move failed, re-create the destination and try coping. |
1030 |
if (!New.WCreate(NameNew,FMF_WRITE|FMF_SHAREREAD)) |
1031 |
return false; |
1032 |
RefMove=false; // Try copying below. |
1033 |
} |
1034 |
else |
1035 |
{ |
1036 |
// If moved successfully, reopen the destination file and seek to |
1037 |
// end for SetOpenFileTime() and possible Truncate() calls later. |
1038 |
if (New.Open(NameNew)) |
1039 |
New.Seek(0,SEEK_END); |
1040 |
// We already moved the file, so clean the name to not try |
1041 |
// deleting non-existent temporary file later. |
1042 |
free(RefList[I].TmpName); |
1043 |
RefList[I].TmpName=NULL; |
1044 |
return true; |
1045 |
} |
1046 |
} |
1047 |
if (!RefMove) |
1048 |
OpenFailed=!Existing.Open(NameExisting); |
1049 |
break; |
1050 |
} |
1051 |
|
1052 |
if (OpenFailed) |
1053 |
{ |
1054 |
ErrHandler.OpenErrorMsg(NameExisting); |
1055 |
uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew); |
1056 |
uiMsg(UIERROR_FILECOPYHINT,ArcName); |
878 |
#ifdef RARDLL |
1057 |
#ifdef RARDLL |
879 |
Cmd->DllError=ERAR_EREFERENCE; |
1058 |
Cmd->DllError=ERAR_EREFERENCE; |
880 |
#endif |
1059 |
#endif |
881 |
return false; |
1060 |
return false; |
|
|
1061 |
} |
882 |
} |
1062 |
} |
883 |
|
1063 |
|
884 |
Array<char> Buffer(0x100000); |
1064 |
Array<byte> Buffer(0x100000); |
885 |
int64 CopySize=0; |
1065 |
int64 CopySize=0; |
886 |
|
1066 |
|
887 |
while (true) |
1067 |
while (true) |
Link Here
|
890 |
int ReadSize=Existing.Read(&Buffer[0],Buffer.Size()); |
1070 |
int ReadSize=Existing.Read(&Buffer[0],Buffer.Size()); |
891 |
if (ReadSize==0) |
1071 |
if (ReadSize==0) |
892 |
break; |
1072 |
break; |
|
|
1073 |
// Update only the current file progress in WinRAR, set the total to 0 |
1074 |
// to keep it as is. It looks better for WinRAR. |
1075 |
uiExtractProgress(CopySize,UnpSize,0,0); |
1076 |
|
893 |
New.Write(&Buffer[0],ReadSize); |
1077 |
New.Write(&Buffer[0],ReadSize); |
894 |
CopySize+=ReadSize; |
1078 |
CopySize+=ReadSize; |
895 |
} |
1079 |
} |
Link Here
|
900 |
|
1084 |
|
901 |
void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize) |
1085 |
void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize) |
902 |
{ |
1086 |
{ |
|
|
1087 |
if (Cmd->Test) |
1088 |
{ |
1089 |
// Destination name conversion isn't needed for simple archive test. |
1090 |
// This check also allows to avoid issuing "Attempting to correct... |
1091 |
// Renaming..." messages in MakeNameCompatible() below for problematic |
1092 |
// names like aux.txt when testing an archive. |
1093 |
wcsncpyz(DestName,ArcFileName,DestSize); |
1094 |
return; |
1095 |
} |
1096 |
|
903 |
wcsncpyz(DestName,Cmd->ExtrPath,DestSize); |
1097 |
wcsncpyz(DestName,Cmd->ExtrPath,DestSize); |
904 |
|
1098 |
|
905 |
if (*Cmd->ExtrPath!=0) |
1099 |
if (*Cmd->ExtrPath!=0) |
Link Here
|
1033 |
|
1227 |
|
1034 |
|
1228 |
|
1035 |
#ifndef RARDLL |
1229 |
#ifndef RARDLL |
1036 |
bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) |
1230 |
bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName,RarCheckPassword *CheckPwd) |
1037 |
{ |
1231 |
{ |
1038 |
if (!Cmd->Password.IsSet()) |
1232 |
if (!Cmd->Password.IsSet()) |
1039 |
{ |
1233 |
{ |
1040 |
if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)/* || !Cmd->Password.IsSet()*/) |
1234 |
if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password,CheckPwd)/* || !Cmd->Password.IsSet()*/) |
1041 |
{ |
1235 |
{ |
1042 |
// Suppress "test is ok" message if user cancelled the password prompt. |
1236 |
// Suppress "test is ok" message if user cancelled the password prompt. |
1043 |
uiMsg(UIERROR_INCERRCOUNT); |
1237 |
uiMsg(UIERROR_INCERRCOUNT); |
Link Here
|
1055 |
case -1: |
1249 |
case -1: |
1056 |
ErrHandler.Exit(RARX_USERBREAK); |
1250 |
ErrHandler.Exit(RARX_USERBREAK); |
1057 |
case 2: |
1251 |
case 2: |
1058 |
if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)) |
1252 |
if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password,CheckPwd)) |
1059 |
return false; |
1253 |
return false; |
1060 |
break; |
1254 |
break; |
1061 |
case 3: |
1255 |
case 3: |
Link Here
|
1131 |
DirExist=FileExist(DestFileName) && IsDir(GetFileAttr(DestFileName)); |
1325 |
DirExist=FileExist(DestFileName) && IsDir(GetFileAttr(DestFileName)); |
1132 |
if (!DirExist) |
1326 |
if (!DirExist) |
1133 |
{ |
1327 |
{ |
|
|
1328 |
if (!Cmd->AbsoluteLinks && ConvertSymlinkPaths) |
1329 |
LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); |
1134 |
CreatePath(DestFileName,true,Cmd->DisableNames); |
1330 |
CreatePath(DestFileName,true,Cmd->DisableNames); |
1135 |
MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); |
1331 |
MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); |
1136 |
} |
1332 |
} |
Link Here
|
1212 |
|
1408 |
|
1213 |
MakeNameUsable(DestFileName,true); |
1409 |
MakeNameUsable(DestFileName,true); |
1214 |
|
1410 |
|
|
|
1411 |
if (!Cmd->AbsoluteLinks && ConvertSymlinkPaths) |
1412 |
LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); |
1215 |
CreatePath(DestFileName,true,Cmd->DisableNames); |
1413 |
CreatePath(DestFileName,true,Cmd->DisableNames); |
1216 |
if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) |
1414 |
if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) |
1217 |
{ |
1415 |
{ |
Link Here
|
1258 |
|
1456 |
|
1259 |
|
1457 |
|
1260 |
#ifndef SFX_MODULE |
1458 |
#ifndef SFX_MODULE |
1261 |
// To speed up solid volumes extraction, try to find a non-first start volume, |
1459 |
// Find non-matched reference sources in solid and non-solid archives. |
1262 |
// which still allows to unpack all files. It is possible for independent |
1460 |
// Detect the optimal start position for semi-solid archives |
1263 |
// solid volumes with solid statistics reset in the beginning. |
1461 |
// and optimal start volume for independent solid volumes. |
1264 |
bool CmdExtract::DetectStartVolume(const wchar *VolName,bool NewNumbering) |
1462 |
// |
|
|
1463 |
// Alternatively we could collect references while extracting an archive |
1464 |
// and perform the second extraction pass for references only. |
1465 |
// But it would be slower for solid archives than scaning headers |
1466 |
// in first pass and extracting everything in second, as implemented now. |
1467 |
// |
1468 |
void CmdExtract::AnalyzeArchive(const wchar *ArcName,bool Volume,bool NewNumbering) |
1265 |
{ |
1469 |
{ |
|
|
1470 |
FreeAnalyzeData(); // If processing non-first archive in multiple archives set. |
1471 |
|
1266 |
wchar *ArgName=Cmd->FileArgs.GetString(); |
1472 |
wchar *ArgName=Cmd->FileArgs.GetString(); |
1267 |
Cmd->FileArgs.Rewind(); |
1473 |
Cmd->FileArgs.Rewind(); |
1268 |
if (ArgName!=NULL && (wcscmp(ArgName,L"*")==0 || wcscmp(ArgName,L"*.*")==0)) |
1474 |
if (ArgName!=NULL && (wcscmp(ArgName,L"*")==0 || wcscmp(ArgName,L"*.*")==0)) |
1269 |
return false; // No need to check further for * and *.* masks. |
1475 |
return; // No need to check further for * and *.* masks. |
1270 |
|
1476 |
|
1271 |
wchar StartName[NM]; |
|
|
1272 |
*StartName=0; |
1273 |
|
1274 |
// Start search from first volume if all volumes preceding current are available. |
1477 |
// Start search from first volume if all volumes preceding current are available. |
1275 |
wchar NextName[NM]; |
1478 |
wchar NextName[NM]; |
1276 |
GetFirstVolIfFullSet(VolName,NewNumbering,NextName,ASIZE(NextName)); |
1479 |
if (Volume) |
|
|
1480 |
GetFirstVolIfFullSet(ArcName,NewNumbering,NextName,ASIZE(NextName)); |
1481 |
else |
1482 |
wcsncpyz(NextName,ArcName,ASIZE(NextName)); |
1483 |
|
1484 |
bool MatchFound=false; |
1485 |
bool PrevMatched=false; |
1486 |
bool OpenNext=false; |
1277 |
|
1487 |
|
1278 |
bool Matched=false; |
1488 |
bool FirstVolume=true; |
1279 |
while (!Matched) |
1489 |
|
|
|
1490 |
// We shall set FirstFile once for all volumes and not for each volume. |
1491 |
// So we do not reuse the outdated Analyze->StartPos from previous volume |
1492 |
// if extracted file resides completely in the beginning of current one. |
1493 |
bool FirstFile=true; |
1494 |
|
1495 |
while (true) |
1280 |
{ |
1496 |
{ |
1281 |
Archive Arc(Cmd); |
1497 |
Archive Arc(Cmd); |
1282 |
if (!Arc.Open(NextName) || !Arc.IsArchive(false) || !Arc.Volume) |
1498 |
if (!Arc.Open(NextName) || !Arc.IsArchive(false)) |
|
|
1499 |
{ |
1500 |
if (OpenNext) |
1501 |
{ |
1502 |
// If we couldn't open trailing volumes, we can't set early exit |
1503 |
// parameters. It is possible that some volume are on removable media |
1504 |
// and will be provided by user when extracting. |
1505 |
*Analyze->EndName=0; |
1506 |
Analyze->EndPos=0; |
1507 |
} |
1283 |
break; |
1508 |
break; |
|
|
1509 |
} |
1284 |
|
1510 |
|
1285 |
bool OpenNext=false; |
1511 |
OpenNext=false; |
1286 |
while (Arc.ReadHeader()>0) |
1512 |
while (Arc.ReadHeader()>0) |
1287 |
{ |
1513 |
{ |
1288 |
Wait(); |
1514 |
Wait(); |
Link Here
|
1295 |
} |
1521 |
} |
1296 |
if (HeaderType==HEAD_FILE) |
1522 |
if (HeaderType==HEAD_FILE) |
1297 |
{ |
1523 |
{ |
|
|
1524 |
if ((Arc.Format==RARFMT14 || Arc.Format==RARFMT15) && Arc.FileHead.UnpVer<=15) |
1525 |
{ |
1526 |
// RAR versions earlier than 2.0 do not set per file solid flag. |
1527 |
// They have only the global archive solid flag, so we can't |
1528 |
// reliably analyze them here. |
1529 |
OpenNext=false; |
1530 |
break; |
1531 |
} |
1532 |
|
1298 |
if (!Arc.FileHead.SplitBefore) |
1533 |
if (!Arc.FileHead.SplitBefore) |
1299 |
{ |
1534 |
{ |
1300 |
if (!Arc.FileHead.Solid) // Can start extraction from here. |
1535 |
if (!MatchFound && !Arc.FileHead.Solid) // Can start extraction from here. |
1301 |
wcsncpyz(StartName,NextName,ASIZE(StartName)); |
1536 |
{ |
|
|
1537 |
// We would gain nothing and unnecessarily complicate extraction |
1538 |
// if we set StartName for first volume or StartPos for first |
1539 |
// archived file. |
1540 |
if (!FirstVolume) |
1541 |
wcsncpyz(Analyze->StartName,NextName,ASIZE(Analyze->StartName)); |
1302 |
|
1542 |
|
|
|
1543 |
// We shall set FirstFile once for all volumes for this code |
1544 |
// to work properly. Alternatively we could append |
1545 |
// "|| Analyze->StartPos!=0" to the condition, so we do not reuse |
1546 |
// the outdated Analyze->StartPos value from previous volume. |
1547 |
if (!FirstFile) |
1548 |
Analyze->StartPos=Arc.CurBlockPos; |
1549 |
} |
1550 |
|
1303 |
if (Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL,0)!=0) |
1551 |
if (Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL,0)!=0) |
1304 |
{ |
1552 |
{ |
1305 |
Matched=true; // First matched file found, must stop further scan. |
1553 |
MatchFound = true; |
1306 |
break; |
1554 |
PrevMatched = true; |
|
|
1555 |
|
1556 |
// Reset the previously set early exit position, if any, because |
1557 |
// we found a new matched file. |
1558 |
Analyze->EndPos=0; |
1559 |
|
1560 |
// Matched file reference pointing at maybe non-matched source file. |
1561 |
// Even though we know RedirName, we can't check if source file |
1562 |
// is certainly non-matched, because it can be filtered out by |
1563 |
// date or attributes, which we do not know here. |
1564 |
if (Arc.FileHead.RedirType==FSREDIR_FILECOPY) |
1565 |
{ |
1566 |
bool AlreadyAdded=false; |
1567 |
for (size_t I=0;I<RefList.Size();I++) |
1568 |
if (wcscmp(Arc.FileHead.RedirName,RefList[I].RefName)==0) |
1569 |
{ |
1570 |
// Increment the reference count if we added such reference |
1571 |
// source earlier. |
1572 |
RefList[I].RefCount++; |
1573 |
AlreadyAdded=true; |
1574 |
break; |
1575 |
} |
1576 |
|
1577 |
// Limit the maximum size of reference sources list to some |
1578 |
// sensible value to prevent the excessive memory allocation. |
1579 |
size_t MaxListSize=1000000; |
1580 |
|
1581 |
if (!AlreadyAdded && RefList.Size()<MaxListSize) |
1582 |
{ |
1583 |
ExtractRef Ref={0}; |
1584 |
Ref.RefName=wcsdup(Arc.FileHead.RedirName); |
1585 |
Ref.RefCount=1; |
1586 |
RefList.Push(Ref); |
1587 |
} |
1588 |
} |
1307 |
} |
1589 |
} |
|
|
1590 |
else |
1591 |
{ |
1592 |
if (PrevMatched) // First non-matched item after matched. |
1593 |
{ |
1594 |
// We would perform the unnecessarily string comparison |
1595 |
// when extracting if we set this value for first volume |
1596 |
// or non-volume archive. |
1597 |
if (!FirstVolume) |
1598 |
wcsncpyz(Analyze->EndName,NextName,ASIZE(Analyze->EndName)); |
1599 |
Analyze->EndPos=Arc.CurBlockPos; |
1600 |
} |
1601 |
PrevMatched=false; |
1602 |
} |
1308 |
} |
1603 |
} |
|
|
1604 |
|
1605 |
FirstFile=false; |
1309 |
if (Arc.FileHead.SplitAfter) |
1606 |
if (Arc.FileHead.SplitAfter) |
1310 |
{ |
1607 |
{ |
1311 |
OpenNext=true; // Allow open next volume. |
1608 |
OpenNext=true; // Allow open next volume. |
Link Here
|
1316 |
} |
1613 |
} |
1317 |
Arc.Close(); |
1614 |
Arc.Close(); |
1318 |
|
1615 |
|
1319 |
if (!OpenNext) |
1616 |
if (Volume && OpenNext) |
1320 |
break; |
1617 |
{ |
|
|
1618 |
NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); |
1619 |
FirstVolume=false; |
1321 |
|
1620 |
|
1322 |
NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); |
1621 |
// Needed for multivolume archives. Added in case some 'break' |
|
|
1622 |
// will quit early from loop above, so we do not set it in the loop. |
1623 |
// Now it can happen for hypothetical archive without file records |
1624 |
// and with HEAD_ENDARC record. |
1625 |
FirstFile=false; |
1626 |
} |
1627 |
else |
1628 |
break; |
1323 |
} |
1629 |
} |
1324 |
bool NewStartFound=wcscmp(VolName,StartName)!=0; |
1630 |
|
1325 |
if (NewStartFound) // Found a new volume to start extraction. |
1631 |
// If file references are present, we can't reliably skip in semi-solid |
1326 |
wcsncpyz(ArcName,StartName,ASIZE(ArcName)); |
1632 |
// archives, because reference source can be present in skipped data. |
1327 |
|
1633 |
if (RefList.Size()!=0) |
1328 |
return NewStartFound; |
1634 |
memset(Analyze,0,sizeof(*Analyze)); |
1329 |
} |
1635 |
} |
1330 |
#endif |
1636 |
#endif |
1331 |
|
1637 |
|