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