// Copyright (C) 2019 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 "inode2filename/data_source.h" #include "common/cmd_utils.h" #include "inode2filename/inode_resolver.h" #include "inode2filename/search_directories.h" #include #include #include namespace rx = rxcpp; namespace iorap::inode2filename { std::vector ToArgs(DataSourceKind data_source_kind) { const char* value = nullptr; switch (data_source_kind) { case DataSourceKind::kDiskScan: value = "diskscan"; break; case DataSourceKind::kTextCache: value = "textcache"; break; case DataSourceKind::kBpf: value = "bpf"; break; } std::vector args; iorap::common::AppendNamedArg(args, "--data-source", value); return args; } std::vector ToArgs(const DataSourceDependencies& deps) { std::vector args; iorap::common::AppendArgsRepeatedly(args, ToArgs(deps.data_source)); // intentionally skip system_call; it does not have a command line equivalent iorap::common::AppendNamedArgRepeatedly(args, "--root", deps.root_directories); if (deps.text_cache_filename) { iorap::common::AppendNamedArg(args, "--textcache", *(deps.text_cache_filename)); } return args; } class DiskScanDataSource : public DataSource { public: DiskScanDataSource(DataSourceDependencies dependencies) : DataSource(std::move(dependencies)) { DCHECK_NE(dependencies_.root_directories.size(), 0u) << "Root directories can't be empty"; } virtual rxcpp::observable EmitInodes() const override { SearchDirectories searcher{/*borrow*/dependencies_.system_call}; return searcher.ListAllFilenames(dependencies_.root_directories); } // Since not all Inodes emitted are the ones searched for, doing additional stat(2) calls here // would be redundant. // // We set the device number to a dummy value here (-1) so that InodeResolver // can fill it in later with stat(2). This is effectively the same thing as always doing // verification. virtual bool ResultIncludesDeviceNumber() const { return false; }; virtual ~DiskScanDataSource() {} }; static inline void LeftTrim(/*inout*/std::string& s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); } class TextCacheDataSource : public DataSource { public: TextCacheDataSource(DataSourceDependencies dependencies) : DataSource(std::move(dependencies)) { DCHECK(dependencies_.text_cache_filename.has_value()) << "Must have text cache filename"; } virtual rxcpp::observable EmitInodes() const override { const std::string& file_name = *dependencies_.text_cache_filename; return rx::observable<>::create( [file_name](rx::subscriber dest) { LOG(VERBOSE) << "TextCacheDataSource (lambda)"; std::ifstream ifs{file_name}; if (!ifs.is_open()) { dest.on_error(rxcpp::util::make_error_ptr( std::ios_base::failure("Could not open specified text cache filename"))); return; } while (dest.is_subscribed() && ifs) { LOG(VERBOSE) << "TextCacheDataSource (read line)"; // TODO. uint64_t device_number = 0; uint64_t inode_number = 0; uint64_t file_size = 0; // Parse lines of form: // "$device_number $inode $filesize $filename..." // This format conforms to system/extras/pagecache/pagecache.py ifs >> device_number; ifs >> inode_number; ifs >> file_size; (void)file_size; // Not used in iorapd. std::string value_filename; std::getline(/*inout*/ifs, /*out*/value_filename); if (value_filename.empty()) { // Ignore empty lines. continue; } // getline, unlike ifstream, does not ignore spaces. // There's always at least 1 space in a textcache output file. // However, drop *all* spaces on the left since filenames starting with a space are // ambiguous to us. LeftTrim(/*inout*/value_filename); Inode inode = Inode::FromDeviceAndInode(static_cast(device_number), static_cast(inode_number)); LOG(VERBOSE) << "TextCacheDataSource (on_next) " << inode << "->" << value_filename; dest.on_next(InodeResult::makeSuccess(inode, value_filename)); } dest.on_completed(); } ); // TODO: plug into the filtering and verification graph. } virtual ~TextCacheDataSource() {} }; DataSource::DataSource(DataSourceDependencies dependencies) : dependencies_{std::move(dependencies)} { DCHECK(dependencies_.system_call != nullptr); } std::shared_ptr DataSource::Create(DataSourceDependencies dependencies) { switch (dependencies.data_source) { case DataSourceKind::kDiskScan: return std::shared_ptr{new DiskScanDataSource{std::move(dependencies)}}; case DataSourceKind::kTextCache: return std::shared_ptr{new TextCacheDataSource{std::move(dependencies)}}; case DataSourceKind::kBpf: // TODO: BPF-based data source. LOG(FATAL) << "Not implemented yet"; return nullptr; default: LOG(FATAL) << "Invalid data source value"; return nullptr; } } void DataSource::StartRecording() {} void DataSource::StopRecording() {} } // namespace iorap::inode2filename