xref: /aosp_15_r20/external/cronet/base/files/file_enumerator_win.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/files/file_enumerator.h"
6 
7 #include <stdint.h>
8 #include <string.h>
9 
10 #include "base/check_op.h"
11 #include "base/notreached.h"
12 #include "base/strings/string_util.h"
13 #include "base/threading/scoped_blocking_call.h"
14 #include "base/win/shlwapi.h"
15 
16 namespace base {
17 
18 namespace {
19 
BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy,const FilePath & root_path,const FilePath::StringType & pattern)20 FilePath BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy,
21                            const FilePath& root_path,
22                            const FilePath::StringType& pattern) {
23   // MATCH_ONLY policy filters incoming files by pattern on OS side. ALL policy
24   // collects all files and filters them manually.
25   switch (policy) {
26     case FileEnumerator::FolderSearchPolicy::MATCH_ONLY:
27       return root_path.Append(pattern);
28     case FileEnumerator::FolderSearchPolicy::ALL:
29       return root_path.Append(FILE_PATH_LITERAL("*"));
30   }
31   NOTREACHED();
32   return {};
33 }
34 
35 }  // namespace
36 
37 // FileEnumerator::FileInfo ----------------------------------------------------
38 
FileInfo()39 FileEnumerator::FileInfo::FileInfo() {
40   memset(&find_data_, 0, sizeof(find_data_));
41 }
42 
IsDirectory() const43 bool FileEnumerator::FileInfo::IsDirectory() const {
44   return (find_data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
45 }
46 
GetName() const47 FilePath FileEnumerator::FileInfo::GetName() const {
48   return FilePath(find_data().cFileName);
49 }
50 
GetSize() const51 int64_t FileEnumerator::FileInfo::GetSize() const {
52   ULARGE_INTEGER size;
53   size.HighPart = find_data().nFileSizeHigh;
54   size.LowPart = find_data().nFileSizeLow;
55   DCHECK_LE(size.QuadPart,
56             static_cast<ULONGLONG>(std::numeric_limits<int64_t>::max()));
57   return static_cast<int64_t>(size.QuadPart);
58 }
59 
GetLastModifiedTime() const60 Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
61   return Time::FromFileTime(find_data().ftLastWriteTime);
62 }
63 
64 // FileEnumerator --------------------------------------------------------------
65 
FileEnumerator(const FilePath & root_path,bool recursive,int file_type)66 FileEnumerator::FileEnumerator(const FilePath& root_path,
67                                bool recursive,
68                                int file_type)
69     : FileEnumerator(root_path,
70                      recursive,
71                      file_type,
72                      FilePath::StringType(),
73                      FolderSearchPolicy::MATCH_ONLY) {}
74 
FileEnumerator(const FilePath & root_path,bool recursive,int file_type,const FilePath::StringType & pattern)75 FileEnumerator::FileEnumerator(const FilePath& root_path,
76                                bool recursive,
77                                int file_type,
78                                const FilePath::StringType& pattern)
79     : FileEnumerator(root_path,
80                      recursive,
81                      file_type,
82                      pattern,
83                      FolderSearchPolicy::MATCH_ONLY) {}
84 
FileEnumerator(const FilePath & root_path,bool recursive,int file_type,const FilePath::StringType & pattern,FolderSearchPolicy folder_search_policy)85 FileEnumerator::FileEnumerator(const FilePath& root_path,
86                                bool recursive,
87                                int file_type,
88                                const FilePath::StringType& pattern,
89                                FolderSearchPolicy folder_search_policy)
90     : FileEnumerator(root_path,
91                      recursive,
92                      file_type,
93                      pattern,
94                      folder_search_policy,
95                      ErrorPolicy::IGNORE_ERRORS) {}
96 
FileEnumerator(const FilePath & root_path,bool recursive,int file_type,const FilePath::StringType & pattern,FolderSearchPolicy folder_search_policy,ErrorPolicy error_policy)97 FileEnumerator::FileEnumerator(const FilePath& root_path,
98                                bool recursive,
99                                int file_type,
100                                const FilePath::StringType& pattern,
101                                FolderSearchPolicy folder_search_policy,
102                                ErrorPolicy error_policy)
103     : recursive_(recursive),
104       file_type_(file_type),
105       pattern_(!pattern.empty() ? pattern : FILE_PATH_LITERAL("*")),
106       folder_search_policy_(folder_search_policy),
107       error_policy_(error_policy) {
108   // INCLUDE_DOT_DOT must not be specified if recursive.
109   DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
110 
111   if (file_type_ & FileType::NAMES_ONLY) {
112     DCHECK(!recursive_);
113     DCHECK_EQ(file_type_ & ~(FileType::NAMES_ONLY | FileType::INCLUDE_DOT_DOT),
114               0);
115     file_type_ |= (FileType::FILES | FileType::DIRECTORIES);
116   }
117 
118   memset(&find_data_, 0, sizeof(find_data_));
119   pending_paths_.push(root_path);
120 }
121 
~FileEnumerator()122 FileEnumerator::~FileEnumerator() {
123   if (find_handle_ != INVALID_HANDLE_VALUE)
124     FindClose(find_handle_);
125 }
126 
GetInfo() const127 FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
128   DCHECK(!(file_type_ & FileType::NAMES_ONLY));
129   if (!has_find_data_) {
130     NOTREACHED();
131     return FileInfo();
132   }
133   FileInfo ret;
134   memcpy(&ret.find_data_, &find_data_, sizeof(find_data_));
135   return ret;
136 }
137 
Next()138 FilePath FileEnumerator::Next() {
139   ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
140 
141   while (has_find_data_ || !pending_paths_.empty()) {
142     if (!has_find_data_) {
143       // The last find FindFirstFile operation is done, prepare a new one.
144       root_path_ = pending_paths_.top();
145       pending_paths_.pop();
146 
147       // Start a new find operation.
148       const FilePath src =
149           BuildSearchFilter(folder_search_policy_, root_path_, pattern_);
150       find_handle_ = FindFirstFileEx(src.value().c_str(),
151                                      FindExInfoBasic,  // Omit short name.
152                                      ChromeToWindowsType(&find_data_),
153                                      FindExSearchNameMatch, nullptr,
154                                      FIND_FIRST_EX_LARGE_FETCH);
155       has_find_data_ = true;
156     } else {
157       // Search for the next file/directory.
158       if (!FindNextFile(find_handle_, ChromeToWindowsType(&find_data_))) {
159         FindClose(find_handle_);
160         find_handle_ = INVALID_HANDLE_VALUE;
161       }
162     }
163 
164     DWORD last_error = GetLastError();
165     if (INVALID_HANDLE_VALUE == find_handle_) {
166       has_find_data_ = false;
167 
168       // MATCH_ONLY policy clears pattern for matched subfolders. ALL policy
169       // applies pattern for all subfolders.
170       if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY) {
171         // This is reached when we have finished a directory and are advancing
172         // to the next one in the queue. We applied the pattern (if any) to the
173         // files in the root search directory, but for those directories which
174         // were matched, we want to enumerate all files inside them. This will
175         // happen when the handle is empty.
176         pattern_ = FILE_PATH_LITERAL("*");
177       }
178 
179       if (last_error == ERROR_NO_MORE_FILES ||
180           error_policy_ == ErrorPolicy::IGNORE_ERRORS) {
181         continue;
182       }
183 
184       error_ = File::OSErrorToFileError(last_error);
185       return FilePath();
186     }
187 
188     const FilePath filename(find_data().cFileName);
189     if (ShouldSkip(filename))
190       continue;
191 
192     const bool is_dir =
193         (find_data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
194     const FilePath abs_path = root_path_.Append(filename);
195 
196     // Check if directory should be processed recursive.
197     if (is_dir && recursive_) {
198       // If |cur_file| is a directory, and we are doing recursive searching,
199       // add it to pending_paths_ so we scan it after we finish scanning this
200       // directory. However, don't do recursion through reparse points or we
201       // may end up with an infinite cycle.
202       DWORD attributes = GetFileAttributes(abs_path.value().c_str());
203       if (!(attributes & FILE_ATTRIBUTE_REPARSE_POINT))
204         pending_paths_.push(abs_path);
205     }
206 
207     if (IsTypeMatched(is_dir) && IsPatternMatched(filename))
208       return abs_path;
209   }
210   return FilePath();
211 }
212 
IsPatternMatched(const FilePath & src) const213 bool FileEnumerator::IsPatternMatched(const FilePath& src) const {
214   switch (folder_search_policy_) {
215     case FolderSearchPolicy::MATCH_ONLY:
216       // MATCH_ONLY policy filters by pattern on search request, so all found
217       // files already fits to pattern.
218       return true;
219     case FolderSearchPolicy::ALL:
220       // ALL policy enumerates all files, we need to check pattern match
221       // manually.
222       return PathMatchSpec(src.value().c_str(), pattern_.c_str()) == TRUE;
223   }
224   NOTREACHED();
225   return false;
226 }
227 
228 }  // namespace base
229