| /* |
| * 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 "protos/perfetto/trace/filesystem/inode_file_map.pbzero.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, |
| uint32_t scan_interval_ms, |
| uint32_t scan_steps) |
| : delegate_(delegate), |
| scan_interval_ms_(scan_interval_ms), |
| scan_steps_(scan_steps), |
| queue_(std::move(root_directories)), |
| weak_factory_(this) {} |
| |
| FileScanner::FileScanner(std::vector<std::string> root_directories, |
| Delegate* delegate) |
| : FileScanner(std::move(root_directories), |
| delegate, |
| 0 /* scan_interval_ms */, |
| 0 /* scan_steps */) {} |
| |
| void FileScanner::Scan() { |
| while (!Done()) |
| Step(); |
| delegate_->OnInodeScanDone(); |
| } |
| void FileScanner::Scan(base::TaskRunner* task_runner) { |
| PERFETTO_DCHECK(scan_interval_ms_ && scan_steps_); |
| 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(uint32_t n) { |
| for (uint32_t i = 0; i < n && !Done(); ++i) |
| Step(); |
| } |
| |
| bool FileScanner::Done() { |
| return !current_dir_handle_ && queue_.empty(); |
| } |
| |
| FileScanner::Delegate::~Delegate() = default; |
| |
| } // namespace perfetto |