checkvintf gets the correct files.

Test: checkvintf with the script provided in the help message
Bug: 72722951

Change-Id: Ibb71a1593e42ee4043030827bb20ffac3a6e70d3
diff --git a/check_vintf.cpp b/check_vintf.cpp
index f64f81d..6acc311 100644
--- a/check_vintf.cpp
+++ b/check_vintf.cpp
@@ -14,14 +14,119 @@
  * limitations under the License.
  */
 
-#include <iostream>
+#include <getopt.h>
+#include <unistd.h>
 
+#include <iostream>
+#include <map>
+
+#include <android-base/parseint.h>
+#include <utils/Errors.h>
+#include <vintf/VintfObject.h>
 #include <vintf/parse_xml.h>
 #include "utils.h"
 
 namespace android {
 namespace vintf {
+namespace details {
 
+// fake sysprops
+using Properties = std::map<std::string, std::string>;
+
+enum Option : int {
+    DUMP_FILE_LIST = 1,
+    ROOTDIR,
+    HELP,
+    PROPERTY,
+    CHECK_COMPAT,
+};
+// command line arguments
+using Args = std::multimap<Option, std::string>;
+
+class HostFileFetcher : public FileFetcher {
+   public:
+    void setRootDir(const std::string& rootdir) {
+        mRootDir = rootdir;
+        if (!mRootDir.empty() && mRootDir.back() != '/') {
+            mRootDir.push_back('/');
+        }
+    }
+    virtual status_t fetch(const std::string& path, std::string& fetched, std::string* error) {
+        return HostFileFetcher::fetchInternal(path, fetched, error);
+    }
+    virtual status_t fetch(const std::string& path, std::string& fetched) {
+        return HostFileFetcher::fetchInternal(path, fetched, nullptr);
+    }
+    virtual status_t listFiles(const std::string& path, std::vector<std::string>* out,
+                               std::string* error) {
+        status_t status = FileFetcher::listFiles(mRootDir + path, out, error);
+        std::cerr << "Debug: List '" << mRootDir << path << "': " << toString(status) << std::endl;
+        return status;
+    }
+
+   private:
+    status_t fetchInternal(const std::string& path, std::string& fetched, std::string* error) {
+        status_t status = FileFetcher::fetchInternal(mRootDir + path, fetched, error);
+        std::cerr << "Debug: Fetch '" << mRootDir << path << "': " << toString(status) << std::endl;
+        return status;
+    }
+    static std::string toString(status_t status) {
+        return status == OK ? "SUCCESS" : strerror(-status);
+    }
+    std::string mRootDir;
+};
+
+class PresetPropertyFetcher : public PropertyFetcher {
+   public:
+    std::string getProperty(const std::string& key,
+                            const std::string& defaultValue) const override {
+        auto it = mProps.find(key);
+        if (it == mProps.end()) {
+            std::cerr << "Debug: Sysprop " << key << " is missing, default to '" << defaultValue
+                      << "'" << std::endl;
+            return defaultValue;
+        }
+        std::cerr << "Debug: Sysprop " << key << "=" << it->second << std::endl;
+        return it->second;
+    }
+    uint64_t getUintProperty(const std::string& key, uint64_t defaultValue,
+                             uint64_t max) const override {
+        uint64_t result;
+        std::string value = getProperty(key, "");
+        if (!value.empty() && android::base::ParseUint(value, &result, max)) return result;
+        return defaultValue;
+    }
+    bool getBoolProperty(const std::string& key, bool defaultValue) const override {
+        std::string value = getProperty(key, "");
+        if (value == "1" || value == "true") {
+            return true;
+        } else if (value == "0" || value == "false") {
+            return false;
+        }
+        return defaultValue;
+    }
+    void setProperties(const Properties& props) { mProps.insert(props.begin(), props.end()); }
+
+   private:
+    std::map<std::string, std::string> mProps;
+};
+
+// globals
+static HostFileFetcher hostFileFetcher;
+FileFetcher* gFetcher = &hostFileFetcher;
+
+static PartitionMounter partitionMounter;
+PartitionMounter* gPartitionMounter = &partitionMounter;
+
+static ObjectFactory<RuntimeInfo> runtimeInfoFactory;
+ObjectFactory<RuntimeInfo>* gRuntimeInfoFactory = &runtimeInfoFactory;
+
+static PresetPropertyFetcher hostPropertyFetcher;
+const PropertyFetcher& getPropertyFetcher() {
+    return hostPropertyFetcher;
+}
+
+// helper functions
 template <typename T>
 std::unique_ptr<T> readObject(const std::string& path, const XmlConverter<T>& converter) {
     std::string xml;
@@ -40,20 +145,9 @@
     return ret;
 }
 
-}  // namespace vintf
-}  // namespace android
-
-int main(int argc, char** argv) {
-    using namespace android::vintf;
-    if (argc < 3) {
-        std::cerr << "usage: " << argv[0] << " <manifest.xml> <matrix.xml>" << std::endl
-                  << "    Checks compatibility between a manifest and a compatibility matrix."
-                  << std::endl;
-        return -1;
-    }
-
-    auto manifest = readObject(argv[1], gHalManifestConverter);
-    auto matrix = readObject(argv[2], gCompatibilityMatrixConverter);
+int checkCompatibilityForFiles(const std::string& manifestPath, const std::string& matrixPath) {
+    auto manifest = readObject(manifestPath, gHalManifestConverter);
+    auto matrix = readObject(matrixPath, gCompatibilityMatrixConverter);
     if (manifest == nullptr || matrix == nullptr) {
         return -1;
     }
@@ -68,3 +162,144 @@
     std::cout << "true" << std::endl;
     return 0;
 }
+
+Args parseArgs(int argc, char** argv) {
+    int longOptFlag;
+    int optionIndex;
+    Args ret;
+    std::vector<struct option> longopts{
+        {"dump-file-list", no_argument, &longOptFlag, DUMP_FILE_LIST},
+        {"rootdir", required_argument, &longOptFlag, ROOTDIR},
+        {"help", no_argument, &longOptFlag, HELP},
+        {"property", required_argument, &longOptFlag, PROPERTY},
+        {"check-compat", no_argument, &longOptFlag, CHECK_COMPAT},
+        {0, 0, 0, 0}};
+    std::map<int, Option> shortopts{
+        {'h', HELP}, {'D', PROPERTY}, {'c', CHECK_COMPAT},
+    };
+    for (;;) {
+        int c = getopt_long(argc, argv, "hcD:", longopts.data(), &optionIndex);
+        if (c == -1) {
+            break;
+        }
+        std::string argValue = optarg ? optarg : std::string{};
+        if (c == 0) {
+            ret.emplace(static_cast<Option>(longOptFlag), std::move(argValue));
+        } else {
+            ret.emplace(shortopts[c], std::move(argValue));
+        }
+    }
+    if (optind < argc) {
+        // see non option
+        std::cerr << "unrecognized option `" << argv[optind] << "'" << std::endl;
+        return {{HELP, ""}};
+    }
+    return ret;
+}
+
+template <typename T>
+Properties getProperties(const T& args) {
+    Properties ret;
+    for (const auto& arg : args) {
+        auto pos = arg.find('=');
+        auto key = arg.substr(0, pos);
+        auto value = pos == std::string::npos ? std::string{} : arg.substr(pos + 1);
+        ret[key] = value;
+    }
+    return ret;
+}
+
+int usage(const char* me) {
+    std::cerr
+        << me << ": check VINTF metadata." << std::endl
+        << "    Options:" << std::endl
+        << "        --dump-file-list: Dump a list of directories / files on device" << std::endl
+        << "                that is required to be used by --check-compat." << std::endl
+        << "        -c, --check-compat: check compatibility for files under the root" << std::endl
+        << "                directory specified by --root-dir." << std::endl
+        << "        --rootdir=<dir>: specify root directory for all metadata." << std::endl
+        << "        -D, --property <key>=<value>: specify sysprops." << std::endl
+        << "        --help: show this message." << std::endl
+        << std::endl
+        << "    Example:" << std::endl
+        << "        # Get the list of required files." << std::endl
+        << "        " << me << " --dump-file-list > /tmp/files.txt" << std::endl
+        << "        # Pull from ADB, or use your own command to extract files from images"
+        << std::endl
+        << "        ROOTDIR=/tmp/device/" << std::endl
+        << "        cat /tmp/files.txt | xargs -I{} bash -c \"mkdir -p $ROOTDIR`dirname {}` && adb "
+           "pull {} $ROOTDIR{}\""
+        << std::endl
+        << "        # Check compatibility." << std::endl
+        << "        " << me << " --check-compat --rootdir=$ROOTDIR \\" << std::endl
+        << "            --property ro.product.first_api_level=`adb shell getprop "
+           "ro.product.first_api_level` \\"
+        << std::endl
+        << "            --property ro.boot.product.hardware.sku=`adb shell getprop "
+           "ro.boot.product.hardware.sku`"
+        << std::endl;
+    return 1;
+}
+
+int checkAllFiles(const std::string& rootdir, const Properties& props, std::string* error) {
+    hostFileFetcher.setRootDir(rootdir);
+    hostPropertyFetcher.setProperties(props);
+
+    return VintfObject::CheckCompatibility({} /* packageInfo */, error, DISABLE_RUNTIME_INFO);
+}
+
+}  // namespace details
+}  // namespace vintf
+}  // namespace android
+
+int main(int argc, char** argv) {
+    using namespace android::vintf;
+    using namespace android::vintf::details;
+    // legacy usage: check_vintf <manifest.xml> <matrix.xml>
+    if (argc == 3) {
+        int ret = checkCompatibilityForFiles(argv[1], argv[2]);
+        if (ret >= 0) return ret;
+    }
+
+    Args args = parseArgs(argc, argv);
+
+    if (!iterateValues(args, HELP).empty()) {
+        return usage(argv[0]);
+    }
+
+    if (!iterateValues(args, DUMP_FILE_LIST).empty()) {
+        for (const auto& file : dumpFileList()) {
+            std::cout << file << std::endl;
+        }
+        return 0;
+    }
+
+    auto rootdirs = iterateValues(args, ROOTDIR);
+    auto properties = getProperties(iterateValues(args, PROPERTY));
+
+    auto checkCompat = iterateValues(args, CHECK_COMPAT);
+    if (!checkCompat.empty()) {
+        if (rootdirs.empty()) {
+            std::cerr << "Missing --rootdir option." << std::endl;
+            return usage(argv[0]);
+        }
+        int ret = COMPATIBLE;
+        for (const auto& rootdir : rootdirs) {
+            std::cerr << "Debug: checking files under " << rootdir << "..." << std::endl;
+            std::string error;
+            int compat = checkAllFiles(rootdir, properties, &error);
+            std::cerr << "Debug: files under " << rootdir
+                      << (compat == COMPATIBLE
+                              ? " is compatible"
+                              : compat == INCOMPATIBLE ? " are incompatible"
+                                                       : (" has encountered an error: " + error))
+                      << std::endl;
+        }
+        if (ret == COMPATIBLE) {
+            std::cout << "true" << std::endl;
+        }
+        return ret;
+    }
+
+    return usage(argv[0]);
+}