Add stateful filesystem scanner.

This will be used to interrupt long scans.

Bug: 74584014
Change-Id: I80206a3dd6cce265fc957532c855f07f08120a7d
diff --git a/Android.bp b/Android.bp
index b9c1996..953276c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -63,6 +63,7 @@
     "src/protozero/proto_utils.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
+    "src/traced/probes/filesystem/file_scanner.cc",
     "src/traced/probes/filesystem/fs_mount.cc",
     "src/traced/probes/filesystem/inode_file_data_source.cc",
     "src/traced/probes/filesystem/lru_inode_cache.cc",
@@ -301,6 +302,7 @@
     "src/protozero/proto_utils.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
+    "src/traced/probes/filesystem/file_scanner.cc",
     "src/traced/probes/filesystem/fs_mount.cc",
     "src/traced/probes/filesystem/inode_file_data_source.cc",
     "src/traced/probes/filesystem/lru_inode_cache.cc",
@@ -3340,6 +3342,7 @@
     "src/protozero/scattered_stream_writer_unittest.cc",
     "src/protozero/test/fake_scattered_buffer.cc",
     "src/protozero/test/protozero_conformance_unittest.cc",
+    "src/traced/probes/filesystem/file_scanner.cc",
     "src/traced/probes/filesystem/fs_mount.cc",
     "src/traced/probes/filesystem/fs_mount_unittest.cc",
     "src/traced/probes/filesystem/inode_file_data_source.cc",
