blob: 5fcce730f8351c4c30f3b4c4f63cbfd7b06b77bc [file] [log] [blame]
// 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 <android-base/logging.h>
#include <fstream>
#include <stdio.h>
namespace rx = rxcpp;
namespace iorap::inode2filename {
std::vector<std::string> 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<std::string> args;
iorap::common::AppendNamedArg(args, "--data-source", value);
return args;
}
std::vector<std::string> ToArgs(const DataSourceDependencies& deps) {
std::vector<std::string> 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<InodeResult> 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<InodeResult> EmitInodes() const override {
const std::string& file_name = *dependencies_.text_cache_filename;
return rx::observable<>::create<InodeResult>(
[file_name](rx::subscriber<InodeResult> 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<dev_t>(device_number),
static_cast<ino_t>(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> DataSource::Create(DataSourceDependencies dependencies) {
switch (dependencies.data_source) {
case DataSourceKind::kDiskScan:
return std::shared_ptr<DataSource>{new DiskScanDataSource{std::move(dependencies)}};
case DataSourceKind::kTextCache:
return std::shared_ptr<DataSource>{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