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

Commit cce988d0 authored by Nirav Atre's avatar Nirav Atre
Browse files

Filter lshal-reported HALs using CL arguments

Currently, lshal reports all available HALs (under 3 labels: binderized
services, passthrough clients, and passthrough libraries). This change
allows users to restrict the command's output to one or more of these
associations.

Bug: 68653063
Test: Manually tested lshal with/without the --types flag, and with
various permutations of the HAL types. Ensured that --debug and --neat
are still functional.

Change-Id: Idcd22746f8ec3f0d4d5d981a84400f2683a1272a
parent c81f3d43
Loading
Loading
Loading
Loading
+80 −25
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

#include <getopt.h>

#include <algorithm>
#include <fstream>
#include <functional>
#include <iomanip>
@@ -27,6 +28,7 @@
#include <sstream>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <hidl-hash/Hash.h>
@@ -220,16 +222,37 @@ const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
    return &pair.first->second;
}

// Must process hwbinder services first, then passthrough services.
bool ListCommand::shouldReportHalType(const HalType &type) const {
    return (std::find(mListTypes.begin(), mListTypes.end(), type) != mListTypes.end());
}

void ListCommand::forEachTable(const std::function<void(Table &)> &f) {
    f(mServicesTable);
    f(mPassthroughRefTable);
    f(mImplementationsTable);
    for (const auto& type : mListTypes) {
        switch (type) {
            case HalType::BINDERIZED_SERVICES:
                f(mServicesTable); break;
            case HalType::PASSTHROUGH_CLIENTS:
                f(mPassthroughRefTable); break;
            case HalType::PASSTHROUGH_LIBRARIES:
                f(mImplementationsTable); break;
            default:
                LOG(FATAL) << __func__ << "Unknown HAL type.";
        }
    }
}
void ListCommand::forEachTable(const std::function<void(const Table &)> &f) const {
    f(mServicesTable);
    f(mPassthroughRefTable);
    f(mImplementationsTable);
    for (const auto& type : mListTypes) {
        switch (type) {
            case HalType::BINDERIZED_SERVICES:
                f(mServicesTable); break;
            case HalType::PASSTHROUGH_CLIENTS:
                f(mPassthroughRefTable); break;
            case HalType::PASSTHROUGH_LIBRARIES:
                f(mImplementationsTable); break;
            default:
                LOG(FATAL) << __func__ << "Unknown HAL type.";
        }
    }
}

