lshal: add Released column.

Example output:

$ lshal --neat -lis
...
Y android.hardware.configstore@1.0::ISurfaceFlingerConfigs/default 7f5fe8f4f8a24037153c504d8b4d3313c2ce33d81c8c69fe5194ddd2d4080e72
  android.hardware.configstore@1.1::ISurfaceFlingerConfigs/default 0000000000000000000000000000000000000000000000000000000000000000
...

Bug: 65123158
Test: lshal_test
Test: lshal
Test: lshal -ils
Test: lshal --help

Change-Id: I18e52eb977461d68909057583be8223d53f6748b
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 5a87505..6cbe7e2 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -20,6 +20,7 @@
         "libutils",
         "libhidlbase",
         "libhidltransport",
+        "libhidl-gen-hash",
         "libhidl-gen-utils",
         "libvintf",
     ],
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index f1f0808..7399692 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -27,6 +27,7 @@
 
 #include <android-base/parseint.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
+#include <hidl-hash/Hash.h>
 #include <hidl-util/FQName.h>
 #include <private/android_filesystem_config.h>
 #include <sys/stat.h>
@@ -590,6 +591,44 @@
             entry->threadCount = pidInfo->threadCount;
         }
     } while (0);
+
+    // hash
+    do {
+        ssize_t hashIndex = -1;
+        auto ifaceChainRet = timeoutIPC(service, &IBase::interfaceChain, [&] (const auto& c) {
+            for (size_t i = 0; i < c.size(); ++i) {
+                if (serviceName == c[i]) {
+                    hashIndex = static_cast<ssize_t>(i);
+                    break;
+                }
+            }
+        });
+        if (!ifaceChainRet.isOk()) {
+            handleError(TRANSACTION_ERROR,
+                        "interfaceChain fails: " + ifaceChainRet.description());
+            break; // skip getHashChain
+        }
+        if (hashIndex < 0) {
+            handleError(BAD_IMPL, "Interface name does not exist in interfaceChain.");
+            break; // skip getHashChain
+        }
+        auto hashRet = timeoutIPC(service, &IBase::getHashChain, [&] (const auto& hashChain) {
+            if (static_cast<size_t>(hashIndex) >= hashChain.size()) {
+                handleError(BAD_IMPL,
+                            "interfaceChain indicates position " + std::to_string(hashIndex) +
+                            " but getHashChain returns " + std::to_string(hashChain.size()) +
+                            " hashes");
+                return;
+            }
+
+            auto&& hashArray = hashChain[hashIndex];
+            std::vector<uint8_t> hashVec{hashArray.data(), hashArray.data() + hashArray.size()};
+            entry->hash = Hash::hexString(hashVec);
+        });
+        if (!hashRet.isOk()) {
+            handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
+        }
+    } while (0);
     return status;
 }
 
@@ -627,6 +666,10 @@
         thiz->mSelectedColumns.push_back(TableColumnType::INTERFACE_NAME);
         return OK;
     }, "print the instance name column"});
+    mOptions.push_back({'l', "released", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::RELEASED);
+        return OK;
+    }, "print the 'is released?' column\n(Y=released, empty=unreleased or unknown)"});
     mOptions.push_back({'t', "transport", no_argument, v++, [](ListCommand* thiz, const char*) {
         thiz->mSelectedColumns.push_back(TableColumnType::TRANSPORT);
         return OK;
@@ -635,6 +678,10 @@
         thiz->mSelectedColumns.push_back(TableColumnType::ARCH);
         return OK;
     }, "print the bitness column"});
