QuietUnrar/libunrar/scantree.cpp

388 lines
11 KiB
C++

#include "rar.hpp"
ScanTree::ScanTree(StringList *FileMasks,RECURSE_MODE Recurse,bool GetLinks,SCAN_DIRS GetDirs)
{
ScanTree::FileMasks=FileMasks;
ScanTree::Recurse=Recurse;
ScanTree::GetLinks=GetLinks;
ScanTree::GetDirs=GetDirs;
ScanEntireDisk=false;
SetAllMaskDepth=0;
*CurMask=0;
*CurMaskW=0;
memset(FindStack,0,sizeof(FindStack));
Depth=0;
Errors=0;
*ErrArcName=0;
Cmd=NULL;
}
ScanTree::~ScanTree()
{
for (int I=Depth;I>=0;I--)
if (FindStack[I]!=NULL)
delete FindStack[I];
}
SCAN_CODE ScanTree::GetNext(FindData *FindData)
{
if (Depth<0)
return(SCAN_DONE);
SCAN_CODE FindCode;
while (1)
{
if (*CurMask==0 && !GetNextMask())
return(SCAN_DONE);
FindCode=FindProc(FindData);
if (FindCode==SCAN_ERROR)
{
Errors++;
continue;
}
if (FindCode==SCAN_NEXT)
continue;
if (FindCode==SCAN_SUCCESS && FindData->IsDir && GetDirs==SCAN_SKIPDIRS)
continue;
if (FindCode==SCAN_DONE && GetNextMask())
continue;
break;
}
return(FindCode);
}
bool ScanTree::GetNextMask()
{
if (!FileMasks->GetString(CurMask,CurMaskW,sizeof(CurMask)))
return(false);
CurMask[ASIZE(CurMask)-1]=0;
CurMaskW[ASIZE(CurMaskW)-1]=0;
#ifdef _WIN_32
UnixSlashToDos(CurMask);
#endif
// We wish to scan entire disk if mask like c:\ is specified
// regardless of recursion mode. Use c:\*.* mask when need to scan only
// the root directory.
ScanEntireDisk=IsDiskLetter(CurMask) && IsPathDiv(CurMask[2]) && CurMask[3]==0;
char *Name=PointToName(CurMask);
if (*Name==0)
strcat(CurMask,MASKALL);
if (Name[0]=='.' && (Name[1]==0 || Name[1]=='.' && Name[2]==0))
{
AddEndSlash(CurMask);
strcat(CurMask,MASKALL);
}
SpecPathLength=Name-CurMask;
bool WideName=(*CurMaskW!=0);
if (WideName)
{
wchar *NameW=PointToName(CurMaskW);
if (*NameW==0)
strcatw(CurMaskW,MASKALLW);
if (NameW[0]=='.' && (NameW[1]==0 || NameW[1]=='.' && NameW[2]==0))
{
AddEndSlash(CurMaskW);
strcatw(CurMaskW,MASKALLW);
}
SpecPathLengthW=NameW-CurMaskW;
}
else
{
wchar WideMask[NM];
CharToWide(CurMask,WideMask);
SpecPathLengthW=PointToName(WideMask)-WideMask;
}
Depth=0;
strcpy(OrigCurMask,CurMask);
strcpyw(OrigCurMaskW,CurMaskW);
return(true);
}
SCAN_CODE ScanTree::FindProc(FindData *FindData)
{
if (*CurMask==0)
return(SCAN_NEXT);
bool FastFindFile=false;
if (FindStack[Depth]==NULL) // No FindFile object for this depth yet.
{
bool Wildcards=IsWildcard(CurMask,CurMaskW);
// If we have a file name without wildcards, we can try to use
// FastFind to optimize speed. For example, in Unix it results in
// stat call instead of opendir/readdir/closedir.
bool FindCode=!Wildcards && FindFile::FastFind(CurMask,CurMaskW,FindData,GetLinks);
bool IsDir=FindCode && FindData->IsDir;
// SearchAll means that we'll use "*" mask for search, so we'll find
// subdirectories and will be able to recurse into them.
// We do not use "*" for directories at any level or for files
// at top level in recursion mode.
bool SearchAll=!IsDir && (Depth>0 || Recurse==RECURSE_ALWAYS ||
Wildcards && Recurse==RECURSE_WILDCARDS ||
ScanEntireDisk && Recurse!=RECURSE_DISABLE);
if (Depth==0)
SearchAllInRoot=SearchAll;
if (SearchAll || Wildcards)
{
// Create the new FindFile object for wildcard based search.
FindStack[Depth]=new FindFile;
char SearchMask[NM];
strcpy(SearchMask,CurMask);
if (SearchAll)
strcpy(PointToName(SearchMask),MASKALL);
FindStack[Depth]->SetMask(SearchMask);
if (*CurMaskW)
{
wchar SearchMaskW[NM];
strcpyw(SearchMaskW,CurMaskW);
if (SearchAll)
strcpyw(PointToName(SearchMaskW),MASKALLW);
FindStack[Depth]->SetMaskW(SearchMaskW);
}
}
else
{
// Either we failed to fast find or we found a file or we found
// a directory in RECURSE_DISABLE mode, so we do not need to scan it.
// We can return here and do not need to process further.
// We need to process further only if we fast found a directory.
if (!FindCode || !FindData->IsDir || Recurse==RECURSE_DISABLE)
{
// Return SCAN_SUCCESS if we found a file.
SCAN_CODE RetCode=SCAN_SUCCESS;
if (!FindCode)
{
// Return SCAN_ERROR if problem is more serious than just
// "file not found".
RetCode=FindData->Error ? SCAN_ERROR:SCAN_NEXT;
// If we failed to find an object, but our current mask is excluded,
// we skip this object and avoid indicating an error.
if (Cmd!=NULL && Cmd->ExclCheck(CurMask,true,true))
RetCode=SCAN_NEXT;
else
ErrHandler.OpenErrorMsg(ErrArcName,CurMask);
}
// If we searched only for one file or directory in "fast find"
// (without a wildcard) mode, let's set masks to zero,
// so calling function will know that current mask is used
// and next one must be read from mask list for next call.
// It is not necessary for directories, because even in "fast find"
// mode, directory recursing will quit by (Depth < 0) condition,
// which returns SCAN_DONE to calling function.
*CurMask=0;
*CurMaskW=0;
return(RetCode);
}
// We found a directory using only FindFile::FastFind function.
FastFindFile=true;
}
}
if (!FastFindFile && !FindStack[Depth]->Next(FindData,GetLinks))
{
// We cannot find anything more in directory either because of
// some error or just as result of all directory entries already read.
bool Error=FindData->Error;
#ifdef _WIN_32
if (Error)
{
// Do not display an error if we cannot scan contents of reparse
// point. Vista contains a lot of reparse (or junction) points,
// which are not accessible.
if ((FindData->FileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0)
Error=false;
// Do not display an error if we cannot scan contents of
// "System Volume Information" folder. Normally it is not accessible.
if (strstr(CurMask,"System Volume Information\\")!=NULL)
Error=false;
}
#endif
if (Error && Cmd!=NULL && Cmd->ExclCheck(CurMask,true,true))
Error=false;
#ifndef SILENT
if (Error)
{
Log(NULL,St(MScanError),CurMask);
}
#endif
char DirName[NM];
wchar DirNameW[NM];
*DirName=0;
*DirNameW=0;
// Going to at least one directory level higher.
delete FindStack[Depth];
FindStack[Depth--]=NULL;
while (Depth>=0 && FindStack[Depth]==NULL)
Depth--;
if (Depth < 0)
{
// Directories scanned both in normal and FastFindFile mode,
// finally exit from scan here, by (Depth < 0) condition.
if (Error)
Errors++;
return(SCAN_DONE);
}
char *Slash=strrchrd(CurMask,CPATHDIVIDER);
if (Slash!=NULL)
{
char Mask[NM];
strcpy(Mask,Slash);
if (Depth<SetAllMaskDepth)
strcpy(Mask+1,PointToName(OrigCurMask));
*Slash=0;
strcpy(DirName,CurMask);
char *PrevSlash=strrchrd(CurMask,CPATHDIVIDER);
if (PrevSlash==NULL)
strcpy(CurMask,Mask+1);
else
strcpy(PrevSlash,Mask);
}
if (*CurMaskW!=0)
{
wchar *Slash=strrchrw(CurMaskW,CPATHDIVIDER);
if (Slash!=NULL)
{
wchar Mask[NM];
strcpyw(Mask,Slash);
if (Depth<SetAllMaskDepth)
strcpyw(Mask+1,PointToName(OrigCurMaskW));
*Slash=0;
strcpyw(DirNameW,CurMaskW);
wchar *PrevSlash=strrchrw(CurMaskW,CPATHDIVIDER);
if (PrevSlash==NULL)
strcpyw(CurMaskW,Mask+1);
else
strcpyw(PrevSlash,Mask);
}
#ifndef _WIN_CE
if (LowAscii(CurMaskW))
*CurMaskW=0;
#endif
}
if (GetDirs==SCAN_GETDIRSTWICE &&
FindFile::FastFind(DirName,DirNameW,FindData,GetLinks) && FindData->IsDir)
{
FindData->Flags|=FDDF_SECONDDIR;
return(Error ? SCAN_ERROR:SCAN_SUCCESS);
}
return(Error ? SCAN_ERROR:SCAN_NEXT);
}
if (FindData->IsDir)
{
// If we found the directory in top (Depth==0) directory
// and if we are not in "fast find" (directory name only as argument)
// or in recurse (SearchAll was set when opening the top directory) mode,
// we do not recurse into this directory. We either return it by itself
// or skip it.
if (!FastFindFile && Depth==0 && !SearchAllInRoot)
return(GetDirs==SCAN_GETCURDIRS ? SCAN_SUCCESS:SCAN_NEXT);
// Let's check if directory name is excluded, so we do not waste
// time searching in directory, which will be excluded anyway.
// We set CheckInclList parameter of ExclCheck to 'true' to ignore
// the inclusion list here. We do it to correctly handle the situation,
// when a user added files in the directory to inclusion list,
// but did not add their parent directory to this list.
if (Cmd!=NULL && Cmd->ExclCheck(FindData->Name,false,false))
{
// If we are here in "fast find" mode, it means that entire directory
// specified in command line is excluded. Then we need to return
// SCAN_DONE to go to next mask and avoid the infinite loop
// in GetNext() function. Such loop would be possible in case of
// SCAN_NEXT code and "rar a arc dir -xdir" command.
return(FastFindFile ? SCAN_DONE:SCAN_NEXT);
}
char Mask[NM];
strcpy(Mask,FastFindFile ? MASKALL:PointToName(CurMask));
strcpy(CurMask,FindData->Name);
if (strlen(CurMask)+strlen(Mask)+1>=NM || Depth>=MAXSCANDEPTH-1)
{
#ifndef SILENT
Log(NULL,"\n%s%c%s",CurMask,CPATHDIVIDER,Mask);
Log(NULL,St(MPathTooLong));
#endif
return(SCAN_ERROR);
}
AddEndSlash(CurMask);
strcat(CurMask,Mask);
if (*CurMaskW && *FindData->NameW==0)
CharToWide(FindData->Name,FindData->NameW);
if (*FindData->NameW!=0)
{
wchar Mask[NM];
if (FastFindFile)
strcpyw(Mask,MASKALLW);
else
if (*CurMaskW)
strcpyw(Mask,PointToName(CurMaskW));
else
CharToWide(PointToName(CurMask),Mask);
strcpyw(CurMaskW,FindData->NameW);
AddEndSlash(CurMaskW);
strcatw(CurMaskW,Mask);
}
Depth++;
// We need to use OrigCurMask for depths less than SetAllMaskDepth
// and "*" for depths equal or larger than SetAllMaskDepth.
// It is important when "fast finding" directories at Depth > 0.
// For example, if current directory is RootFolder and we compress
// the following directories structure:
// RootFolder
// +--Folder1
// | +--Folder2
// | +--Folder3
// +--Folder4
// with 'rar a -r arcname Folder2' command, rar could add not only
// Folder1\Folder2 contents, but also Folder1\Folder3 if we were using
// "*" mask at all levels. We need to use "*" mask inside of Folder2,
// but return to "Folder2" mask when completing scanning Folder2.
// We can rewrite SearchAll expression above to avoid fast finding
// directories at Depth > 0, but then 'rar a -r arcname Folder2'
// will add the empty Folder2 and do not add its contents.
if (FastFindFile)
SetAllMaskDepth=Depth;
}
if (!FastFindFile && !CmpName(CurMask,FindData->Name,MATCH_NAMES))
return(SCAN_NEXT);
return(SCAN_SUCCESS);
}