• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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