lshal: Add option to print cmd lines instead of pids.

Add -m to arguments of lshal. When this flag is set,
/proc/{pid}/cmdline is printed instead of a plain PID.
If the file doesn't exist, it will be striped out from
the PID column as well (the process is considered died
and won't hold a reference to the binder object.)

Test: lshal -icm

Bug: 35160832

Change-Id: I4345bf06112a1f87ce91bec6f6f787703e46cd17
diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp
index a2dabce..ce058c8 100644
--- a/cmds/lshal/Lshal.cpp
+++ b/cmds/lshal/Lshal.cpp
@@ -81,6 +81,32 @@
     return components;
 }
 
+std::string getCmdline(pid_t pid) {
+    std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline");
+    std::string cmdline;
+    if (!ifs.is_open()) {
+        return "";
+    }
+    ifs >> cmdline;
+    return cmdline;
+}
+
+const std::string &Lshal::getCmdline(pid_t pid) {
+    auto pair = mCmdlines.find(pid);
+    if (pair != mCmdlines.end()) {
+        return pair->second;
+    }
+    mCmdlines[pid] = ::android::lshal::getCmdline(pid);
+    return mCmdlines[pid];
+}
+
+void Lshal::removeDeadProcesses(Pids *pids) {
+    static const pid_t myPid = getpid();
+    std::remove_if(pids->begin(), pids->end(), [this](auto pid) {
+        return pid == myPid || this->getCmdline(pid).empty();
+    });
+}
+
 bool Lshal::getReferencedPids(
         pid_t serverPid, std::map<uint64_t, Pids> *objects) const {
 
@@ -125,35 +151,56 @@
     if (mSortColumn) {
         std::sort(mTable.begin(), mTable.end(), mSortColumn);
     }
+    for (TableEntry &entry : mTable) {
+        entry.serverCmdline = getCmdline(entry.serverPid);
+        removeDeadProcesses(&entry.clientPids);
+        for (auto pid : entry.clientPids) {
+            entry.clientCmdlines.push_back(this->getCmdline(pid));
+        }
+    }
 }
 
 void Lshal::printLine(
         const std::string &interfaceName,
         const std::string &transport, const std::string &server,
-        const std::string &address, const std::string &clients) const {
+        const std::string &serverCmdline,
+        const std::string &address, const std::string &clients,
+        const std::string &clientCmdlines) const {
     if (mSelectedColumns & ENABLE_INTERFACE_NAME)
         mOut << std::setw(80) << interfaceName << "\t";
     if (mSelectedColumns & ENABLE_TRANSPORT)
         mOut << std::setw(10) << transport << "\t";
-    if (mSelectedColumns & ENABLE_SERVER_PID)
-        mOut << std::setw(5)  << server << "\t";
+    if (mSelectedColumns & ENABLE_SERVER_PID) {
+        if (mEnableCmdlines) {
+            mOut << std::setw(15) << serverCmdline << "\t";
+        } else {
+            mOut << std::setw(5)  << server << "\t";
+        }
+    }
     if (mSelectedColumns & ENABLE_SERVER_ADDR)
         mOut << std::setw(16) << address << "\t";
-    if (mSelectedColumns & ENABLE_CLIENT_PIDS)
-        mOut << std::setw(0)  << clients;
+    if (mSelectedColumns & ENABLE_CLIENT_PIDS) {
+        if (mEnableCmdlines) {
+            mOut << std::setw(0)  << clientCmdlines;
+        } else {
+            mOut << std::setw(0)  << clients;
+        }
+    }
     mOut << std::endl;
 }
 
 void Lshal::dump() const {
     mOut << "All services:" << std::endl;
     mOut << std::left;
-    printLine("Interface", "Transport", "Server", "PTR", "Clients");
+    printLine("Interface", "Transport", "Server", "Server CMD", "PTR", "Clients", "Clients CMD");
     for (const auto &entry : mTable) {
         printLine(entry.interfaceName,
                 entry.transport,
                 entry.serverPid == NO_PID ? "N/A" : std::to_string(entry.serverPid),
+                entry.serverCmdline,
                 entry.serverObjectAddress == NO_PTR ? "N/A" : toHexString(entry.serverObjectAddress),
-                join(entry.clientPids, " "));
+                join(entry.clientPids, " "),
+                join(entry.clientCmdlines, ";"));
     }
 }
 
@@ -324,9 +371,11 @@
         << "           -i, --interface: print the interface name column" << std::endl
         << "           -n, --instance: print the instance name column" << std::endl
         << "           -t, --transport: print the transport mode column" << std::endl
-        << "           -p, --pid: print the server PID column" << std::endl
+        << "           -p, --pid: print the server PID, or server cmdline if -m is set" << std::endl
         << "           -a, --address: print the server object address column" << std::endl
-        << "           -c, --clients: print the client PIDs column" << std::endl
+        << "           -c, --clients: print the client PIDs, or client cmdlines if -m is set"
+                                                                              << std::endl
+        << "           -m, --cmdline: print cmdline instead of PIDs" << std::endl
         << "           --sort=i, --sort=interface: sort by interface name" << std::endl
         << "           --sort=p, --sort=pid: sort by server pid" << std::endl
         << "       lshal [-h|--help]" << std::endl
@@ -342,6 +391,7 @@
         {"pid",       no_argument,       0, 'p' },
         {"address",   no_argument,       0, 'a' },
         {"clients",   no_argument,       0, 'c' },
+        {"cmdline",   no_argument,       0, 'm' },
 
         // long options without short alternatives
         {"sort",      required_argument, 0, 's' },
@@ -353,7 +403,7 @@
     optind = 1;
     for (;;) {
         // using getopt_long in case we want to add other options in the future
-        c = getopt_long(argc, argv, "hitpac", longOptions, &optionIndex);
+        c = getopt_long(argc, argv, "hitpacm", longOptions, &optionIndex);
         if (c == -1) {
             break;
         }
@@ -390,6 +440,10 @@
             mSelectedColumns |= ENABLE_CLIENT_PIDS;
             break;
         }
+        case 'm': {
+            mEnableCmdlines = true;
+            break;
+        }
         case 'h': // falls through
         default: // see unrecognized options
             usage();