1 // Copyright (C) 2018 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 "common/debug.h"
16 #include "common/expected.h"
17 #include "inode2filename/inode_resolver.h"
18
19 #include <android-base/strings.h>
20
21 #include <iostream>
22 #include <fstream>
23 #include <sstream>
24 #include <string_view>
25
26 #if defined(IORAP_INODE2FILENAME_MAIN)
27
28 namespace iorap::inode2filename {
29
Usage(char ** argv)30 void Usage(char** argv) {
31 std::cerr << "Usage: " << argv[0] << " <options> <<inode_syntax>> [inode_syntax1 inode_syntax2 ...]" << std::endl;
32 std::cerr << "" << std::endl;
33 std::cerr << " Block until all inodes have been read in, then begin searching for filenames for those inodes." << std::endl;
34 std::cerr << " Results are written immediately as they are available, and once all inodes are found, " << std::endl;
35 std::cerr << " the program will terminate." << std::endl;
36 std::cerr << "" << std::endl;
37 std::cerr << " Inode syntax: ('dev_t@inode' | 'major:minor:inode')" << std::endl;
38 std::cerr << "" << std::endl; // CLI-only flags.
39 std::cerr << " --help,-h Print this Usage." << std::endl;
40 std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
41 std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl;
42 std::cerr << "" << std::endl; // General flags.
43 std::cerr << " --all,-a Enumerate all inode->filename mappings in the dataset (default off)." << std::endl;
44 std::cerr << " All <<inode_syntaxN>> arguments are ignored." << std::endl;
45 std::cerr << " --data-source=, Choose a data source (default 'diskscan')." << std::endl;
46 std::cerr << " -ds " << std::endl;
47 std::cerr << " diskscan Scan disk recursively using readdir." << std::endl;
48 std::cerr << " textcache Use the file from the '--output-format=text'." << std::endl;
49 std::cerr << " bpf Query kernel BPF maps (experimental)." << std::endl;
50 std::cerr << " --output=,-o Choose an output file (default 'stdout')." << std::endl;
51 std::cerr << " --output-format=, Choose an output format (default 'log')." << std::endl;
52 std::cerr << " -of " << std::endl;
53 std::cerr << " log Log human-readable, non-parsable format to stdout+logcat." << std::endl;
54 std::cerr << " textcache Results are in the same format as system/extras/pagecache." << std::endl;
55 std::cerr << " ipc Results are in a binary inter-process communications format" << std::endl;
56 std::cerr << " --process-mode=, Choose a process mode (default 'in'). Test-oriented." << std::endl;
57 std::cerr << " -pm " << std::endl;
58 std::cerr << " in Use a single process to do the work in." << std::endl;
59 std::cerr << " out Out-of-process work (forks into a -pm=in)." << std::endl;
60 std::cerr << " --verify=,-vy Verification modes for the data source (default 'stat')." << std::endl;
61 std::cerr << " stat Use stat(2) call to validate data inodes are up-to-date. " << std::endl;
62 std::cerr << " none Trust that the data-source is up-to-date without checking." << std::endl;
63 std::cerr << "" << std::endl; // --data-source=<?> specific flags.
64 std::cerr << " Data-source-specific commands:" << std::endl;
65 std::cerr << " --data-source=diskscan" << std::endl;
66 std::cerr << " --root=,-r Add root directory (default '.'). Repeatable." << std::endl;
67 std::cerr << " --data-source=textcache" << std::endl;
68 std::cerr << " --textcache=,-tc Name of file that contains the textcache." << std::endl;
69 std::cerr << "" << std::endl; // Programmatic flags.
70 std::cerr << " --in-fd=# Input file descriptor. Default input is from argv." << std::endl;
71 std::cerr << " --out-fd=# Output file descriptor. Default stdout." << std::endl;
72 exit(1);
73 }
74
GetSystemCallComponent()75 static fruit::Component<SystemCall> GetSystemCallComponent() {
76 return fruit::createComponent().bind<SystemCall, SystemCallImpl>();
77 }
78
ParseDataSourceKind(std::string_view str)79 std::optional<DataSourceKind> ParseDataSourceKind(std::string_view str) {
80 if (str == "diskscan") {
81 return DataSourceKind::kDiskScan;
82 } else if (str == "textcache") {
83 return DataSourceKind::kTextCache;
84 } else if (str == "bpf") {
85 return DataSourceKind::kBpf;
86 }
87 return std::nullopt;
88 }
89
90 enum class OutputFormatKind {
91 kLog,
92 kTextCache,
93 kIpc,
94 };
95
ParseOutputFormatKind(std::string_view str)96 std::optional<OutputFormatKind> ParseOutputFormatKind(std::string_view str) {
97 if (str == "log") {
98 return OutputFormatKind::kLog;
99 } else if (str == "textcache") {
100 return OutputFormatKind::kTextCache;
101 } else if (str == "ipc") {
102 return OutputFormatKind::kIpc;
103 }
104 return std::nullopt;
105 }
106
ParseVerifyKind(std::string_view str)107 std::optional<VerifyKind> ParseVerifyKind(std::string_view str) {
108 if (str == "none") {
109 return VerifyKind::kNone;
110 } else if (str == "stat") {
111 return VerifyKind::kStat;
112 }
113 return std::nullopt;
114 }
115
ParseProcessMode(std::string_view str)116 std::optional<ProcessMode> ParseProcessMode(std::string_view str) {
117 if (str == "in") {
118 return ProcessMode::kInProcessDirect;
119 } else if (str == "out") {
120 return ProcessMode::kOutOfProcessIpc;
121 }
122 return std::nullopt;
123 }
124
StartsWith(std::string_view haystack,std::string_view needle)125 bool StartsWith(std::string_view haystack, std::string_view needle) {
126 return haystack.size() >= needle.size()
127 && haystack.compare(0, needle.size(), needle) == 0;
128 }
129
EndsWith(std::string_view haystack,std::string_view needle)130 bool EndsWith(std::string_view haystack, std::string_view needle) {
131 return haystack.size() >= needle.size()
132 && haystack.compare(haystack.size() - needle.size(), haystack.npos, needle) == 0;
133 }
134
StartsWithOneOf(std::string_view haystack,std::string_view needle,std::string_view needle2)135 bool StartsWithOneOf(std::string_view haystack,
136 std::string_view needle,
137 std::string_view needle2) {
138 return StartsWith(haystack, needle) || StartsWith(haystack, needle2);
139 }
140
141 enum ParseError {
142 kParseSkip,
143 kParseFailed,
144 };
145
ParseNamedArgument(std::initializer_list<std::string> names,std::string argstr,std::optional<std::string> arg_next,int * arg_pos)146 std::optional<std::string> ParseNamedArgument(std::initializer_list<std::string> names,
147 std::string argstr,
148 std::optional<std::string> arg_next,
149 /*inout*/
150 int* arg_pos) {
151 for (const std::string& name : names) {
152 {
153 // Try parsing only 'argstr' for '--foo=bar' type parameters.
154 std::vector<std::string> split_str = ::android::base::Split(argstr, "=");
155 if (split_str.size() >= 2) {
156 /*
157 std::cerr << "ParseNamedArgument(name=" << name << ", argstr='"
158 << argstr << "')" << std::endl;
159 */
160
161 if (split_str[0] + "=" == name) {
162 return split_str[1];
163 }
164 }
165 }
166 //if (EndsWith(name, "=")) {
167 // continue;
168 /*} else */ {
169 // Try parsing 'argstr arg_next' for '-foo bar' type parameters.
170 if (argstr == name) {
171 ++(*arg_pos);
172
173 if (arg_next) {
174 return arg_next;
175 } else {
176 // Missing argument, e.g. '-foo' was the last token in the argv.
177 std::cerr << "Missing " << name << " flag value." << std::endl;
178 exit(1);
179 }
180 }
181 }
182 }
183
184 return std::nullopt;
185 }
186
main(int argc,char ** argv)187 int main(int argc, char** argv) {
188 android::base::InitLogging(argv);
189 android::base::SetLogger(android::base::StderrLogger);
190
191 bool all = false;
192 bool wait_for_keystroke = false;
193 bool enable_verbose = false;
194 std::vector<std::string> root_directories;
195 std::vector<Inode> inode_list;
196 int recording_time_sec = 0;
197
198 DataSourceKind data_source = DataSourceKind::kDiskScan;
199 OutputFormatKind output_format = OutputFormatKind::kLog;
200 VerifyKind verify = VerifyKind::kStat;
201 ProcessMode process_mode = ProcessMode::kInProcessDirect;
202
203 std::optional<std::string> output_filename;
204 std::optional<int /*fd*/> in_fd, out_fd; // input-output file descriptors [for fork+exec].
205 std::optional<std::string> text_cache_filename;
206
207 if (argc == 1) {
208 Usage(argv);
209 }
210
211 for (int arg = 1; arg < argc; ++arg) {
212 std::string argstr = argv[arg];
213 std::optional<std::string> arg_next;
214 if ((arg + 1) < argc) {
215 arg_next = argv[arg+1];
216 }
217
218 if (argstr == "--help" || argstr == "-h") {
219 Usage(argv);
220 } else if (auto val = ParseNamedArgument({"--root=", "-r"}, argstr, arg_next, /*inout*/&arg);
221 val) {
222 root_directories.push_back(*val);
223 } else if (argstr == "--verbose" || argstr == "-v") {
224 enable_verbose = true;
225 } else if (argstr == "--wait" || argstr == "-w") {
226 wait_for_keystroke = true;
227 }
228 else if (argstr == "--all" || argstr == "-a") {
229 all = true;
230 } else if (auto val = ParseNamedArgument({"--data-source=", "-ds"},
231 argstr,
232 arg_next,
233 /*inout*/&arg);
234 val) {
235 auto ds = ParseDataSourceKind(*val);
236 if (!ds) {
237 std::cerr << "Invalid --data-source=<value>" << std::endl;
238 return 1;
239 }
240 data_source = *ds;
241 } else if (auto val = ParseNamedArgument({"--output=", "-o"},
242 argstr,
243 arg_next,
244 /*inout*/&arg);
245 val) {
246 output_filename = *val;
247 } else if (auto val = ParseNamedArgument({"--process-mode=", "-pm"},
248 argstr,
249 arg_next,
250 /*inout*/&arg);
251 val) {
252 auto pm = ParseProcessMode(*val);
253 if (!pm) {
254 std::cerr << "Invalid --process-mode=<value>" << std::endl;
255 return 1;
256 }
257 process_mode = *pm;
258 }
259 else if (auto val = ParseNamedArgument({"--output-format=", "-of"},
260 argstr,
261 arg_next,
262 /*inout*/&arg);
263 val) {
264 auto of = ParseOutputFormatKind(*val);
265 if (!of) {
266 std::cerr << "Missing --output-format=<value>" << std::endl;
267 return 1;
268 }
269 output_format = *of;
270 } else if (auto val = ParseNamedArgument({"--verify=", "-vy="},
271 argstr,
272 arg_next,
273 /*inout*/&arg);
274 val) {
275 auto vy = ParseVerifyKind(*val);
276 if (!vy) {
277 std::cerr << "Invalid --verify=<value>" << std::endl;
278 return 1;
279 }
280 verify = *vy;
281 } else if (auto val = ParseNamedArgument({"--textcache=", "-tc"},
282 argstr,
283 arg_next,
284 /*inout*/&arg);
285 val) {
286 text_cache_filename = *val;
287 } else {
288 Inode maybe_inode{};
289
290 std::string error_msg;
291 if (Inode::Parse(argstr, /*out*/&maybe_inode, /*out*/&error_msg)) {
292 inode_list.push_back(maybe_inode);
293 } else {
294 if (argstr.size() >= 1) {
295 if (argstr[0] == '-') {
296 std::cerr << "Unrecognized flag: " << argstr << std::endl;
297 return 1;
298 }
299 }
300
301 std::cerr << "Failed to parse inode (" << argstr << ") because: " << error_msg << std::endl;
302 return 1;
303 }
304 }
305 }
306
307 if (root_directories.size() == 0) {
308 root_directories.push_back(".");
309 }
310
311 if (inode_list.size() == 0 && !all) {
312 std::cerr << "Provide at least one inode. Or use --all to dump everything." << std::endl;
313 return 1;
314 } else if (all && inode_list.size() > 0) {
315 std::cerr << "[WARNING]: --all flag ignores all inodes passed on command line." << std::endl;
316 }
317
318 std::ofstream fout;
319 if (output_filename) {
320 fout.open(*output_filename);
321 if (!fout) {
322 std::cerr << "Failed to open output file for writing: \"" << *output_filename << "\"";
323 return 1;
324 }
325 } else {
326 fout.open("/dev/null"); // have to open *something* otherwise rdbuf fails.
327 fout.basic_ios<char>::rdbuf(std::cout.rdbuf());
328 }
329
330 if (enable_verbose) {
331 android::base::SetMinimumLogSeverity(android::base::VERBOSE);
332
333 LOG(VERBOSE) << "Verbose check";
334 LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild;
335
336 for (auto& inode_num : inode_list) {
337 LOG(VERBOSE) << "Searching for inode " << inode_num;
338 }
339
340 LOG(VERBOSE) << "Dumping all inodes? " << all;
341 }
342 // else use
343 // $> ANDROID_LOG_TAGS='*:d' iorap.inode2filename <args>
344 // which will enable arbitrary log levels.
345
346 // Useful to attach a debugger...
347 // 1) $> inode2filename -w <args>
348 // 2) $> gdbclient <pid>
349 if (wait_for_keystroke) {
350 LOG(INFO) << "Self pid: " << getpid();
351 LOG(INFO) << "Press any key to continue...";
352 std::cin >> wait_for_keystroke;
353 }
354
355 fruit::Injector<SystemCall> injector(GetSystemCallComponent);
356
357 InodeResolverDependencies ir_dependencies;
358 // Passed from command-line.
359 ir_dependencies.data_source = data_source;
360 ir_dependencies.process_mode = process_mode;
361 ir_dependencies.root_directories = root_directories;
362 ir_dependencies.text_cache_filename = text_cache_filename;
363 ir_dependencies.verify = verify;
364 // Hardcoded.
365 ir_dependencies.system_call = injector.get<SystemCall*>();
366
367 std::shared_ptr<InodeResolver> inode_resolver =
368 InodeResolver::Create(ir_dependencies);
369
370 inode_resolver->StartRecording();
371 sleep(recording_time_sec); // TODO: add cli flag for this when we add something that needs it.
372 inode_resolver->StopRecording();
373
374 auto/*observable<InodeResult>*/ inode_results = all
375 ? inode_resolver->EmitAll()
376 : inode_resolver->FindFilenamesFromInodes(std::move(inode_list));
377
378 int return_code = 2;
379 inode_results.subscribe(
380 /*on_next*/[&return_code, output_format, &fout](const InodeResult& result) {
381 if (result) {
382 LOG(DEBUG) << "Inode match: " << result;
383 if (output_format == OutputFormatKind::kLog) {
384 fout << "\033[1;32m[OK]\033[0m "
385 << result.inode
386 << " \"" << result.data.value() << "\"" << std::endl;
387 } else if (output_format == OutputFormatKind::kIpc) {
388 std::stringstream stream;
389 stream << "K " << result.inode << " " << result.data.value();
390 std::string line = stream.str();
391
392 // Convert the size to 4 bytes.
393 int32_t size = line.size();
394 char buf[sizeof(int32_t)];
395 memcpy(buf, &size, sizeof(int32_t));
396 fout.write(buf, sizeof(int32_t));
397 fout.write(line.c_str(), size);
398 } else if (output_format == OutputFormatKind::kTextCache) {
399 // Same format as TextCacheDataSource (system/extras/pagecache/pagecache.py -d)
400 // "$device_number $inode $filesize $filename..."
401 const Inode& inode = result.inode;
402 fout << inode.GetDevice() << " "
403 << inode.GetInode()
404 << " -1 " // always -1 for filesize, since we don't track what it is.
405 << result.data.value() << "\n"; // don't use endl which flushes, for faster writes.
406 } else {
407 LOG(FATAL) << "Not implemented this kind of --output-format";
408 }
409
410 return_code = 0;
411 } else {
412 LOG(DEBUG) << "Failed to match inode: " << result;
413 if (output_format == OutputFormatKind::kLog) {
414 fout << "\033[1;31m[ERR]\033[0m "
415 << result.inode
416 << " '" << *result.ErrorMessage() << "'" << std::endl;
417 } else if (output_format == OutputFormatKind::kIpc) {
418 std::stringstream stream;
419 stream << "E " << result.inode << " " << result.data.error() << std::endl;
420 std::string line = stream.str();
421
422 // Convert the size to 4 bytes.
423 int32_t size = line.size();
424 char buf[sizeof(int32_t)];
425 memcpy(buf, &size, sizeof(int32_t));
426 fout.write(buf, sizeof(int32_t));
427 fout.write(line.c_str(), size);
428 }
429 else if (output_format == OutputFormatKind::kTextCache) {
430 // Don't add bad results to the textcache. They are dropped.
431 } else {
432 LOG(FATAL) << "Not implemented this kind of --output-format";
433 }
434 }
435 }, /*on_error*/[&return_code](rxcpp::util::error_ptr error) {
436 // Usually occurs very early on before we see the first result.
437 // In this case the error is terminal so we just end up exiting out soon.
438 return_code = 3;
439 LOG(ERROR) << "Critical error: " << rxcpp::util::what(error);
440 });
441
442 // 0 -> found at least a single match,
443 // 1 -> bad parameters,
444 // 2 -> could not find any matches,
445 // 3 -> rxcpp on_error.
446 return return_code;
447 }
448
449 } // namespace iorap::inode2filename
450
main(int argc,char ** argv)451 int main(int argc, char** argv) {
452 return ::iorap::inode2filename::main(argc, argv);
453 }
454
455 #endif
456