lshal: add "Status" column and "Manifest HALs" section.

- Added "Status" column that has following values:
    - alive: running hwbinder service
    - registered;dead: registered, but service cannot accept calls
    - declared: only in VINTF, not in hwservicemanager
    - N/A: passthrough HALs

- Added a "Manifest HALs" section that lists all
  HALs (hwbinder or passthrough) in device / framework manifest

Test: lshal_test

Bug: 71555570

Change-Id: I202b562ee73bcd49506bb43cc9af27b86f32651c
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index d497e5c..1a412b2 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -57,6 +57,19 @@
     return (p == Partition::SYSTEM) ? vintf::SchemaType::FRAMEWORK : vintf::SchemaType::DEVICE;
 }
 
+Partition toPartition(vintf::SchemaType t) {
+    switch (t) {
+        case vintf::SchemaType::FRAMEWORK: return Partition::SYSTEM;
+        // TODO(b/71555570): Device manifest does not distinguish HALs from vendor or ODM.
+        case vintf::SchemaType::DEVICE: return Partition::VENDOR;
+    }
+    return Partition::UNKNOWN;
+}
+
+std::string getPackageAndVersion(const std::string& fqInstance) {
+    return splitFirst(fqInstance, ':').first;
+}
+
 NullableOStream<std::ostream> ListCommand::out() const {
     return mLshal.out();
 }
@@ -77,6 +90,8 @@
 }
 
 const std::string &ListCommand::getCmdline(pid_t pid) {
+    static const std::string kEmptyString{};
+    if (pid == NO_PID) return kEmptyString;
     auto pair = mCmdlines.find(pid);
     if (pair != mCmdlines.end()) {
         return pair->second;
@@ -93,6 +108,7 @@
 }
 
 Partition ListCommand::getPartition(pid_t pid) {
+    if (pid == NO_PID) return Partition::UNKNOWN;
     auto it = mPartitions.find(pid);
     if (it != mPartitions.end()) {
         return it->second;
@@ -176,7 +192,7 @@
     FqInstance fqInstance;
     if (!fqInstance.setTo(fqInstanceName) &&
         // Ignore interface / instance for passthrough libs
-        !fqInstance.setTo(splitFirst(fqInstanceName, ':').first)) {
+        !fqInstance.setTo(getPackageAndVersion(fqInstanceName))) {
         err() << "Warning: Cannot parse '" << fqInstanceName << "'; no VINTF info." << std::endl;
         return VINTF_INFO_EMPTY;
     }
@@ -283,8 +299,8 @@
     return &pair.first->second;
 }
 
-bool ListCommand::shouldReportHalType(const HalType &type) const {
-    return (std::find(mListTypes.begin(), mListTypes.end(), type) != mListTypes.end());
+bool ListCommand::shouldFetchHalType(const HalType &type) const {
+    return (std::find(mFetchTypes.begin(), mFetchTypes.end(), type) != mFetchTypes.end());
 }
 
 Table* ListCommand::tableForType(HalType type) {
@@ -295,6 +311,8 @@
             return &mPassthroughRefTable;
         case HalType::PASSTHROUGH_LIBRARIES:
             return &mImplementationsTable;
+        case HalType::VINTF_MANIFEST:
+            return &mManifestHalsTable;
         default:
             LOG(FATAL) << "Unknown HAL type " << static_cast<int64_t>(type);
             return nullptr;
@@ -328,7 +346,9 @@
             }
         }
         for (TableEntry& entry : table) {
-            entry.partition = getPartition(entry.serverPid);
+            if (entry.partition == Partition::UNKNOWN) {
+                entry.partition = getPartition(entry.serverPid);
+            }
             entry.vintfInfo = getVintfInfo(entry.interfaceName, {entry.transport, entry.arch});
         }
     });
@@ -365,6 +385,8 @@
     mImplementationsTable.setDescription(
             "All available passthrough implementations (all -impl.so files).\n"
             "These may return subclasses through their respective HIDL_FETCH_I* functions.");
