Merge changes from topic "lshal_released"
am: 757fd44b93
Change-Id: Id1459c0576a5b016ed1b83ee482b96b57bfddb1e
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 4781468..5a87505 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -25,6 +25,7 @@
],
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 0000000..4f128ab
--- /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 672cad6..622f866 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 @@
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 @@
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 fa0f0fa..9b91084 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 @@
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 0000000..6773ace
--- /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 0000000..cc709f8
--- /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 4550e41..8b59fb8 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -44,9 +44,6 @@
namespace android {
namespace lshal {
-ListCommand::ListCommand(Lshal &lshal) : mLshal(lshal) {
-}
-
NullableOStream<std::ostream> ListCommand::out() const {
return mLshal.out();
}
@@ -55,6 +52,13 @@
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 @@
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' },
+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
- {"sort", required_argument, 0, 's' },
- {"init-vintf",optional_argument, 0, 'v' },
- {"neat", no_argument, 0, 'n' },
- { 0, 0, 0, 0 }
- };
+ // 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."});
+}
- std::vector<TableColumnType> selectedColumns;
- bool enableCmdlines = false;
+// 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;
}
- break;
+ } else {
+ // see short option
+ for (const auto& e : mOptions) {
+ if (c == e.shortOption) found = &e;
+ }
}
- 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 @@
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 9833d43..5bc834c 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 @@
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 @@
NullableOStream<std::ostream> err() const;
NullableOStream<std::ostream> out() const;
- Lshal &mLshal;
+ void registerAllOptions();
Table mServicesTable{};
Table mPassthroughRefTable{};
@@ -101,6 +129,13 @@
// 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 da45d65..c6f28ac 100644
--- a/cmds/lshal/Lshal.cpp
+++ b/cmds/lshal/Lshal.cpp
@@ -34,9 +34,8 @@
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 @@
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";
+void Lshal::forEachCommand(const std::function<void(const Command* c)>& f) const {
+ for (const auto& e : mRegisteredCommands) f(e.get());
+}
- 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";
+void Lshal::usage() {
+ err() << "lshal: List and debug HALs." << std::endl << std::endl
+ << "commands:" << std::endl;
- 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";
+ 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;
- 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;
- }
-
- err() << helpSummary << "\n" << list << "\n" << debug << "\n" << help;
+ 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::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 @@
}
}
+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);
+ 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);
}
- if (mCommand == "debug") {
- return DebugCommand{*this}.main(mCommand, arg);
- }
- usage();
- return USAGE;
+
+ return status;
}
NullableOStream<std::ostream> Lshal::err() const {
diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h
index d3cc4e2..9f8eeaa 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 @@
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 @@
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 @@
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 eac0f21..cbcf979 100644
--- a/cmds/lshal/TableEntry.cpp
+++ b/cmds/lshal/TableEntry.cpp
@@ -44,73 +44,44 @@
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 44b196e..06b6819 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -183,8 +183,8 @@
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 @@
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 45b922c..7eca14e 100644
--- a/cmds/lshal/utils.h
+++ b/cmds/lshal/utils.h
@@ -29,14 +29,20 @@
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;