Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 13ba0a97 authored by Yifan Hong's avatar Yifan Hong
Browse files

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
parent b72f19ee
Loading
Loading
Loading
Loading
+87 −12
Original line number Diff line number Diff line
@@ -57,6 +57,19 @@ vintf::SchemaType toSchemaType(Partition p) {
    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 @@ std::string ListCommand::parseCmdline(pid_t pid) const {
}

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 @@ void ListCommand::removeDeadProcesses(Pids *pids) {
}

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 @@ VintfInfo ListCommand::getVintfInfo(const std::string& fqInstanceName,
    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 @@ const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
    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 @@ Table* ListCommand::tableForType(HalType type) {
            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 @@ void ListCommand::postprocess() {
            }
        }
        for (TableEntry& entry : table) {
            if (entry.partition == Partition::UNKNOWN) {
                entry.partition = getPartition(entry.serverPid);
            }
            entry.vintfInfo = getVintfInfo(entry.interfaceName, {entry.transport, entry.arch});
        }
    });
@@ -365,6 +385,8 @@ void ListCommand::postprocess() {
    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::addEntryWithInstance(const TableEntry& entry,

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 @@ void ListCommand::dumpVintf(const NullableOStream<std::ostream>& out) const {
        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 @@ void ListCommand::putEntry(HalType type, TableEntry &&entry) {
}

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::fetchAllLibraries(const sp<IServiceManager> &manager) {
}

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::fetchPassthrough(const sp<IServiceManager> &manager) {
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 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) {
        TableEntry& entry = allTableEntries[fqInstanceName];
        entry.interfaceName = fqInstanceName;
        entry.transport = mode;
        entry.serviceStatus = ServiceStatus::NON_RESPONSIVE;

        status |= fetchBinderizedEntry(manager, &entry);
    }
@@ -748,6 +773,40 @@ Status ListCommand::fetchBinderizedEntry(const sp<IServiceManager> &manager,
            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 @@ Status ListCommand::fetch() {
    } 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 @@ void ListCommand::registerAllOptions() {
       "    - 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 @@ void ListCommand::registerAllOptions() {
            {"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 @@ void ListCommand::registerAllOptions() {

        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 @@ Status ListCommand::parseArgs(const Arg &arg) {
        mListTypes = {HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS,
                      HalType::PASSTHROUGH_LIBRARIES};
    }
    initFetchTypes();

    forEachTable([this] (Table& table) {
        table.setSelectedColumns(this->mSelectedColumns);
+12 −6
Original line number Diff line number Diff line
@@ -50,7 +50,8 @@ struct PidInfo {
enum class HalType {
    BINDERIZED_SERVICES = 0,
    PASSTHROUGH_CLIENTS,
    PASSTHROUGH_LIBRARIES
    PASSTHROUGH_LIBRARIES,
    VINTF_MANIFEST,
};

class ListCommand : public Command {
@@ -97,6 +98,7 @@ protected:
    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 @@ protected:
    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 @@ protected:
    // 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.
+15 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ static std::string getTitle(TableColumnType type) {
        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 @@ std::string TableEntry::getField(TableColumnType type) const {
            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 @@ std::string TableEntry::getVintfInfo() const {
    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 {

+11 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ enum class TableColumnType : unsigned int {
    RELEASED,
    HASH,
    VINTF,
    SERVICE_STATUS,
};

enum : unsigned int {
@@ -64,6 +65,14 @@ enum {
    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 @@ struct TableEntry {
    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;
+58 −11
Original line number Diff line number Diff line
@@ -327,7 +327,7 @@ private:

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 @@ public:
        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, GetPidInfoCached) {
}

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 @@ TEST_F(ListTest, Fetch) {
                .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 @@ TEST_F(ListTest, Vintf) {
    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 {
Loading