| 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 |