1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "src/traced/probes/filesystem/file_scanner.h"
18
19#include <dirent.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22#include <unistd.h>
23
24#include "src/traced/probes/filesystem/inode_file_data_source.h"
25
26namespace perfetto {
27namespace {
28
29std::string JoinPaths(const std::string& one, const std::string& other) {
30  std::string result;
31  result.reserve(one.size() + other.size() + 1);
32  result += one;
33  if (!result.empty() && result.back() != '/')
34    result += '/';
35  result += other;
36  return result;
37}
38
39}  // namespace
40
41FileScanner::FileScanner(std::vector<std::string> root_directories,
42                         Delegate* delegate,
43                         uint32_t scan_interval_ms,
44                         uint32_t scan_steps)
45    : delegate_(delegate),
46      scan_interval_ms_(scan_interval_ms),
47      scan_steps_(scan_steps),
48      queue_(std::move(root_directories)),
49      weak_factory_(this) {}
50
51FileScanner::FileScanner(std::vector<std::string> root_directories,
52                         Delegate* delegate)
53    : FileScanner(std::move(root_directories),
54                  delegate,
55                  0 /* scan_interval_ms */,
56                  0 /* scan_steps */) {}
57
58void FileScanner::Scan() {
59  while (!Done())
60    Step();
61  delegate_->OnInodeScanDone();
62}
63void FileScanner::Scan(base::TaskRunner* task_runner) {
64  PERFETTO_DCHECK(scan_interval_ms_ && scan_steps_);
65  Steps(scan_steps_);
66  if (Done())
67    return delegate_->OnInodeScanDone();
68  auto weak_this = weak_factory_.GetWeakPtr();
69  task_runner->PostDelayedTask(
70      [weak_this, task_runner] {
71        if (!weak_this)
72          return;
73        weak_this->Scan(task_runner);
74      },
75      scan_interval_ms_);
76}
77
78void FileScanner::NextDirectory() {
79  std::string directory = std::move(queue_.back());
80  queue_.pop_back();
81  current_dir_handle_.reset(opendir(directory.c_str()));
82  if (!current_dir_handle_) {
83    PERFETTO_DPLOG("opendir %s", directory.c_str());
84    current_directory_.clear();
85    return;
86  }
87  current_directory_ = std::move(directory);
88
89  struct stat buf;
90  if (fstat(dirfd(current_dir_handle_.get()), &buf) != 0) {
91    PERFETTO_DPLOG("fstat %s", current_directory_.c_str());
92    current_dir_handle_.reset();
93    current_directory_.clear();
94    return;
95  }
96
97  if (S_ISLNK(buf.st_mode)) {
98    current_dir_handle_.reset();
99    current_directory_.clear();
100    return;
101  }
102  current_block_device_id_ = buf.st_dev;
103}
104
105void FileScanner::Step() {
106  if (!current_dir_handle_) {
107    if (queue_.empty())
108      return;
109    NextDirectory();
110  }
111
112  if (!current_dir_handle_)
113    return;
114
115  struct dirent* entry = readdir(current_dir_handle_.get());
116  if (entry == nullptr) {
117    current_dir_handle_.reset();
118    return;
119  }
120
121  std::string filename = entry->d_name;
122  if (filename == "." || filename == "..")
123    return;
124
125  std::string filepath = JoinPaths(current_directory_, filename);
126
127  protos::pbzero::InodeFileMap_Entry_Type type =
128      protos::pbzero::InodeFileMap_Entry_Type_UNKNOWN;
129  // Readdir and stat not guaranteed to have directory info for all systems
130  if (entry->d_type == DT_DIR) {
131    // Continue iterating through files if current entry is a directory
132    queue_.emplace_back(filepath);
133    type = protos::pbzero::InodeFileMap_Entry_Type_DIRECTORY;
134  } else if (entry->d_type == DT_REG) {
135    type = protos::pbzero::InodeFileMap_Entry_Type_FILE;
136  }
137
138  if (!delegate_->OnInodeFound(current_block_device_id_, entry->d_ino, filepath,
139                               type)) {
140    queue_.clear();
141    current_dir_handle_.reset();
142  }
143}
144
145void FileScanner::Steps(uint32_t n) {
146  for (uint32_t i = 0; i < n && !Done(); ++i)
147    Step();
148}
149
150bool FileScanner::Done() {
151  return !current_dir_handle_ && queue_.empty();
152}
153
154FileScanner::Delegate::~Delegate() = default;
155
156}  // namespace perfetto
157