+    mOptions.push_back({'s', "hash", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::HASH);
+        return OK;
+    }, "print hash of the interface"});
     mOptions.push_back({'p', "pid", no_argument, v++, [](ListCommand* thiz, const char*) {
         thiz->mSelectedColumns.push_back(TableColumnType::SERVER_PID);
         return OK;
@@ -777,7 +824,8 @@
     }
 
     if (mSelectedColumns.empty()) {
-        mSelectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
+        mSelectedColumns = {TableColumnType::RELEASED,
+                            TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
                             TableColumnType::SERVER_PID, TableColumnType::CLIENT_PIDS};
     }
 
@@ -845,7 +893,7 @@
     err() << "list:" << std::endl
           << "    lshal" << std::endl
           << "    lshal list" << std::endl
-          << "        List all hals with default ordering and columns (`lshal list -iepc`)" << std::endl
+          << "        List all hals with default ordering and columns (`lshal list -riepc`)" << 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;
diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp
index cbcf979..e8792a4 100644
--- a/cmds/lshal/TableEntry.cpp
+++ b/cmds/lshal/TableEntry.cpp
@@ -16,6 +16,8 @@
 #define LOG_TAG "lshal"
 #include <android-base/logging.h>
 
+#include <hidl-hash/Hash.h>
+
 #include "TableEntry.h"
 
 #include "TextTable.h"
@@ -53,8 +55,10 @@
         case TableColumnType::CLIENT_CMDS:      return "Clients CMD";
         case TableColumnType::ARCH:             return "Arch";
         case TableColumnType::THREADS:          return "Thread Use";
+        case TableColumnType::RELEASED:         return "R";
+        case TableColumnType::HASH:             return "Hash";
         default:
-            LOG(FATAL) << "Should not reach here.";
+            LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
     }
 }
@@ -79,12 +83,25 @@
             return getArchString(arch);
         case TableColumnType::THREADS:
             return getThreadUsage();
+        case TableColumnType::RELEASED:
+            return isReleased();
+        case TableColumnType::HASH:
+            return hash;
         default:
-            LOG(FATAL) << "Should not reach here.";
+            LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
     }
 }
 