void ListCommand::postprocess() {
@@ -498,6 +521,8 @@ void ListCommand::putEntry(TableEntrySource source, TableEntry &&entry) {
}

Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) {
    if (!shouldReportHalType(HalType::PASSTHROUGH_LIBRARIES)) { return OK; }

    using namespace ::android::hardware;
    using namespace ::android::hidl::manager::V1_0;
    using namespace ::android::hidl::base::V1_0;
@@ -526,6 +551,8 @@ Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) {
}

Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) {
    if (!shouldReportHalType(HalType::PASSTHROUGH_CLIENTS)) { return OK; }

    using namespace ::android::hardware;
    using namespace ::android::hardware::details;
    using namespace ::android::hidl::manager::V1_0;
@@ -555,8 +582,9 @@ Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) {
}

Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) {
    const std::string mode = "hwbinder";
    if (!shouldReportHalType(HalType::BINDERIZED_SERVICES)) { return OK; }

    const std::string mode = "hwbinder";
    hidl_vec<hidl_string> fqInstanceNames;
    // copying out for timeoutIPC
    auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) {
@@ -790,6 +818,42 @@ void ListCommand::registerAllOptions() {
        thiz->mNeat = true;
        return OK;
    }, "output is machine parsable (no explanatory text).\nCannot be used with --debug."});
    mOptions.push_back({'\0', "types", required_argument, v++, [](ListCommand* thiz, const char* arg) {
        if (!arg) { return USAGE; }

        static const std::map<std::string, HalType> kHalTypeMap {
            {"binderized", HalType::BINDERIZED_SERVICES},
            {"b", HalType::BINDERIZED_SERVICES},
            {"passthrough_clients", HalType::PASSTHROUGH_CLIENTS},
            {"c", HalType::PASSTHROUGH_CLIENTS},
            {"passthrough_libs", HalType::PASSTHROUGH_LIBRARIES},
            {"l", HalType::PASSTHROUGH_LIBRARIES}
        };

        std::vector<std::string> halTypesArgs = split(std::string(arg), ',');
        for (const auto& halTypeArg : halTypesArgs) {
            if (halTypeArg.empty()) continue;

            const auto& halTypeIter = kHalTypeMap.find(halTypeArg);
            if (halTypeIter == kHalTypeMap.end()) {

                thiz->err() << "Unrecognized HAL type: " << halTypeArg << std::endl;
                return USAGE;
            }

            // Append unique (non-repeated) HAL types to the reporting list
            HalType halType = halTypeIter->second;
            if (std::find(thiz->mListTypes.begin(), thiz->mListTypes.end(), halType) ==
                thiz->mListTypes.end()) {
                thiz->mListTypes.push_back(halType);
            }
        }

        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."});
}

// Create 'longopts' argument to getopt_long. Caller is responsible for maintaining
@@ -828,6 +892,7 @@ static std::string getShortOptions(const ListCommand::RegisteredOptions& options
}

Status ListCommand::parseArgs(const Arg &arg) {
    mListTypes.clear();

    if (mOptions.empty()) {
        registerAllOptions();
@@ -900,6 +965,12 @@ Status ListCommand::parseArgs(const Arg &arg) {
        }
    }

    // By default, list all HAL types
    if (mListTypes.empty()) {
        mListTypes = {HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS,
                      HalType::PASSTHROUGH_LIBRARIES};
    }

    forEachTable([this] (Table& table) {
        table.setSelectedColumns(this->mSelectedColumns);
    });
@@ -918,22 +989,6 @@ Status ListCommand::main(const Arg &arg) {
    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>]"};
@@ -969,7 +1024,7 @@ void ListCommand::usage() const {
        if (!e.longOption.empty())
            err() << "--" << e.longOption << e.getHelpMessageForArgument();
        err() << ": ";
        std::vector<std::string> lines = splitString(e.help, '\n');
        std::vector<std::string> lines = split(e.help, '\n');
        for (const auto& line : lines) {
            if (&line != &lines.front())
                err() << "            ";
+13 −0
Original line number Diff line number Diff line
@@ -46,6 +46,12 @@ struct PidInfo {
    uint32_t threadCount; // number of threads total
};

enum class HalType {
    BINDERIZED_SERVICES = 0,
    PASSTHROUGH_CLIENTS,
    PASSTHROUGH_LIBRARIES
};

class ListCommand : public Command {
public:
    ListCommand(Lshal &lshal) : Command(lshal) {}
@@ -128,6 +134,9 @@ 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;

    Table mServicesTable{};
    Table mPassthroughRefTable{};
    Table mImplementationsTable{};
@@ -144,6 +153,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};

    // 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.
    // If an entry exist and not empty, it contains the cached content of /proc/{pid}/cmdline.
+84 −0
Original line number Diff line number Diff line
@@ -567,6 +567,90 @@ TEST_F(ListTest, DumpNeat) {
    EXPECT_EQ("", err.str());
}

TEST_F(ListTest, DumpSingleHalType) {
    const std::string expected =
        "[fake description 0]\n"
        "Interface            Transport Arch Thread Use Server PTR              Clients\n"
        "a.h.foo1@1.0::IFoo/1 hwbinder  64   11/21      1      0000000000002711 2 4\n"
        "a.h.foo2@2.0::IFoo/2 hwbinder  64   12/22      2      0000000000002712 3 5\n"
        "\n";

    optind = 1; // mimic Lshal::parseArg()
    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=binderized"})));
    EXPECT_EQ(expected, out.str());
    EXPECT_EQ("", err.str());
}

TEST_F(ListTest, DumpReorderedHalTypes) {
    const std::string expected =
        "[fake description 0]\n"
        "Interface            Transport   Arch Thread Use Server PTR Clients\n"
        "a.h.foo3@3.0::IFoo/3 passthrough 32   N/A        N/A    N/A 4 6\n"
        "a.h.foo4@4.0::IFoo/4 passthrough 32   N/A        N/A    N/A 5 7\n"
        "\n"
        "[fake description 1]\n"
        "Interface            Transport   Arch Thread Use Server PTR Clients\n"
        "a.h.foo5@5.0::IFoo/5 passthrough 32   N/A        N/A    N/A 6 8\n"
        "a.h.foo6@6.0::IFoo/6 passthrough 32   N/A        N/A    N/A 7 9\n"
        "\n"
        "[fake description 2]\n"
        "Interface            Transport Arch Thread Use Server PTR              Clients\n"
        "a.h.foo1@1.0::IFoo/1 hwbinder  64   11/21      1      0000000000002711 2 4\n"
        "a.h.foo2@2.0::IFoo/2 hwbinder  64   12/22      2      0000000000002712 3 5\n"
        "\n";

    optind = 1; // mimic Lshal::parseArg()
    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=passthrough_clients",
                                            "--types=passthrough_libs", "--types=binderized"})));
    EXPECT_EQ(expected, out.str());
    EXPECT_EQ("", err.str());
}

TEST_F(ListTest, DumpAbbreviatedHalTypes) {
    const std::string expected =
        "[fake description 0]\n"
        "Interface            Transport   Arch Thread Use Server PTR Clients\n"
        "a.h.foo3@3.0::IFoo/3 passthrough 32   N/A        N/A    N/A 4 6\n"
        "a.h.foo4@4.0::IFoo/4 passthrough 32   N/A        N/A    N/A 5 7\n"
        "\n"
        "[fake description 1]\n"
        "Interface            Transport   Arch Thread Use Server PTR Clients\n"
        "a.h.foo5@5.0::IFoo/5 passthrough 32   N/A        N/A    N/A 6 8\n"
        "a.h.foo6@6.0::IFoo/6 passthrough 32   N/A        N/A    N/A 7 9\n"
        "\n";

    optind = 1; // mimic Lshal::parseArg()
    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,l"})));
    EXPECT_EQ(expected, out.str());
    EXPECT_EQ("", err.str());
}

TEST_F(ListTest, DumpEmptyAndDuplicateHalTypes) {
    const std::string expected =
        "[fake description 0]\n"
        "Interface            Transport   Arch Thread Use Server PTR Clients\n"
        "a.h.foo3@3.0::IFoo/3 passthrough 32   N/A        N/A    N/A 4 6\n"
        "a.h.foo4@4.0::IFoo/4 passthrough 32   N/A        N/A    N/A 5 7\n"
        "\n"
        "[fake description 1]\n"
        "Interface            Transport   Arch Thread Use Server PTR Clients\n"
        "a.h.foo5@5.0::IFoo/5 passthrough 32   N/A        N/A    N/A 6 8\n"
        "a.h.foo6@6.0::IFoo/6 passthrough 32   N/A        N/A    N/A 7 9\n"
        "\n";

    optind = 1; // mimic Lshal::parseArg()
    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,l,,,l,l,c,",
                                            "--types=passthrough_libs,passthrough_clients"})));
    EXPECT_EQ(expected, out.str());
    EXPECT_EQ("", err.str());
}

TEST_F(ListTest, UnknownHalType) {
    optind = 1; // mimic Lshal::parseArg()
    EXPECT_EQ(1u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,a"})));
    EXPECT_THAT(err.str(), HasSubstr("Unrecognized HAL type: a"));
}

class HelpTest : public ::testing::Test {
public:
    void SetUp() override {