summaryrefslogtreecommitdiffstats
path: root/src/inode2filename/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/inode2filename/main.cc')
-rw-r--r--src/inode2filename/main.cc356
1 files changed, 322 insertions, 34 deletions
diff --git a/src/inode2filename/main.cc b/src/inode2filename/main.cc
index 2da364c..38d6eab 100644
--- a/src/inode2filename/main.cc
+++ b/src/inode2filename/main.cc
@@ -14,12 +14,18 @@
#include "common/debug.h"
#include "common/expected.h"
-#include "inode2filename/search_directories.h"
+#include "inode2filename/inode_resolver.h"
-using namespace iorap::inode2filename; // NOLINT
+#include <android-base/strings.h>
+
+#include <iostream>
+#include <fstream>
+#include <string_view>
#if defined(IORAP_INODE2FILENAME_MAIN)
+namespace iorap::inode2filename {
+
void Usage(char** argv) {
std::cerr << "Usage: " << argv[0] << " <options> <<inode_syntax>> [inode_syntax1 inode_syntax2 ...]" << std::endl;
std::cerr << "" << std::endl;
@@ -28,25 +34,174 @@ void Usage(char** argv) {
std::cerr << " the program will terminate." << std::endl;
std::cerr << "" << std::endl;
std::cerr << " Inode syntax: ('dev_t@inode' | 'major:minor:inode')" << std::endl;
+ std::cerr << "" << std::endl; // CLI-only flags.
std::cerr << " --help,-h Print this Usage." << std::endl;
- std::cerr << " --root,-r Add root directory (default '.'). Repeatable." << std::endl;
std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl;
+ std::cerr << "" << std::endl; // General flags.
+ std::cerr << " --all,-a Enumerate all inode->filename mappings in the dataset (default off)." << std::endl;
+ std::cerr << " All <<inode_syntaxN>> arguments are ignored." << std::endl;
+ std::cerr << " --data-source=, Choose a data source (default 'diskscan')." << std::endl;
+ std::cerr << " -ds " << std::endl;
+ std::cerr << " diskscan Scan disk recursively using readdir." << std::endl;
+ std::cerr << " textcache Use the file from the '--output-format=text'." << std::endl;
+ std::cerr << " bpf Query kernel BPF maps (experimental)." << std::endl;
+ std::cerr << " --output=,-o Choose an output file (default 'stdout')." << std::endl;
+ std::cerr << " --output-format=, Choose an output format (default 'log')." << std::endl;
+ std::cerr << " -of " << std::endl;
+ std::cerr << " log Log human-readable, non-parsable format to stdout+logcat." << std::endl;
+ std::cerr << " textcache Results are in the same format as system/extras/pagecache." << std::endl;
+ std::cerr << " ipc Results are in a binary inter-process communications format" << std::endl;
+ std::cerr << " --process-mode=, Choose a process mode (default 'in'). Test-oriented." << std::endl;
+ std::cerr << " -pm " << std::endl;
+ std::cerr << " in Use a single process to do the work in." << std::endl;
+ std::cerr << " out Out-of-process work (forks into a -pm=in)." << std::endl;
+ std::cerr << " --verify=,-vy Verification modes for the data source (default 'stat')." << std::endl;
+ std::cerr << " stat Use stat(2) call to validate data inodes are up-to-date. " << std::endl;
+ std::cerr << " none Trust that the data-source is up-to-date without checking." << std::endl;
+ std::cerr << "" << std::endl; // --data-source=<?> specific flags.
+ std::cerr << " Data-source-specific commands:" << std::endl;
+ std::cerr << " --data-source=diskscan" << std::endl;
+ std::cerr << " --root=,-r Add root directory (default '.'). Repeatable." << std::endl;
+ std::cerr << " --data-source=textcache" << std::endl;
+ std::cerr << " --textcache=,-tc Name of file that contains the textcache." << std::endl;
+ std::cerr << "" << std::endl; // Programmatic flags.
+ std::cerr << " --in-fd=# Input file descriptor. Default input is from argv." << std::endl;
+ std::cerr << " --out-fd=# Output file descriptor. Default stdout." << std::endl;
exit(1);
}
-static fruit::Component<SearchDirectories> GetSearchDirectoriesComponent() {
+static fruit::Component<SystemCall> GetSystemCallComponent() {
return fruit::createComponent().bind<SystemCall, SystemCallImpl>();
}
+std::optional<DataSourceKind> ParseDataSourceKind(std::string_view str) {
+ if (str == "diskscan") {
+ return DataSourceKind::kDiskScan;
+ } else if (str == "textcache") {
+ return DataSourceKind::kTextCache;
+ } else if (str == "bpf") {
+ return DataSourceKind::kBpf;
+ }
+ return std::nullopt;
+}
+
+enum class OutputFormatKind {
+ kLog,
+ kTextCache,
+ kIpc,
+};
+
+std::optional<OutputFormatKind> ParseOutputFormatKind(std::string_view str) {
+ if (str == "log") {
+ return OutputFormatKind::kLog;
+ } else if (str == "textcache") {
+ return OutputFormatKind::kTextCache;
+ } else if (str == "ipc") {
+ return OutputFormatKind::kIpc;
+ }
+ return std::nullopt;
+}
+
+std::optional<VerifyKind> ParseVerifyKind(std::string_view str) {
+ if (str == "none") {
+ return VerifyKind::kNone;
+ } else if (str == "stat") {
+ return VerifyKind::kStat;
+ }
+ return std::nullopt;
+}
+
+std::optional<ProcessMode> ParseProcessMode(std::string_view str) {
+ if (str == "in") {
+ return ProcessMode::kInProcessDirect;
+ } else if (str == "out") {
+ return ProcessMode::kOutOfProcessIpc;
+ }
+ return std::nullopt;
+}
+
+bool StartsWith(std::string_view haystack, std::string_view needle) {
+ return haystack.size() >= needle.size()
+ && haystack.compare(0, needle.size(), needle) == 0;
+}
+
+bool EndsWith(std::string_view haystack, std::string_view needle) {
+ return haystack.size() >= needle.size()
+ && haystack.compare(haystack.size() - needle.size(), haystack.npos, needle) == 0;
+}
+
+bool StartsWithOneOf(std::string_view haystack,
+ std::string_view needle,
+ std::string_view needle2) {
+ return StartsWith(haystack, needle) || StartsWith(haystack, needle2);
+}
+
+enum ParseError {
+ kParseSkip,
+ kParseFailed,
+};
+
+std::optional<std::string> ParseNamedArgument(std::initializer_list<std::string> names,
+ std::string argstr,
+ std::optional<std::string> arg_next,
+ /*inout*/
+ int* arg_pos) {
+ for (const std::string& name : names) {
+ {
+ // Try parsing only 'argstr' for '--foo=bar' type parameters.
+ std::vector<std::string> split_str = ::android::base::Split(argstr, "=");
+ if (split_str.size() >= 2) {
+ /*
+ std::cerr << "ParseNamedArgument(name=" << name << ", argstr='"
+ << argstr << "')" << std::endl;
+ */
+
+ if (split_str[0] + "=" == name) {
+ return split_str[1];
+ }
+ }
+ }
+ //if (EndsWith(name, "=")) {
+ // continue;
+ /*} else */ {
+ // Try parsing 'argstr arg_next' for '-foo bar' type parameters.
+ if (argstr == name) {
+ ++(*arg_pos);
+
+ if (arg_next) {
+ return arg_next;
+ } else {
+ // Missing argument, e.g. '-foo' was the last token in the argv.
+ std::cerr << "Missing " << name << " flag value." << std::endl;
+ exit(1);
+ }
+ }
+ }
+ }
+
+ return std::nullopt;
+}
+
int main(int argc, char** argv) {
android::base::InitLogging(argv);
android::base::SetLogger(android::base::StderrLogger);
+ bool all = false;
bool wait_for_keystroke = false;
bool enable_verbose = false;
std::vector<std::string> root_directories;
std::vector<Inode> inode_list;
+ int recording_time_sec = 0;
+
+ DataSourceKind data_source = DataSourceKind::kDiskScan;
+ OutputFormatKind output_format = OutputFormatKind::kLog;
+ VerifyKind verify = VerifyKind::kStat;
+ ProcessMode process_mode = ProcessMode::kInProcessDirect;
+
+ std::optional<std::string> output_filename;
+ std::optional<int /*fd*/> in_fd, out_fd; // input-output file descriptors [for fork+exec].
+ std::optional<std::string> text_cache_filename;
if (argc == 1) {
Usage(argv);
@@ -54,22 +209,80 @@ int main(int argc, char** argv) {
for (int arg = 1; arg < argc; ++arg) {
std::string argstr = argv[arg];
- bool has_arg_next = (arg+1)<argc;
- std::string arg_next = has_arg_next ? argv[arg+1] : "";
+ std::optional<std::string> arg_next;
+ if ((arg + 1) < argc) {
+ arg_next = argv[arg+1];
+ }
if (argstr == "--help" || argstr == "-h") {
Usage(argv);
- } else if (argstr == "--root" || argstr == "-r") {
- if (!has_arg_next) {
- std::cerr << "Missing --root <value>" << std::endl;
- return 1;
- }
- root_directories.push_back(arg_next);
- ++arg;
+ } else if (auto val = ParseNamedArgument({"--root=", "-r"}, argstr, arg_next, /*inout*/&arg);
+ val) {
+ root_directories.push_back(*val);
} else if (argstr == "--verbose" || argstr == "-v") {
enable_verbose = true;
} else if (argstr == "--wait" || argstr == "-w") {
wait_for_keystroke = true;
+ }
+ else if (argstr == "--all" || argstr == "-a") {
+ all = true;
+ } else if (auto val = ParseNamedArgument({"--data-source=", "-ds"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto ds = ParseDataSourceKind(*val);
+ if (!ds) {
+ std::cerr << "Invalid --data-source=<value>" << std::endl;
+ return 1;
+ }
+ data_source = *ds;
+ } else if (auto val = ParseNamedArgument({"--output=", "-o"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ output_filename = *val;
+ } else if (auto val = ParseNamedArgument({"--process-mode=", "-pm"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto pm = ParseProcessMode(*val);
+ if (!pm) {
+ std::cerr << "Invalid --process-mode=<value>" << std::endl;
+ return 1;
+ }
+ process_mode = *pm;
+ }
+ else if (auto val = ParseNamedArgument({"--output-format=", "-of"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto of = ParseOutputFormatKind(*val);
+ if (!of) {
+ std::cerr << "Missing --output-format=<value>" << std::endl;
+ return 1;
+ }
+ output_format = *of;
+ } else if (auto val = ParseNamedArgument({"--verify=", "-vy="},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto vy = ParseVerifyKind(*val);
+ if (!vy) {
+ std::cerr << "Invalid --verify=<value>" << std::endl;
+ return 1;
+ }
+ verify = *vy;
+ } else if (auto val = ParseNamedArgument({"--textcache=", "-tc"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ text_cache_filename = *val;
} else {
Inode maybe_inode{};
@@ -94,10 +307,23 @@ int main(int argc, char** argv) {
root_directories.push_back(".");
}
- if (inode_list.size() == 0) {
- DCHECK_EQ(true, false);
- std::cerr << "Provide at least one inode." << std::endl;
+ if (inode_list.size() == 0 && !all) {
+ std::cerr << "Provide at least one inode. Or use --all to dump everything." << std::endl;
return 1;
+ } else if (all && inode_list.size() > 0) {
+ std::cerr << "[WARNING]: --all flag ignores all inodes passed on command line." << std::endl;
+ }
+
+ std::ofstream fout;
+ if (output_filename) {
+ fout.open(*output_filename);
+ if (!fout) {
+ std::cerr << "Failed to open output file for writing: \"" << *output_filename << "\"";
+ return 1;
+ }
+ } else {
+ fout.open("/dev/null"); // have to open *something* otherwise rdbuf fails.
+ fout.basic_ios<char>::rdbuf(std::cout.rdbuf());
}
if (enable_verbose) {
@@ -109,7 +335,12 @@ int main(int argc, char** argv) {
for (auto& inode_num : inode_list) {
LOG(VERBOSE) << "Searching for inode " << inode_num;
}
+
+ LOG(VERBOSE) << "Dumping all inodes? " << all;
}
+ // else use
+ // $> ANDROID_LOG_TAGS='*:d' iorap.inode2filename <args>
+ // which will enable arbitrary log levels.
// Useful to attach a debugger...
// 1) $> inode2filename -w <args>
@@ -120,33 +351,90 @@ int main(int argc, char** argv) {
std::cin >> wait_for_keystroke;
}
- fruit::Injector<SearchDirectories> injector(GetSearchDirectoriesComponent);
- SearchDirectories* search_directories = injector.get<SearchDirectories*>();
+ fruit::Injector<SystemCall> injector(GetSystemCallComponent);
+
+ InodeResolverDependencies ir_dependencies;
+ // Passed from command-line.
+ ir_dependencies.data_source = data_source;
+ ir_dependencies.process_mode = process_mode;
+ ir_dependencies.root_directories = root_directories;
+ ir_dependencies.text_cache_filename = text_cache_filename;
+ ir_dependencies.verify = verify;
+ // Hardcoded.
+ ir_dependencies.system_call = injector.get<SystemCall*>();
+
+ std::shared_ptr<InodeResolver> inode_resolver =
+ InodeResolver::Create(ir_dependencies);
- auto/*observable[2]*/ [inode_results, connectable] =
- search_directories->FindFilenamesFromInodesPair(
- std::move(root_directories),
- std::move(inode_list),
- SearchMode::kInProcessDirect);
+ inode_resolver->StartRecording();
+ sleep(recording_time_sec); // TODO: add cli flag for this when we add something that needs it.
+ inode_resolver->StopRecording();
- int return_code = 1;
- inode_results.subscribe([&return_code](const InodeResult& result) {
+ auto/*observable<InodeResult>*/ inode_results = all
+ ? inode_resolver->EmitAll()
+ : inode_resolver->FindFilenamesFromInodes(std::move(inode_list));
+
+ int return_code = 2;
+ inode_results.subscribe(
+ /*on_next*/[&return_code, output_format, &fout](const InodeResult& result) {
if (result) {
- LOG(DEBUG) << "Inode match: " << result.inode << ", " << result.data.value();
- std::cout << "Inode match: " << result.inode << ", " << result.data.value() << std::endl;
+ LOG(DEBUG) << "Inode match: " << result;
+ if (output_format == OutputFormatKind::kLog) {
+ fout << "\033[1;32m[OK]\033[0m "
+ << result.inode
+ << " \"" << result.data.value() << "\"" << std::endl;
+ } else if (output_format == OutputFormatKind::kIpc) {
+ fout << "K "
+ << result.inode
+ << " " << result.data.value() << std::endl;
+ } else if (output_format == OutputFormatKind::kTextCache) {
+ // Same format as TextCacheDataSource (system/extras/pagecache/pagecache.py -d)
+ // "$device_number $inode $filesize $filename..."
+ const Inode& inode = result.inode;
+ fout << inode.GetDevice() << " "
+ << inode.GetInode()
+ << " -1 " // always -1 for filesize, since we don't track what it is.
+ << result.data.value() << "\n"; // don't use endl which flushes, for faster writes.
+ } else {
+ LOG(FATAL) << "Not implemented this kind of --output-format";
+ }
+
return_code = 0;
} else {
- LOG(WARNING) << "Failed to match inode: " << result.inode;
+ LOG(DEBUG) << "Failed to match inode: " << result;
+ if (output_format == OutputFormatKind::kLog) {
+ fout << "\033[1;31m[ERR]\033[0m "
+ << result.inode
+ << " '" << *result.ErrorMessage() << "'" << std::endl;
+ } else if (output_format == OutputFormatKind::kIpc) {
+ fout << "E "
+ << result.inode
+ << " " << result.data.error() << std::endl;
+ }
+ else if (output_format == OutputFormatKind::kTextCache) {
+ // Don't add bad results to the textcache. They are dropped.
+ } else {
+ LOG(FATAL) << "Not implemented this kind of --output-format";
+ }
}
+ }, /*on_error*/[&return_code](rxcpp::util::error_ptr error) {
+ // Usually occurs very early on before we see the first result.
+ // In this case the error is terminal so we just end up exiting out soon.
+ return_code = 3;
+ LOG(ERROR) << "Critical error: " << rxcpp::util::what(error);
});
- // Normally #subscribe would start emitting items immediately, but this does nothing yet
- // because one of the nodes in the flow graph was published. Published streams make the entire
- // downstream inert until #connect is called.
- connectable->connect();
-
- // 0 -> found at least a single match, 1 -> could not find any matches.
+ // 0 -> found at least a single match,
+ // 1 -> bad parameters,
+ // 2 -> could not find any matches,
+ // 3 -> rxcpp on_error.
return return_code;
}
+} // namespace iorap::inode2filename
+
+int main(int argc, char** argv) {
+ return ::iorap::inode2filename::main(argc, argv);
+}
+
#endif