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;