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
11#include "base/logging.h"
12#include "base/threading/thread_restrictions.h"
13
14namespace base {
15
16// FileEnumerator::FileInfo ----------------------------------------------------
17
18FileEnumerator::FileInfo::FileInfo() {
19  memset(&stat_, 0, sizeof(stat_));
20}
21
22bool FileEnumerator::FileInfo::IsDirectory() const {
23  return S_ISDIR(stat_.st_mode);
24}
25
26FilePath FileEnumerator::FileInfo::GetName() const {
27  return filename_;
28}
29
30int64 FileEnumerator::FileInfo::GetSize() const {
31  return stat_.st_size;
32}
33
34base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
35  return base::Time::FromTimeT(stat_.st_mtime);
36}
37
38// FileEnumerator --------------------------------------------------------------
39
40FileEnumerator::FileEnumerator(const FilePath& root_path,
41                               bool recursive,
42                               int file_type)
43    : current_directory_entry_(0),
44      root_path_(root_path),
45      recursive_(recursive),
46      file_type_(file_type) {
47  // INCLUDE_DOT_DOT must not be specified if recursive.
48  DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
49  pending_paths_.push(root_path);
50}
51
52FileEnumerator::FileEnumerator(const FilePath& root_path,
53                               bool recursive,
54                               int file_type,
55                               const FilePath::StringType& pattern)
56    : current_directory_entry_(0),
57      root_path_(root_path),
58      recursive_(recursive),
59      file_type_(file_type),
60      pattern_(root_path.Append(pattern).value()) {
61  // INCLUDE_DOT_DOT must not be specified if recursive.
62  DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
63  // The Windows version of this code appends the pattern to the root_path,
64  // potentially only matching against items in the top-most directory.
65  // Do the same here.
66  if (pattern.empty())
67    pattern_ = FilePath::StringType();
68  pending_paths_.push(root_path);
69}
70
71FileEnumerator::~FileEnumerator() {
72}
73
74FilePath FileEnumerator::Next() {
75  ++current_directory_entry_;
76
77  // While we've exhausted the entries in the current directory, do the next
78  while (current_directory_entry_ >= directory_entries_.size()) {
79    if (pending_paths_.empty())
80      return FilePath();
81
82    root_path_ = pending_paths_.top();
83    root_path_ = root_path_.StripTrailingSeparators();
84    pending_paths_.pop();
85
86    std::vector<FileInfo> entries;
87    if (!ReadDirectory(&entries, root_path_, file_type_ & SHOW_SYM_LINKS))
88      continue;
89
90    directory_entries_.clear();
91    current_directory_entry_ = 0;
92    for (std::vector<FileInfo>::const_iterator i = entries.begin();
93         i != entries.end(); ++i) {
94      FilePath full_path = root_path_.Append(i->filename_);
95      if (ShouldSkip(full_path))
96        continue;
97
98      if (pattern_.size() &&
99          fnmatch(pattern_.c_str(), full_path.value().c_str(), FNM_NOESCAPE))
100        continue;
101
102      if (recursive_ && S_ISDIR(i->stat_.st_mode))
103        pending_paths_.push(full_path);
104
105      if ((S_ISDIR(i->stat_.st_mode) && (file_type_ & DIRECTORIES)) ||
106          (!S_ISDIR(i->stat_.st_mode) && (file_type_ & FILES)))
107        directory_entries_.push_back(*i);
108    }
109  }
110
111  return root_path_.Append(
112      directory_entries_[current_directory_entry_].filename_);
113}
114
115FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
116  return directory_entries_[current_directory_entry_];
117}
118
119bool FileEnumerator::ReadDirectory(std::vector<FileInfo>* entries,
120                                   const FilePath& source, bool show_links) {
121  base::ThreadRestrictions::AssertIOAllowed();
122  DIR* dir = opendir(source.value().c_str());
123  if (!dir)
124    return false;
125
126#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_BSD) && \
127    !defined(OS_SOLARIS) && !defined(OS_ANDROID)
128  #error Port warning: depending on the definition of struct dirent, \
129         additional space for pathname may be needed
130#endif
131
132  struct dirent dent_buf;
133  struct dirent* dent;
134  while (readdir_r(dir, &dent_buf, &dent) == 0 && dent) {
135    FileInfo info;
136    info.filename_ = FilePath(dent->d_name);
137
138    FilePath full_name = source.Append(dent->d_name);
139    int ret;
140    if (show_links)
141      ret = lstat(full_name.value().c_str(), &info.stat_);
142    else
143      ret = stat(full_name.value().c_str(), &info.stat_);
144    if (ret < 0) {
145      // Print the stat() error message unless it was ENOENT and we're
146      // following symlinks.
147      if (!(errno == ENOENT && !show_links)) {
148        DPLOG(ERROR) << "Couldn't stat "
149                     << source.Append(dent->d_name).value();
150      }
151      memset(&info.stat_, 0, sizeof(info.stat_));
152    }
153    entries->push_back(info);
154  }
155
156  closedir(dir);
157  return true;
158}
159
160}  // namespace base
161