+std::string TableEntry::isReleased() const {
+    static const std::string unreleased = Hash::hexString(Hash::kEmptyHash);
+
+    if (hash.empty() || hash == unreleased) {
+        return " "; // unknown or unreleased
+    }
+    return "Y"; // released
+}
+
 TextTable Table::createTextTable(bool neat,
     const std::function<std::string(const std::string&)>& emitDebugInfo) const {
 
diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h
index 497bedf..69206cc 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -55,6 +55,8 @@
     CLIENT_CMDS,
     ARCH,
     THREADS,
+    RELEASED,
+    HASH,
 };
 
 enum {
@@ -73,6 +75,8 @@
     Pids clientPids{};
     std::vector<std::string> clientCmdlines{};
     Architecture arch{ARCH_UNKNOWN};
+    // empty: unknown, all zeros: unreleased, otherwise: released
+    std::string hash{};
 
     static bool sortByInterfaceName(const TableEntry &a, const TableEntry &b) {
         return a.interfaceName < b.interfaceName;
@@ -89,6 +93,8 @@
         return std::to_string(threadUsage) + "/" + std::to_string(threadCount);
     }
 
+    std::string isReleased() const;
+
     std::string getField(TableColumnType type) const;
 
     bool operator==(const TableEntry& other) const;
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index 7d87d6d..9220fc0 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -39,6 +39,7 @@
 using ::android::hidl::base::V1_0::IBase;
 using ::android::hidl::manager::V1_0::IServiceManager;
 using ::android::hidl::manager::V1_0::IServiceNotification;
+using ::android::hardware::hidl_array;
 using ::android::hardware::hidl_death_recipient;
 using ::android::hardware::hidl_handle;
 using ::android::hardware::hidl_string;
@@ -46,6 +47,8 @@
 
 using InstanceDebugInfo = IServiceManager::InstanceDebugInfo;
 
+using hidl_hash = hidl_array<uint8_t, 32>;
+
 namespace android {
 namespace hardware {
 namespace tests {
@@ -277,17 +280,34 @@
     if (serverId == NO_PID) return "";
     return "command_line_" + std::to_string(serverId);
 }
+static bool getIsReleasedFromId(pid_t p) { return p % 2 == 0; }
+static hidl_hash getHashFromId(pid_t serverId) {
+    hidl_hash hash;
+    bool isReleased = getIsReleasedFromId(serverId);
+    for (size_t i = 0; i < hash.size(); ++i) {
+        hash[i] = isReleased ? static_cast<uint8_t>(serverId) : 0u;
+    }
+    return hash;
+}
 
 // Fake service returned by mocked IServiceManager::get.
 class TestService : public IBase {
 public:
-    TestService(DebugInfo&& info) : mInfo(std::move(info)) {}
+    TestService(pid_t id) : mId(id) {}
     hardware::Return<void> getDebugInfo(getDebugInfo_cb cb) override {
-        cb(mInfo);
+        cb({ mId /* pid */, getPtr(mId), DebugInfo::Architecture::IS_64BIT });
+        return hardware::Void();
+    }
+    hardware::Return<void> interfaceChain(interfaceChain_cb cb) override {
+        cb({getInterfaceName(mId), IBase::descriptor});
+        return hardware::Void();
+    }
+    hardware::Return<void> getHashChain(getHashChain_cb cb) override {
+        cb({getHashFromId(mId), getHashFromId(0xff)});
         return hardware::Void();
     }
 private:
-    DebugInfo mInfo;
+    pid_t mId;
 };
 
 class ListTest : public ::testing::Test {
@@ -328,7 +348,7 @@
         ON_CALL(*serviceManager, get(_, _)).WillByDefault(Invoke(
             [&](const hidl_string&, const hidl_string& instance) {
                 int id = getIdFromInstanceName(instance);
-                return sp<IBase>(new TestService({ id /* pid */, getPtr(id), A::IS_64BIT }));
+                return sp<IBase>(new TestService(id));
             }));
 
         ON_CALL(*serviceManager, debugDump(_)).WillByDefault(Invoke(
@@ -473,6 +493,55 @@
         << vintf::gHalManifestConverter.lastError();
 }
 
+// test default columns
+TEST_F(ListTest, DumpDefault) {
+    const std::string expected =
+        "[fake description 0]\n"
+        "R Interface            Thread Use Server Clients\n"
+        "  a.h.foo1@1.0::IFoo/1 11/21      1      2 4\n"
+        "Y a.h.foo2@2.0::IFoo/2 12/22      2      3 5\n"
+        "\n"
+        "[fake description 1]\n"
+        "R Interface            Thread Use Server Clients\n"
+        "  a.h.foo3@3.0::IFoo/3 N/A        N/A    4 6\n"
+        "  a.h.foo4@4.0::IFoo/4 N/A        N/A    5 7\n"
+        "\n"
+        "[fake description 2]\n"
+        "R Interface            Thread Use Server Clients\n"
+        "  a.h.foo5@5.0::IFoo/5 N/A        N/A    6 8\n"
+        "  a.h.foo6@6.0::IFoo/6 N/A        N/A    7 9\n"
+        "\n";
+
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_EQ(0u, mockList->main(createArg({"lshal"})));
+    EXPECT_EQ(expected, out.str());
+    EXPECT_EQ("", err.str());
+}
+
+TEST_F(ListTest, DumpHash) {
+    const std::string expected =
+        "[fake description 0]\n"
+        "Interface            R Hash\n"
+        "a.h.foo1@1.0::IFoo/1   0000000000000000000000000000000000000000000000000000000000000000\n"
+        "a.h.foo2@2.0::IFoo/2 Y 0202020202020202020202020202020202020202020202020202020202020202\n"
+        "\n"
+        "[fake description 1]\n"
+        "Interface            R Hash\n"
+        "a.h.foo3@3.0::IFoo/3   \n"
+        "a.h.foo4@4.0::IFoo/4   \n"
+        "\n"
+        "[fake description 2]\n"
+        "Interface            R Hash\n"
+        "a.h.foo5@5.0::IFoo/5   \n"
+        "a.h.foo6@6.0::IFoo/6   \n"
+        "\n";
+
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-ils"})));
+    EXPECT_EQ(expected, out.str());
+    EXPECT_EQ("", err.str());
+}
+
 TEST_F(ListTest, Dump) {
     const std::string expected =
         "[fake description 0]\n"
diff --git a/cmds/lshal/utils.h b/cmds/lshal/utils.h
index 7eca14e..c09e8b1 100644
--- a/cmds/lshal/utils.h
+++ b/cmds/lshal/utils.h
@@ -44,6 +44,8 @@
     NO_INTERFACE                            = 1 << 7,
     // Transaction error from hwbinder transactions
     TRANSACTION_ERROR                       = 1 << 8,
+    // No transaction error, but return value is unexpected.
+    BAD_IMPL                                = 1 << 9,
 };
 using Status = unsigned int;