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