1 /*
2 scandir.cc Scan Directory Functions
3 Copyright (c) 2000,2001,2003,2005,2007 Kriang Lerdsuwanakij
4 email: lerdsuwa@users.sourceforge.net
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "scandir.h"
22
23 #include CXX__HEADER_iomanip
24 #include CXX__HEADER_fstream
25 #include CXX__HEADER_algorithm
26 #include CXX__HEADER_cerrno
27
28 #include "conffile.h"
29 #include "gentree.h"
30 #include "cxxlib.h"
31 #include "strmisc.h"
32 #include "cstrlib.h"
33 #include "dirtree.h"
34
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h>
37 #endif
38
39 /*************************************************************************
40 Log scan results to command line output
41 *************************************************************************/
42
PrintTime(long secdiff,const string & mode)43 void CommandLineScanLog::PrintTime(long secdiff, const string &mode)
44 {
45 struct tm elapse_time;
46 elapse_time.tm_sec = secdiff % 60;
47 int total_min = secdiff / 60;
48 elapse_time.tm_min = total_min % 60;
49 elapse_time.tm_hour = total_min / 60;
50
51 if (elapse_time.tm_hour) // hour > 0
52 gtout(cout, _("Time spent: %$ (%$)\n"))
53 << my_strftime(_("%H:%M:%S"), &elapse_time)
54 << mode;
55 else
56 gtout(cout, _("Time spent: %$ (%$)\n"))
57 << my_strftime(_("%M:%S"), &elapse_time)
58 << mode;
59 }
60
PrintChdirError(const string & dir,const string & str)61 void CommandLineScanLog::PrintChdirError(const string &dir, const string &str)
62 {
63 cout << flush; // Avoid out-of-order display
64 gtout(cerr, _(": cannot change to directory %$ - %$\n"))
65 << dir << str;
66 }
67
PrintStartDirNotDirectory(const string & str)68 void ScanCommandScanLog::PrintStartDirNotDirectory(const string &str)
69 {
70 cout << flush; // Avoid out-of-order display
71 gtout(cerr, _("%$: %$ specified in `StartDir\' command is not"
72 " a directory. Command ignored\n"))
73 << progName << str;
74 }
75
PrintStartDirNotFound(const string & str)76 void ScanCommandScanLog::PrintStartDirNotFound(const string &str)
77 {
78 cout << flush; // Avoid out-of-order display
79 gtout(cerr, _("%$: cannot find directory %$"
80 " as specified in `StartDir\' command. Command ignored\n"))
81 << progName << str;
82 }
83
PrintStartDirNotDirectory(const string & str)84 void TreeCommandScanLog::PrintStartDirNotDirectory(const string &str)
85 {
86 cout << flush; // Avoid out-of-order display
87 gtout(cerr, _("%$: %$ is not a directory. Command ignored\n"))
88 << progName << str;
89 }
90
PrintStartDirNotFound(const string & str)91 void TreeCommandScanLog::PrintStartDirNotFound(const string &str)
92 {
93 cout << flush; // Avoid out-of-order display
94 gtout(cerr, _("%$: cannot find directory %$. Command ignored\n"))
95 << progName << str;
96 }
97
PrintStartDirNotDirectory(const string & str)98 void MountCommandScanLog::PrintStartDirNotDirectory(const string &str)
99 {
100 cout << flush; // Avoid out-of-order display
101 gtout(cerr, _("%$: %$ specified in `MountDir\' command is not"
102 " a directory. Command ignored\n"))
103 << progName << str;
104 }
105
PrintStartDirNotFound(const string & str)106 void MountCommandScanLog::PrintStartDirNotFound(const string &str)
107 {
108 cout << flush; // Avoid out-of-order display
109 gtout(cerr, _("%$: cannot find directory %$"
110 " as specified in `MountDir\' command. Command ignored\n"))
111 << progName << str;
112 }
113
114
HandleChdirError(ScanLog & scan_log,const string & dir,int e)115 void HandleChdirError(ScanLog &scan_log, const string &dir, int e)
116 {
117 if (e == EACCES)
118 return;
119 scan_log.PrintChdirError(dir, strerror(e));
120 }
121
122 /*************************************************************************
123 Variables
124 *************************************************************************/
125
126 int dirUpdated = 0; // Equals 1 if recan in this session
127
ExpandSymLink(ScanLog & scan_log)128 void ExpandSymLink(ScanLog &scan_log)
129 {
130 scan_log.PrintWork(_("Resolving symbolic links"));
131 RecursiveResolveSymLink(&dirTree, dirTree, string(""));
132 }
133
134 /*************************************************************************
135 Full directory rescan
136 *************************************************************************/
137
RecursiveFullScanDir(ScanLog & scan_log,sptr<DirectoryEntry> & d,const string & current_dir)138 void RecursiveFullScanDir(ScanLog &scan_log, sptr<DirectoryEntry> &d,
139 const string ¤t_dir)
140 {
141 if (FindSkipDir(current_dir)
142 || FindMountDir(current_dir)) { // Match skipped dir.
143 d->isSkip = 1;
144 return;
145 }
146
147 DIR *dir = opendir(".");
148 if (dir == NULL) { // No read access
149 d->isUnreadable = 2;
150 return; // Skip scanning this dir
151 }
152
153 try {
154 struct dirent *entry;
155 while((entry = readdir(dir)) != NULL) {
156 // Skip "." and ".." dir
157 if (strcmp(entry->d_name, ".") != 0 &&
158 strcmp(entry->d_name, "..") != 0) {
159
160 // Do not delete d2 if it is added to
161 // dirTree!
162 sptr<DirectoryEntry> d2(new DirectoryEntry(entry));
163
164 if (d2->IsDir()) {
165
166 d->subDir.push_back(d2);
167
168 string new_dir = current_dir;
169 if (new_dir[new_dir.size()-1] != '/')
170 new_dir += '/';
171 new_dir += entry->d_name;
172
173 // Display current progress
174 scan_log.PrintDirectory(new_dir);
175
176 if (!(d2->IsSymLink())) {
177 // For dirs
178 int ret = k_chdir(entry->d_name);
179 if (ret == 0) {
180 // Dir. can be accesed
181
182
183 // Scan inside this dir
184 RecursiveFullScanDir(scan_log, d2, new_dir);
185
186 // Back to current dir.
187 k_chdir("..");
188 }
189 else { // No exec. permission
190 int e = errno;
191 HandleChdirError(scan_log, entry->d_name, e);
192
193 d2->isUnreadable = 1;
194 }
195 }
196 }
197 // Else it is not a dir. entry.
198 // It is automatically deleted.
199
200 }
201 }
202 }
203 catch (...) {
204 closedir(dir);
205 throw;
206 }
207 closedir(dir);
208
209 if (kcdConfig.cfgSortTree)
210 d->subDir.sort();
211 }
212
FullScanFromDir(ScanLog & scan_log,const string & dir,bool set_flags=false)213 void FullScanFromDir(ScanLog &scan_log, const string &dir, bool set_flags = false)
214 {
215 if (k_access (dir, F_OK))
216 scan_log.PrintStartDirNotFound(dir);
217 else if (k_chdir(dir))
218 scan_log.PrintStartDirNotDirectory(dir);
219 else {
220 sptr<DirectoryEntry> d(new DirectoryEntry(dir));
221 dirTree.push_back(d);
222
223 if (set_flags) // Set flags if requested
224 d->flags = 1;
225
226 scan_log.PrintDirectory(dir);
227
228 if (!d->IsSymLink()) {
229
230 // Recursion...
231 RecursiveFullScanDir(scan_log, d, dir);
232 }
233 }
234 }
235
236
FullScanDir(ScanLog & scan_log)237 void FullScanDir(ScanLog &scan_log)
238 {
239 time_t t1, t2; // Get current time
240 time(&t1);
241 SetScanTime(t1);
242
243 scan_log.SetQuiet(kcdConfig.cfgQuietFull);
244
245 dirTree.clear();
246
247 if (kcdConfig.cfgStartDir->empty()) {
248 FullScanFromDir(scan_log, "/");
249 }
250 else {
251 for (DirList::iterator iter = kcdConfig.cfgStartDir->begin();
252 iter != kcdConfig.cfgStartDir->end(); ++iter) {
253
254 try {
255 FullScanFromDir(scan_log, UnquoteShellChars((*iter)->dir));
256 }
257 catch (ErrorRange &) { // Ignore quoting error
258 }
259 }
260
261 if (kcdConfig.cfgSortTree)
262 dirTree.sort();
263 }
264
265 ExpandSymLink(scan_log);
266 WriteDirFile();
267
268 time(&t2);
269
270 scan_log.PrintTime(t2-t1, _("full scan mode"));
271
272 dirUpdated = 1;
273 }
274
275 /*************************************************************************
276 Smart directory rescan
277 *************************************************************************/
278
279 int hit = 0, miss = 0;
280
281 struct IsDirFlagCleared {
operator ()IsDirFlagCleared282 bool operator()(sptr<DirectoryEntry> &d) const {
283 return d->flags == 0;
284 }
285 };
286
287 struct SetDirFlag {
288 char flags;
SetDirFlagSetDirFlag289 SetDirFlag(char flags_) : flags(flags_) {}
operator ()SetDirFlag290 void operator()(sptr<DirectoryEntry> &d) {
291 d->flags = flags;
292 }
293 };
294
295 struct AssertDirFlag {
296 char flags;
AssertDirFlagAssertDirFlag297 AssertDirFlag(char flags_) : flags(flags_) {}
operator ()AssertDirFlag298 void operator()(sptr<DirectoryEntry> &d) {
299 if (d->flags != flags) {
300 throw ErrorGeneric("Invalid directory tree state, program aborted");
301 }
302 }
303 };
304
RecursiveSmartScanDir(ScanLog & scan_log,sptr<DirectoryEntry> & d,const string & current_dir)305 void RecursiveSmartScanDir(ScanLog &scan_log, sptr<DirectoryEntry> &d,
306 const string ¤t_dir)
307 {
308 int ret;
309 int fullDirRead = 0;
310
311 if (FindSkipDir(current_dir)
312 || FindMountDir(current_dir)) { // Match skipped dir.
313 d->isSkip = 1;
314 d->subDir.clear();
315 return;
316 }
317
318 if (d->isSkip) { // No longer skipped
319 fullDirRead = 1;
320 d->isSkip = 0; // Clear skip flag
321
322 DirectoryEntry d2("."); // Update info
323 d->UpdateEntry(&d2);
324 }
325 else {
326 // Assumes "." is dir. files
327 // This will read new stat
328 // from disk
329 DirectoryEntry d2(".");
330
331 // Check date
332 if (Max(d2.GetModTime(), d2.GetChangeTime())
333 > Max(d->GetModTime(), d->GetChangeTime())) {
334 d->UpdateEntry(&d2); // New info
335 fullDirRead = 1;
336 }
337 }
338
339 if (!fullDirRead) { // Fast directory scanning
340
341 if (d->isUnreadable) {
342 d->subDir.clear();
343 return; // Unreadable
344 }
345
346 for (sptr_list<DirectoryEntry>::iterator iter = d->subDir.begin();
347 iter != d->subDir.end(); ++iter) {
348
349 string new_dir = current_dir;
350 if (new_dir[new_dir.size()-1] != '/')
351 new_dir += '/';
352 new_dir += (*iter)->GetNameStr();
353 // Display current progress
354 scan_log.PrintDirectory(new_dir);
355
356 if (!((*iter)->IsSymLink())) {
357 // For dirs
358 ret = k_chdir((*iter)->GetNameStr());
359 if (ret == 0) {
360 // Dir. can be accesed
361
362 // Scan inside this dir
363 RecursiveSmartScanDir(scan_log, *iter, new_dir);
364
365 // Back to current dir.
366 k_chdir("..");
367 }
368 else { // No exec. permission
369 // Note: permissions of subdir may
370 // be changed when the last
371 // modification time of the
372 // current dir is the same
373
374 int e = errno;
375 HandleChdirError(scan_log, (*iter)->GetNameStr(), e);
376
377 (*iter)->isUnreadable = 1;
378 // Remove all subdir
379 (*iter)->subDir.clear();
380 }
381 }
382 }
383 hit++;
384 if (kcdConfig.cfgSortTree)
385 d->subDir.sort();
386 return;
387 }
388
389 // Cases requiring slow scanning
390
391 DIR *dir = opendir(".");
392 if (dir == NULL) { // No read access
393 d->isUnreadable = 2;
394 d->subDir.clear();
395 return; // Skip scanning this dir
396 }
397
398 if (d->isUnreadable) { // Used to be unreadable
399 d->isUnreadable = 0; // Clear unreadable flag
400 }
401
402 for_each(d->subDir.begin(), d->subDir.end(), AssertDirFlag(0));
403
404 try {
405 struct dirent *entry;
406 while((entry = readdir(dir)) != NULL) {
407
408 // Skip "." and ".." dir
409 if (strcmp(entry->d_name, ".") != 0 &&
410 strcmp(entry->d_name, "..") != 0) {
411
412 // Do not delete d2 if it is added to
413 // dirTree!
414 sptr<DirectoryEntry> d2(new DirectoryEntry(entry));
415
416 if (d2->IsDir()) {
417
418 string new_dir = current_dir;
419 if (new_dir[new_dir.size()-1] != '/')
420 new_dir += '/';
421 new_dir += entry->d_name;
422
423 // Display current progress
424 scan_log.PrintDirectory(new_dir);
425
426 sptr_list<DirectoryEntry>::iterator iter = d->subDir.begin();
427 for ( ; iter != d->subDir.end(); ++iter) {
428 if ((*iter)->GetNameStr() == d2->GetNameStr()
429 && (*iter)->IsSymLink() == d2->IsSymLink()) {
430 (*iter)->flags = 1; // Mark it
431 break;
432 }
433 }
434 bool is_new_dir = false;
435 if (iter == d->subDir.end()) { // Newly created dir
436
437 d2->flags = 1; // Mark it
438 d->subDir.push_back(d2);
439 is_new_dir = true;
440 }
441
442 if (!(d2->IsSymLink())) {
443 // For dirs
444 ret = k_chdir(entry->d_name);
445 if (ret == 0) {
446 // Dir. can be accesed
447
448 // Scan inside this dir
449 if (is_new_dir)
450 RecursiveFullScanDir(scan_log, d2, new_dir);
451 else
452 // Use *iter instead of d2
453 RecursiveSmartScanDir(scan_log, *iter, new_dir);
454
455 // Back to current dir.
456 k_chdir("..");
457 }
458 else { // No exec. permission
459 int e = errno;
460 HandleChdirError(scan_log, entry->d_name, e);
461
462 if (is_new_dir)
463 // New directory, no need
464 // to clear subdirectories.
465 d2->isUnreadable = 1;
466 else {
467 (*iter)->isUnreadable = 1;
468 (*iter)->subDir.clear();
469 }
470 }
471 }
472 else { // For symbolic links
473
474 // Check date
475 if (!is_new_dir
476 && (Max(d2->GetModTime(), d2->GetChangeTime())
477 > Max((*iter)->GetModTime(), (*iter)->GetChangeTime()))) {
478 (*iter)->UpdateEntry(d2()); // New info
479 }
480 }
481 }
482 }
483 }
484 }
485 catch (...) {
486 closedir(dir);
487 throw;
488 }
489 closedir(dir);
490
491 d->subDir.remove_if(IsDirFlagCleared());
492 for_each(d->subDir.begin(), d->subDir.end(), SetDirFlag(0));
493
494 miss++;
495
496 if (kcdConfig.cfgSortTree)
497 d->subDir.sort();
498 }
499
SmartScanDir(ScanLog & scan_log)500 void SmartScanDir(ScanLog &scan_log)
501 {
502 time_t t1, t2; // Get current time
503 time(&t1);
504
505 scan_log.SetQuiet(kcdConfig.cfgQuietSmart);
506
507 LoadDirFile(true); // Load saved tree for timestamp info
508
509 SetScanTime(t1); // New scan time
510
511 if (dirUpdated) // Full scan already enforced in
512 // LoadDirFile(...)
513 return;
514
515 for_each(dirTree.begin(), dirTree.end(), AssertDirFlag(0));
516
517 // Add and scan directory added to StartDir
518 // We mark flags to 1 for newly added dir
519 // and 2 for old dir that still exists
520
521 if (kcdConfig.cfgStartDir->empty()) {
522 bool found_dir = false;
523
524 // Remove all previous StartDir entry
525 // that is not '/'. Duplicate '/' is
526 // also removed.
527 for (sptr_list<DirectoryEntry>::iterator iter = dirTree.begin();
528 iter != dirTree.end(); ++iter) {
529
530 if ((*iter)->GetNameStr() == "/" && found_dir == false) {
531 // This is the directory to keep
532 (*iter)->flags = 2;
533 found_dir = true;
534 }
535 }
536
537 // If '/' wasn't in previous StartDir
538 // start full scan
539 if (found_dir == false) {
540 FullScanFromDir(scan_log, "/", true);
541 }
542 }
543 else {
544 for (DirList::iterator iter = kcdConfig.cfgStartDir->begin();
545 iter != kcdConfig.cfgStartDir->end(); ++iter) {
546
547 try {
548 bool found_dir = false;
549 string dir_name = UnquoteShellChars((*iter)->dir);
550
551 sptr<DirectoryEntry> d2(new DirectoryEntry(dir_name));
552
553 for (sptr_list<DirectoryEntry>::iterator iter2 = dirTree.begin();
554 iter2 != dirTree.end(); ++iter2) {
555 if ((*iter2)->GetNameStr() == dir_name
556 && found_dir == false
557 && d2->IsDir()
558 && (*iter2)->IsSymLink() == d2->IsSymLink()) {
559 // This is the directory to keep
560 (*iter2)->flags = 2;
561 found_dir = true;
562 }
563 }
564
565 // If it wasn't in previous StartDir
566 // start full scan
567 if (found_dir == false) {
568 FullScanFromDir(scan_log, dir_name, true);
569 }
570 }
571 catch (ErrorRange &) { // Ignore quoting error
572 }
573 }
574 }
575
576 // Remove directory removed from StartDir
577 dirTree.remove_if(IsDirFlagCleared());
578
579 // Smart scan dir
580
581 for (sptr_list<DirectoryEntry>::iterator iter = dirTree.begin();
582 iter != dirTree.end(); ++iter) {
583
584 // Directory not just added
585 if ((*iter)->IsDir()) {
586
587 if ((*iter)->flags == 1) // Already scanned
588 // by FullScanFromDir
589 continue;
590
591 string new_dir = (*iter)->GetNameStr();
592
593 scan_log.PrintDirectory(new_dir);
594
595 // Change dir. to StartDir
596 if(k_chdir(new_dir) == 0) {
597 if (!(*iter)->IsSymLink()) {
598
599 // Recursion...
600 RecursiveSmartScanDir(scan_log, *iter, new_dir);
601 }
602 else {
603 DirectoryEntry d2((*iter)->GetNameStr());
604
605 // Check date
606 if (Max(d2.GetModTime(), d2.GetChangeTime())
607 > Max((*iter)->GetModTime(), (*iter)->GetChangeTime())) {
608 (*iter)->UpdateEntry(&d2); // New info
609 }
610 }
611 }
612 else { // No exec. permission
613 int e = errno;
614 HandleChdirError(scan_log, new_dir, e);
615
616 (*iter)->isUnreadable = 1;
617 (*iter)->subDir.clear();
618 }
619 }
620 else {
621 // No longer a directory
622 (*iter)->flags = 0;
623 }
624 }
625
626 // Remove entries that are no longer directory
627 dirTree.remove_if(IsDirFlagCleared());
628 for_each(dirTree.begin(), dirTree.end(), SetDirFlag(0));
629
630 if (kcdConfig.cfgSortTree)
631 dirTree.sort();
632
633 ExpandSymLink(scan_log);
634 WriteDirFile();
635
636 time(&t2);
637
638 scan_log.PrintTime(t2-t1, _("smart scan mode"));
639
640 dirUpdated = 1;
641 }
642
643 /*************************************************************************
644 Partial directory rescan
645 *************************************************************************/
646
DoPartialScanDir(ScanLog & scan_log,const string & str_,bool full_scan)647 void DoPartialScanDir(ScanLog &scan_log, const string &str_, bool full_scan)
648 {
649 if (dirUpdated) // Full scan already enforced in
650 // LoadDirFile(...)
651 return;
652
653 if (k_chdir(str_)) {
654 throw ErrorGenericCommandLine(_("cannot scan directory %$"),
655 str_);
656 }
657
658 // Save current working dir
659 string str = k_getcwd();
660 if (!str.size()) {
661 throw ErrorGenericCommandLine(_("cannot obtain full directory name for %$"),
662 str_);
663 }
664
665 FindDirInfo *dir_info = FindDir(&dirTree, str, true, false, true);
666 if (!dir_info) {
667 throw ErrorGenericCommandLine(_("%$ is either a special directory, in `SkipDir\' or not in `StartDir\'"),
668 str_);
669 }
670
671 string current_dir = dir_info->str;
672 sptr<DirectoryEntry> dir = dir_info->dir_ptr; // We don't own this
673
674 scan_log.PrintDirectory(current_dir);
675
676 // Start scanning
677 if (! k_chdir(current_dir)) {
678 if (full_scan) {
679 dir->subDir.clear();
680 RecursiveFullScanDir(scan_log, dir, dir_info->str);
681 }
682 else
683 RecursiveSmartScanDir(scan_log, dir, dir_info->str);
684 }
685 else {
686 int e = errno;
687 HandleChdirError(scan_log, current_dir, e);
688 }
689
690 delete dir_info;
691 }
692
PartialScanDir(ScanLog & scan_log,const vector<string> & str_,bool full_scan)693 void PartialScanDir(ScanLog &scan_log, const vector<string> &str_, bool full_scan)
694 {
695 time_t t1, t2; // Get current time
696 time(&t1);
697 // Do not update scan time here
698
699 scan_log.SetQuiet(kcdConfig.cfgQuietPartial);
700
701 LoadDirFile(true);
702 for (size_t i = 0; i < str_.size(); ++i) {
703 DoPartialScanDir(scan_log, str_[i], full_scan);
704 if (i != str_.size()-1 && saveCwd.size())
705 k_chdir(saveCwd);
706 }
707
708 ExpandSymLink(scan_log);
709 WriteDirFile();
710
711 time(&t2);
712
713 scan_log.PrintTime(t2-t1, _("partial scan mode"));
714 }
715
716 /*************************************************************************
717 Helper functions
718 *************************************************************************/
719
SmartScanDir()720 void SmartScanDir()
721 {
722 ScanCommandScanLog scan_log;
723 SmartScanDir(scan_log);
724 }
725
FullScanDir()726 void FullScanDir()
727 {
728 ScanCommandScanLog scan_log;
729 FullScanDir(scan_log);
730 }
731
ScanDir(bool full_scan)732 void ScanDir(bool full_scan)
733 {
734 if (full_scan)
735 FullScanDir();
736 else
737 SmartScanDir();
738 }
739
PartialScanDir(const vector<string> & str_,bool full_scan)740 void PartialScanDir(const vector<string> &str_, bool full_scan)
741 {
742 ScanCommandScanLog scan_log;
743 PartialScanDir(scan_log, str_, full_scan);
744 }
745
ScanDirAndDisplay(const string & str_,bool full_scan)746 void ScanDirAndDisplay(const string &str_, bool full_scan)
747 {
748 TreeCommandScanLog scan_log;
749 scan_log.SetQuiet(kcdConfig.cfgQuietPartial);
750
751 FindDirInfo *find = FindDir(&dirTree, str_, false, false);
752 if (find) {
753 DoPartialScanDir(scan_log, str_, full_scan);
754 ExpandSymLink(scan_log);
755 WriteDirFile(); // Update saved tree too
756 }
757 else {
758 dirTree.clear();
759 // Allow scanning inside
760 // skipped directory
761 delete kcdConfig.cfgSkipDir;
762 kcdConfig.cfgSkipDir = new DirList;
763
764 FullScanFromDir(scan_log, str_);
765 ExpandSymLink(scan_log);
766 }
767 }
768
MountDirScanDir(const string & str_)769 void MountDirScanDir(const string &str_)
770 {
771 MountCommandScanLog scan_log;
772 scan_log.SetQuiet(true);
773
774 gtout(cout, _("scanning MountDir: %$\n")) << str_;
775
776 FindDirInfo *find = FindDir(&dirTree, str_, false, false);
777 if (find) {
778 DoPartialScanDir(scan_log, str_, true);
779 }
780 delete find;
781 }
782