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 &current_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 &current_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