| /* |
| * Copyright (C) 2016 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 "Coordinator.h" |
| |
| #include <dirent.h> |
| #include <sys/stat.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| |
| #include <android-base/logging.h> |
| #include <hidl-util/StringHelper.h> |
| |
| #include "AST.h" |
| #include "Interface.h" |
| |
| extern android::status_t parseFile(android::AST *ast); |
| |
| static bool existdir(const char *name) { |
| DIR *dir = opendir(name); |
| if (dir == NULL) { |
| return false; |
| } |
| closedir(dir); |
| return true; |
| } |
| |
| namespace android { |
| |
| Coordinator::Coordinator( |
| const std::vector<std::string> &packageRootPaths, |
| const std::vector<std::string> &packageRoots) |
| : mPackageRootPaths(packageRootPaths), |
| mPackageRoots(packageRoots) { |
| // empty |
| } |
| |
| Coordinator::~Coordinator() { |
| // empty |
| } |
| |
| AST *Coordinator::parse(const FQName &fqName, std::set<AST *> *parsedASTs) { |
| CHECK(fqName.isFullyQualified()); |
| |
| auto it = mCache.find(fqName); |
| if (it != mCache.end()) { |
| AST *ast = (*it).second; |
| |
| if (ast != nullptr && parsedASTs != nullptr) { |
| parsedASTs->insert(ast); |
| } |
| |
| return ast; |
| } |
| |
| // Add this to the cache immediately, so we can discover circular imports. |
| mCache[fqName] = nullptr; |
| |
| AST *typesAST = nullptr; |
| |
| if (fqName.name() != "types") { |
| // Any interface file implicitly imports its package's types.hal. |
| FQName typesName = fqName.getTypesForPackage(); |
| typesAST = parse(typesName, parsedASTs); |
| |
| // fall through. |
| } |
| |
| std::string path = getPackagePath(fqName); |
| |
| path.append(fqName.name()); |
| path.append(".hal"); |
| |
| AST *ast = new AST(this, path); |
| |
| if (typesAST != NULL) { |
| // If types.hal for this AST's package existed, make it's defined |
| // types available to the (about to be parsed) AST right away. |
| ast->addImportedAST(typesAST); |
| } |
| |
| status_t err = parseFile(ast); |
| |
| if (err != OK) { |
| // LOG(ERROR) << "parsing '" << path << "' FAILED."; |
| |
| delete ast; |
| ast = nullptr; |
| |
| return nullptr; |
| } |
| |
| if (ast->package().package() != fqName.package() |
| || ast->package().version() != fqName.version()) { |
| fprintf(stderr, |
| "ERROR: File at '%s' does not match expected package and/or " |
| "version.\n", |
| path.c_str()); |
| |
| err = UNKNOWN_ERROR; |
| } else { |
| std::string ifaceName; |
| if (ast->isInterface(&ifaceName)) { |
| if (fqName.name() == "types") { |
| fprintf(stderr, |
| "ERROR: File at '%s' declares an interface '%s' " |
| "instead of the expected types common to the package.\n", |
| path.c_str(), |
| ifaceName.c_str()); |
| |
| err = UNKNOWN_ERROR; |
| } else if (ifaceName != fqName.name()) { |
| fprintf(stderr, |
| "ERROR: File at '%s' does not declare interface type " |
| "'%s'.\n", |
| path.c_str(), |
| fqName.name().c_str()); |
| |
| err = UNKNOWN_ERROR; |
| } |
| } else if (fqName.name() != "types") { |
| fprintf(stderr, |
| "ERROR: File at '%s' declares types rather than the " |
| "expected interface type '%s'.\n", |
| path.c_str(), |
| fqName.name().c_str()); |
| |
| err = UNKNOWN_ERROR; |
| } else if (ast->containsInterfaces()) { |
| fprintf(stderr, |
| "ERROR: types.hal file at '%s' declares at least one " |
| "interface type.\n", |
| path.c_str()); |
| |
| err = UNKNOWN_ERROR; |
| } |
| } |
| |
| if (err != OK) { |
| delete ast; |
| ast = nullptr; |
| |
| return nullptr; |
| } |
| |
| if (parsedASTs != nullptr) { parsedASTs->insert(ast); } |
| |
| // put it into the cache now, so that enforceRestrictionsOnPackage can |
| // parse fqName. |
| mCache[fqName] = ast; |
| |
| // For each .hal file that hidl-gen parses, the whole package will be checked. |
| err = enforceRestrictionsOnPackage(fqName); |
| if (err != OK) { |
| mCache[fqName] = nullptr; |
| delete ast; |
| ast = nullptr; |
| return nullptr; |
| } |
| |
| return ast; |
| } |
| |
| std::vector<std::string>::const_iterator |
| Coordinator::findPackageRoot(const FQName &fqName) const { |
| CHECK(!fqName.package().empty()); |
| CHECK(!fqName.version().empty()); |
| |
| // Find the right package prefix and path for this FQName. For |
| // example, if FQName is "android.hardware.nfc@1.0::INfc", and the |
| // prefix:root is set to [ "android.hardware:hardware/interfaces", |
| // "vendor.qcom.hardware:vendor/qcom"], then we will identify the |
| // prefix "android.hardware" and the package root |
| // "hardware/interfaces". |
| |
| auto it = mPackageRoots.begin(); |
| auto ret = mPackageRoots.end(); |
| for (; it != mPackageRoots.end(); it++) { |
| if (!fqName.inPackage(*it)) { |
| continue; |
| } |
| |
| CHECK(ret == mPackageRoots.end()) |
| << "Multiple package roots found for " << fqName.string() |
| << " (" << *it << " and " << *ret << ")"; |
| |
| ret = it; |
| } |
| CHECK(ret != mPackageRoots.end()) |
| << "Unable to find package root for " << fqName.string(); |
| |
| return ret; |
| } |
| |
| std::string Coordinator::getPackageRoot(const FQName &fqName) const { |
| auto it = findPackageRoot(fqName); |
| auto prefix = *it; |
| return prefix; |
| } |
| |
| std::string Coordinator::getPackageRootPath(const FQName &fqName) const { |
| auto it = findPackageRoot(fqName); |
| auto root = mPackageRootPaths[std::distance(mPackageRoots.begin(), it)]; |
| return root; |
| } |
| |
| std::string Coordinator::getPackageRootOption(const FQName &fqName) const { |
| return getPackageRoot(fqName) + ":" + getPackageRootPath(fqName); |
| } |
| |
| std::string Coordinator::getPackagePath( |
| const FQName &fqName, bool relative, bool sanitized) const { |
| |
| auto it = findPackageRoot(fqName); |
| auto prefix = *it; |
| auto root = mPackageRootPaths[std::distance(mPackageRoots.begin(), it)]; |
| |
| // Make sure the prefix ends on a '.' and the root path on a '/' |
| if ((*--prefix.end()) != '.') { |
| prefix += '.'; |
| } |
| |
| if ((*--root.end()) != '/') { |
| root += '/'; |
| } |
| |
| // Given FQName of "android.hardware.nfc@1.0::IFoo" and a prefix |
| // "android.hardware.", the suffix is "nfc@1.0::IFoo". |
| const std::string packageSuffix = fqName.package().substr(prefix.length()); |
| |
| std::string packagePath; |
| if (!relative) { |
| packagePath = root; |
| } |
| |
| size_t startPos = 0; |
| size_t dotPos; |
| while ((dotPos = packageSuffix.find('.', startPos)) != std::string::npos) { |
| packagePath.append(packageSuffix.substr(startPos, dotPos - startPos)); |
| packagePath.append("/"); |
| |
| startPos = dotPos + 1; |
| } |
| CHECK_LT(startPos + 1, packageSuffix.length()); |
| packagePath.append(packageSuffix.substr(startPos)); |
| packagePath.append("/"); |
| |
| packagePath.append(sanitized ? fqName.sanitizedVersion() : fqName.version()); |
| packagePath.append("/"); |
| |
| return packagePath; |
| } |
| |
| status_t Coordinator::getPackageInterfaceFiles( |
| const FQName &package, |
| std::vector<std::string> *fileNames) const { |
| fileNames->clear(); |
| |
| const std::string packagePath = getPackagePath(package); |
| |
| DIR *dir = opendir(packagePath.c_str()); |
| |
| if (dir == NULL) { |
| LOG(ERROR) << "Could not open package path: " << packagePath; |
| return -errno; |
| } |
| |
| struct dirent *ent; |
| while ((ent = readdir(dir)) != NULL) { |
| if (ent->d_type != DT_REG) { |
| continue; |
| } |
| |
| const auto suffix = ".hal"; |
| const auto suffix_len = std::strlen(suffix); |
| const auto d_namelen = strlen(ent->d_name); |
| |
| if (d_namelen < suffix_len |
| || strcmp(ent->d_name + d_namelen - suffix_len, suffix)) { |
| continue; |
| } |
| |
| fileNames->push_back(std::string(ent->d_name, d_namelen - suffix_len)); |
| } |
| |
| closedir(dir); |
| dir = NULL; |
| |
| std::sort(fileNames->begin(), fileNames->end(), |
| [](const std::string& lhs, const std::string& rhs) -> bool { |
| if (lhs == "types") { |
| return true; |
| } |
| if (rhs == "types") { |
| return false; |
| } |
| return lhs < rhs; |
| }); |
| |
| return OK; |
| } |
| |
| status_t Coordinator::appendPackageInterfacesToVector( |
| const FQName &package, |
| std::vector<FQName> *packageInterfaces) const { |
| packageInterfaces->clear(); |
| |
| std::vector<std::string> fileNames; |
| status_t err = getPackageInterfaceFiles(package, &fileNames); |
| |
| if (err != OK) { |
| return err; |
| } |
| |
| for (const auto &fileName : fileNames) { |
| FQName subFQName( |
| package.package() + package.atVersion() + "::" + fileName); |
| |
| if (!subFQName.isValid()) { |
| LOG(WARNING) |
| << "Whole-package import encountered invalid filename '" |
| << fileName |
| << "' in package " |
| << package.package() |
| << package.atVersion(); |
| |
| continue; |
| } |
| |
| packageInterfaces->push_back(subFQName); |
| } |
| |
| return OK; |
| } |
| |
| std::string Coordinator::convertPackageRootToPath(const FQName &fqName) const { |
| std::string packageRoot = getPackageRoot(fqName); |
| |
| if (*(packageRoot.end()--) != '.') { |
| packageRoot += '.'; |
| } |
| |
| std::replace(packageRoot.begin(), packageRoot.end(), '.', '/'); |
| |
| return packageRoot; // now converted to a path |
| } |
| |
| |
| status_t Coordinator::enforceRestrictionsOnPackage(const FQName &fqName) { |
| // need fqName to be something like android.hardware.foo@1.0. |
| // name and valueName is ignored. |
| if (fqName.package().empty() || fqName.version().empty()) { |
| LOG(ERROR) << "Cannot enforce restrictions on package " << fqName.string() |
| << ": package or version is missing."; |
| return BAD_VALUE; |
| } |
| FQName package = fqName.getPackageAndVersion(); |
| // look up cache. |
| if (mPackagesEnforced.find(package) != mPackagesEnforced.end()) { |
| return OK; |
| } |
| |
| // enforce all rules. |
| status_t err = enforceMinorVersionUprevs(package); |
| if (err != OK) { |
| return err; |
| } |
| |
| // cache it so that it won't need to be enforced again. |
| mPackagesEnforced.insert(package); |
| return OK; |
| } |
| |
| status_t Coordinator::enforceMinorVersionUprevs(const FQName ¤tPackage) { |
| if(!currentPackage.hasVersion()) { |
| LOG(ERROR) << "Cannot enforce minor version uprevs for " << currentPackage.string() |
| << ": missing version."; |
| return UNKNOWN_ERROR; |
| } |
| |
| if (currentPackage.getPackageMinorVersion() == 0) { |
| return OK; // ignore for @x.0 |
| } |
| |
| bool hasPrevPackage = false; |
| FQName prevPacakge = currentPackage; |
| while (prevPacakge.getPackageMinorVersion() > 0) { |
| prevPacakge = prevPacakge.downRev(); |
| if (existdir(getPackagePath(prevPacakge).c_str())) { |
| hasPrevPackage = true; |
| break; |
| } |
| } |
| if (!hasPrevPackage) { |
| // no @x.z, where z < y, exist. |
| return OK; |
| } |
| |
| if (prevPacakge != currentPackage.downRev()) { |
| LOG(ERROR) << "Cannot enforce minor version uprevs for " << currentPackage.string() |
| << ": Found package " << prevPacakge.string() << " but missing " |
| << currentPackage.downRev().string() << "; you cannot skip a minor version."; |
| return UNKNOWN_ERROR; |
| } |
| |
| status_t err; |
| std::vector<FQName> packageInterfaces; |
| err = appendPackageInterfacesToVector(currentPackage, &packageInterfaces); |
| if (err != OK) { |
| return err; |
| } |
| |
| bool extendedInterface = false; |
| for (const FQName ¤tFQName : packageInterfaces) { |
| if (currentFQName.name() == "types") { |
| continue; // ignore types.hal |
| } |
| // Assume that currentFQName == android.hardware.foo@2.2::IFoo. |
| // Then prevFQName == android.hardware.foo@2.1::IFoo. |
| const Interface *iface = nullptr; |
| AST *currentAST = parse(currentFQName); |
| if (currentAST != nullptr) { |
| iface = currentAST->getInterface(); |
| } |
| if (iface == nullptr) { |
| if (currentAST == nullptr) { |
| LOG(WARNING) << "Warning: Skipping " << currentFQName.string() |
| << " because it could not be found or parsed" |
| << " or " << currentPackage.string() |
| << " doesn't pass all requirements."; |
| } else { |
| LOG(WARNING) << "Warning: Skipping " << currentFQName.string() |
| << " because the file might contain more than one interface."; |
| } |
| continue; |
| } |
| |
| // android.hardware.foo@2.2::IFoo exists. Now make sure |
| // @2.2::IFoo extends @2.1::IFoo. If any interface IFoo in @2.2 |
| // ensures this, @2.2 passes the enforcement. |
| FQName prevFQName(prevPacakge.package(), prevPacakge.version(), |
| currentFQName.name()); |
| if (iface->superType() == nullptr) { |
| // @2.2::IFoo doesn't extend anything. (This is probably IBase.) |
| continue; |
| } |
| if (iface->superType()->fqName() != prevFQName) { |
| // @2.2::IFoo doesn't extend @2.1::IFoo. |
| if (iface->superType()->fqName().getPackageAndVersion() == |
| prevPacakge.getPackageAndVersion()) { |
| LOG(ERROR) << "Cannot enforce minor version uprevs for " << currentPackage.string() |
| << ": " << iface->fqName().string() << " extends " |
| << iface->superType()->fqName().string() << ", which is not allowed."; |
| return UNKNOWN_ERROR; |
| } |
| // @2.2::IFoo extends something from a package with a different package name. |
| // Check the next interface. |
| continue; |
| } |
| |
| // @2.2::IFoo passes. Check next interface. |
| extendedInterface = true; |
| LOG(VERBOSE) << "enforceMinorVersionUprevs: " << currentFQName.string() << " passes."; |
| } |
| |
| if (!extendedInterface) { |
| // No interface extends the interface with the same name in @x.(y-1). |
| LOG(ERROR) << currentPackage.string() << " doesn't pass minor version uprev requirement. " |
| << "Requires at least one interface to extend an interface with the same name " |
| << "from " << prevPacakge.string() << "."; |
| return UNKNOWN_ERROR; |
| } |
| |
| return OK; |
| } |
| |
| // static |
| bool Coordinator::MakeParentHierarchy(const std::string &path) { |
| static const mode_t kMode = 0755; |
| |
| size_t start = 1; // Ignore leading '/' |
| size_t slashPos; |
| while ((slashPos = path.find("/", start)) != std::string::npos) { |
| std::string partial = path.substr(0, slashPos); |
| |
| struct stat st; |
| if (stat(partial.c_str(), &st) < 0) { |
| if (errno != ENOENT) { |
| return false; |
| } |
| |
| int res = mkdir(partial.c_str(), kMode); |
| if (res < 0) { |
| return false; |
| } |
| } else if (!S_ISDIR(st.st_mode)) { |
| return false; |
| } |
| |
| start = slashPos + 1; |
| } |
| |
| return true; |
| } |
| |
| } // namespace android |
| |