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 "util/build_config.h"
15
16namespace base {
17namespace {
18
19void GetStat(const FilePath& path, bool show_links, struct stat* st) {
20  DCHECK(st);
21  const int res = show_links ? lstat(path.value().c_str(), st)
22                             : stat(path.value().c_str(), st);
23  if (res < 0) {
24    // Print the stat() error message unless it was ENOENT and we're following
25    // symlinks.
26    if (!(errno == ENOENT && !show_links))
27      DPLOG(ERROR) << "Couldn't stat" << path.value();
28    memset(st, 0, sizeof(*st));
29  }
30}
31
32}  // namespace
33
34// FileEnumerator::FileInfo ----------------------------------------------------
35
36FileEnumerator::FileInfo::FileInfo() {
37  memset(&stat_, 0, sizeof(stat_));
38}
39
40bool FileEnumerator::FileInfo::IsDirectory() const {
41  return S_ISDIR(stat_.st_mode);
42}
43
44FilePath FileEnumerator::FileInfo::GetName() const {
45  return filename_;
46}
47
48int64_t FileEnumerator::FileInfo::GetSize() const {
49  return stat_.st_size;
50}
51
52Ticks FileEnumerator::FileInfo::GetLastModifiedTime() const {
53  return stat_.st_mtime;
54}
55
56// FileEnumerator --------------------------------------------------------------
57
58FileEnumerator::FileEnumerator(const FilePath& root_path,
59                               bool recursive,
60                               int file_type)
61    : FileEnumerator(root_path,
62                     recursive,
63                     file_type,
64                     FilePath::StringType(),
65                     FolderSearchPolicy::MATCH_ONLY) {}
66
67FileEnumerator::FileEnumerator(const FilePath& root_path,
68                               bool recursive,
69                               int file_type,
70                               const FilePath::StringType& pattern)
71    : FileEnumerator(root_path,
72                     recursive,
73                     file_type,
74                     pattern,
75                     FolderSearchPolicy::MATCH_ONLY) {}
76
77FileEnumerator::FileEnumerator(const FilePath& root_path,
78                               bool recursive,
79                               int file_type,
80                               const FilePath::StringType& pattern,
81                               FolderSearchPolicy folder_search_policy)
82    : current_directory_entry_(0),
83      root_path_(root_path),
84      recursive_(recursive),
85      file_type_(file_type),
86      pattern_(pattern),
87      folder_search_policy_(folder_search_policy) {
88  // INCLUDE_DOT_DOT must not be specified if recursive.
89  DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
90
91  pending_paths_.push(root_path);
92}
93
94FileEnumerator::~FileEnumerator() = default;
95
96FilePath FileEnumerator::Next() {
97  ++current_directory_entry_;
98
99  // While we've exhausted the entries in the current directory, do the next
100  while (current_directory_entry_ >= directory_entries_.size()) {
101    if (pending_paths_.empty())
102      return FilePath();
103
104    root_path_ = pending_paths_.top();
105    root_path_ = root_path_.StripTrailingSeparators();
106    pending_paths_.pop();
107
108    DIR* dir = opendir(root_path_.value().c_str());
109    if (!dir)
110      continue;
111
112    directory_entries_.clear();
113
114    current_directory_entry_ = 0;
115    struct dirent* dent;
116    while ((dent = readdir(dir))) {
117      FileInfo info;
118      info.filename_ = FilePath(dent->d_name);
119
120      if (ShouldSkip(info.filename_))
121        continue;
122
123      const bool is_pattern_matched = IsPatternMatched(info.filename_);
124
125      // MATCH_ONLY policy enumerates files and directories which matching
126      // pattern only. So we can early skip further checks.
127      if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY &&
128          !is_pattern_matched)
129        continue;
130
131      // Do not call OS stat/lstat if there is no sense to do it. If pattern is
132      // not matched (file will not appear in results) and search is not
133      // recursive (possible directory will not be added to pending paths) -
134      // there is no sense to obtain item below.
135      if (!recursive_ && !is_pattern_matched)
136        continue;
137
138      const FilePath full_path = root_path_.Append(info.filename_);
139      GetStat(full_path, file_type_ & SHOW_SYM_LINKS, &info.stat_);
140
141      const bool is_dir = info.IsDirectory();
142
143      if (recursive_ && is_dir)
144        pending_paths_.push(full_path);
145
146      if (is_pattern_matched && IsTypeMatched(is_dir))
147        directory_entries_.push_back(std::move(info));
148    }
149    closedir(dir);
150
151    // MATCH_ONLY policy enumerates files in matched subfolders by "*" pattern.
152    // ALL policy enumerates files in all subfolders by origin pattern.
153    if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY)
154      pattern_.clear();
155  }
156
157  return root_path_.Append(
158      directory_entries_[current_directory_entry_].filename_);
159}
160
161FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
162  return directory_entries_[current_directory_entry_];
163}
164
165bool FileEnumerator::IsPatternMatched(const FilePath& path) const {
166  return pattern_.empty() ||
167         !fnmatch(pattern_.c_str(), path.value().c_str(), FNM_NOESCAPE);
168}
169
170}  // namespace base
171