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
26 namespace perfetto {
27 namespace {
28
JoinPaths(const std::string & one,const std::string & other)29 std::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
FileScanner(std::vector<std::string> root_directories,Delegate * delegate,uint32_t scan_interval_ms,uint32_t scan_steps)41 FileScanner::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
FileScanner(std::vector<std::string> root_directories,Delegate * delegate)51 FileScanner::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
Scan()58 void FileScanner::Scan() {
59 while (!Done())
60 Step();
61 delegate_->OnInodeScanDone();
62 }
Scan(base::TaskRunner * task_runner)63 void 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
NextDirectory()78 void 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
Step()105 void 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
Steps(uint32_t n)145 void FileScanner::Steps(uint32_t n) {
146 for (uint32_t i = 0; i < n && !Done(); ++i)
147 Step();
148 }
149
Done()150 bool FileScanner::Done() {
151 return !current_dir_handle_ && queue_.empty();
152 }
153
154 FileScanner::Delegate::~Delegate() = default;
155
156 } // namespace perfetto
157