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/inode_file_data_source.h"
18
19#include <dirent.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22#include <unistd.h>
23#include <queue>
24#include <unordered_map>
25
26#include "perfetto/base/logging.h"
27#include "perfetto/base/scoped_file.h"
28#include "perfetto/tracing/core/trace_packet.h"
29#include "perfetto/tracing/core/trace_writer.h"
30
31#include "perfetto/trace/trace_packet.pbzero.h"
32#include "src/traced/probes/filesystem/file_scanner.h"
33
34namespace perfetto {
35namespace {
36constexpr uint32_t kScanIntervalMs = 10000;  // 10s
37constexpr uint32_t kScanDelayMs = 10000;     // 10s
38constexpr uint32_t kScanBatchSize = 15000;
39
40uint32_t OrDefault(uint32_t value, uint32_t def) {
41  return value ? value : def;
42}
43
44std::string DbgFmt(const std::vector<std::string>& values) {
45  if (values.empty())
46    return "";
47
48  std::string result;
49  for (auto it = values.cbegin(); it != values.cend() - 1; ++it)
50    result += *it + ",";
51
52  result += values.back();
53  return result;
54}
55
56std::map<std::string, std::vector<std::string>> BuildMountpointMapping(
57    const DataSourceConfig& source_config) {
58  std::map<std::string, std::vector<std::string>> m;
59  for (const auto& map_entry :
60       source_config.inode_file_config().mount_point_mapping())
61    m.emplace(map_entry.mountpoint(), map_entry.scan_roots());
62
63  return m;
64}
65
66class StaticMapDelegate : public FileScanner::Delegate {
67 public:
68  StaticMapDelegate(
69      std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* map)
70      : map_(map) {}
71  ~StaticMapDelegate() {}
72
73 private:
74  bool OnInodeFound(BlockDeviceID block_device_id,
75                    Inode inode_number,
76                    const std::string& path,
77                    protos::pbzero::InodeFileMap_Entry_Type type) {
78    std::unordered_map<Inode, InodeMapValue>& inode_map =
79        (*map_)[block_device_id];
80    inode_map[inode_number].SetType(type);
81    inode_map[inode_number].AddPath(path);
82    return true;
83  }
84  void OnInodeScanDone() {}
85  std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* map_;
86};
87
88}  // namespace
89
90void CreateStaticDeviceToInodeMap(
91    const std::string& root_directory,
92    std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
93        static_file_map) {
94  StaticMapDelegate delegate(static_file_map);
95  FileScanner scanner({root_directory}, &delegate);
96  scanner.Scan();
97}
98
99void InodeFileDataSource::FillInodeEntry(InodeFileMap* destination,
100                                         Inode inode_number,
101                                         const InodeMapValue& inode_map_value) {
102  auto* entry = destination->add_entries();
103  entry->set_inode_number(inode_number);
104  entry->set_type(inode_map_value.type());
105  for (const auto& path : inode_map_value.paths())
106    entry->add_paths(path.c_str());
107}
108
109InodeFileDataSource::InodeFileDataSource(
110    DataSourceConfig source_config,
111    base::TaskRunner* task_runner,
112    TracingSessionID id,
113    std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
114        static_file_map,
115    LRUInodeCache* cache,
116    std::unique_ptr<TraceWriter> writer)
117    : source_config_(std::move(source_config)),
118      scan_mount_points_(
119          source_config_.inode_file_config().scan_mount_points().cbegin(),
120          source_config_.inode_file_config().scan_mount_points().cend()),
121      mount_point_mapping_(BuildMountpointMapping(source_config_)),
122      task_runner_(task_runner),
123      session_id_(id),
124      static_file_map_(static_file_map),
125      cache_(cache),
126      writer_(std::move(writer)),
127      weak_factory_(this) {}
128
129InodeFileDataSource::~InodeFileDataSource() = default;
130
131void InodeFileDataSource::AddInodesFromStaticMap(
132    BlockDeviceID block_device_id,
133    std::set<Inode>* inode_numbers) {
134  // Check if block device id exists in static file map
135  auto static_map_entry = static_file_map_->find(block_device_id);
136  if (static_map_entry == static_file_map_->end())
137    return;
138
139  uint64_t system_found_count = 0;
140  for (auto it = inode_numbers->begin(); it != inode_numbers->end();) {
141    Inode inode_number = *it;
142    // Check if inode number exists in static file map for given block device id
143    auto inode_it = static_map_entry->second.find(inode_number);
144    if (inode_it == static_map_entry->second.end()) {
145      ++it;
146      continue;
147    }
148    system_found_count++;
149    it = inode_numbers->erase(it);
150    FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
151                   inode_it->second);
152  }
153  PERFETTO_DLOG("%" PRIu64 " inodes found in static file map",
154                system_found_count);
155}
156
157void InodeFileDataSource::AddInodesFromLRUCache(
158    BlockDeviceID block_device_id,
159    std::set<Inode>* inode_numbers) {
160  uint64_t cache_found_count = 0;
161  for (auto it = inode_numbers->begin(); it != inode_numbers->end();) {
162    Inode inode_number = *it;
163    auto value = cache_->Get(std::make_pair(block_device_id, inode_number));
164    if (value == nullptr) {
165      ++it;
166      continue;
167    }
168    cache_found_count++;
169    it = inode_numbers->erase(it);
170    FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
171                   *value);
172  }
173  if (cache_found_count > 0)
174    PERFETTO_DLOG("%" PRIu64 " inodes found in cache", cache_found_count);
175}
176
177void InodeFileDataSource::Flush() {
178  ResetTracePacket();
179  writer_->Flush();
180}
181
182void InodeFileDataSource::OnInodes(
183    const std::vector<std::pair<Inode, BlockDeviceID>>& inodes) {
184  if (mount_points_.empty()) {
185    mount_points_ = ParseMounts();
186  }
187  // Group inodes from FtraceMetadata by block device
188  std::map<BlockDeviceID, std::set<Inode>> inode_file_maps;
189  for (const auto& inodes_pair : inodes) {
190    Inode inode_number = inodes_pair.first;
191    BlockDeviceID block_device_id = inodes_pair.second;
192    inode_file_maps[block_device_id].emplace(inode_number);
193  }
194  if (inode_file_maps.size() > 1)
195    PERFETTO_DLOG("Saw %zu block devices.", inode_file_maps.size());
196
197  // Write a TracePacket with an InodeFileMap proto for each block device id
198  for (auto& inode_file_map_data : inode_file_maps) {
199    BlockDeviceID block_device_id = inode_file_map_data.first;
200    std::set<Inode>& inode_numbers = inode_file_map_data.second;
201    PERFETTO_DLOG("Saw %zu unique inode numbers.", inode_numbers.size());
202
203    // Add entries to InodeFileMap as inodes are found and resolved to their
204    // paths/type
205    AddInodesFromStaticMap(block_device_id, &inode_numbers);
206    AddInodesFromLRUCache(block_device_id, &inode_numbers);
207
208    if (source_config_.inode_file_config().do_not_scan())
209      inode_numbers.clear();
210
211    // If we defined mount points we want to scan in the config,
212    // skip inodes on other mount points.
213    if (!scan_mount_points_.empty()) {
214      bool process = true;
215      auto range = mount_points_.equal_range(block_device_id);
216      for (auto it = range.first; it != range.second; ++it) {
217        if (scan_mount_points_.count(it->second) == 0) {
218          process = false;
219          break;
220        }
221      }
222      if (!process)
223        continue;
224    }
225
226    if (!inode_numbers.empty()) {
227      // Try to piggy back the current scan.
228      auto it = missing_inodes_.find(block_device_id);
229      if (it != missing_inodes_.end()) {
230        it->second.insert(inode_numbers.cbegin(), inode_numbers.cend());
231      }
232      next_missing_inodes_[block_device_id].insert(inode_numbers.cbegin(),
233                                                   inode_numbers.cend());
234      if (!scan_running_) {
235        scan_running_ = true;
236        auto weak_this = GetWeakPtr();
237        task_runner_->PostDelayedTask(
238            [weak_this] {
239              if (!weak_this) {
240                PERFETTO_DLOG("Giving up filesystem scan.");
241                return;
242              }
243              weak_this.get()->FindMissingInodes();
244            },
245            GetScanDelayMs());
246      }
247    }
248  }
249}
250
251InodeFileMap* InodeFileDataSource::AddToCurrentTracePacket(
252    BlockDeviceID block_device_id) {
253  seen_block_devices_.emplace(block_device_id);
254  if (!has_current_trace_packet_ ||
255      current_block_device_id_ != block_device_id) {
256    if (has_current_trace_packet_)
257      current_trace_packet_->Finalize();
258    current_trace_packet_ = writer_->NewTracePacket();
259    current_file_map_ = current_trace_packet_->set_inode_file_map();
260    has_current_trace_packet_ = true;
261
262    // Add block device id to InodeFileMap
263    current_file_map_->set_block_device_id(
264        static_cast<uint64_t>(block_device_id));
265    // Add mount points to InodeFileMap
266    auto range = mount_points_.equal_range(block_device_id);
267    for (std::multimap<BlockDeviceID, std::string>::iterator it = range.first;
268         it != range.second; ++it)
269      current_file_map_->add_mount_points(it->second.c_str());
270  }
271  return current_file_map_;
272}
273
274void InodeFileDataSource::RemoveFromNextMissingInodes(
275    BlockDeviceID block_device_id,
276    Inode inode_number) {
277  auto it = next_missing_inodes_.find(block_device_id);
278  if (it == next_missing_inodes_.end())
279    return;
280  it->second.erase(inode_number);
281}
282
283bool InodeFileDataSource::OnInodeFound(
284    BlockDeviceID block_device_id,
285    Inode inode_number,
286    const std::string& path,
287    protos::pbzero::InodeFileMap_Entry_Type type) {
288  auto it = missing_inodes_.find(block_device_id);
289  if (it == missing_inodes_.end())
290    return true;
291
292  size_t n = it->second.erase(inode_number);
293  if (n == 0)
294    return true;
295
296  if (it->second.empty())
297    missing_inodes_.erase(it);
298
299  RemoveFromNextMissingInodes(block_device_id, inode_number);
300
301  std::pair<BlockDeviceID, Inode> key{block_device_id, inode_number};
302  auto cur_val = cache_->Get(key);
303  if (cur_val) {
304    cur_val->AddPath(path);
305    FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
306                   *cur_val);
307  } else {
308    InodeMapValue new_val(InodeMapValue(type, {path}));
309    cache_->Insert(key, new_val);
310    FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
311                   new_val);
312  }
313  PERFETTO_DLOG("Filled %s", path.c_str());
314  return !missing_inodes_.empty();
315}
316
317void InodeFileDataSource::ResetTracePacket() {
318  current_block_device_id_ = 0;
319  current_file_map_ = nullptr;
320  if (has_current_trace_packet_)
321    current_trace_packet_->Finalize();
322  has_current_trace_packet_ = false;
323}
324
325void InodeFileDataSource::OnInodeScanDone() {
326  // Finalize the accumulated trace packets.
327  ResetTracePacket();
328  file_scanner_.reset();
329  if (!missing_inodes_.empty()) {
330    // At least write mount point mapping for inodes that are not found.
331    for (const auto& p : missing_inodes_) {
332      if (seen_block_devices_.count(p.first) == 0)
333        AddToCurrentTracePacket(p.first);
334    }
335  }
336
337  if (next_missing_inodes_.empty()) {
338    scan_running_ = false;
339  } else {
340    auto weak_this = GetWeakPtr();
341    PERFETTO_DLOG("Starting another filesystem scan.");
342    task_runner_->PostDelayedTask(
343        [weak_this] {
344          if (!weak_this) {
345            PERFETTO_DLOG("Giving up filesystem scan.");
346            return;
347          }
348          weak_this->FindMissingInodes();
349        },
350        GetScanDelayMs());
351  }
352}
353
354void InodeFileDataSource::AddRootsForBlockDevice(
355    BlockDeviceID block_device_id,
356    std::vector<std::string>* roots) {
357  auto range = mount_points_.equal_range(block_device_id);
358  for (auto it = range.first; it != range.second; ++it) {
359    PERFETTO_DLOG("Trying to replace %s", it->second.c_str());
360    auto replace_it = mount_point_mapping_.find(it->second);
361    if (replace_it != mount_point_mapping_.end()) {
362      roots->insert(roots->end(), replace_it->second.cbegin(),
363                    replace_it->second.cend());
364      return;
365    }
366  }
367
368  for (auto it = range.first; it != range.second; ++it)
369    roots->emplace_back(it->second);
370}
371
372void InodeFileDataSource::FindMissingInodes() {
373  missing_inodes_ = std::move(next_missing_inodes_);
374  std::vector<std::string> roots;
375  for (auto& p : missing_inodes_)
376    AddRootsForBlockDevice(p.first, &roots);
377
378  PERFETTO_DCHECK(file_scanner_.get() == nullptr);
379  auto weak_this = GetWeakPtr();
380  PERFETTO_DLOG("Starting scan of %s", DbgFmt(roots).c_str());
381  file_scanner_ = std::unique_ptr<FileScanner>(new FileScanner(
382      std::move(roots), this, GetScanIntervalMs(), GetScanBatchSize()));
383
384  file_scanner_->Scan(task_runner_);
385}
386
387uint32_t InodeFileDataSource::GetScanIntervalMs() const {
388  return OrDefault(source_config_.inode_file_config().scan_interval_ms(),
389                   kScanIntervalMs);
390}
391
392uint32_t InodeFileDataSource::GetScanDelayMs() const {
393  return OrDefault(source_config_.inode_file_config().scan_delay_ms(),
394                   kScanDelayMs);
395}
396
397uint32_t InodeFileDataSource::GetScanBatchSize() const {
398  return OrDefault(source_config_.inode_file_config().scan_batch_size(),
399                   kScanBatchSize);
400}
401
402base::WeakPtr<InodeFileDataSource> InodeFileDataSource::GetWeakPtr() const {
403  return weak_factory_.GetWeakPtr();
404}
405
406}  // namespace perfetto
407