blob: 803b7bd4437b4e9e66ecf7614bd727c7839d0409 [file] [log] [blame]
Scott Graham66962112018-06-08 12:42:08 -07001// Copyright (c) 2013 The Chromium Authors. All rights reserved.
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 <shlwapi.h>
8#include <stdint.h>
9#include <string.h>
10
11#include "base/logging.h"
Scott Graham66962112018-06-08 12:42:08 -070012
13namespace base {
14
15namespace {
16
17FilePath BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy,
18 const FilePath& root_path,
19 const FilePath::StringType& pattern) {
20 // MATCH_ONLY policy filters incoming files by pattern on OS side. ALL policy
21 // collects all files and filters them manually.
22 switch (policy) {
23 case FileEnumerator::FolderSearchPolicy::MATCH_ONLY:
24 return root_path.Append(pattern);
25 case FileEnumerator::FolderSearchPolicy::ALL:
26 return root_path.Append(L"*");
27 }
28 NOTREACHED();
29 return {};
30}
31
32} // namespace
33
34// FileEnumerator::FileInfo ----------------------------------------------------
35
36FileEnumerator::FileInfo::FileInfo() {
37 memset(&find_data_, 0, sizeof(find_data_));
38}
39
40bool FileEnumerator::FileInfo::IsDirectory() const {
41 return (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
42}
43
44FilePath FileEnumerator::FileInfo::GetName() const {
45 return FilePath(find_data_.cFileName);
46}
47
48int64_t FileEnumerator::FileInfo::GetSize() const {
49 ULARGE_INTEGER size;
50 size.HighPart = find_data_.nFileSizeHigh;
51 size.LowPart = find_data_.nFileSizeLow;
52 DCHECK_LE(size.QuadPart,
53 static_cast<ULONGLONG>(std::numeric_limits<int64_t>::max()));
54 return static_cast<int64_t>(size.QuadPart);
55}
56
Scott Grahamce047c92018-06-19 15:56:56 -070057Ticks FileEnumerator::FileInfo::GetLastModifiedTime() const {
58 return *reinterpret_cast<const uint64_t*>(&find_data_.ftLastWriteTime);
Scott Graham66962112018-06-08 12:42:08 -070059}
60
61// FileEnumerator --------------------------------------------------------------
62
63FileEnumerator::FileEnumerator(const FilePath& root_path,
64 bool recursive,
65 int file_type)
66 : FileEnumerator(root_path,
67 recursive,
68 file_type,
69 FilePath::StringType(),
70 FolderSearchPolicy::MATCH_ONLY) {}
71
72FileEnumerator::FileEnumerator(const FilePath& root_path,
73 bool recursive,
74 int file_type,
75 const FilePath::StringType& pattern)
76 : FileEnumerator(root_path,
77 recursive,
78 file_type,
79 pattern,
80 FolderSearchPolicy::MATCH_ONLY) {}
81
82FileEnumerator::FileEnumerator(const FilePath& root_path,
83 bool recursive,
84 int file_type,
85 const FilePath::StringType& pattern,
86 FolderSearchPolicy folder_search_policy)
87 : recursive_(recursive),
88 file_type_(file_type),
89 pattern_(!pattern.empty() ? pattern : L"*"),
90 folder_search_policy_(folder_search_policy) {
91 // INCLUDE_DOT_DOT must not be specified if recursive.
92 DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
93 memset(&find_data_, 0, sizeof(find_data_));
94 pending_paths_.push(root_path);
95}
96
97FileEnumerator::~FileEnumerator() {
98 if (find_handle_ != INVALID_HANDLE_VALUE)
99 FindClose(find_handle_);
100}
101
102FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
103 if (!has_find_data_) {
104 NOTREACHED();
105 return FileInfo();
106 }
107 FileInfo ret;
108 memcpy(&ret.find_data_, &find_data_, sizeof(find_data_));
109 return ret;
110}
111
112FilePath FileEnumerator::Next() {
Scott Graham66962112018-06-08 12:42:08 -0700113 while (has_find_data_ || !pending_paths_.empty()) {
114 if (!has_find_data_) {
115 // The last find FindFirstFile operation is done, prepare a new one.
116 root_path_ = pending_paths_.top();
117 pending_paths_.pop();
118
119 // Start a new find operation.
120 const FilePath src =
121 BuildSearchFilter(folder_search_policy_, root_path_, pattern_);
122 find_handle_ = FindFirstFileEx(src.value().c_str(),
123 FindExInfoBasic, // Omit short name.
124 &find_data_, FindExSearchNameMatch,
125 nullptr, FIND_FIRST_EX_LARGE_FETCH);
126 has_find_data_ = true;
127 } else {
128 // Search for the next file/directory.
129 if (!FindNextFile(find_handle_, &find_data_)) {
130 FindClose(find_handle_);
131 find_handle_ = INVALID_HANDLE_VALUE;
132 }
133 }
134
135 if (INVALID_HANDLE_VALUE == find_handle_) {
136 has_find_data_ = false;
137
138 // MATCH_ONLY policy clears pattern for matched subfolders. ALL policy
139 // applies pattern for all subfolders.
140 if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY) {
141 // This is reached when we have finished a directory and are advancing
142 // to the next one in the queue. We applied the pattern (if any) to the
143 // files in the root search directory, but for those directories which
144 // were matched, we want to enumerate all files inside them. This will
145 // happen when the handle is empty.
146 pattern_ = L"*";
147 }
148
149 continue;
150 }
151
152 const FilePath filename(find_data_.cFileName);
153 if (ShouldSkip(filename))
154 continue;
155
156 const bool is_dir =
157 (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
158 const FilePath abs_path = root_path_.Append(filename);
159
160 // Check if directory should be processed recursive.
161 if (is_dir && recursive_) {
162 // If |cur_file| is a directory, and we are doing recursive searching,
163 // add it to pending_paths_ so we scan it after we finish scanning this
164 // directory. However, don't do recursion through reparse points or we
165 // may end up with an infinite cycle.
166 DWORD attributes = GetFileAttributes(abs_path.value().c_str());
167 if (!(attributes & FILE_ATTRIBUTE_REPARSE_POINT))
168 pending_paths_.push(abs_path);
169 }
170
171 if (IsTypeMatched(is_dir) && IsPatternMatched(filename))
172 return abs_path;
173 }
174 return FilePath();
175}
176
177bool FileEnumerator::IsPatternMatched(const FilePath& src) const {
178 switch (folder_search_policy_) {
179 case FolderSearchPolicy::MATCH_ONLY:
180 // MATCH_ONLY policy filters by pattern on search request, so all found
181 // files already fits to pattern.
182 return true;
183 case FolderSearchPolicy::ALL:
184 // ALL policy enumerates all files, we need to check pattern match
185 // manually.
186 return PathMatchSpec(src.value().c_str(), pattern_.c_str()) == TRUE;
187 }
188 NOTREACHED();
189 return false;
190}
191
192} // namespace base