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

Commit a6b93f0d authored by Yifan Hong's avatar Yifan Hong
Browse files

lshal: register options for ListCommand.

ListCommand parseArgs() and usage() uses a list of RegisteredOption's
instead of hardcoding all options.

Test: lshal_test
Change-Id: I94418b22791fbff7a17756c4266252a76570fb84
parent 795b6ec8
Loading
Loading
Loading
Loading
+197 −125
Original line number Diff line number Diff line
@@ -611,29 +611,119 @@ Status ListCommand::fetch() {
    return status;
}

Status ListCommand::parseArgs(const Arg &arg) {
    static struct option longOptions[] = {
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
        {"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' },
    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  }
    };
    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;
@@ -642,77 +732,34 @@ Status ListCommand::parseArgs(const Arg &arg) {
    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;
                return USAGE;
            }
            break;
        }
        case 'v': {
            mVintf = true;
            if (optarg) mFileOutputPath = optarg;
            break;
        }
        case 'i': {
            selectedColumns.push_back(TableColumnType::INTERFACE_NAME);
            break;
        const RegisteredOption* found = nullptr;
        if (c == 0) {
            // see long option
            for (const auto& e : mOptions) {
                if (longOptFlag == e.val) found = &e;
            }
        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;
        } else {
            // see short option
            for (const auto& e : mOptions) {
                if (c == e.shortOption) found = &e;
            }
        case 'n': {
            mNeat = true;
            break;
        }
        case 'h': {
            return USAGE;
        }
        default: // see unrecognized options

        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
@@ -725,24 +772,24 @@ Status ListCommand::parseArgs(const Arg &arg) {
        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;
@@ -759,39 +806,64 @@ 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>]"};
    static const std::string required{"=<arg>"};

    if (hasArg == optional_argument) {
        return optional;
    }
    if (hasArg == required_argument) {
        return required;
    }
    return empty;
}

void ListCommand::usage() const {

    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";

    err() << list;
    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
+30 −0
Original line number Diff line number Diff line
@@ -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>
@@ -53,6 +54,27 @@ public:
    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 Arg &arg);
    Status fetch();
@@ -85,6 +107,8 @@ protected:
    NullableOStream<std::ostream> err() const;
    NullableOStream<std::ostream> out() const;

    void registerAllOptions();

    Table mServicesTable{};
    Table mPassthroughRefTable{};
    Table mImplementationsTable{};
@@ -105,6 +129,12 @@ protected:
    // 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);
};