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