+    mManifestHalsTable.setDescription(
+            "All HALs that are in VINTF manifest.");
 }
 
 bool ListCommand::addEntryWithInstance(const TableEntry& entry,
@@ -415,7 +437,7 @@
 
 bool ListCommand::addEntryWithoutInstance(const TableEntry& entry,
                                           const vintf::HalManifest* manifest) const {
-    const auto& packageAndVersion = splitFirst(splitFirst(entry.interfaceName, ':').first, '@');
+    const auto& packageAndVersion = splitFirst(getPackageAndVersion(entry.interfaceName), '@');
     const auto& package = packageAndVersion.first;
     vintf::Version version;
     if (!vintf::parse(packageAndVersion.second, &version)) {
@@ -445,6 +467,8 @@
         if (!addEntryWithInstance(entry, &manifest)) error.push_back(entry.interfaceName);
     for (const TableEntry& entry : mPassthroughRefTable)
         if (!addEntryWithInstance(entry, &manifest)) error.push_back(entry.interfaceName);
+    for (const TableEntry& entry : mManifestHalsTable)
+        if (!addEntryWithInstance(entry, &manifest)) error.push_back(entry.interfaceName);
 
     std::vector<std::string> passthrough;
     for (const TableEntry& entry : mImplementationsTable)
@@ -559,7 +583,7 @@
 }
 
 Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) {
-    if (!shouldReportHalType(HalType::PASSTHROUGH_LIBRARIES)) { return OK; }
+    if (!shouldFetchHalType(HalType::PASSTHROUGH_LIBRARIES)) { return OK; }
 
     using namespace ::android::hardware;
     using namespace ::android::hidl::manager::V1_0;
@@ -589,7 +613,7 @@
 }
 
 Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) {
-    if (!shouldReportHalType(HalType::PASSTHROUGH_CLIENTS)) { return OK; }
+    if (!shouldFetchHalType(HalType::PASSTHROUGH_CLIENTS)) { return OK; }
 
     using namespace ::android::hardware;
     using namespace ::android::hardware::details;
@@ -622,7 +646,7 @@
 Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) {
     using vintf::operator<<;
 
-    if (!shouldReportHalType(HalType::BINDERIZED_SERVICES)) { return OK; }
+    if (!shouldFetchHalType(HalType::BINDERIZED_SERVICES)) { return OK; }
 
     const vintf::Transport mode = vintf::Transport::HWBINDER;
     hidl_vec<hidl_string> fqInstanceNames;
@@ -643,6 +667,7 @@
         TableEntry& entry = allTableEntries[fqInstanceName];
         entry.interfaceName = fqInstanceName;
         entry.transport = mode;
+        entry.serviceStatus = ServiceStatus::NON_RESPONSIVE;
 
         status |= fetchBinderizedEntry(manager, &entry);
     }
@@ -748,6 +773,40 @@
             handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
         }
     } while (0);
+    if (status == OK) {
+        entry->serviceStatus = ServiceStatus::ALIVE;
+    }
+    return status;
+}
+
+Status ListCommand::fetchManifestHals() {
+    if (!shouldFetchHalType(HalType::VINTF_MANIFEST)) { return OK; }
+    Status status = OK;
+
+    for (auto manifest : {getDeviceManifest(), getFrameworkManifest()}) {
+        if (manifest == nullptr) {
+            status |= VINTF_ERROR;
+            continue;
+        }
+
+        std::map<std::string, TableEntry> entries;
+
+        manifest->forEachInstance([&] (const vintf::ManifestInstance& manifestInstance) {
+            TableEntry entry{
+                .interfaceName = manifestInstance.getFqInstance().string(),
+                .transport = manifestInstance.transport(),
+                .arch = manifestInstance.arch(),
+                // TODO(b/71555570): Device manifest does not distinguish HALs from vendor or ODM.
+                .partition = toPartition(manifest->type()),
+                .serviceStatus = ServiceStatus::DECLARED};
+            std::string key = entry.interfaceName;
+            entries.emplace(std::move(key), std::move(entry));
+            return true;
+        });
+
+        for (auto&& pair : entries)
+            mManifestHalsTable.add(std::move(pair.second));
+    }
     return status;
 }
 
@@ -770,9 +829,14 @@
     } else {
         status |= fetchAllLibraries(pManager);
     }
+    status |= fetchManifestHals();
     return status;
 }
 
+void ListCommand::initFetchTypes() {
+    mFetchTypes.insert(mListTypes.begin(), mListTypes.end());
+}
+
 void ListCommand::registerAllOptions() {
     int v = mOptions.size();
     // A list of acceptable command line options
@@ -836,6 +900,14 @@
        "    - DC: device compatibility matrix\n"
        "    - FM: framework manifest\n"
        "    - FC: framework compatibility matrix"});
