1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
base / files / file_enumerator_posix.cc [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_enumerator.h"
#include <dirent.h>
#include <errno.h>
#include <fnmatch.h>
#include <stdint.h>
#include <string.h>
#include "base/logging.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/content_uri_utils.h"
#endif
namespace base {
namespace {
bool GetStat(const FilePath& path, bool show_links, stat_wrapper_t* st) {
DCHECK(st);
const int res = show_links ? File::Lstat(path, st) : File::Stat(path, st);
if (res < 0) {
// Print the stat() error message unless it was ENOENT and we're following
// symlinks.
DPLOG_IF(ERROR, errno != ENOENT || show_links)
<< "Cannot stat '" << path << "'";
memset(st, 0, sizeof(*st));
return false;
}
return true;
}
#if BUILDFLAG(IS_FUCHSIA)
bool ShouldShowSymLinks(int file_type) {
return false;
}
#else
bool ShouldShowSymLinks(int file_type) {
return file_type & FileEnumerator::SHOW_SYM_LINKS;
}
#endif // BUILDFLAG(IS_FUCHSIA)
#if BUILDFLAG(IS_FUCHSIA)
bool ShouldTrackVisitedDirectories(int file_type) {
return false;
}
#else
bool ShouldTrackVisitedDirectories(int file_type) {
return !(file_type & FileEnumerator::SHOW_SYM_LINKS);
}
#endif // BUILDFLAG(IS_FUCHSIA)
} // namespace
// FileEnumerator::FileInfo ----------------------------------------------------
FileEnumerator::FileInfo::FileInfo() {
memset(&stat_, 0, sizeof(stat_));
}
#if BUILDFLAG(IS_ANDROID)
FileEnumerator::FileInfo::FileInfo(base::FilePath content_uri,
base::FilePath filename,
bool is_directory,
off_t size,
Time time)
: content_uri_(std::move(content_uri)), filename_(std::move(filename)) {
memset(&stat_, 0, sizeof(stat_));
stat_.st_mode = is_directory ? S_IFDIR : S_IFREG;
stat_.st_size = size;
stat_.st_mtime = time.ToTimeT();
}
#endif
bool FileEnumerator::FileInfo::IsDirectory() const {
return S_ISDIR(stat_.st_mode);
}
FilePath FileEnumerator::FileInfo::GetName() const {
return filename_;
}
int64_t FileEnumerator::FileInfo::GetSize() const {
return stat_.st_size;
}
base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
return base::Time::FromTimeT(stat_.st_mtime);
}
// FileEnumerator --------------------------------------------------------------
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type)
: FileEnumerator(root_path,
recursive,
file_type,
FilePath::StringType(),
FolderSearchPolicy::MATCH_ONLY) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern)
: FileEnumerator(root_path,
recursive,
file_type,
pattern,
FolderSearchPolicy::MATCH_ONLY) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy)
: FileEnumerator(root_path,
recursive,
file_type,
pattern,
folder_search_policy,
ErrorPolicy::IGNORE_ERRORS) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy,
ErrorPolicy error_policy)
: current_directory_entry_(0),
root_path_(root_path),
recursive_(recursive),
file_type_(file_type),
pattern_(pattern),
folder_search_policy_(folder_search_policy),
error_policy_(error_policy) {
// INCLUDE_DOT_DOT must not be specified if recursive.
DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
#if BUILDFLAG(IS_ANDROID)
// Content-URIs have limited support.
if (root_path.IsContentUri()) {
CHECK_EQ(file_type_, FileType::FILES | FileType::DIRECTORIES);
// Get display-name of root path.
FileInfo root_info;
internal::ContentUriGetFileInfo(root_path, &root_info);
pending_subdirs_.push(
std::vector<std::string>({root_info.GetName().value()}));
}
#endif
if (file_type_ & FileType::NAMES_ONLY) {
DCHECK(!recursive_);
DCHECK_EQ(file_type_ & ~(FileType::NAMES_ONLY | FileType::INCLUDE_DOT_DOT),
0);
file_type_ |= (FileType::FILES | FileType::DIRECTORIES);
}
if (recursive && ShouldTrackVisitedDirectories(file_type_)) {
if (stat_wrapper_t st; GetStat(root_path, false, &st)) {
MarkVisited(st);
}
}
pending_paths_.push(root_path);
}
FileEnumerator::~FileEnumerator() = default;
FilePath FileEnumerator::Next() {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
++current_directory_entry_;
// While we've exhausted the entries in the current directory, do the next
while (current_directory_entry_ >= directory_entries_.size()) {
if (pending_paths_.empty())
return FilePath();
root_path_ = pending_paths_.top();
root_path_ = root_path_.StripTrailingSeparators();
pending_paths_.pop();
#if BUILDFLAG(IS_ANDROID)
if (root_path_.IsContentUri()) {
subdirs_ = pending_subdirs_.top();
pending_subdirs_.pop();
directory_entries_ = internal::ListContentUriDirectory(root_path_);
current_directory_entry_ = 0;
if (directory_entries_.empty()) {
continue;
}
if (recursive_) {
for (auto& info : directory_entries_) {
info.subdirs_ = subdirs_;
if (info.IsDirectory()) {
pending_paths_.push(info.content_uri_);
pending_subdirs_.push(subdirs_);
pending_subdirs_.top().push_back(info.GetName().value());
}
}
}
break;
}
#endif
DIR* dir = opendir(root_path_.value().c_str());
if (!dir) {
if (errno == 0 || error_policy_ == ErrorPolicy::IGNORE_ERRORS)
continue;
error_ = File::OSErrorToFileError(errno);
return FilePath();
}
directory_entries_.clear();
#if BUILDFLAG(IS_FUCHSIA)
// Fuchsia does not support .. on the file system server side, see
// https://fuchsia.googlesource.com/docs/+/master/dotdot.md and
// https://crbug.com/735540. However, for UI purposes, having the parent
// directory show up in directory listings makes sense, so we add it here to
// match the expectation on other operating systems. In cases where this
// is useful it should be resolvable locally.
FileInfo dotdot;
dotdot.stat_.st_mode = S_IFDIR;
dotdot.filename_ = FilePath("..");
if (!ShouldSkip(dotdot.filename_)) {
directory_entries_.push_back(std::move(dotdot));
}
#endif // BUILDFLAG(IS_FUCHSIA)
current_directory_entry_ = 0;
struct dirent* dent;
// NOTE: Per the readdir() documentation, when the end of the directory is
// reached with no errors, null is returned and errno is not changed.
// Therefore we must reset errno to zero before calling readdir() if we
// wish to know whether a null result indicates an error condition.
while (errno = 0, dent = readdir(dir)) {
FileInfo info;
info.filename_ = FilePath(dent->d_name);
if (ShouldSkip(info.filename_))
continue;
const bool is_pattern_matched = IsPatternMatched(info.filename_);
// MATCH_ONLY policy enumerates files and directories which matching
// pattern only. So we can early skip further checks.
if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY &&
!is_pattern_matched)
continue;
// Do not call OS stat/lstat if there is no sense to do it. If pattern is
// not matched (file will not appear in results) and search is not
// recursive (possible directory will not be added to pending paths) -
// there is no sense to obtain item below.
if (!recursive_ && !is_pattern_matched)
continue;
// If the caller only wants the names of files and directories, then
// continue without populating `info` further.
if (file_type_ & FileType::NAMES_ONLY) {
directory_entries_.push_back(std::move(info));
continue;
}
FilePath full_path = root_path_.Append(info.filename_);
if (!GetStat(full_path, ShouldShowSymLinks(file_type_), &info.stat_)) {
continue;
}
const bool is_dir = info.IsDirectory();
// Recursive mode: schedule traversal of a directory if either
// SHOW_SYM_LINKS is on or we haven't visited the directory yet.
if (recursive_ && is_dir &&
(!ShouldTrackVisitedDirectories(file_type_) ||
MarkVisited(info.stat_))) {
pending_paths_.push(std::move(full_path));
}
if (is_pattern_matched && IsTypeMatched(is_dir))
directory_entries_.push_back(std::move(info));
}
int readdir_errno = errno;
closedir(dir);
if (readdir_errno != 0 && error_policy_ != ErrorPolicy::IGNORE_ERRORS) {
error_ = File::OSErrorToFileError(readdir_errno);
return FilePath();
}
// MATCH_ONLY policy enumerates files in matched subfolders by "*" pattern.
// ALL policy enumerates files in all subfolders by origin pattern.
if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY)
pattern_.clear();
}
#if BUILDFLAG(IS_ANDROID)
if (root_path_.IsContentUri()) {
return directory_entries_[current_directory_entry_].content_uri_;
}
#endif
return root_path_.Append(
directory_entries_[current_directory_entry_].filename_);
}
FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
DCHECK(!(file_type_ & FileType::NAMES_ONLY));
return directory_entries_[current_directory_entry_];
}
bool FileEnumerator::IsPatternMatched(const FilePath& path) const {
return pattern_.empty() ||
!fnmatch(pattern_.c_str(), path.value().c_str(), FNM_NOESCAPE);
}
} // namespace base