diff options
author | 2017-09-15 02:04:53 +0000 | |
---|---|---|
committer | 2017-09-15 02:04:53 +0000 | |
commit | 163a64a339e90320bf6134035ffb7d1f12565fcc (patch) | |
tree | 4d82904f43d9fec45d819380476dbd6b65884c22 | |
parent | 655da335700eda383b2f99c8a90c72a4dc1e0a9d (diff) | |
parent | 880df0f7440e7a5897907d85e433abc8381e23ce (diff) |
Merge changes from topic "lshal_released" am: 757fd44b93
am: 880df0f744
Change-Id: Iff081ead4bb8dc83c2ba982627edacd22dc007dd
-rw-r--r-- | cmds/lshal/Android.bp | 1 | ||||
-rw-r--r-- | cmds/lshal/Command.h | 52 | ||||
-rw-r--r-- | cmds/lshal/DebugCommand.cpp | 27 | ||||
-rw-r--r-- | cmds/lshal/DebugCommand.h | 14 | ||||
-rw-r--r-- | cmds/lshal/HelpCommand.cpp | 75 | ||||
-rw-r--r-- | cmds/lshal/HelpCommand.h | 48 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.cpp | 319 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.h | 45 | ||||
-rw-r--r-- | cmds/lshal/Lshal.cpp | 148 | ||||
-rw-r--r-- | cmds/lshal/Lshal.h | 13 | ||||
-rw-r--r-- | cmds/lshal/TableEntry.cpp | 69 | ||||
-rw-r--r-- | cmds/lshal/test.cpp | 68 | ||||
-rw-r--r-- | cmds/lshal/utils.h | 6 |
13 files changed, 628 insertions, 257 deletions
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index 47814688a9..5a87505113 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -25,6 +25,7 @@ cc_library_shared { ], srcs: [ "DebugCommand.cpp", + "HelpCommand.cpp", "Lshal.cpp", "ListCommand.cpp", "PipeRelay.cpp", diff --git a/cmds/lshal/Command.h b/cmds/lshal/Command.h new file mode 100644 index 0000000000..4f128ab56b --- /dev/null +++ b/cmds/lshal/Command.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FRAMEWORK_NATIVE_CMDS_LSHAL_COMMAND_H_ +#define FRAMEWORK_NATIVE_CMDS_LSHAL_COMMAND_H_ + +#include "utils.h" + +namespace android { +namespace lshal { + +class Lshal; + +// Base class for all *Commands +class Command { +public: + Command(Lshal& lshal) : mLshal(lshal) {} + virtual ~Command() = default; + // Expect optind to be set by Lshal::main and points to the next argument + // to process. + virtual Status main(const Arg &arg) = 0; + + virtual void usage() const = 0; + + // e.g. "list" + virtual std::string getName() const = 0; + + // e.g. "list HALs" + virtual std::string getSimpleDescription() const = 0; + +protected: + Lshal& mLshal; +}; + + +} // namespace lshal +} // namespace android + +#endif // FRAMEWORK_NATIVE_CMDS_LSHAL_LIST_COMMAND_H_ diff --git a/cmds/lshal/DebugCommand.cpp b/cmds/lshal/DebugCommand.cpp index 672cad67cb..622f866617 100644 --- a/cmds/lshal/DebugCommand.cpp +++ b/cmds/lshal/DebugCommand.cpp @@ -21,12 +21,16 @@ namespace android { namespace lshal { -DebugCommand::DebugCommand(Lshal &lshal) : mLshal(lshal) { +std::string DebugCommand::getName() const { + return "debug"; } -Status DebugCommand::parseArgs(const std::string &command, const Arg &arg) { +std::string DebugCommand::getSimpleDescription() const { + return "Debug a specified HAL."; +} + +Status DebugCommand::parseArgs(const Arg &arg) { if (optind >= arg.argc) { - mLshal.usage(command); return USAGE; } mInterfaceName = arg.argv[optind]; @@ -37,8 +41,8 @@ Status DebugCommand::parseArgs(const std::string &command, const Arg &arg) { return OK; } -Status DebugCommand::main(const std::string &command, const Arg &arg) { - Status status = parseArgs(command, arg); +Status DebugCommand::main(const Arg &arg) { + Status status = parseArgs(arg); if (status != OK) { return status; } @@ -49,6 +53,19 @@ Status DebugCommand::main(const std::string &command, const Arg &arg) { mLshal.err()); } +void DebugCommand::usage() const { + + static const std::string debug = + "debug:\n" + " lshal debug <interface> [options [options [...]]] \n" + " Print debug information of a specified interface.\n" + " <inteface>: Format is `android.hardware.foo@1.0::IFoo/default`.\n" + " If instance name is missing `default` is used.\n" + " options: space separated options to IBase::debug.\n"; + + mLshal.err() << debug; +} + } // namespace lshal } // namespace android diff --git a/cmds/lshal/DebugCommand.h b/cmds/lshal/DebugCommand.h index fa0f0faafb..9b91084bb8 100644 --- a/cmds/lshal/DebugCommand.h +++ b/cmds/lshal/DebugCommand.h @@ -21,6 +21,7 @@ #include <android-base/macros.h> +#include "Command.h" #include "utils.h" namespace android { @@ -28,14 +29,17 @@ namespace lshal { class Lshal; -class DebugCommand { +class DebugCommand : public Command { public: - DebugCommand(Lshal &lshal); - Status main(const std::string &command, const Arg &arg); + DebugCommand(Lshal &lshal) : Command(lshal) {} + ~DebugCommand() = default; + Status main(const Arg &arg) override; + void usage() const override; + std::string getSimpleDescription() const override; + std::string getName() const override; private: - Status parseArgs(const std::string &command, const Arg &arg); + Status parseArgs(const Arg &arg); - Lshal &mLshal; std::string mInterfaceName; std::vector<std::string> mOptions; diff --git a/cmds/lshal/HelpCommand.cpp b/cmds/lshal/HelpCommand.cpp new file mode 100644 index 0000000000..6773ace6d2 --- /dev/null +++ b/cmds/lshal/HelpCommand.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HelpCommand.h" + +#include "Lshal.h" + +namespace android { +namespace lshal { + +std::string HelpCommand::GetName() { + return "help"; +} + +std::string HelpCommand::getSimpleDescription() const { + return "Print help message."; +} + +Status HelpCommand::main(const Arg &arg) { + if (optind >= arg.argc) { + // `lshal help` prints global usage. + mLshal.usage(); + return OK; + } + (void)usageOfCommand(arg.argv[optind]); + return OK; +} + +Status HelpCommand::usageOfCommand(const std::string& c) const { + if (c.empty()) { + mLshal.usage(); + return USAGE; + } + auto command = mLshal.selectCommand(c); + if (command == nullptr) { + // from HelpCommand::main, `lshal help unknown` + mLshal.usage(); + return USAGE; + } + + command->usage(); + return USAGE; + +} + +void HelpCommand::usage() const { + mLshal.err() + << "help:" << std::endl + << " lshal -h" << std::endl + << " lshal --help" << std::endl + << " lshal help" << std::endl + << " Print this help message" << std::endl; + mLshal.forEachCommand([&](const Command* e) { + mLshal.err() << " lshal help " << e->getName() << std::endl + << " Print help message for " << e->getName() << std::endl; + }); + +} + +} // namespace lshal +} // namespace android + diff --git a/cmds/lshal/HelpCommand.h b/cmds/lshal/HelpCommand.h new file mode 100644 index 0000000000..cc709f8ee4 --- /dev/null +++ b/cmds/lshal/HelpCommand.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_ +#define FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_ + +#include <string> + +#include <android-base/macros.h> + +#include "Command.h" +#include "utils.h" + +namespace android { +namespace lshal { + +class Lshal; + +class HelpCommand : public Command { +public: + HelpCommand(Lshal &lshal) : Command(lshal) {} + ~HelpCommand() = default; + Status main(const Arg &arg) override; + void usage() const override; + std::string getSimpleDescription() const override; + std::string getName() const override { return GetName(); } + static std::string GetName(); + Status usageOfCommand(const std::string& c) const; +}; + + +} // namespace lshal +} // namespace android + +#endif // FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_ diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp index 4550e410a0..8b59fb867d 100644 --- a/cmds/lshal/ListCommand.cpp +++ b/cmds/lshal/ListCommand.cpp @@ -44,9 +44,6 @@ using ::android::hidl::manager::V1_0::IServiceManager; namespace android { namespace lshal { -ListCommand::ListCommand(Lshal &lshal) : mLshal(lshal) { -} - NullableOStream<std::ostream> ListCommand::out() const { return mLshal.out(); } @@ -55,6 +52,13 @@ NullableOStream<std::ostream> ListCommand::err() const { return mLshal.err(); } +std::string ListCommand::GetName() { + return "list"; +} +std::string ListCommand::getSimpleDescription() const { + return "List HALs."; +} + std::string ListCommand::parseCmdline(pid_t pid) const { std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline"); std::string cmdline; @@ -607,143 +611,192 @@ Status ListCommand::fetch() { return status; } -Status ListCommand::parseArgs(const std::string &command, 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; +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) { + + 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; int optionIndex; 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; - mLshal.usage(command); - 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': // falls through - default: // see unrecognized options - mLshal.usage(command); + + 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 - err() << "Unrecognized option `" << arg.argv[optind] << "`" << std::endl; - mLshal.usage(command); + err() << "unrecognized option `" << arg.argv[optind] << "'" << std::endl; return USAGE; } if (mNeat && mEmitDebugInfo) { err() << "Error: --neat should not be used with --debug." << std::endl; - mLshal.usage(command); 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; } -Status ListCommand::main(const std::string &command, const Arg &arg) { - Status status = parseArgs(command, arg); +Status ListCommand::main(const Arg &arg) { + Status status = parseArgs(arg); if (status != OK) { return status; } @@ -753,6 +806,66 @@ Status ListCommand::main(const std::string &command, 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 { + + 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 } // namespace android diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h index 9833d43de5..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> @@ -26,6 +27,7 @@ #include <android-base/macros.h> #include <android/hidl/manager/1.0/IServiceManager.h> +#include "Command.h" #include "NullableOStream.h" #include "TableEntry.h" #include "TextTable.h" @@ -42,13 +44,39 @@ struct PidInfo { uint32_t threadCount; // number of threads total }; -class ListCommand { +class ListCommand : public Command { public: - ListCommand(Lshal &lshal); + ListCommand(Lshal &lshal) : Command(lshal) {} virtual ~ListCommand() = default; - Status main(const std::string &command, const Arg &arg); + Status main(const Arg &arg) override; + void usage() const override; + std::string getSimpleDescription() const override; + 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 std::string &command, const Arg &arg); + Status parseArgs(const Arg &arg); Status fetch(); void postprocess(); Status dump(); @@ -79,7 +107,7 @@ protected: NullableOStream<std::ostream> err() const; NullableOStream<std::ostream> out() const; - Lshal &mLshal; + void registerAllOptions(); Table mServicesTable{}; Table mPassthroughRefTable{}; @@ -101,6 +129,13 @@ 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); }; diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp index da45d65e4a..c6f28ac502 100644 --- a/cmds/lshal/Lshal.cpp +++ b/cmds/lshal/Lshal.cpp @@ -34,9 +34,8 @@ namespace lshal { using ::android::hidl::manager::V1_0::IServiceManager; Lshal::Lshal() - : mOut(std::cout), mErr(std::cerr), - mServiceManager(::android::hardware::defaultServiceManager()), - mPassthroughManager(::android::hardware::getPassthroughServiceManager()) { + : Lshal(std::cout, std::cerr, ::android::hardware::defaultServiceManager(), + ::android::hardware::getPassthroughServiceManager()) { } Lshal::Lshal(std::ostream &out, std::ostream &err, @@ -46,78 +45,39 @@ Lshal::Lshal(std::ostream &out, std::ostream &err, mServiceManager(serviceManager), mPassthroughManager(passthroughManager) { + mRegisteredCommands.push_back({std::make_unique<ListCommand>(*this)}); + mRegisteredCommands.push_back({std::make_unique<DebugCommand>(*this)}); + mRegisteredCommands.push_back({std::make_unique<HelpCommand>(*this)}); } -void Lshal::usage(const std::string &command) const { - static const std::string helpSummary = - "lshal: List and debug HALs.\n" - "\n" - "commands:\n" - " help Print help message\n" - " list list HALs\n" - " debug debug a specified HAL\n" - "\n" - "If no command is specified, `list` is the default.\n"; - - 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"; - - static const std::string debug = - "debug:\n" - " lshal debug <interface> [options [options [...]]] \n" - " Print debug information of a specified interface.\n" - " <inteface>: Format is `android.hardware.foo@1.0::IFoo/default`.\n" - " If instance name is missing `default` is used.\n" - " options: space separated options to IBase::debug.\n"; - - static const std::string help = - "help:\n" - " lshal -h\n" - " lshal --help\n" - " lshal help\n" - " Print this help message\n" - " lshal help list\n" - " Print help message for list\n" - " lshal help debug\n" - " Print help message for debug\n"; - - if (command == "list") { - err() << list; - return; - } - if (command == "debug") { - err() << debug; - return; - } +void Lshal::forEachCommand(const std::function<void(const Command* c)>& f) const { + for (const auto& e : mRegisteredCommands) f(e.get()); +} - err() << helpSummary << "\n" << list << "\n" << debug << "\n" << help; +void Lshal::usage() { + err() << "lshal: List and debug HALs." << std::endl << std::endl + << "commands:" << std::endl; + + size_t nameMaxLength = 0; + forEachCommand([&](const Command* e) { + nameMaxLength = std::max(nameMaxLength, e->getName().length()); + }); + bool first = true; + forEachCommand([&](const Command* e) { + if (!first) err() << std::endl; + first = false; + err() << " " << std::left << std::setw(nameMaxLength + 8) << e->getName() + << e->getSimpleDescription(); + }); + err() << std::endl << "If no command is specified, `" << ListCommand::GetName() + << "` is the default." << std::endl << std::endl; + + first = true; + forEachCommand([&](const Command* e) { + if (!first) err() << std::endl; + first = false; + e->usage(); + }); } // A unique_ptr type using a custom deleter function. @@ -188,26 +148,24 @@ Status Lshal::emitDebugInfo( } Status Lshal::parseArgs(const Arg &arg) { - static std::set<std::string> sAllCommands{"list", "debug", "help"}; optind = 1; if (optind >= arg.argc) { // no options at all. return OK; } mCommand = arg.argv[optind]; - if (sAllCommands.find(mCommand) != sAllCommands.end()) { + if (selectCommand(mCommand) != nullptr) { ++optind; return OK; // mCommand is set correctly } if (mCommand.size() > 0 && mCommand[0] == '-') { // first argument is an option, set command to "" (which is recognized as "list") - mCommand = ""; + mCommand.clear(); return OK; } - err() << arg.argv[0] << ": unrecognized option `" << arg.argv[optind] << "`" << std::endl; - usage(); + err() << arg.argv[0] << ": unrecognized option `" << arg.argv[optind] << "'" << std::endl; return USAGE; } @@ -218,27 +176,43 @@ void signalHandler(int sig) { } } +Command* Lshal::selectCommand(const std::string& command) const { + if (command.empty()) { + return selectCommand(ListCommand::GetName()); + } + for (const auto& e : mRegisteredCommands) { + if (e->getName() == command) { + return e.get(); + } + } + return nullptr; +} + Status Lshal::main(const Arg &arg) { // Allow SIGINT to terminate all threads. signal(SIGINT, signalHandler); Status status = parseArgs(arg); if (status != OK) { + usage(); return status; } - if (mCommand == "help") { - usage(optind < arg.argc ? arg.argv[optind] : ""); + auto c = selectCommand(mCommand); + if (c == nullptr) { + // unknown command, print global usage + usage(); return USAGE; } - // Default command is list - if (mCommand == "list" || mCommand == "") { - return ListCommand{*this}.main(mCommand, arg); - } - if (mCommand == "debug") { - return DebugCommand{*this}.main(mCommand, arg); + status = c->main(arg); + if (status == USAGE) { + // bad options. Run `lshal help ${mCommand}` instead. + // For example, `lshal --unknown-option` becomes `lshal help` (prints global help) + // and `lshal list --unknown-option` becomes `lshal help list` + auto&& help = selectCommand(HelpCommand::GetName()); + return static_cast<HelpCommand*>(help)->usageOfCommand(mCommand); } - usage(); - return USAGE; + + return status; } NullableOStream<std::ostream> Lshal::err() const { diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h index d3cc4e20a7..9f8eeaab37 100644 --- a/cmds/lshal/Lshal.h +++ b/cmds/lshal/Lshal.h @@ -24,6 +24,8 @@ #include <android/hidl/manager/1.0/IServiceManager.h> #include <utils/StrongPointer.h> +#include "Command.h" +#include "HelpCommand.h" #include "NullableOStream.h" #include "utils.h" @@ -38,7 +40,8 @@ public: sp<hidl::manager::V1_0::IServiceManager> serviceManager, sp<hidl::manager::V1_0::IServiceManager> passthroughManager); Status main(const Arg &arg); - void usage(const std::string &command = "") const; + // global usage + void usage(); virtual NullableOStream<std::ostream> err() const; virtual NullableOStream<std::ostream> out() const; const sp<hidl::manager::V1_0::IServiceManager> &serviceManager() const; @@ -50,8 +53,14 @@ public: const std::vector<std::string> &options, std::ostream &out, NullableOStream<std::ostream> err) const; + + Command* selectCommand(const std::string& command) const; + + void forEachCommand(const std::function<void(const Command* c)>& f) const; + private: Status parseArgs(const Arg &arg); + std::string mCommand; Arg mCmdArgs; NullableOStream<std::ostream> mOut; @@ -60,6 +69,8 @@ private: sp<hidl::manager::V1_0::IServiceManager> mServiceManager; sp<hidl::manager::V1_0::IServiceManager> mPassthroughManager; + std::vector<std::unique_ptr<Command>> mRegisteredCommands; + DISALLOW_COPY_AND_ASSIGN(Lshal); }; diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp index eac0f2144a..cbcf979f90 100644 --- a/cmds/lshal/TableEntry.cpp +++ b/cmds/lshal/TableEntry.cpp @@ -44,73 +44,44 @@ static const std::string &getArchString(Architecture arch) { static std::string getTitle(TableColumnType type) { switch (type) { - case TableColumnType::INTERFACE_NAME: { - return "Interface"; - } break; - case TableColumnType::TRANSPORT: { - return "Transport"; - } break; - case TableColumnType::SERVER_PID: { - return "Server"; - } break; - case TableColumnType::SERVER_CMD: { - return "Server CMD"; - } - case TableColumnType::SERVER_ADDR: { - return "PTR"; - } break; - case TableColumnType::CLIENT_PIDS: { - return "Clients"; - } break; - case TableColumnType::CLIENT_CMDS: { - return "Clients CMD"; - } break; - case TableColumnType::ARCH: { - return "Arch"; - } break; - case TableColumnType::THREADS: { - return "Thread Use"; - } break; - default: { + case TableColumnType::INTERFACE_NAME: return "Interface"; + case TableColumnType::TRANSPORT: return "Transport"; + case TableColumnType::SERVER_PID: return "Server"; + case TableColumnType::SERVER_CMD: return "Server CMD"; + case TableColumnType::SERVER_ADDR: return "PTR"; + case TableColumnType::CLIENT_PIDS: return "Clients"; + case TableColumnType::CLIENT_CMDS: return "Clients CMD"; + case TableColumnType::ARCH: return "Arch"; + case TableColumnType::THREADS: return "Thread Use"; + default: LOG(FATAL) << "Should not reach here."; return ""; - } } } std::string TableEntry::getField(TableColumnType type) const { switch (type) { - case TableColumnType::INTERFACE_NAME: { + case TableColumnType::INTERFACE_NAME: return interfaceName; - } break; - case TableColumnType::TRANSPORT: { + case TableColumnType::TRANSPORT: return transport; - } break; - case TableColumnType::SERVER_PID: { + case TableColumnType::SERVER_PID: return serverPid == NO_PID ? "N/A" : std::to_string(serverPid); - } break; - case TableColumnType::SERVER_CMD: { + case TableColumnType::SERVER_CMD: return serverCmdline; - } break; - case TableColumnType::SERVER_ADDR: { + case TableColumnType::SERVER_ADDR: return serverObjectAddress == NO_PTR ? "N/A" : toHexString(serverObjectAddress); - } break; - case TableColumnType::CLIENT_PIDS: { + case TableColumnType::CLIENT_PIDS: return join(clientPids, " "); - } break; - case TableColumnType::CLIENT_CMDS: { + case TableColumnType::CLIENT_CMDS: return join(clientCmdlines, ";"); - } break; - case TableColumnType::ARCH: { + case TableColumnType::ARCH: return getArchString(arch); - } break; - case TableColumnType::THREADS: { + case TableColumnType::THREADS: return getThreadUsage(); - } break; - default: { + default: LOG(FATAL) << "Should not reach here."; return ""; - } } } diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp index 44b196e11f..06b68199cd 100644 --- a/cmds/lshal/test.cpp +++ b/cmds/lshal/test.cpp @@ -183,8 +183,8 @@ class MockListCommand : public ListCommand { public: MockListCommand(Lshal* lshal) : ListCommand(*lshal) {} - Status parseArgs(const Arg& arg) { return ListCommand::parseArgs("", arg); } - Status main(const Arg& arg) { return ListCommand::main("", arg); } + Status parseArgs(const Arg& arg) { return ListCommand::parseArgs(arg); } + Status main(const Arg& arg) { return ListCommand::main(arg); } void forEachTable(const std::function<void(const Table &)> &f) const { return ListCommand::forEachTable(f); } @@ -528,6 +528,70 @@ TEST_F(ListTest, DumpNeat) { EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } + +class HelpTest : public ::testing::Test { +public: + void SetUp() override { + lshal = std::make_unique<Lshal>(out, err, new MockServiceManager() /* serviceManager */, + new MockServiceManager() /* passthruManager */); + } + + std::stringstream err; + std::stringstream out; + std::unique_ptr<Lshal> lshal; +}; + +TEST_F(HelpTest, GlobalUsage) { + (void)callMain(lshal, {"lshal", "--help"}); // ignore return + std::string errStr = err.str(); + EXPECT_THAT(errStr, ContainsRegex("(^|\n)commands:($|\n)")) + << "`lshal --help` does not contain global usage"; + EXPECT_THAT(errStr, ContainsRegex("(^|\n)list:($|\n)")) + << "`lshal --help` does not contain usage for 'list' command"; + EXPECT_THAT(errStr, ContainsRegex("(^|\n)debug:($|\n)")) + << "`lshal --help` does not contain usage for 'debug' command"; + EXPECT_THAT(errStr, ContainsRegex("(^|\n)help:($|\n)")) + << "`lshal --help` does not contain usage for 'help' command"; + + err.str(""); + (void)callMain(lshal, {"lshal", "help"}); // ignore return + EXPECT_EQ(errStr, err.str()) << "`lshal help` should have the same output as `lshal --help`"; + + err.str(""); + EXPECT_NE(0u, callMain(lshal, {"lshal", "--unknown-option"})); + EXPECT_THAT(err.str(), ContainsRegex("unrecognized option")); + EXPECT_THAT(err.str(), EndsWith(errStr)) + << "`lshal --unknown-option` should have the same output as `lshal --help`"; + EXPECT_EQ("", out.str()); +} + +TEST_F(HelpTest, UnknownOptionList1) { + (void)callMain(lshal, {"lshal", "help", "list"}); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)list:($|\n)")) + << "`lshal help list` does not contain usage for 'list' command"; +} + +TEST_F(HelpTest, UnknownOptionList2) { + EXPECT_NE(0u, callMain(lshal, {"lshal", "list", "--unknown-option"})); + EXPECT_THAT(err.str(), ContainsRegex("unrecognized option")); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)list:($|\n)")) + << "`lshal list --unknown-option` does not contain usage for 'list' command"; + EXPECT_EQ("", out.str()); +} + +TEST_F(HelpTest, UnknownOptionHelp1) { + (void)callMain(lshal, {"lshal", "help", "help"}); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)help:($|\n)")) + << "`lshal help help` does not contain usage for 'help' command"; +} + +TEST_F(HelpTest, UnknownOptionHelp2) { + (void)callMain(lshal, {"lshal", "help", "--unknown-option"}); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)help:($|\n)")) + << "`lshal help --unknown-option` does not contain usage for 'help' command"; + EXPECT_EQ("", out.str()); +} + } // namespace lshal } // namespace android diff --git a/cmds/lshal/utils.h b/cmds/lshal/utils.h index 45b922cc03..7eca14e0f7 100644 --- a/cmds/lshal/utils.h +++ b/cmds/lshal/utils.h @@ -29,14 +29,20 @@ namespace lshal { enum : unsigned int { OK = 0, + // Return to Lshal::main to print help info. USAGE = 1 << 0, + // no service managers NO_BINDERIZED_MANAGER = 1 << 1, NO_PASSTHROUGH_MANAGER = 1 << 2, + // general error in getting information from the three sources DUMP_BINDERIZED_ERROR = 1 << 3, DUMP_PASSTHROUGH_ERROR = 1 << 4, DUMP_ALL_LIBS_ERROR = 1 << 5, + // I/O error in reading files IO_ERROR = 1 << 6, + // Interface does not exist (IServiceManager::get fails) NO_INTERFACE = 1 << 7, + // Transaction error from hwbinder transactions TRANSACTION_ERROR = 1 << 8, }; using Status = unsigned int; |