You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
10 KiB
286 lines
10 KiB
/* |
|
* Copyright (C) 2017 The Android Open Source Project |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
#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 HostFileSystem : public FileSystemUnderPath { |
|
public: |
|
HostFileSystem(const std::string& rootdir) : FileSystemUnderPath(rootdir) {} |
|
status_t fetch(const std::string& path, std::string* fetched, |
|
std::string* error) const override { |
|
status_t status = FileSystemUnderPath::fetch(path, fetched, error); |
|
std::cerr << "Debug: Fetch '" << getRootDir() << path << "': " << toString(status) |
|
<< std::endl; |
|
return status; |
|
} |
|
status_t listFiles(const std::string& path, std::vector<std::string>* out, |
|
std::string* error) const override { |
|
status_t status = FileSystemUnderPath::listFiles(path, out, error); |
|
std::cerr << "Debug: List '" << getRootDir() << path << "': " << toString(status) |
|
<< std::endl; |
|
return status; |
|
} |
|
|
|
private: |
|
static std::string toString(status_t status) { |
|
return status == OK ? "SUCCESS" : strerror(-status); |
|
} |
|
}; |
|
|
|
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; |
|
}; |
|
|
|
// helper functions |
|
template <typename T> |
|
std::unique_ptr<T> readObject(FileSystem* fileSystem, const std::string& path, |
|
const XmlConverter<T>& converter) { |
|
std::string xml; |
|
std::string error; |
|
status_t err = fileSystem->fetch(path, &xml, &error); |
|
if (err != OK) { |
|
std::cerr << "Error: Cannot read '" << path << "' (" << strerror(-err) << "): " << error |
|
<< std::endl; |
|
return nullptr; |
|
} |
|
auto ret = std::make_unique<T>(); |
|
if (!converter(ret.get(), xml, &error)) { |
|
std::cerr << "Error: Cannot parse '" << path << "': " << error << std::endl; |
|
return nullptr; |
|
} |
|
return ret; |
|
} |
|
|
|
int checkCompatibilityForFiles(const std::string& manifestPath, const std::string& matrixPath) { |
|
auto fileSystem = std::make_unique<FileSystemImpl>(); |
|
auto manifest = readObject(fileSystem.get(), manifestPath, gHalManifestConverter); |
|
auto matrix = readObject(fileSystem.get(), matrixPath, gCompatibilityMatrixConverter); |
|
if (manifest == nullptr || matrix == nullptr) { |
|
return -1; |
|
} |
|
|
|
std::string error; |
|
if (!manifest->checkCompatibility(*matrix, &error)) { |
|
std::cerr << "Error: Incompatible: " << error << std::endl; |
|
std::cout << "false" << std::endl; |
|
return 1; |
|
} |
|
|
|
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) { |
|
auto hostPropertyFetcher = std::make_unique<PresetPropertyFetcher>(); |
|
hostPropertyFetcher->setProperties(props); |
|
auto vintfObject = VintfObject::Builder() |
|
.setFileSystem(std::make_unique<HostFileSystem>(rootdir)) |
|
.setPropertyFetcher(std::move(hostPropertyFetcher)) |
|
.build(); |
|
return vintfObject->checkCompatibility(error, CheckFlags::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]); |
|
}
|
|
|