+    mOptions.push_back({'S', "service-status", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::SERVICE_STATUS);
+        return OK;
+    }, "print service status column. Possible values are:\n"
+       "    - alive: alive and running hwbinder service;\n"
+       "    - registered;dead: registered to hwservicemanager but is not responsive;\n"
+       "    - declared: only declared in VINTF manifest but is not registered to hwservicemanager;\n"
+       "    - N/A: no information for passthrough HALs."});
 
     // long options without short alternatives
     mOptions.push_back({'\0', "init-vintf", no_argument, v++, [](ListCommand* thiz, const char* arg) {
@@ -876,7 +948,9 @@
             {"passthrough_clients", HalType::PASSTHROUGH_CLIENTS},
             {"c", HalType::PASSTHROUGH_CLIENTS},
             {"passthrough_libs", HalType::PASSTHROUGH_LIBRARIES},
-            {"l", HalType::PASSTHROUGH_LIBRARIES}
+            {"l", HalType::PASSTHROUGH_LIBRARIES},
+            {"vintf", HalType::VINTF_MANIFEST},
+            {"v", HalType::VINTF_MANIFEST},
         };
 
         std::vector<std::string> halTypesArgs = split(std::string(arg), ',');
@@ -900,9 +974,9 @@
 
         if (thiz->mListTypes.empty()) { return USAGE; }
         return OK;
-    }, "comma-separated list of one or more HAL types.\nThe output is restricted to the selected "
-       "association(s). Valid options\nare: (b|binderized), (c|passthrough_clients), and (l|"
-       "passthrough_libs).\nBy default, lists all available HALs."});
+    }, "comma-separated list of one or more sections.\nThe output is restricted to the selected "
+       "section(s). Valid options\nare: (b|binderized), (c|passthrough_clients), (l|"
+       "passthrough_libs), and (v|vintf).\nDefault is `bcl`."});
 }
 
 // Create 'longopts' argument to getopt_long. Caller is responsible for maintaining
@@ -1019,6 +1093,7 @@
         mListTypes = {HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS,
                       HalType::PASSTHROUGH_LIBRARIES};
     }
