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 "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h"
25 #include "src/traced/probes/filesystem/inode_file_data_source.h"
26
27 namespace perfetto {
28 namespace {
29
JoinPaths(const std::string & one,const std::string & other)30 std::string JoinPaths(const std::string& one, const std::string& other) {
31 std::string result;
32 result.reserve(one.size() + other.size() + 1);
33 result += one;
34 if (!result.empty() && result.back() != '/')
35 result += '/';
36 result += other;
37 return result;
38 }
39
40 } // namespace
41
FileScanner(std::vector<std::string> root_directories,Delegate * delegate,uint32_t scan_interval_ms,uint32_t scan_steps)42 FileScanner::FileScanner(std::vector<std::string> root_directories,
43 Delegate* delegate,
44 uint32_t scan_interval_ms,
45 uint32_t scan_steps)
46 : delegate_(delegate),
47 scan_interval_ms_(scan_interval_ms),
48 scan_steps_(scan_steps),
49 queue_(std::move(root_directories)),
50 weak_factory_(this) {}
51
FileScanner(std::vector<std::string> root_directories,Delegate * delegate)52 FileScanner::FileScanner(std::vector<std::string> root_directories,
53 Delegate* delegate)
54 : FileScanner(std::move(root_directories),
55 delegate,
56 0 /* scan_interval_ms */,
57 0 /* scan_steps */) {}
58
Scan()59 void FileScanner::Scan() {
60 while (!Done())
61 Step();
62 delegate_->OnInodeScanDone();
63 }
Scan(base::TaskRunner * task_runner)64 void FileScanner::Scan(base::TaskRunner* task_runner) {
65 PERFETTO_DCHECK(scan_interval_ms_ && scan_steps_);
66 Steps(scan_steps_);
67 if (Done())
68 return delegate_->OnInodeScanDone();
69 auto weak_this = weak_factory_.GetWeakPtr();
70 task_runner->PostDelayedTask(
71 [weak_this, task_runner] {
72 if (!weak_this)
73 return;
74 weak_this->Scan(task_runner);
75 },
76 scan_interval_ms_);
77 }
78
NextDirectory()79 void FileScanner::NextDirectory() {
80 std::string directory = std::move(queue_.back());
81 queue_.pop_back();
82 current_dir_handle_.reset(opendir(directory.c_str()));
83 if (!current_dir_handle_) {
84 PERFETTO_DPLOG("opendir %s", directory.c_str());
85 current_directory_.clear();
86 return;
87 }
88 current_directory_ = std::move(directory);
89
90 struct stat buf;
91 if (fstat(dirfd(current_dir_handle_.get()), &buf) != 0) {
92 PERFETTO_DPLOG("fstat %s", current_directory_.c_str());
93 current_dir_handle_.reset();
94 current_directory_.clear();
95 return;
96 }
97
98 if (S_ISLNK(buf.st_mode)) {
99 current_dir_handle_.reset();
100 current_directory_.clear();
101 return;
102 }
103 current_block_device_id_ = buf.st_dev;
104 }
105
Step()106 void FileScanner::Step() {
107 if (!current_dir_handle_) {
108 if (queue_.empty())
109 return;
110 NextDirectory();
111 }
112
113 if (!current_dir_handle_)
114 return;
115
116 struct dirent* entry = readdir(current_dir_handle_.get());
117 if (entry == nullptr) {
118 current_dir_handle_.reset();
119 return;
120 }
121
122 std::string filename = entry->d_name;
123 if (filename == "." || filename == "..")
124 return;
125
126 std::string filepath = JoinPaths(current_directory_, filename);
127
128 protos::pbzero::InodeFileMap_Entry_Type type =
129 protos::pbzero::InodeFileMap_Entry_Type_UNKNOWN;
130 // Readdir and stat not guaranteed to have directory info for all systems
131 if (entry->d_type == DT_DIR) {
132 // Continue iterating through files if current entry is a directory
133 queue_.emplace_back(filepath);
134 type = protos::pbzero::InodeFileMap_Entry_Type_DIRECTORY;
135 } else if (entry->d_type == DT_REG) {
136 type = protos::pbzero::InodeFileMap_Entry_Type_FILE;
137 }
138
139 if (!delegate_->OnInodeFound(current_block_device_id_, entry->d_ino, filepath,
140 type)) {
141 queue_.clear();
142 current_dir_handle_.reset();
143 }
144 }
145
Steps(uint32_t n)146 void FileScanner::Steps(uint32_t n) {
147 for (uint32_t i = 0; i < n && !Done(); ++i)
148 Step();
149 }
150
Done()151 bool FileScanner::Done() {
152 return !current_dir_handle_ && queue_.empty();
153 }
154
155 FileScanner::Delegate::~Delegate() = default;
156
157 } // namespace perfetto
158