1 // 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 <dirent.h>
8 #include <errno.h>
9 #include <fnmatch.h>
10 #include <stdint.h>
11 #include <string.h>
12
13 #include "base/logging.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "build/build_config.h"
16
17 namespace base {
18 namespace {
19
GetStat(const FilePath & path,bool show_links,struct stat * st)20 void GetStat(const FilePath& path, bool show_links, struct stat* st) {
21 DCHECK(st);
22 const int res = show_links ? lstat(path.value().c_str(), st)
23 : stat(path.value().c_str(), st);
24 if (res < 0) {
25 // Print the stat() error message unless it was ENOENT and we're following
26 // symlinks.
27 if (!(errno == ENOENT && !show_links))
28 DPLOG(ERROR) << "Couldn't stat" << path.value();
29 memset(st, 0, sizeof(*st));
30 }
31 }
32
33 } // namespace
34
35 // FileEnumerator::FileInfo ----------------------------------------------------
36
FileInfo()37 FileEnumerator::FileInfo::FileInfo() {
38 memset(&stat_, 0, sizeof(stat_));
39 }
40
IsDirectory() const41 bool FileEnumerator::FileInfo::IsDirectory() const {
42 return S_ISDIR(stat_.st_mode);
43 }
44
GetName() const45 FilePath FileEnumerator::FileInfo::GetName() const {
46 return filename_;
47 }
48
GetSize() const49 int64_t FileEnumerator::FileInfo::GetSize() const {
50 return stat_.st_size;
51 }
52
GetLastModifiedTime() const53 base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
54 return base::Time::FromTimeT(stat_.st_mtime);
55 }
56
57 // FileEnumerator --------------------------------------------------------------
58
FileEnumerator(const FilePath & root_path,bool recursive,int file_type)59 FileEnumerator::FileEnumerator(const FilePath& root_path,
60 bool recursive,
61 int file_type)
62 : FileEnumerator(root_path,
63 recursive,
64 file_type,
65 FilePath::StringType(),
66 FolderSearchPolicy::MATCH_ONLY) {}
67
FileEnumerator(const FilePath & root_path,bool recursive,int file_type,const FilePath::StringType & pattern)68 FileEnumerator::FileEnumerator(const FilePath& root_path,
69 bool recursive,
70 int file_type,
71 const FilePath::StringType& pattern)
72 : FileEnumerator(root_path,
73 recursive,
74 file_type,
75 pattern,
76 FolderSearchPolicy::MATCH_ONLY) {}
77
FileEnumerator(const FilePath & root_path,bool recursive,int file_type,const FilePath::StringType & pattern,FolderSearchPolicy folder_search_policy)78 FileEnumerator::FileEnumerator(const FilePath& root_path,
79 bool recursive,
80 int file_type,
81 const FilePath::StringType& pattern,
82 FolderSearchPolicy folder_search_policy)
83 : current_directory_entry_(0),
84 root_path_(root_path),
85 recursive_(recursive),
86 file_type_(file_type),
87 pattern_(pattern),
88 folder_search_policy_(folder_search_policy) {
89 // INCLUDE_DOT_DOT must not be specified if recursive.
90 DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
91
92 pending_paths_.push(root_path);
93 }
94
95 FileEnumerator::~FileEnumerator() = default;
96
Next()97 FilePath FileEnumerator::Next() {
98 AssertBlockingAllowed();
99
100 ++current_directory_entry_;
101
102 // While we've exhausted the entries in the current directory, do the next
103 while (current_directory_entry_ >= directory_entries_.size()) {
104 if (pending_paths_.empty())
105 return FilePath();
106
107 root_path_ = pending_paths_.top();
108 root_path_ = root_path_.StripTrailingSeparators();
109 pending_paths_.pop();
110
111 DIR* dir = opendir(root_path_.value().c_str());
112 if (!dir)
113 continue;
114
115 directory_entries_.clear();
116
117 #if defined(OS_FUCHSIA)
118 // Fuchsia does not support .. on the file system server side, see
119 // https://fuchsia.googlesource.com/docs/+/master/dotdot.md and
120 // https://crbug.com/735540. However, for UI purposes, having the parent
121 // directory show up in directory listings makes sense, so we add it here to
122 // match the expectation on other operating systems. In cases where this
123 // is useful it should be resolvable locally.
124 FileInfo dotdot;
125 dotdot.stat_.st_mode = S_IFDIR;
126 dotdot.filename_ = FilePath("..");
127 if (!ShouldSkip(dotdot.filename_)) {
128 directory_entries_.push_back(std::move(dotdot));
129 }
130 #endif // OS_FUCHSIA
131
132 current_directory_entry_ = 0;
133 struct dirent* dent;
134 while ((dent = readdir(dir))) {
135 FileInfo info;
136 info.filename_ = FilePath(dent->d_name);
137
138 if (ShouldSkip(info.filename_))
139 continue;
140
141 const bool is_pattern_matched = IsPatternMatched(info.filename_);
142
143 // MATCH_ONLY policy enumerates files and directories which matching
144 // pattern only. So we can early skip further checks.
145 if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY &&
146 !is_pattern_matched)
147 continue;
148
149 // Do not call OS stat/lstat if there is no sense to do it. If pattern is
150 // not matched (file will not appear in results) and search is not
151 // recursive (possible directory will not be added to pending paths) -
152 // there is no sense to obtain item below.
153 if (!recursive_ && !is_pattern_matched)
154 continue;
155
156 const FilePath full_path = root_path_.Append(info.filename_);
157 GetStat(full_path, file_type_ & SHOW_SYM_LINKS, &info.stat_);
158
159 const bool is_dir = info.IsDirectory();
160
161 if (recursive_ && is_dir)
162 pending_paths_.push(full_path);
163
164 if (is_pattern_matched && IsTypeMatched(is_dir))
165 directory_entries_.push_back(std::move(info));
166 }
167 closedir(dir);
168
169 // MATCH_ONLY policy enumerates files in matched subfolders by "*" pattern.
170 // ALL policy enumerates files in all subfolders by origin pattern.
171 if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY)
172 pattern_.clear();
173 }
174
175 return root_path_.Append(
176 directory_entries_[current_directory_entry_].filename_);
177 }
178
GetInfo() const179 FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
180 return directory_entries_[current_directory_entry_];
181 }
182
IsPatternMatched(const FilePath & path) const183 bool FileEnumerator::IsPatternMatched(const FilePath& path) const {
184 return pattern_.empty() ||
185 !fnmatch(pattern_.c_str(), path.value().c_str(), FNM_NOESCAPE);
186 }
187
188 } // namespace base
189