1
0

scantree.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. #include "rar.hpp"
  2. ScanTree::ScanTree(StringList *FileMasks,RECURSE_MODE Recurse,bool GetLinks,SCAN_DIRS GetDirs)
  3. {
  4. ScanTree::FileMasks=FileMasks;
  5. ScanTree::Recurse=Recurse;
  6. ScanTree::GetLinks=GetLinks;
  7. ScanTree::GetDirs=GetDirs;
  8. ScanEntireDisk=false;
  9. FolderWildcards=false;
  10. SetAllMaskDepth=0;
  11. *CurMask=0;
  12. memset(FindStack,0,sizeof(FindStack));
  13. Depth=0;
  14. Errors=0;
  15. *ErrArcName=0;
  16. Cmd=NULL;
  17. ErrDirList=NULL;
  18. ErrDirSpecPathLength=NULL;
  19. }
  20. ScanTree::~ScanTree()
  21. {
  22. for (int I=Depth;I>=0;I--)
  23. if (FindStack[I]!=NULL)
  24. delete FindStack[I];
  25. }
  26. SCAN_CODE ScanTree::GetNext(FindData *FD)
  27. {
  28. if (Depth<0)
  29. return SCAN_DONE;
  30. #ifndef SILENT
  31. uint LoopCount=0;
  32. #endif
  33. SCAN_CODE FindCode;
  34. while (1)
  35. {
  36. if (*CurMask==0 && !GetNextMask())
  37. return SCAN_DONE;
  38. #ifndef SILENT
  39. // Let's return some ticks to system or WinRAR can become irresponsible
  40. // while scanning files in command like "winrar a -r arc c:\file.ext".
  41. // Also we reset system sleep timer here.
  42. if ((++LoopCount & 0x3ff)==0)
  43. Wait();
  44. #endif
  45. FindCode=FindProc(FD);
  46. if (FindCode==SCAN_ERROR)
  47. {
  48. Errors++;
  49. continue;
  50. }
  51. if (FindCode==SCAN_NEXT)
  52. continue;
  53. if (FindCode==SCAN_SUCCESS && FD->IsDir && GetDirs==SCAN_SKIPDIRS)
  54. continue;
  55. if (FindCode==SCAN_DONE && GetNextMask())
  56. continue;
  57. if (FilterList.ItemsCount()>0 && FindCode==SCAN_SUCCESS)
  58. if (!CommandData::CheckArgs(&FilterList,FD->IsDir,FD->Name,false,MATCH_WILDSUBPATH))
  59. continue;
  60. break;
  61. }
  62. return FindCode;
  63. }
  64. // For masks like dir1\dir2*\*.ext in non-recursive mode.
  65. bool ScanTree::ExpandFolderMask()
  66. {
  67. bool WildcardFound=false;
  68. uint SlashPos=0;
  69. for (int I=0;CurMask[I]!=0;I++)
  70. {
  71. if (CurMask[I]=='?' || CurMask[I]=='*')
  72. WildcardFound=true;
  73. if (WildcardFound && IsPathDiv(CurMask[I]))
  74. {
  75. // First path separator position after folder wildcard mask.
  76. // In case of dir1\dir2*\dir3\name.ext mask it may point not to file
  77. // name, so we cannot use PointToName() here.
  78. SlashPos=I;
  79. break;
  80. }
  81. }
  82. wchar Mask[NM];
  83. wcsncpyz(Mask,CurMask,ASIZE(Mask));
  84. Mask[SlashPos]=0;
  85. // Prepare the list of all folders matching the wildcard mask.
  86. ExpandedFolderList.Reset();
  87. FindFile Find;
  88. Find.SetMask(Mask);
  89. FindData FD;
  90. while (Find.Next(&FD))
  91. if (FD.IsDir)
  92. {
  93. wcsncatz(FD.Name,CurMask+SlashPos,ASIZE(FD.Name));
  94. // Treat dir*\* or dir*\*.* as dir, so empty 'dir' is also matched
  95. // by such mask. Skipping empty dir with dir*\*.* confused some users.
  96. wchar *LastMask=PointToName(FD.Name);
  97. if (wcscmp(LastMask,L"*")==0 || wcscmp(LastMask,L"*.*")==0)
  98. RemoveNameFromPath(FD.Name);
  99. ExpandedFolderList.AddString(FD.Name);
  100. }
  101. if (ExpandedFolderList.ItemsCount()==0)
  102. return false;
  103. // Return the first matching folder name now.
  104. ExpandedFolderList.GetString(CurMask,ASIZE(CurMask));
  105. return true;
  106. }
  107. // For masks like dir1\dir2*\file.ext this function sets 'dir1' recursive mask
  108. // and '*\dir2*\file.ext' filter. Masks without folder wildcards are
  109. // returned as is.
  110. bool ScanTree::GetFilteredMask()
  111. {
  112. // If we have some matching folders left for non-recursive folder wildcard
  113. // mask, we return it here.
  114. if (ExpandedFolderList.ItemsCount()>0 && ExpandedFolderList.GetString(CurMask,ASIZE(CurMask)))
  115. return true;
  116. FolderWildcards=false;
  117. FilterList.Reset();
  118. if (!FileMasks->GetString(CurMask,ASIZE(CurMask)))
  119. return false;
  120. // Check if folder wildcards present.
  121. bool WildcardFound=false;
  122. uint FolderWildcardCount=0;
  123. uint SlashPos=0;
  124. uint StartPos=0;
  125. #ifdef _WIN_ALL // Not treat the special NTFS \\?\d: path prefix as a wildcard.
  126. if (CurMask[0]=='\\' && CurMask[1]=='\\' && CurMask[2]=='?' && CurMask[3]=='\\')
  127. StartPos=4;
  128. #endif
  129. for (uint I=StartPos;CurMask[I]!=0;I++)
  130. {
  131. if (CurMask[I]=='?' || CurMask[I]=='*')
  132. WildcardFound=true;
  133. if (IsPathDiv(CurMask[I]) || IsDriveDiv(CurMask[I]))
  134. {
  135. if (WildcardFound)
  136. {
  137. // Calculate a number of folder wildcards in current mask.
  138. FolderWildcardCount++;
  139. WildcardFound=false;
  140. }
  141. if (FolderWildcardCount==0)
  142. SlashPos=I; // Slash position before first folder wildcard mask.
  143. }
  144. }
  145. if (FolderWildcardCount==0)
  146. return true;
  147. FolderWildcards=true; // Global folder wildcards flag.
  148. // If we have only one folder wildcard component and -r is missing or -r-
  149. // is specified, prepare matching folders in non-recursive mode.
  150. // We assume -r for masks like dir1*\dir2*\file*, because it is complicated
  151. // to fast find them using OS file find API call.
  152. if ((Recurse==RECURSE_NONE || Recurse==RECURSE_DISABLE) && FolderWildcardCount==1)
  153. return ExpandFolderMask();
  154. wchar Filter[NM];
  155. // Convert path\dir*\ to *\dir filter to search for 'dir' in all 'path' subfolders.
  156. wcsncpyz(Filter,L"*",ASIZE(Filter));
  157. AddEndSlash(Filter,ASIZE(Filter));
  158. // SlashPos might point or not point to path separator for masks like 'dir*', '\dir*' or 'd:dir*'
  159. wchar *WildName=IsPathDiv(CurMask[SlashPos]) || IsDriveDiv(CurMask[SlashPos]) ? CurMask+SlashPos+1 : CurMask+SlashPos;
  160. wcsncatz(Filter,WildName,ASIZE(Filter));
  161. // Treat dir*\* or dir*\*.* as dir\, so empty 'dir' is also matched
  162. // by such mask. Skipping empty dir with dir*\*.* confused some users.
  163. wchar *LastMask=PointToName(Filter);
  164. if (wcscmp(LastMask,L"*")==0 || wcscmp(LastMask,L"*.*")==0)
  165. *LastMask=0;
  166. FilterList.AddString(Filter);
  167. bool RelativeDrive=IsDriveDiv(CurMask[SlashPos]);
  168. if (RelativeDrive)
  169. SlashPos++; // Use "d:" instead of "d" for d:* mask.
  170. CurMask[SlashPos]=0;
  171. if (!RelativeDrive) // Keep d: mask as is, not convert to d:\*
  172. {
  173. // We need to append "\*" both for -ep1 to work correctly and to
  174. // convert d:\* masks previously truncated to d: back to original form.
  175. AddEndSlash(CurMask,ASIZE(CurMask));
  176. wcsncatz(CurMask,MASKALL,ASIZE(CurMask));
  177. }
  178. return true;
  179. }
  180. bool ScanTree::GetNextMask()
  181. {
  182. if (!GetFilteredMask())
  183. return false;
  184. #ifdef _WIN_ALL
  185. UnixSlashToDos(CurMask,CurMask,ASIZE(CurMask));
  186. #endif
  187. // We wish to scan entire disk if mask like c:\ is specified
  188. // regardless of recursion mode. Use c:\*.* mask when need to scan only
  189. // the root directory.
  190. ScanEntireDisk=IsDriveLetter(CurMask) && IsPathDiv(CurMask[2]) && CurMask[3]==0;
  191. wchar *Name=PointToName(CurMask);
  192. if (*Name==0)
  193. wcsncatz(CurMask,MASKALL,ASIZE(CurMask));
  194. if (Name[0]=='.' && (Name[1]==0 || Name[1]=='.' && Name[2]==0))
  195. {
  196. AddEndSlash(CurMask,ASIZE(CurMask));
  197. wcsncatz(CurMask,MASKALL,ASIZE(CurMask));
  198. }
  199. SpecPathLength=Name-CurMask;
  200. Depth=0;
  201. wcsncpyz(OrigCurMask,CurMask,ASIZE(OrigCurMask));
  202. return true;
  203. }
  204. SCAN_CODE ScanTree::FindProc(FindData *FD)
  205. {
  206. if (*CurMask==0)
  207. return SCAN_NEXT;
  208. bool FastFindFile=false;
  209. if (FindStack[Depth]==NULL) // No FindFile object for this depth yet.
  210. {
  211. bool Wildcards=IsWildcard(CurMask);
  212. // If we have a file name without wildcards, we can try to use
  213. // FastFind to optimize speed. For example, in Unix it results in
  214. // stat call instead of opendir/readdir/closedir.
  215. bool FindCode=!Wildcards && FindFile::FastFind(CurMask,FD,GetLinks);
  216. // Link check is important for NTFS, where links can have "Directory"
  217. // attribute, but we do not want to recurse to them in "get links" mode.
  218. bool IsDir=FindCode && FD->IsDir && (!GetLinks || !FD->IsLink);
  219. // SearchAll means that we'll use "*" mask for search, so we'll find
  220. // subdirectories and will be able to recurse into them.
  221. // We do not use "*" for directories at any level or for files
  222. // at top level in recursion mode. We always comrpess the entire directory
  223. // if folder wildcard is specified.
  224. bool SearchAll=!IsDir && (Depth>0 || Recurse==RECURSE_ALWAYS ||
  225. FolderWildcards && Recurse!=RECURSE_DISABLE ||
  226. Wildcards && Recurse==RECURSE_WILDCARDS ||
  227. ScanEntireDisk && Recurse!=RECURSE_DISABLE);
  228. if (Depth==0)
  229. SearchAllInRoot=SearchAll;
  230. if (SearchAll || Wildcards)
  231. {
  232. // Create the new FindFile object for wildcard based search.
  233. FindStack[Depth]=new FindFile;
  234. wchar SearchMask[NM];
  235. wcsncpyz(SearchMask,CurMask,ASIZE(SearchMask));
  236. if (SearchAll)
  237. SetName(SearchMask,MASKALL,ASIZE(SearchMask));
  238. FindStack[Depth]->SetMask(SearchMask);
  239. }
  240. else
  241. {
  242. // Either we failed to fast find or we found a file or we found
  243. // a directory in RECURSE_DISABLE mode, so we do not need to scan it.
  244. // We can return here and do not need to process further.
  245. // We need to process further only if we fast found a directory.
  246. if (!FindCode || !IsDir || Recurse==RECURSE_DISABLE)
  247. {
  248. // Return SCAN_SUCCESS if we found a file.
  249. SCAN_CODE RetCode=SCAN_SUCCESS;
  250. if (!FindCode)
  251. {
  252. // Return SCAN_ERROR if problem is more serious than just
  253. // "file not found".
  254. RetCode=FD->Error ? SCAN_ERROR:SCAN_NEXT;
  255. // If we failed to find an object, but our current mask is excluded,
  256. // we skip this object and avoid indicating an error.
  257. if (Cmd!=NULL && Cmd->ExclCheck(CurMask,false,true,true))
  258. RetCode=SCAN_NEXT;
  259. else
  260. {
  261. ErrHandler.OpenErrorMsg(ErrArcName,CurMask);
  262. // User asked to return RARX_NOFILES and not RARX_OPEN here.
  263. ErrHandler.SetErrorCode(RARX_NOFILES);
  264. }
  265. }
  266. // If we searched only for one file or directory in "fast find"
  267. // (without a wildcard) mode, let's set masks to zero,
  268. // so calling function will know that current mask is used
  269. // and next one must be read from mask list for next call.
  270. // It is not necessary for directories, because even in "fast find"
  271. // mode, directory recursing will quit by (Depth < 0) condition,
  272. // which returns SCAN_DONE to calling function.
  273. *CurMask=0;
  274. return RetCode;
  275. }
  276. // We found a directory using only FindFile::FastFind function.
  277. FastFindFile=true;
  278. }
  279. }
  280. if (!FastFindFile && !FindStack[Depth]->Next(FD,GetLinks))
  281. {
  282. // We cannot find anything more in directory either because of
  283. // some error or just as result of all directory entries already read.
  284. bool Error=FD->Error;
  285. if (Error)
  286. ScanError(Error);
  287. wchar DirName[NM];
  288. *DirName=0;
  289. // Going to at least one directory level higher.
  290. delete FindStack[Depth];
  291. FindStack[Depth--]=NULL;
  292. while (Depth>=0 && FindStack[Depth]==NULL)
  293. Depth--;
  294. if (Depth < 0)
  295. {
  296. // Directories scanned both in normal and FastFindFile mode,
  297. // finally exit from scan here, by (Depth < 0) condition.
  298. if (Error)
  299. Errors++;
  300. return SCAN_DONE;
  301. }
  302. wchar *Slash=wcsrchr(CurMask,CPATHDIVIDER);
  303. if (Slash!=NULL)
  304. {
  305. wchar Mask[NM];
  306. wcsncpyz(Mask,Slash,ASIZE(Mask));
  307. if (Depth<SetAllMaskDepth)
  308. wcsncpyz(Mask+1,PointToName(OrigCurMask),ASIZE(Mask)-1);
  309. *Slash=0;
  310. wcsncpyz(DirName,CurMask,ASIZE(DirName));
  311. wchar *PrevSlash=wcsrchr(CurMask,CPATHDIVIDER);
  312. if (PrevSlash==NULL)
  313. wcsncpyz(CurMask,Mask+1,ASIZE(CurMask));
  314. else
  315. {
  316. *PrevSlash=0;
  317. wcsncatz(CurMask,Mask,ASIZE(CurMask));
  318. }
  319. }
  320. if (GetDirs==SCAN_GETDIRSTWICE &&
  321. FindFile::FastFind(DirName,FD,GetLinks) && FD->IsDir)
  322. {
  323. FD->Flags|=FDDF_SECONDDIR;
  324. return Error ? SCAN_ERROR:SCAN_SUCCESS;
  325. }
  326. return Error ? SCAN_ERROR:SCAN_NEXT;
  327. }
  328. // Link check is required for NTFS links, not for Unix.
  329. if (FD->IsDir && (!GetLinks || !FD->IsLink))
  330. {
  331. // If we found the directory in top (Depth==0) directory
  332. // and if we are not in "fast find" (directory name only as argument)
  333. // or in recurse (SearchAll was set when opening the top directory) mode,
  334. // we do not recurse into this directory. We either return it by itself
  335. // or skip it.
  336. if (!FastFindFile && Depth==0 && !SearchAllInRoot)
  337. return GetDirs==SCAN_GETCURDIRS ? SCAN_SUCCESS:SCAN_NEXT;
  338. // Let's check if directory name is excluded, so we do not waste
  339. // time searching in directory, which will be excluded anyway.
  340. if (Cmd!=NULL && (Cmd->ExclCheck(FD->Name,true,false,false) ||
  341. Cmd->ExclDirByAttr(FD->FileAttr)))
  342. {
  343. // If we are here in "fast find" mode, it means that entire directory
  344. // specified in command line is excluded. Then we need to return
  345. // SCAN_DONE to go to next mask and avoid the infinite loop
  346. // in GetNext() function. Such loop would be possible in case of
  347. // SCAN_NEXT code and "rar a arc dir -xdir" command.
  348. return FastFindFile ? SCAN_DONE:SCAN_NEXT;
  349. }
  350. wchar Mask[NM];
  351. wcsncpyz(Mask,FastFindFile ? MASKALL:PointToName(CurMask),ASIZE(Mask));
  352. wcsncpyz(CurMask,FD->Name,ASIZE(CurMask));
  353. if (wcslen(CurMask)+wcslen(Mask)+1>=NM || Depth>=MAXSCANDEPTH-1)
  354. {
  355. uiMsg(UIERROR_PATHTOOLONG,CurMask,SPATHDIVIDER,Mask);
  356. return SCAN_ERROR;
  357. }
  358. AddEndSlash(CurMask,ASIZE(CurMask));
  359. wcsncatz(CurMask,Mask,ASIZE(CurMask));
  360. Depth++;
  361. // We need to use OrigCurMask for depths less than SetAllMaskDepth
  362. // and "*" for depths equal or larger than SetAllMaskDepth.
  363. // It is important when "fast finding" directories at Depth > 0.
  364. // For example, if current directory is RootFolder and we compress
  365. // the following directories structure:
  366. // RootFolder
  367. // +--Folder1
  368. // | +--Folder2
  369. // | +--Folder3
  370. // +--Folder4
  371. // with 'rar a -r arcname Folder2' command, rar could add not only
  372. // Folder1\Folder2 contents, but also Folder1\Folder3 if we were using
  373. // "*" mask at all levels. We need to use "*" mask inside of Folder2,
  374. // but return to "Folder2" mask when completing scanning Folder2.
  375. // We can rewrite SearchAll expression above to avoid fast finding
  376. // directories at Depth > 0, but then 'rar a -r arcname Folder2'
  377. // will add the empty Folder2 and do not add its contents.
  378. if (FastFindFile)
  379. SetAllMaskDepth=Depth;
  380. }
  381. if (!FastFindFile && !CmpName(CurMask,FD->Name,MATCH_NAMES))
  382. return SCAN_NEXT;
  383. return SCAN_SUCCESS;
  384. }
  385. void ScanTree::ScanError(bool &Error)
  386. {
  387. #ifdef _WIN_ALL
  388. if (Error)
  389. {
  390. // Get attributes of parent folder and do not display an error
  391. // if it is reparse point. We cannot scan contents of standard
  392. // Windows reparse points like "C:\Documents and Settings"
  393. // and we do not want to issue numerous useless errors for them.
  394. // We cannot just check FD->FileAttr here, it can be undefined
  395. // if we process "folder\*" mask or if we process "folder" mask,
  396. // but "folder" is inaccessible.
  397. wchar *Slash=PointToName(CurMask);
  398. if (Slash>CurMask)
  399. {
  400. *(Slash-1)=0;
  401. DWORD Attr=GetFileAttributes(CurMask);
  402. *(Slash-1)=CPATHDIVIDER;
  403. if (Attr!=0xffffffff && (Attr & FILE_ATTRIBUTE_REPARSE_POINT)!=0)
  404. Error=false;
  405. }
  406. // Do not display an error if we cannot scan contents of
  407. // "System Volume Information" folder. Normally it is not accessible.
  408. if (wcsstr(CurMask,L"System Volume Information\\")!=NULL)
  409. Error=false;
  410. }
  411. #endif
  412. if (Error && Cmd!=NULL && Cmd->ExclCheck(CurMask,false,true,true))
  413. Error=false;
  414. if (Error)
  415. {
  416. if (ErrDirList!=NULL)
  417. ErrDirList->AddString(CurMask);
  418. if (ErrDirSpecPathLength!=NULL)
  419. ErrDirSpecPathLength->Push((uint)SpecPathLength);
  420. wchar FullName[NM];
  421. // This conversion works for wildcard masks too.
  422. ConvertNameToFull(CurMask,FullName,ASIZE(FullName));
  423. uiMsg(UIERROR_DIRSCAN,FullName);
  424. ErrHandler.SysErrMsg();
  425. }
  426. }