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.
566 lines
20 KiB
566 lines
20 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. |
|
*/ |
|
|
|
#define LOG_TAG "libvintf" |
|
#include <android-base/logging.h> |
|
|
|
#include "HalManifest.h" |
|
|
|
#include <dirent.h> |
|
|
|
#include <mutex> |
|
#include <set> |
|
|
|
#include <android-base/strings.h> |
|
|
|
#include "parse_string.h" |
|
#include "parse_xml.h" |
|
#include "utils.h" |
|
#include "CompatibilityMatrix.h" |
|
|
|
namespace android { |
|
namespace vintf { |
|
|
|
using details::Instances; |
|
using details::InstancesOfVersion; |
|
using details::mergeField; |
|
|
|
// Check <version> tag for all <hal> with the same name. |
|
bool HalManifest::shouldAdd(const ManifestHal& hal) const { |
|
if (!hal.isValid()) { |
|
return false; |
|
} |
|
if (hal.isOverride()) { |
|
return true; |
|
} |
|
auto existingHals = mHals.equal_range(hal.name); |
|
std::set<size_t> existingMajorVersions; |
|
for (auto it = existingHals.first; it != existingHals.second; ++it) { |
|
for (const auto& v : it->second.versions) { |
|
// Assume integrity on existingHals, so no check on emplace().second |
|
existingMajorVersions.insert(v.majorVer); |
|
} |
|
} |
|
for (const auto& v : hal.versions) { |
|
if (!existingMajorVersions.emplace(v.majorVer).second /* no insertion */) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
// Remove elements from "list" if p(element) returns true. |
|
template <typename List, typename Predicate> |
|
static void removeIf(List& list, Predicate predicate) { |
|
for (auto it = list.begin(); it != list.end();) { |
|
if (predicate(*it)) { |
|
it = list.erase(it); |
|
} else { |
|
++it; |
|
} |
|
} |
|
} |
|
|
|
void HalManifest::removeHals(const std::string& name, size_t majorVer) { |
|
removeIf(mHals, [&name, majorVer](auto& existingHalPair) { |
|
auto& existingHal = existingHalPair.second; |
|
if (existingHal.name != name) { |
|
return false; |
|
} |
|
auto& existingVersions = existingHal.versions; |
|
removeIf(existingVersions, [majorVer](const auto& existingVersion) { |
|
return existingVersion.majorVer == majorVer; |
|
}); |
|
return existingVersions.empty(); |
|
}); |
|
} |
|
|
|
bool HalManifest::add(ManifestHal&& halToAdd) { |
|
if (halToAdd.isOverride()) { |
|
if (halToAdd.isDisabledHal()) { |
|
// Special syntax when there are no instances at all. Remove all existing HALs |
|
// with the given name. |
|
mHals.erase(halToAdd.name); |
|
} |
|
// If there are <version> tags, remove all existing major versions that causes a conflict. |
|
for (const Version& versionToAdd : halToAdd.versions) { |
|
removeHals(halToAdd.name, versionToAdd.majorVer); |
|
} |
|
} |
|
|
|
return HalGroup::add(std::move(halToAdd)); |
|
} |
|
|
|
bool HalManifest::shouldAddXmlFile(const ManifestXmlFile& xmlFile) const { |
|
auto existingXmlFiles = getXmlFiles(xmlFile.name()); |
|
for (auto it = existingXmlFiles.first; it != existingXmlFiles.second; ++it) { |
|
if (xmlFile.version() == it->second.version()) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
std::set<std::string> HalManifest::getHalNames() const { |
|
std::set<std::string> names{}; |
|
for (const auto &hal : mHals) { |
|
names.insert(hal.first); |
|
} |
|
return names; |
|
} |
|
|
|
std::set<std::string> HalManifest::getHalNamesAndVersions() const { |
|
std::set<std::string> names{}; |
|
forEachInstance([&names](const ManifestInstance& e) { |
|
names.insert(toFQNameString(e.package(), e.version())); |
|
return true; |
|
}); |
|
return names; |
|
} |
|
|
|
Transport HalManifest::getTransport(const std::string &package, const Version &v, |
|
const std::string &interfaceName, const std::string &instanceName) const { |
|
Transport transport{Transport::EMPTY}; |
|
forEachInstanceOfInterface(package, v, interfaceName, [&](const auto& e) { |
|
if (e.instance() == instanceName) { |
|
transport = e.transport(); |
|
} |
|
return transport == Transport::EMPTY; // if not found, continue |
|
}); |
|
if (transport == Transport::EMPTY) { |
|
LOG(DEBUG) << "HalManifest::getTransport(" << mType << "): Cannot find " |
|
<< toFQNameString(package, v, interfaceName, instanceName); |
|
} |
|
return transport; |
|
} |
|
|
|
bool HalManifest::forEachInstanceOfVersion( |
|
const std::string& package, const Version& expectVersion, |
|
const std::function<bool(const ManifestInstance&)>& func) const { |
|
for (const ManifestHal* hal : getHals(package)) { |
|
bool cont = hal->forEachInstance([&](const ManifestInstance& manifestInstance) { |
|
if (manifestInstance.version().minorAtLeast(expectVersion)) { |
|
return func(manifestInstance); |
|
} |
|
return true; |
|
}); |
|
if (!cont) return false; |
|
} |
|
return true; |
|
} |
|
|
|
// indent = 2, {"foo"} => "foo" |
|
// indent = 2, {"foo", "bar"} => "\n foo\n bar"; |
|
template <typename Container> |
|
void multilineIndent(std::ostream& os, size_t indent, const Container& lines) { |
|
if (lines.size() == 1) { |
|
os << *lines.begin(); |
|
return; |
|
} |
|
for (const auto& line : lines) { |
|
os << "\n"; |
|
for (size_t i = 0; i < indent; ++i) os << " "; |
|
os << line; |
|
} |
|
} |
|
|
|
// For each hal in mat, there must be a hal in manifest that supports this. |
|
std::vector<std::string> HalManifest::checkIncompatibleHals(const CompatibilityMatrix& mat) const { |
|
std::vector<std::string> ret; |
|
for (const MatrixHal &matrixHal : mat.getHals()) { |
|
if (matrixHal.optional) { |
|
continue; |
|
} |
|
|
|
std::set<FqInstance> manifestInstances; |
|
std::set<FqInstance> manifestInstancesNoPackage; |
|
std::set<Version> versions; |
|
for (const ManifestHal* manifestHal : getHals(matrixHal.name)) { |
|
manifestHal->forEachInstance([&](const auto& manifestInstance) { |
|
manifestInstances.insert(manifestInstance.getFqInstance()); |
|
manifestInstancesNoPackage.insert(manifestInstance.getFqInstanceNoPackage()); |
|
return true; |
|
}); |
|
manifestHal->appendAllVersions(&versions); |
|
} |
|
|
|
if (!matrixHal.isCompatible(manifestInstances, versions)) { |
|
std::ostringstream oss; |
|
oss << matrixHal.name << ":\n required: "; |
|
multilineIndent(oss, 8, android::vintf::expandInstances(matrixHal)); |
|
oss << "\n provided: "; |
|
if (manifestInstances.empty()) { |
|
multilineIndent(oss, 8, versions); |
|
} else { |
|
multilineIndent(oss, 8, manifestInstancesNoPackage); |
|
} |
|
|
|
ret.insert(ret.end(), oss.str()); |
|
} |
|
} |
|
return ret; |
|
} |
|
|
|
std::set<std::string> HalManifest::checkUnusedHals(const CompatibilityMatrix& mat) const { |
|
std::set<std::string> ret; |
|
|
|
forEachInstance([&ret, &mat](const auto& manifestInstance) { |
|
const auto& fqInstance = manifestInstance.getFqInstance(); |
|
if (!mat.matchInstance(fqInstance.getPackage(), fqInstance.getVersion(), |
|
fqInstance.getInterface(), fqInstance.getInstance())) { |
|
ret.insert(fqInstance.string()); |
|
} |
|
return true; |
|
}); |
|
|
|
return ret; |
|
} |
|
|
|
static bool checkVendorNdkCompatibility(const VendorNdk& matVendorNdk, |
|
const std::vector<VendorNdk>& manifestVendorNdk, |
|
std::string* error) { |
|
// For pre-P vendor images, device compatibility matrix does not specify <vendor-ndk> |
|
// tag. Ignore the check for these devices. |
|
if (matVendorNdk.version().empty()) { |
|
return true; |
|
} |
|
for (const auto& vndk : manifestVendorNdk) { |
|
if (vndk.version() != matVendorNdk.version()) { |
|
continue; |
|
} |
|
// version matches, check libraries |
|
std::vector<std::string> diff; |
|
std::set_difference(matVendorNdk.libraries().begin(), matVendorNdk.libraries().end(), |
|
vndk.libraries().begin(), vndk.libraries().end(), |
|
std::inserter(diff, diff.begin())); |
|
if (!diff.empty()) { |
|
if (error != nullptr) { |
|
*error = "Vndk libs incompatible for version " + matVendorNdk.version() + |
|
". These libs are not in framework manifest:"; |
|
for (const auto& name : diff) { |
|
*error += " " + name; |
|
} |
|
} |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
// no match is found. |
|
if (error != nullptr) { |
|
*error = "Vndk version " + matVendorNdk.version() + " is not supported. " + |
|
"Supported versions in framework manifest are:"; |
|
for (const auto& vndk : manifestVendorNdk) { |
|
*error += " " + vndk.version(); |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
static bool checkSystemSdkCompatibility(const SystemSdk& matSystemSdk, |
|
const SystemSdk& manifestSystemSdk, std::string* error) { |
|
SystemSdk notSupported = matSystemSdk.removeVersions(manifestSystemSdk); |
|
if (!notSupported.empty()) { |
|
if (error) { |
|
*error = |
|
"The following System SDK versions are required by device " |
|
"compatibility matrix but not supported by the framework manifest: [" + |
|
base::Join(notSupported.versions(), ", ") + "]. Supported versions are: [" + |
|
base::Join(manifestSystemSdk.versions(), ", ") + "]."; |
|
} |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool HalManifest::checkCompatibility(const CompatibilityMatrix &mat, std::string *error) const { |
|
if (mType == mat.mType) { |
|
if (error != nullptr) { |
|
*error = "Wrong type; checking " + to_string(mType) + " manifest against " |
|
+ to_string(mat.mType) + " compatibility matrix"; |
|
} |
|
return false; |
|
} |
|
auto incompatibleHals = checkIncompatibleHals(mat); |
|
if (!incompatibleHals.empty()) { |
|
if (error != nullptr) { |
|
*error = "HALs incompatible."; |
|
if (mat.level() != Level::UNSPECIFIED) |
|
*error += " Matrix level = " + to_string(mat.level()) + "."; |
|
if (level() != Level::UNSPECIFIED) |
|
*error += " Manifest level = " + to_string(level()) + "."; |
|
*error += " The following requirements are not met:\n"; |
|
for (const auto& e : incompatibleHals) { |
|
*error += e + "\n"; |
|
} |
|
} |
|
return false; |
|
} |
|
if (mType == SchemaType::FRAMEWORK) { |
|
if (!checkVendorNdkCompatibility(mat.device.mVendorNdk, framework.mVendorNdks, error)) { |
|
return false; |
|
} |
|
|
|
if (!checkSystemSdkCompatibility(mat.device.mSystemSdk, framework.mSystemSdk, error)) { |
|
return false; |
|
} |
|
} else if (mType == SchemaType::DEVICE) { |
|
bool sepolicyMatch = false; |
|
for (const auto &range : mat.framework.mSepolicy.sepolicyVersions()) { |
|
if (range.supportedBy(device.mSepolicyVersion)) { |
|
sepolicyMatch = true; |
|
break; |
|
} |
|
} |
|
if (!sepolicyMatch) { |
|
if (error != nullptr) { |
|
*error = "Sepolicy version " + to_string(device.mSepolicyVersion) |
|
+ " doesn't satisify the requirements."; |
|
} |
|
return false; |
|
} |
|
|
|
if (!!kernel() && !kernel()->matchKernelRequirements(mat.framework.mKernels, error)) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
CompatibilityMatrix HalManifest::generateCompatibleMatrix() const { |
|
CompatibilityMatrix matrix; |
|
|
|
forEachInstance([&matrix](const ManifestInstance& e) { |
|
matrix.add(MatrixHal{ |
|
.format = e.format(), |
|
.name = e.package(), |
|
.optional = true, |
|
.versionRanges = {VersionRange{e.version().majorVer, e.version().minorVer}}, |
|
.interfaces = {{e.interface(), HalInterface{e.interface(), {e.instance()}}}}}); |
|
return true; |
|
}); |
|
if (mType == SchemaType::FRAMEWORK) { |
|
matrix.mType = SchemaType::DEVICE; |
|
// VNDK does not need to be added for compatibility |
|
} else if (mType == SchemaType::DEVICE) { |
|
matrix.mType = SchemaType::FRAMEWORK; |
|
matrix.framework.mSepolicy = Sepolicy(0u /* kernelSepolicyVersion */, |
|
{{device.mSepolicyVersion.majorVer, device.mSepolicyVersion.minorVer}}); |
|
} |
|
|
|
return matrix; |
|
} |
|
|
|
status_t HalManifest::fetchAllInformation(const FileSystem* fileSystem, const std::string& path, |
|
std::string* error) { |
|
return details::fetchAllInformation(fileSystem, path, gHalManifestConverter, this, error); |
|
} |
|
|
|
SchemaType HalManifest::type() const { |
|
return mType; |
|
} |
|
|
|
void HalManifest::setType(SchemaType type) { |
|
mType = type; |
|
} |
|
|
|
Level HalManifest::level() const { |
|
return mLevel; |
|
} |
|
|
|
Version HalManifest::getMetaVersion() const { |
|
return mMetaVersion; |
|
} |
|
|
|
const Version &HalManifest::sepolicyVersion() const { |
|
CHECK(mType == SchemaType::DEVICE); |
|
return device.mSepolicyVersion; |
|
} |
|
|
|
const std::vector<VendorNdk>& HalManifest::vendorNdks() const { |
|
CHECK(mType == SchemaType::FRAMEWORK); |
|
return framework.mVendorNdks; |
|
} |
|
|
|
std::string HalManifest::getXmlFilePath(const std::string& xmlFileName, |
|
const Version& version) const { |
|
using std::literals::string_literals::operator""s; |
|
auto range = getXmlFiles(xmlFileName); |
|
for (auto it = range.first; it != range.second; ++it) { |
|
const ManifestXmlFile& manifestXmlFile = it->second; |
|
if (manifestXmlFile.version() == version) { |
|
if (!manifestXmlFile.overriddenPath().empty()) { |
|
return manifestXmlFile.overriddenPath(); |
|
} |
|
return "/"s + (type() == SchemaType::DEVICE ? "vendor" : "system") + "/etc/" + |
|
xmlFileName + "_V" + std::to_string(version.majorVer) + "_" + |
|
std::to_string(version.minorVer) + ".xml"; |
|
} |
|
} |
|
return ""; |
|
} |
|
|
|
bool operator==(const HalManifest &lft, const HalManifest &rgt) { |
|
return lft.mType == rgt.mType && lft.mLevel == rgt.mLevel && lft.mHals == rgt.mHals && |
|
lft.mXmlFiles == rgt.mXmlFiles && |
|
(lft.mType != SchemaType::DEVICE || |
|
(lft.device.mSepolicyVersion == rgt.device.mSepolicyVersion && |
|
lft.device.mKernel == rgt.device.mKernel)) && |
|
(lft.mType != SchemaType::FRAMEWORK || |
|
( |
|
#pragma clang diagnostic push |
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations" |
|
lft.framework.mVndks == rgt.framework.mVndks && |
|
#pragma clang diagnostic pop |
|
lft.framework.mVendorNdks == rgt.framework.mVendorNdks && |
|
lft.framework.mSystemSdk == rgt.framework.mSystemSdk)); |
|
} |
|
|
|
// Alternative to forEachInstance if you just need a set of instance names instead. |
|
std::set<std::string> HalManifest::getInstances(const std::string& halName, const Version& version, |
|
const std::string& interfaceName) const { |
|
std::set<std::string> ret; |
|
(void)forEachInstanceOfInterface(halName, version, interfaceName, [&ret](const auto& e) { |
|
ret.insert(e.instance()); |
|
return true; |
|
}); |
|
return ret; |
|
} |
|
|
|
// Return whether instance is in getInstances(...). |
|
bool HalManifest::hasInstance(const std::string& halName, const Version& version, |
|
const std::string& interfaceName, const std::string& instance) const { |
|
bool found = false; |
|
(void)forEachInstanceOfInterface(halName, version, interfaceName, |
|
[&found, &instance](const auto& e) { |
|
found |= (instance == e.instance()); |
|
return !found; // if not found, continue |
|
}); |
|
return found; |
|
} |
|
|
|
bool HalManifest::insertInstance(const FqInstance& fqInstance, Transport transport, Arch arch, |
|
HalFormat format, std::string* error) { |
|
for (ManifestHal& hal : getHals()) { |
|
if (hal.name == fqInstance.getPackage() && hal.format == format && |
|
hal.transport() == transport && hal.arch() == arch) { |
|
return hal.insertInstance(fqInstance, error); |
|
} |
|
} |
|
|
|
ManifestHal hal; |
|
hal.name = fqInstance.getPackage(); |
|
hal.format = format; |
|
hal.transportArch = TransportArch(transport, arch); |
|
if (!hal.insertInstance(fqInstance, error)) return false; |
|
return add(std::move(hal)); |
|
} |
|
|
|
bool HalManifest::empty() const { |
|
HalManifest emptyManifest; |
|
emptyManifest.setType(type()); |
|
return (*this) == emptyManifest; |
|
} |
|
|
|
const std::optional<KernelInfo>& HalManifest::kernel() const { |
|
return device.mKernel; |
|
} |
|
|
|
bool HalManifest::addAll(HalManifest* other, std::string* error) { |
|
if (other->mMetaVersion.majorVer != mMetaVersion.majorVer) { |
|
if (error) { |
|
*error = "Cannot merge manifest version " + to_string(mMetaVersion) + " and " + |
|
to_string(other->mMetaVersion); |
|
} |
|
return false; |
|
} |
|
mMetaVersion.minorVer = std::max(mMetaVersion.minorVer, other->mMetaVersion.minorVer); |
|
|
|
if (type() != other->type()) { |
|
if (error) { |
|
*error = "Cannot add a " + to_string(other->type()) + " manifest to a " + |
|
to_string(type()) + " manifest"; |
|
} |
|
return false; |
|
} |
|
|
|
if (!addAllHals(other, error)) { |
|
return false; |
|
} |
|
|
|
if (!addAllXmlFiles(other, error)) { |
|
return false; |
|
} |
|
|
|
if (!mergeField(&mLevel, &other->mLevel, Level::UNSPECIFIED)) { |
|
if (error) { |
|
*error = "Conflicting target-level: " + to_string(level()) + " vs. " + |
|
to_string(other->level()); |
|
} |
|
return false; |
|
} |
|
|
|
if (type() == SchemaType::DEVICE) { |
|
if (!mergeField(&device.mSepolicyVersion, &other->device.mSepolicyVersion)) { |
|
if (error) { |
|
*error = "Conflicting sepolicy version: " + to_string(sepolicyVersion()) + " vs. " + |
|
to_string(other->sepolicyVersion()); |
|
} |
|
return false; |
|
} |
|
|
|
if (!mergeField(&device.mKernel, &other->device.mKernel)) { |
|
// If fails, both have values. |
|
if (error) { |
|
*error = "Conflicting kernel: " + to_string(device.mKernel->version()) + " vs. " + |
|
to_string(other->device.mKernel->version()); |
|
} |
|
return false; |
|
} |
|
} else if (type() == SchemaType::FRAMEWORK) { |
|
#pragma clang diagnostic push |
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations" |
|
framework.mVndks.insert(framework.mVndks.end(), other->framework.mVndks.begin(), |
|
other->framework.mVndks.end()); |
|
other->framework.mVndks.clear(); |
|
#pragma clang diagnostic pop |
|
|
|
framework.mVendorNdks.insert(framework.mVendorNdks.end(), |
|
other->framework.mVendorNdks.begin(), |
|
other->framework.mVendorNdks.end()); |
|
other->framework.mVendorNdks.clear(); |
|
|
|
framework.mSystemSdk.addAll(&other->framework.mSystemSdk); |
|
} else { |
|
LOG(FATAL) << "unknown SchemaType: " |
|
<< static_cast<std::underlying_type_t<SchemaType>>(type()); |
|
} |
|
|
|
if (!other->empty()) { |
|
if (error) { |
|
*error = |
|
"Cannot add another manifest because it contains extraneous entries that " |
|
"are not recognized."; |
|
} |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
} // namespace vintf |
|
} // namespace android
|
|
|