1 // Copyright (C) 2019 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "inode2filename/data_source.h"
16
17 #include "common/cmd_utils.h"
18 #include "inode2filename/inode_resolver.h"
19 #include "inode2filename/search_directories.h"
20
21 #include <android-base/logging.h>
22
23 #include <fstream>
24 #include <stdio.h>
25
26 namespace rx = rxcpp;
27
28 namespace iorap::inode2filename {
29
ToArgs(DataSourceKind data_source_kind)30 std::vector<std::string> ToArgs(DataSourceKind data_source_kind) {
31 const char* value = nullptr;
32
33 switch (data_source_kind) {
34 case DataSourceKind::kDiskScan:
35 value = "diskscan";
36 break;
37 case DataSourceKind::kTextCache:
38 value = "textcache";
39 break;
40 case DataSourceKind::kBpf:
41 value = "bpf";
42 break;
43 }
44
45 std::vector<std::string> args;
46 iorap::common::AppendNamedArg(args, "--data-source", value);
47 return args;
48 }
49
ToArgs(const DataSourceDependencies & deps)50 std::vector<std::string> ToArgs(const DataSourceDependencies& deps) {
51 std::vector<std::string> args;
52
53 iorap::common::AppendArgsRepeatedly(args, ToArgs(deps.data_source));
54 // intentionally skip system_call; it does not have a command line equivalent
55 iorap::common::AppendNamedArgRepeatedly(args, "--root", deps.root_directories);
56
57 if (deps.text_cache_filename) {
58 iorap::common::AppendNamedArg(args, "--textcache", *(deps.text_cache_filename));
59 }
60
61 return args;
62 }
63
64 class DiskScanDataSource : public DataSource {
65 public:
DiskScanDataSource(DataSourceDependencies dependencies)66 DiskScanDataSource(DataSourceDependencies dependencies)
67 : DataSource(std::move(dependencies)) {
68 DCHECK_NE(dependencies_.root_directories.size(), 0u) << "Root directories can't be empty";
69 }
70
EmitInodes() const71 virtual rxcpp::observable<InodeResult> EmitInodes() const override {
72 SearchDirectories searcher{/*borrow*/dependencies_.system_call};
73 return searcher.ListAllFilenames(dependencies_.root_directories);
74 }
75
76 // Since not all Inodes emitted are the ones searched for, doing additional stat(2) calls here
77 // would be redundant.
78 //
79 // We set the device number to a dummy value here (-1) so that InodeResolver
80 // can fill it in later with stat(2). This is effectively the same thing as always doing
81 // verification.
ResultIncludesDeviceNumber() const82 virtual bool ResultIncludesDeviceNumber() const { return false; };
83
~DiskScanDataSource()84 virtual ~DiskScanDataSource() {}
85 };
86
LeftTrim(std::string & s)87 static inline void LeftTrim(/*inout*/std::string& s) {
88 s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
89 return !std::isspace(ch);
90 }));
91 }
92
93 class TextCacheDataSource : public DataSource {
94 public:
TextCacheDataSource(DataSourceDependencies dependencies)95 TextCacheDataSource(DataSourceDependencies dependencies)
96 : DataSource(std::move(dependencies)) {
97 DCHECK(dependencies_.text_cache_filename.has_value()) << "Must have text cache filename";
98 }
99
EmitInodes() const100 virtual rxcpp::observable<InodeResult> EmitInodes() const override {
101 const std::string& file_name = *dependencies_.text_cache_filename;
102
103 return rx::observable<>::create<InodeResult>(
104 [file_name](rx::subscriber<InodeResult> dest) {
105 LOG(VERBOSE) << "TextCacheDataSource (lambda)";
106
107 std::ifstream ifs{file_name};
108
109 if (!ifs.is_open()) {
110 dest.on_error(rxcpp::util::make_error_ptr(
111 std::ios_base::failure("Could not open specified text cache filename")));
112 return;
113 }
114
115 while (dest.is_subscribed() && ifs) {
116 LOG(VERBOSE) << "TextCacheDataSource (read line)";
117 // TODO.
118
119 uint64_t device_number = 0;
120 uint64_t inode_number = 0;
121 uint64_t file_size = 0;
122
123 // Parse lines of form:
124 // "$device_number $inode $filesize $filename..."
125 // This format conforms to system/extras/pagecache/pagecache.py
126
127 ifs >> device_number;
128 ifs >> inode_number;
129 ifs >> file_size;
130 (void)file_size; // Not used in iorapd.
131
132 std::string value_filename;
133 std::getline(/*inout*/ifs, /*out*/value_filename);
134
135 if (value_filename.empty()) {
136 // Ignore empty lines.
137 continue;
138 }
139
140 // getline, unlike ifstream, does not ignore spaces.
141 // There's always at least 1 space in a textcache output file.
142 // However, drop *all* spaces on the left since filenames starting with a space are
143 // ambiguous to us.
144 LeftTrim(/*inout*/value_filename);
145
146 Inode inode = Inode::FromDeviceAndInode(static_cast<dev_t>(device_number),
147 static_cast<ino_t>(inode_number));
148
149 LOG(VERBOSE) << "TextCacheDataSource (on_next) " << inode << "->" << value_filename;
150 dest.on_next(InodeResult::makeSuccess(inode, value_filename));
151 }
152
153 dest.on_completed();
154 }
155 );
156
157 // TODO: plug into the filtering and verification graph.
158 }
159
~TextCacheDataSource()160 virtual ~TextCacheDataSource() {}
161 };
162
DataSource(DataSourceDependencies dependencies)163 DataSource::DataSource(DataSourceDependencies dependencies)
164 : dependencies_{std::move(dependencies)} {
165 DCHECK(dependencies_.system_call != nullptr);
166 }
167
Create(DataSourceDependencies dependencies)168 std::shared_ptr<DataSource> DataSource::Create(DataSourceDependencies dependencies) {
169 switch (dependencies.data_source) {
170 case DataSourceKind::kDiskScan:
171 return std::shared_ptr<DataSource>{new DiskScanDataSource{std::move(dependencies)}};
172 case DataSourceKind::kTextCache:
173 return std::shared_ptr<DataSource>{new TextCacheDataSource{std::move(dependencies)}};
174 case DataSourceKind::kBpf:
175 // TODO: BPF-based data source.
176 LOG(FATAL) << "Not implemented yet";
177 return nullptr;
178 default:
179 LOG(FATAL) << "Invalid data source value";
180 return nullptr;
181 }
182 }
183
StartRecording()184 void DataSource::StartRecording() {}
StopRecording()185 void DataSource::StopRecording() {}
186
187 } // namespace iorap::inode2filename
188