diff --git a/src/traced/probes/filesystem/BUILD.gn b/src/traced/probes/filesystem/BUILD.gn
index ce481d6..6beb973 100644
--- a/src/traced/probes/filesystem/BUILD.gn
+++ b/src/traced/probes/filesystem/BUILD.gn
@@ -22,6 +22,8 @@
     "../../../base",
   ]
   sources = [
+    "file_scanner.cc",
+    "file_scanner.h",
     "fs_mount.cc",
     "fs_mount.h",
     "inode_file_data_source.cc",
diff --git a/src/traced/probes/filesystem/file_scanner.cc b/src/traced/probes/filesystem/file_scanner.cc
new file mode 100644
index 0000000..3fb9cfe
--- /dev/null
+++ b/src/traced/probes/filesystem/file_scanner.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/filesystem/file_scanner.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "src/traced/probes/filesystem/inode_file_data_source.h"
+
+namespace perfetto {
+namespace {
+
+std::string JoinPaths(const std::string& one, const std::string& other) {
+  std::string result;
+  result.reserve(one.size() + other.size() + 1);
+  result += one;
+  if (!result.empty() && result.back() != '/')
+    result += '/';
+  result += other;
+  return result;
+}
+
+}  // namespace
+
+FileScanner::FileScanner(std::vector<std::string> root_directories,
+                         Delegate* delegate,
+                         uint64_t scan_interval_ms,
+                         uint64_t scan_steps)
+    : delegate_(delegate),
+      scan_interval_ms_(scan_interval_ms),
+      scan_steps_(scan_steps),
+      queue_(std::move(root_directories)),
+      weak_factory_(this) {}
+
+void FileScanner::Scan(base::TaskRunner* task_runner) {
+  Steps(scan_steps_);
+  if (Done())
+    return delegate_->OnInodeScanDone();
+  auto weak_this = weak_factory_.GetWeakPtr();
+  task_runner->PostDelayedTask(
+      [weak_this, task_runner] {
+        if (!weak_this)
+          return;
+        weak_this->Scan(task_runner);
+      },
+      scan_interval_ms_);
+}
+
+void FileScanner::NextDirectory() {
+  std::string directory = std::move(queue_.back());
+  queue_.pop_back();
+  current_dir_handle_.reset(opendir(directory.c_str()));
+  if (!current_dir_handle_) {
+    PERFETTO_DPLOG("opendir %s", directory.c_str());
+    current_directory_.clear();
+    return;
+  }
+  current_directory_ = std::move(directory);
+
+  struct stat buf;
+  if (fstat(dirfd(current_dir_handle_.get()), &buf) != 0) {
+    PERFETTO_DPLOG("fstat %s", current_directory_.c_str());
+    current_dir_handle_.reset();
+    current_directory_.clear();
+    return;
+  }
+
+  if (S_ISLNK(buf.st_mode)) {
+    current_dir_handle_.reset();
+    current_directory_.clear();
+    return;
+  }
+  current_block_device_id_ = buf.st_dev;
+}
+
+void FileScanner::Step() {
+  if (!current_dir_handle_) {
+    if (queue_.empty())
+      return;
+    NextDirectory();
+  }
+
+  if (!current_dir_handle_)
+    return;
+
+  struct dirent* entry = readdir(current_dir_handle_.get());
+  if (entry == nullptr) {
+    current_dir_handle_.reset();
+    return;
+  }
+
+  std::string filename = entry->d_name;
+  if (filename == "." || filename == "..")
+    return;
+
+  std::string filepath = JoinPaths(current_directory_, filename);
+
+  protos::pbzero::InodeFileMap_Entry_Type type =
+      protos::pbzero::InodeFileMap_Entry_Type_UNKNOWN;
+  // Readdir and stat not guaranteed to have directory info for all systems
+  if (entry->d_type == DT_DIR) {
+    // Continue iterating through files if current entry is a directory
+    queue_.emplace_back(filepath);
+    type = protos::pbzero::InodeFileMap_Entry_Type_DIRECTORY;
+  } else if (entry->d_type == DT_REG) {
+    type = protos::pbzero::InodeFileMap_Entry_Type_FILE;
+  }
+
+  if (!delegate_->OnInodeFound(current_block_device_id_, entry->d_ino, filepath,
+                               type)) {
+    queue_.clear();
+    current_dir_handle_.reset();
+  }
+}
+
+void FileScanner::Steps(uint64_t n) {
+  for (uint64_t i = 0; i < n && !Done(); ++i)
+    Step();
+}
+
+bool FileScanner::Done() {
+  return !current_dir_handle_ && queue_.empty();
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/filesystem/file_scanner.h b/src/traced/probes/filesystem/file_scanner.h
new file mode 100644
index 0000000..24475a4
--- /dev/null
+++ b/src/traced/probes/filesystem/file_scanner.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACED_PROBES_FILESYSTEM_FILE_SCANNER_H_
+#define SRC_TRACED_PROBES_FILESYSTEM_FILE_SCANNER_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/base/scoped_file.h"
+#include "perfetto/base/task_runner.h"
+#include "perfetto/base/weak_ptr.h"
+#include "perfetto/traced/data_source_types.h"
+
+namespace perfetto {
+
+class FileScanner {
+ public:
+  class Delegate {
+   public:
+    virtual bool OnInodeFound(BlockDeviceID,
+                              Inode,
+                              const std::string&,
+                              protos::pbzero::InodeFileMap_Entry_Type) = 0;
+    virtual void OnInodeScanDone() = 0;
+    virtual ~Delegate() {}
+  };
+
+  FileScanner(std::vector<std::string> root_directories,
+              Delegate* delegate,
+              uint64_t scan_interval_ms,
+              uint64_t scan_steps);
+
+  FileScanner(const FileScanner&) = delete;
+  FileScanner& operator=(const FileScanner&) = delete;
+
+  void Scan(base::TaskRunner* task_runner);
+
+ private:
+  void NextDirectory();
+  void Step();
+  void Steps(uint64_t n);
+  bool Done();
+
+  Delegate* delegate_;
+  const uint64_t scan_interval_ms_;
+  const uint64_t scan_steps_;
+
+  std::vector<std::string> queue_;
+  base::ScopedDir current_dir_handle_;
+  std::string current_directory_;
+  BlockDeviceID current_block_device_id_;
+  base::WeakPtrFactory<FileScanner> weak_factory_;  // Keep last.
+};
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_FILESYSTEM_FILE_SCANNER_H_
diff --git a/src/traced/probes/filesystem/inode_file_data_source.cc b/src/traced/probes/filesystem/inode_file_data_source.cc
index 0a75dc0..b0086cf 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.cc
+++ b/src/traced/probes/filesystem/inode_file_data_source.cc
@@ -29,9 +29,9 @@
 #include "perfetto/tracing/core/trace_writer.h"
 
 #include "perfetto/trace/trace_packet.pbzero.h"
+#include "src/traced/probes/filesystem/file_scanner.h"
 
 namespace perfetto {
-
 namespace {
 const int kScanIntervalMs = 10000;  // 10s
 }