diff options
author | 2017-09-13 16:53:37 -0700 | |
---|---|---|
committer | 2017-09-14 13:40:27 -0700 | |
commit | a6b93f0d61201b0318b65a8ffeb2b9cfeea0c9ea (patch) | |
tree | 0fdcdaa5d03960916ee32cf7b073b13d1c4c7534 | |
parent | 795b6ec89541879d7c11d20842ab5e854bfc4caa (diff) |
lshal: register options for ListCommand.
ListCommand parseArgs() and usage() uses a list of RegisteredOption's
instead of hardcoding all options.
Test: lshal_test
Change-Id: I94418b22791fbff7a17756c4266252a76570fb84
-rw-r--r-- | cmds/lshal/ListCommand.cpp | 322 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.h | 30 |
2 files changed, 227 insertions, 125 deletions
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp index c7a41a3ba1..8b59fb867d 100644 --- a/cmds/lshal/ListCommand.cpp +++ b/cmds/lshal/ListCommand.cpp @@ -611,29 +611,119 @@ Status ListCommand::fetch() { return status; } +void ListCommand::registerAllOptions() { + int v = mOptions.size(); + // A list of acceptable command line options + // key: value returned by getopt_long + // long options with short alternatives + mOptions.push_back({'h', "help", no_argument, v++, [](ListCommand*, const char*) { + return USAGE; + }, ""}); + mOptions.push_back({'i', "interface", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mSelectedColumns.push_back(TableColumnType::INTERFACE_NAME); + return OK; + }, "print the instance name column"}); + mOptions.push_back({'t', "transport", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mSelectedColumns.push_back(TableColumnType::TRANSPORT); + return OK; + }, "print the transport mode column"}); + mOptions.push_back({'r', "arch", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mSelectedColumns.push_back(TableColumnType::ARCH); + return OK; + }, "print the bitness column"}); + mOptions.push_back({'p', "pid", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mSelectedColumns.push_back(TableColumnType::SERVER_PID); + return OK; + }, "print the server PID, or server cmdline if -m is set"}); + mOptions.push_back({'a', "address", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mSelectedColumns.push_back(TableColumnType::SERVER_ADDR); + return OK; + }, "print the server object address column"}); + mOptions.push_back({'c', "clients", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mSelectedColumns.push_back(TableColumnType::CLIENT_PIDS); + return OK; + }, "print the client PIDs, or client cmdlines if -m is set"}); + mOptions.push_back({'e', "threads", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mSelectedColumns.push_back(TableColumnType::THREADS); + return OK; + }, "print currently used/available threads\n(note, available threads created lazily)"}); + mOptions.push_back({'m', "cmdline", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mEnableCmdlines = true; + return OK; + }, "print cmdline instead of PIDs"}); + mOptions.push_back({'d', "debug", optional_argument, v++, [](ListCommand* thiz, const char* arg) { + thiz->mEmitDebugInfo = true; + if (arg) thiz->mFileOutputPath = arg; + return OK; + }, "Emit debug info from\nIBase::debug with empty options. Cannot be used with --neat.\n" + "Writes to specified file if 'arg' is provided, otherwise stdout."}); + + // long options without short alternatives + mOptions.push_back({'\0', "init-vintf", no_argument, v++, [](ListCommand* thiz, const char* arg) { + thiz->mVintf = true; + if (arg) thiz->mFileOutputPath = arg; + return OK; + }, "form a skeleton HAL manifest to specified file,\nor stdout if no file specified."}); + mOptions.push_back({'\0', "sort", required_argument, v++, [](ListCommand* thiz, const char* arg) { + if (strcmp(arg, "interface") == 0 || strcmp(arg, "i") == 0) { + thiz->mSortColumn = TableEntry::sortByInterfaceName; + } else if (strcmp(arg, "pid") == 0 || strcmp(arg, "p") == 0) { + thiz->mSortColumn = TableEntry::sortByServerPid; + } else { + thiz->err() << "Unrecognized sorting column: " << arg << std::endl; + return USAGE; + } + return OK; + }, "sort by a column. 'arg' can be (i|interface) or (p|pid)."}); + mOptions.push_back({'\0', "neat", no_argument, v++, [](ListCommand* thiz, const char*) { + thiz->mNeat = true; + return OK; + }, "output is machine parsable (no explanatory text).\nCannot be used with --debug."}); +} + +// Create 'longopts' argument to getopt_long. Caller is responsible for maintaining +// the lifetime of "options" during the usage of the returned array. +static std::unique_ptr<struct option[]> getLongOptions( + const ListCommand::RegisteredOptions& options, + int* longOptFlag) { + std::unique_ptr<struct option[]> ret{new struct option[options.size() + 1]}; + int i = 0; + for (const auto& e : options) { + ret[i].name = e.longOption.c_str(); + ret[i].has_arg = e.hasArg; + ret[i].flag = longOptFlag; + ret[i].val = e.val; + + i++; + } + // getopt_long last option has all zeros + ret[i].name = NULL; + ret[i].has_arg = 0; + ret[i].flag = NULL; + ret[i].val = 0; + + return ret; +} + +// Create 'optstring' argument to getopt_long. +static std::string getShortOptions(const ListCommand::RegisteredOptions& options) { + std::stringstream ss; + for (const auto& e : options) { + if (e.shortOption != '\0') { + ss << e.shortOption; + } + } + return ss.str(); +} + Status ListCommand::parseArgs(const Arg &arg) { - static struct option longOptions[] = { - // long options with short alternatives - {"help", no_argument, 0, 'h' }, - {"interface", no_argument, 0, 'i' }, - {"transport", no_argument, 0, 't' }, - {"arch", no_argument, 0, 'r' }, - {"pid", no_argument, 0, 'p' }, - {"address", no_argument, 0, 'a' }, - {"clients", no_argument, 0, 'c' }, - {"threads", no_argument, 0, 'e' }, - {"cmdline", no_argument, 0, 'm' }, - {"debug", optional_argument, 0, 'd' }, - - // long options without short alternatives - {"sort", required_argument, 0, 's' }, - {"init-vintf",optional_argument, 0, 'v' }, - {"neat", no_argument, 0, 'n' }, - { 0, 0, 0, 0 } - }; - - std::vector<TableColumnType> selectedColumns; - bool enableCmdlines = false; + + if (mOptions.empty()) { + registerAllOptions(); + } + int longOptFlag; + std::unique_ptr<struct option[]> longOptions = getLongOptions(mOptions, &longOptFlag); + std::string shortOptions = getShortOptions(mOptions); // suppress output to std::err for unknown options opterr = 0; @@ -642,77 +732,34 @@ Status ListCommand::parseArgs(const Arg &arg) { int c; // Lshal::parseArgs has set optind to the next option to parse for (;;) { - // using getopt_long in case we want to add other options in the future c = getopt_long(arg.argc, arg.argv, - "hitrpacmde", longOptions, &optionIndex); + shortOptions.c_str(), longOptions.get(), &optionIndex); if (c == -1) { break; } - switch (c) { - case 's': { - if (strcmp(optarg, "interface") == 0 || strcmp(optarg, "i") == 0) { - mSortColumn = TableEntry::sortByInterfaceName; - } else if (strcmp(optarg, "pid") == 0 || strcmp(optarg, "p") == 0) { - mSortColumn = TableEntry::sortByServerPid; - } else { - err() << "Unrecognized sorting column: " << optarg << std::endl; - return USAGE; + const RegisteredOption* found = nullptr; + if (c == 0) { + // see long option + for (const auto& e : mOptions) { + if (longOptFlag == e.val) found = &e; + } + } else { + // see short option + for (const auto& e : mOptions) { + if (c == e.shortOption) found = &e; } - break; - } - case 'v': { - mVintf = true; - if (optarg) mFileOutputPath = optarg; - break; - } - case 'i': { - selectedColumns.push_back(TableColumnType::INTERFACE_NAME); - break; - } - case 't': { - selectedColumns.push_back(TableColumnType::TRANSPORT); - break; - } - case 'r': { - selectedColumns.push_back(TableColumnType::ARCH); - break; - } - case 'p': { - selectedColumns.push_back(TableColumnType::SERVER_PID); - break; - } - case 'a': { - selectedColumns.push_back(TableColumnType::SERVER_ADDR); - break; - } - case 'c': { - selectedColumns.push_back(TableColumnType::CLIENT_PIDS); - break; - } - case 'e': { - selectedColumns.push_back(TableColumnType::THREADS); - break; - } - case 'm': { - enableCmdlines = true; - break; - } - case 'd': { - mEmitDebugInfo = true; - if (optarg) mFileOutputPath = optarg; - break; - } - case 'n': { - mNeat = true; - break; - } - case 'h': { - return USAGE; } - default: // see unrecognized options + + if (found == nullptr) { + // see unrecognized options err() << "unrecognized option `" << arg.argv[optind - 1] << "'" << std::endl; return USAGE; } + + Status status = found->op(this, optarg); + if (status != OK) { + return status; + } } if (optind < arg.argc) { // see non option @@ -725,24 +772,24 @@ Status ListCommand::parseArgs(const Arg &arg) { return USAGE; } - if (selectedColumns.empty()) { - selectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS, + if (mSelectedColumns.empty()) { + mSelectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS, TableColumnType::SERVER_PID, TableColumnType::CLIENT_PIDS}; } - if (enableCmdlines) { - for (size_t i = 0; i < selectedColumns.size(); ++i) { - if (selectedColumns[i] == TableColumnType::SERVER_PID) { - selectedColumns[i] = TableColumnType::SERVER_CMD; + if (mEnableCmdlines) { + for (size_t i = 0; i < mSelectedColumns.size(); ++i) { + if (mSelectedColumns[i] == TableColumnType::SERVER_PID) { + mSelectedColumns[i] = TableColumnType::SERVER_CMD; } - if (selectedColumns[i] == TableColumnType::CLIENT_PIDS) { - selectedColumns[i] = TableColumnType::CLIENT_CMDS; + if (mSelectedColumns[i] == TableColumnType::CLIENT_PIDS) { + mSelectedColumns[i] = TableColumnType::CLIENT_CMDS; } } } - forEachTable([&selectedColumns] (Table& table) { - table.setSelectedColumns(selectedColumns); + forEachTable([this] (Table& table) { + table.setSelectedColumns(this->mSelectedColumns); }); return OK; @@ -759,39 +806,64 @@ Status ListCommand::main(const Arg &arg) { return status; } +static std::vector<std::string> splitString(const std::string &s, char c) { + std::vector<std::string> components; + + size_t startPos = 0; + size_t matchPos; + while ((matchPos = s.find(c, startPos)) != std::string::npos) { + components.push_back(s.substr(startPos, matchPos - startPos)); + startPos = matchPos + 1; + } + + if (startPos <= s.length()) { + components.push_back(s.substr(startPos)); + } + return components; +} + +const std::string& ListCommand::RegisteredOption::getHelpMessageForArgument() const { + static const std::string empty{}; + static const std::string optional{"[=<arg>]"}; + static const std::string required{"=<arg>"}; + + if (hasArg == optional_argument) { + return optional; + } + if (hasArg == required_argument) { + return required; + } + return empty; +} + void ListCommand::usage() const { - static const std::string list = - "list:\n" - " lshal\n" - " lshal list\n" - " List all hals with default ordering and columns (`lshal list -iepc`)\n" - " lshal list [-h|--help]\n" - " -h, --help: Print help message for list (`lshal help list`)\n" - " lshal [list] [--interface|-i] [--transport|-t] [-r|--arch] [-e|--threads]\n" - " [--pid|-p] [--address|-a] [--clients|-c] [--cmdline|-m]\n" - " [--sort={interface|i|pid|p}] [--init-vintf[=<output file>]]\n" - " [--debug|-d[=<output file>]] [--neat]\n" - " -i, --interface: print the interface name column\n" - " -n, --instance: print the instance name column\n" - " -t, --transport: print the transport mode column\n" - " -r, --arch: print if the HAL is in 64-bit or 32-bit\n" - " -e, --threads: print currently used/available threads\n" - " (note, available threads created lazily)\n" - " -p, --pid: print the server PID, or server cmdline if -m is set\n" - " -a, --address: print the server object address column\n" - " -c, --clients: print the client PIDs, or client cmdlines if -m is set\n" - " -m, --cmdline: print cmdline instead of PIDs\n" - " -d[=<output file>], --debug[=<output file>]: emit debug info from \n" - " IBase::debug with empty options. Cannot be used with --neat.\n" - " --sort=i, --sort=interface: sort by interface name\n" - " --sort=p, --sort=pid: sort by server pid\n" - " --neat: output is machine parsable (no explanatory text)\n" - " Cannot be used with --debug.\n" - " --init-vintf[=<output file>]: form a skeleton HAL manifest to specified\n" - " file, or stdout if no file specified.\n"; - - err() << list; + err() << "list:" << std::endl + << " lshal" << std::endl + << " lshal list" << std::endl + << " List all hals with default ordering and columns (`lshal list -iepc`)" << std::endl + << " lshal list [-h|--help]" << std::endl + << " -h, --help: Print help message for list (`lshal help list`)" << std::endl + << " lshal [list] [OPTIONS...]" << std::endl; + for (const auto& e : mOptions) { + if (e.help.empty()) { + continue; + } + err() << " "; + if (e.shortOption != '\0') + err() << "-" << e.shortOption << e.getHelpMessageForArgument(); + if (e.shortOption != '\0' && !e.longOption.empty()) + err() << ", "; + if (!e.longOption.empty()) + err() << "--" << e.longOption << e.getHelpMessageForArgument(); + err() << ": "; + std::vector<std::string> lines = splitString(e.help, '\n'); + for (const auto& line : lines) { + if (&line != &lines.front()) + err() << " "; + err() << line << std::endl; + } + } } } // namespace lshal diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h index 346af28589..5bc834c320 100644 --- a/cmds/lshal/ListCommand.h +++ b/cmds/lshal/ListCommand.h @@ -17,6 +17,7 @@ #ifndef FRAMEWORK_NATIVE_CMDS_LSHAL_LIST_COMMAND_H_ #define FRAMEWORK_NATIVE_CMDS_LSHAL_LIST_COMMAND_H_ +#include <getopt.h> #include <stdint.h> #include <fstream> @@ -53,6 +54,27 @@ public: std::string getName() const override { return GetName(); } static std::string GetName(); + + struct RegisteredOption { + // short alternative, e.g. 'v'. If '\0', no short options is available. + char shortOption; + // long alternative, e.g. 'init-vintf' + std::string longOption; + // no_argument, required_argument or optional_argument + int hasArg; + // value written to 'flag' by getopt_long + int val; + // operation when the argument is present + std::function<Status(ListCommand* thiz, const char* arg)> op; + // help message + std::string help; + + const std::string& getHelpMessageForArgument() const; + }; + // A list of acceptable command line options + // key: value returned by getopt_long + using RegisteredOptions = std::vector<RegisteredOption>; + protected: Status parseArgs(const Arg &arg); Status fetch(); @@ -85,6 +107,8 @@ protected: NullableOStream<std::ostream> err() const; NullableOStream<std::ostream> out() const; + void registerAllOptions(); + Table mServicesTable{}; Table mPassthroughRefTable{}; Table mImplementationsTable{}; @@ -105,6 +129,12 @@ protected: // If an entry exist and not empty, it contains the cached content of /proc/{pid}/cmdline. std::map<pid_t, std::string> mCmdlines; + RegisteredOptions mOptions; + // All selected columns + std::vector<TableColumnType> mSelectedColumns; + // If true, emit cmdlines instead of PIDs + bool mEnableCmdlines = false; + private: DISALLOW_COPY_AND_ASSIGN(ListCommand); }; |