+    initFetchTypes();
 
     forEachTable([this] (Table& table) {
         table.setSelectedColumns(this->mSelectedColumns);
diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h
index b10901c..f4d3fdf 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -50,7 +50,8 @@
 enum class HalType {
     BINDERIZED_SERVICES = 0,
     PASSTHROUGH_CLIENTS,
-    PASSTHROUGH_LIBRARIES
+    PASSTHROUGH_LIBRARIES,
+    VINTF_MANIFEST,
 };
 
 class ListCommand : public Command {
@@ -97,6 +98,7 @@
     Status fetchPassthrough(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager);
     Status fetchBinderized(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager);
     Status fetchAllLibraries(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager);
+    Status fetchManifestHals();
 
     Status fetchBinderizedEntry(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager,
                                 TableEntry *entry);
@@ -146,12 +148,15 @@
     bool addEntryWithInstance(const TableEntry &entry, vintf::HalManifest *manifest) const;
     bool addEntryWithoutInstance(const TableEntry &entry, const vintf::HalManifest *manifest) const;
 
-    // Helper function. Whether to list entries corresponding to a given HAL type.
-    bool shouldReportHalType(const HalType &type) const;
+    // Helper function. Whether to fetch entries corresponding to a given HAL type.
+    bool shouldFetchHalType(const HalType &type) const;
+
+    void initFetchTypes();
 
     Table mServicesTable{};
     Table mPassthroughRefTable{};
     Table mImplementationsTable{};
+    Table mManifestHalsTable{};
 
     std::string mFileOutputPath;
     TableEntryCompare mSortColumn = nullptr;
@@ -165,9 +170,10 @@
     // If true, explanatory text are not emitted.
     bool mNeat = false;
 
-    // Type(s) of HAL associations to list. By default, report all.
-    std::vector<HalType> mListTypes{HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS,
-                                    HalType::PASSTHROUGH_LIBRARIES};
+    // Type(s) of HAL associations to list.
+    std::vector<HalType> mListTypes{};
+    // Type(s) of HAL associations to fetch.
+    std::set<HalType> mFetchTypes{};
 
     // If an entry does not exist, need to ask /proc/{pid}/cmdline to get it.
     // If an entry exist but is an empty string, process might have died.
diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp
index 4ad3e92..8e21975 100644
--- a/cmds/lshal/TableEntry.cpp
+++ b/cmds/lshal/TableEntry.cpp
@@ -62,6 +62,7 @@
         case TableColumnType::RELEASED:         return "R";
         case TableColumnType::HASH:             return "Hash";
         case TableColumnType::VINTF:            return "VINTF";
+        case TableColumnType::SERVICE_STATUS:   return "Status";
         default:
             LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
@@ -94,6 +95,8 @@
             return hash;
         case TableColumnType::VINTF:
             return getVintfInfo();
+        case TableColumnType::SERVICE_STATUS:
+            return lshal::to_string(serviceStatus);
         default:
             LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
@@ -129,6 +132,18 @@
     return joined.empty() ? "X" : joined;
 }
 
+std::string to_string(ServiceStatus s) {
+    switch (s) {
+        case ServiceStatus::ALIVE: return "alive";
+        case ServiceStatus::NON_RESPONSIVE: return "non-responsive";
+        case ServiceStatus::DECLARED: return "declared";
+        case ServiceStatus::UNKNOWN: return "N/A";
+    }
+
+    LOG(FATAL) << __func__ << "Should not reach here." << static_cast<int>(s);
+    return "";
+}
+
 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 d68417b..7294b0a 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -48,6 +48,7 @@
     RELEASED,
     HASH,
     VINTF,
+    SERVICE_STATUS,
 };
 
 enum : unsigned int {
@@ -64,6 +65,14 @@
     NO_PTR = 0
 };
 
+enum class ServiceStatus {
+    UNKNOWN, // For passthrough
+    ALIVE,
+    NON_RESPONSIVE, // registered but not respond to calls
+    DECLARED, // in VINTF manifest
+};
+std::string to_string(ServiceStatus s);
+
 struct TableEntry {
     std::string interfaceName{};
     vintf::Transport transport{vintf::Transport::EMPTY};
@@ -79,6 +88,8 @@
     std::string hash{};
     Partition partition{Partition::UNKNOWN};
     VintfInfo vintfInfo{VINTF_INFO_EMPTY};
+    // true iff hwbinder and service started
+    ServiceStatus serviceStatus{ServiceStatus::UNKNOWN};
 
     static bool sortByInterfaceName(const TableEntry &a, const TableEntry &b) {
         return a.interfaceName < b.interfaceName;
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index 096b67d..b05946b 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -327,7 +327,7 @@
 
 class ListTest : public ::testing::Test {
 public:
-    void SetUp() override {
+    virtual void SetUp() override {
         initMockServiceManager();
         lshal = std::make_unique<Lshal>(out, err, serviceManager, passthruManager);
         initMockList();
@@ -351,13 +351,13 @@
         ON_CALL(*mockList, getPartition(_)).WillByDefault(Return(Partition::VENDOR));
 
         ON_CALL(*mockList, getDeviceManifest())
-                .WillByDefault(Return(VintfObject::GetDeviceHalManifest()));
+                .WillByDefault(Return(std::make_shared<HalManifest>()));
         ON_CALL(*mockList, getDeviceMatrix())
-                .WillByDefault(Return(VintfObject::GetDeviceCompatibilityMatrix()));
+                .WillByDefault(Return(std::make_shared<CompatibilityMatrix>()));
         ON_CALL(*mockList, getFrameworkManifest())
-                .WillByDefault(Return(VintfObject::GetFrameworkHalManifest()));
+                .WillByDefault(Return(std::make_shared<HalManifest>()));
         ON_CALL(*mockList, getFrameworkMatrix())
-                .WillByDefault(Return(VintfObject::GetFrameworkCompatibilityMatrix()));
+                .WillByDefault(Return(std::make_shared<CompatibilityMatrix>()));
     }
 
     void initMockServiceManager() {
@@ -411,16 +411,22 @@
 }
 
 TEST_F(ListTest, Fetch) {
-    EXPECT_EQ(0u, mockList->fetch());
+    optind = 1; // mimic Lshal::parseArg()
+    ASSERT_EQ(0u, mockList->parseArgs(createArg({"lshal"})));
+    ASSERT_EQ(0u, mockList->fetch());
     vintf::TransportArch hwbinder{Transport::HWBINDER, Arch::ARCH_64};
     vintf::TransportArch passthrough{Transport::PASSTHROUGH, Arch::ARCH_32};
     std::array<vintf::TransportArch, 6> transportArchs{{hwbinder, hwbinder, passthrough,
                                                         passthrough, passthrough, passthrough}};
-    int id = 1;
+    int i = 0;
     mockList->forEachTable([&](const Table& table) {
-        ASSERT_EQ(2u, table.size());
         for (const auto& entry : table) {
-            auto transport = transportArchs.at(id - 1).transport;
+            if (i >= transportArchs.size()) {
+                break;
+            }
+
+            int id = i + 1;
+            auto transport = transportArchs.at(i).transport;
             TableEntry expected{
                 .interfaceName = getFqInstanceName(id),
                 .transport = transport,
@@ -433,14 +439,16 @@
                 .serverObjectAddress = transport == Transport::HWBINDER ? getPtr(id) : NO_PTR,
                 .clientPids = getClients(id),
                 .clientCmdlines = {},
-                .arch = transportArchs.at(id - 1).arch,
+                .arch = transportArchs.at(i).arch,
             };
             EXPECT_EQ(expected, entry) << expected.to_string() << " vs. " << entry.to_string();
 
-            ++id;
+            ++i;
         }
     });
 
+    EXPECT_EQ(transportArchs.size(), i) << "Not all entries are tested.";
+
 }
 
 TEST_F(ListTest, DumpVintf) {
@@ -758,6 +766,45 @@
     EXPECT_EQ("", err.str());
 }
 
+class ListVintfTest : public ListTest {
+public:
+    virtual void SetUp() override {
+        ListTest::SetUp();
+        const std::string mockManifestXml =
+                "<manifest version=\"1.0\" type=\"device\">\n"
+                "    <hal format=\"hidl\">\n"
+                "        <name>a.h.foo1</name>\n"
+                "        <transport>hwbinder</transport>\n"
+                "        <fqname>@1.0::IFoo/1</fqname>\n"
+                "    </hal>\n"
+                "    <hal format=\"hidl\">\n"
+                "        <name>a.h.bar1</name>\n"
+                "        <transport>hwbinder</transport>\n"
+                "        <fqname>@1.0::IBar/1</fqname>\n"
+                "    </hal>\n"
+                "    <hal format=\"hidl\">\n"
+                "        <name>a.h.bar2</name>\n"
+                "        <transport arch=\"32+64\">passthrough</transport>\n"
+                "        <fqname>@2.0::IBar/2</fqname>\n"
+                "    </hal>\n"
+                "</manifest>";
+        auto manifest = std::make_shared<HalManifest>();
+        EXPECT_TRUE(gHalManifestConverter(manifest.get(), mockManifestXml));
+        EXPECT_CALL(*mockList, getDeviceManifest())
+            .Times(AnyNumber())
+            .WillRepeatedly(Return(manifest));
+    }
+};
+
+TEST_F(ListVintfTest, ManifestHals) {
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-iStr", "--types=v", "--neat"})));
+    EXPECT_THAT(out.str(), HasSubstr("a.h.bar1@1.0::IBar/1 declared hwbinder    ?"));
+    EXPECT_THAT(out.str(), HasSubstr("a.h.bar2@2.0::IBar/2 declared passthrough 32+64"));
+    EXPECT_THAT(out.str(), HasSubstr("a.h.foo1@1.0::IFoo/1 declared hwbinder    ?"));
+    EXPECT_EQ("", err.str());
+}
+
 class HelpTest : public ::testing::Test {
 public:
     void SetUp() override {
diff --git a/cmds/lshal/utils.h b/cmds/lshal/utils.h
index c09e8b1..240155e 100644
--- a/cmds/lshal/utils.h
+++ b/cmds/lshal/utils.h
@@ -46,6 +46,8 @@
     TRANSACTION_ERROR                       = 1 << 8,
     // No transaction error, but return value is unexpected.
     BAD_IMPL                                = 1 << 9,
+    // Cannot fetch VINTF data.
+    VINTF_ERROR                             = 1 << 10,
 };
 using Status = unsigned int;