Merge pull request #2212 from unxed/ctrl_yo
[far2l.git] / far2l / src / scantree.cpp
blob1e02fd62e0bb7775343f1d1fb8135a26c492c2de
1 /*
2 scantree.cpp
4 Сканирование текущего каталога и, опционально, подкаталогов на
5 предмет имен файлов
6 */
7 /*
8 Copyright (c) 1996 Eugene Roshal
9 Copyright (c) 2000 Far Group
10 All rights reserved.
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions
14 are met:
15 1. Redistributions of source code must retain the above copyright
16 notice, this list of conditions and the following disclaimer.
17 2. Redistributions in binary form must reproduce the above copyright
18 notice, this list of conditions and the following disclaimer in the
19 documentation and/or other materials provided with the distribution.
20 3. The name of the authors may not be used to endorse or promote products
21 derived from this software without specific prior written permission.
23 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #include "headers.hpp"
37 #include "scantree.hpp"
38 #include "syslog.hpp"
39 #include "config.hpp"
40 #include "pathmix.hpp"
41 #include "processname.hpp"
43 ScanTree::ScanTree(int RetUpDir, int Recurse, int ScanJunction)
45 Flags.Change(FSCANTREE_RETUPDIR, RetUpDir);
46 Flags.Change(FSCANTREE_RECUR, Recurse);
47 Flags.Change(FSCANTREE_SCANSYMLINK, (ScanJunction == -1 ? Opt.ScanJunction : ScanJunction));
50 void ScanTree::SetFindPath(const wchar_t *Path, const wchar_t *Mask, const DWORD NewScanFlags)
52 Flags.Flags = (Flags.Flags & 0x0000FFFF) | (NewScanFlags & 0xFFFF0000);
53 strFindPath = *Path ? Path : L".";
54 strFindMask = wcscmp(Mask, L"*") ? Mask : L"";
55 ScanDirStack.clear();
57 if (strFindPath != WGOOD_SLASH) {
58 DeleteEndSlash(strFindPath);
61 ScanDirStack.emplace_back();
62 ConvertNameToReal(strFindPath.c_str(), ScanDirStack.back().RealPath);
64 StartEnumSubdir();
67 void ScanTree::CheckForEnterSubdir(const FAR_FIND_DATA_EX *fdata)
69 if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 || !Flags.Check(FSCANTREE_RECUR))
70 return;
72 if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && !Flags.Check(FSCANTREE_SCANSYMLINK))
73 return;
75 const bool InsideSymlink =
76 (fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 || IsInsideSymlink();
78 ScanDirStack.emplace_back();
79 strFindPath.append(fdata->strFileName.CPtr(), fdata->strFileName.GetLength());
81 if (fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
82 ConvertNameToReal(strFindPath.c_str(), ScanDirStack.back().RealPath);
84 // check if converted path points to same location is already scanned or to parent path of already scanned location
85 // NB: in original FAR here was exact-match check all paths (not only symlinks)
86 // that caused excessive scan from FS root cuz in Linux links pointing to / are usual situation unlike Windows
87 // NB2: There're two recursive protection checks, excessive on first look:
88 // Check by path: if real path is equal OR represents parent of some previously scanned path
89 // Check by inode: inode matches some previously scanned inode
90 // while check-by-path 'includes' check-by-node functionality, later is needed cuz in some
91 // complex cases realpath() fails with error like ELOOP fails resulting path be unresolved,
92 // so check-by-inode works as last resort to avoid unlimited recursion scan in such cases.
93 const auto &RealPath = ScanDirStack.back().RealPath;
94 for (auto it = ScanDirStack.rbegin();;) {
95 if (ScanDirStack.rend() == ++it)
96 break;
97 const auto &IthPath = it->RealPath;
98 if ((it->UnixDevice == fdata->UnixDevice && it->UnixNode == fdata->UnixNode)
99 || (IthPath.Begins(RealPath)
100 && (IthPath.GetLength() == RealPath.GetLength() || IthPath.At(RealPath.GetLength()) == GOOD_SLASH
101 || RealPath.GetLength() == 1))) { // recursion! revert state changes made so far and bail out
102 ScanDirStack.pop_back();
103 strFindPath.resize(strFindPath.size() - fdata->strFileName.GetLength());
104 return;
107 } else
108 ScanDirStack.back().RealPath = strFindPath;
110 ScanDirStack.back().UnixDevice = fdata->UnixDevice;
111 ScanDirStack.back().UnixNode = fdata->UnixNode;
112 ScanDirStack.back().InsideSymlink = InsideSymlink;
114 StartEnumSubdir();
117 void ScanTree::StartEnumSubdir()
119 if (!strFindPath.empty() && strFindPath.back() != LGOOD_SLASH)
120 strFindPath+= LGOOD_SLASH;
122 DWORD WinPortFindFlags = 0;
123 if (Flags.Check(FSCANTREE_NOLINKS))
124 WinPortFindFlags|= FIND_FILE_FLAG_NO_LINKS;
125 if (Flags.Check(FSCANTREE_NOFILES))
126 WinPortFindFlags|= FIND_FILE_FLAG_NO_FILES;
127 if (Flags.Check(FSCANTREE_NODEVICES))
128 WinPortFindFlags|= FIND_FILE_FLAG_NO_DEVICES;
129 if (Flags.Check(FSCANTREE_CASE_INSENSITIVE))
130 WinPortFindFlags|= FIND_FILE_FLAG_CASE_INSENSITIVE;
132 strFindPath+= L'*'; // append temporary asterisk
134 ScanDirStack.back().Enumer.reset(
135 new FindFile(strFindPath.c_str(), Flags.Check(FSCANTREE_SCANSYMLINK), WinPortFindFlags));
137 strFindPath.pop_back(); // strip asterisk
140 void ScanTree::LeaveSubdir()
142 if (!ScanDirStack.empty()) {
143 ScanDirStack.pop_back();
144 size_t p = strFindPath.rfind(GOOD_SLASH, strFindPath.size() - 2);
145 if (p != std::string::npos) {
146 strFindPath.resize(p + 1);
149 } else
150 fprintf(stderr, "ScanTree::LeaveSubdir() invoked on empty stack!\n");
153 bool ScanTree::ScanDir::GetNext(FAR_FIND_DATA_EX *fdata, bool FilesFirst)
155 if (Enumer)
156 for (;;) {
157 if (!Enumer->Get(*fdata)) {
158 Enumer.reset();
159 break;
161 if (!FilesFirst || (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
162 return true;
164 Postponed.emplace_back(std::move(*fdata));
167 if (!Postponed.empty()) {
168 *fdata = std::move(Postponed.front());
169 Postponed.pop_front();
170 return true;
173 return false;
176 bool ScanTree::GetNextName(FAR_FIND_DATA_EX *fdata, FARString &strFullName)
178 Flags.Clear(FSCANTREE_SECONDDIRNAME);
180 for (;;) {
181 if (ScanDirStack.empty())
182 return false;
184 if (!ScanDirStack.back().GetNext(fdata, Flags.Check(FSCANTREE_FILESFIRST))) {
185 if (!Flags.Check(FSCANTREE_RETUPDIR) || ScanDirStack.size() == 1) {
186 LeaveSubdir();
187 continue;
190 strFullName = strFindPath;
191 DeleteEndSlash(strFullName);
192 apiGetFindDataEx(strFullName, *fdata);
193 LeaveSubdir();
194 Flags.Set(FSCANTREE_SECONDDIRNAME);
195 return true;
198 const bool Matched = strFindMask.empty() || CmpName(strFindMask.c_str(), fdata->strFileName, false);
199 if (Matched) {
200 strFullName = strFindPath;
201 strFullName+= fdata->strFileName;
204 CheckForEnterSubdir(fdata);
206 if (Matched)
207 return true;
211 void ScanTree::SkipDir()
213 LeaveSubdir();