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