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