| /* | 
 |  * 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-hash/Hash.h> | 
 | #include <hidl-util/Formatter.h> | 
 | #include <hidl-util/StringHelper.h> | 
 | #include <iostream> | 
 |  | 
 | #include "AST.h" | 
 | #include "Interface.h" | 
 | #include "hidl-gen_l.h" | 
 |  | 
 | static bool existdir(const char *name) { | 
 |     DIR *dir = opendir(name); | 
 |     if (dir == nullptr) { | 
 |         return false; | 
 |     } | 
 |     closedir(dir); | 
 |     return true; | 
 | } | 
 |  | 
 | namespace android { | 
 |  | 
 | const std::string &Coordinator::getRootPath() const { | 
 |     return mRootPath; | 
 | } | 
 |  | 
 | void Coordinator::setRootPath(const std::string &rootPath) { | 
 |     mRootPath = rootPath; | 
 |  | 
 |     if (!mRootPath.empty() && !StringHelper::EndsWith(mRootPath, "/")) { | 
 |         mRootPath += "/"; | 
 |     } | 
 | } | 
 |  | 
 | void Coordinator::setOutputPath(const std::string& outputPath) { | 
 |     mOutputPath = outputPath; | 
 | } | 
 |  | 
 | void Coordinator::setVerbose(bool verbose) { | 
 |     mVerbose = verbose; | 
 | } | 
 |  | 
 | void Coordinator::setRequireFrozen(bool requireFrozen) { | 
 |     mRequireFrozen = requireFrozen; | 
 | } | 
 |  | 
 | bool Coordinator::isVerbose() const { | 
 |     return mVerbose; | 
 | } | 
 |  | 
 | void Coordinator::setDepFile(const std::string& depFile) { | 
 |     mDepFile = depFile; | 
 | } | 
 |  | 
 | const std::string& Coordinator::getOwner() const { | 
 |     return mOwner; | 
 | } | 
 | void Coordinator::setOwner(const std::string& owner) { | 
 |     mOwner = owner; | 
 | } | 
 |  | 
 | status_t Coordinator::addPackagePath(const std::string& root, const std::string& path, std::string* error) { | 
 |     FQName package = FQName(root, "0.0", ""); | 
 |     for (const PackageRoot &packageRoot : mPackageRoots) { | 
 |         if (packageRoot.root.inPackage(root) || package.inPackage(packageRoot.root.package())) { | 
 |             if (error != nullptr) { | 
 |                 *error = "ERROR: conflicting package roots " + | 
 |                          packageRoot.root.package() + | 
 |                          " and " + | 
 |                          root; | 
 |             } | 
 |  | 
 |             return UNKNOWN_ERROR; | 
 |         } | 
 |     } | 
 |  | 
 |     mPackageRoots.push_back({path, package}); | 
 |     return OK; | 
 | } | 
 | void Coordinator::addDefaultPackagePath(const std::string& root, const std::string& path) { | 
 |     addPackagePath(root, path, nullptr /* error */); | 
 | } | 
 |  | 
 | Formatter Coordinator::getFormatter(const FQName& fqName, Location location, | 
 |                                     const std::string& fileName) const { | 
 |     if (location == Location::STANDARD_OUT) { | 
 |         return Formatter(stdout); | 
 |     } | 
 |  | 
 |     std::string filepath; | 
 |     status_t err = getFilepath(fqName, location, fileName, &filepath); | 
 |     if (err != OK) { | 
 |         return Formatter::invalid(); | 
 |     } | 
 |  | 
 |     onFileAccess(filepath, "w"); | 
 |  | 
 |     if (!Coordinator::MakeParentHierarchy(filepath)) { | 
 |         fprintf(stderr, "ERROR: could not make directories for %s.\n", filepath.c_str()); | 
 |         return Formatter::invalid(); | 
 |     } | 
 |  | 
 |     FILE* file = fopen(filepath.c_str(), "w"); | 
 |  | 
 |     if (file == nullptr) { | 
 |         fprintf(stderr, "ERROR: could not open file %s: %d\n", filepath.c_str(), errno); | 
 |         return Formatter::invalid(); | 
 |     } | 
 |  | 
 |     return Formatter(file); | 
 | } | 
 |  | 
 | status_t Coordinator::getFilepath(const FQName& fqName, Location location, | 
 |                                   const std::string& fileName, std::string* path) const { | 
 |     status_t err; | 
 |     std::string packagePath; | 
 |     std::string packageRootPath; | 
 |  | 
 |     switch (location) { | 
 |         case Location::DIRECT: { /* nothing */ | 
 |             *path = mOutputPath + fileName; | 
 |         } break; | 
 |         case Location::PACKAGE_ROOT: { | 
 |             err = getPackagePath(fqName, false /* relative */, false /* sanitized */, &packagePath); | 
 |             if (err != OK) return err; | 
 |  | 
 |             *path = mOutputPath + packagePath + fileName; | 
 |         } break; | 
 |         case Location::GEN_OUTPUT: { | 
 |             err = convertPackageRootToPath(fqName, &packageRootPath); | 
 |             if (err != OK) return err; | 
 |             err = getPackagePath(fqName, true /* relative */, false /* sanitized */, &packagePath); | 
 |             if (err != OK) return err; | 
 |  | 
 |             *path = mOutputPath + packageRootPath + packagePath + fileName; | 
 |         } break; | 
 |         case Location::GEN_SANITIZED: { | 
 |             err = convertPackageRootToPath(fqName, &packageRootPath); | 
 |             if (err != OK) return err; | 
 |             err = getPackagePath(fqName, true /* relative */, true /* sanitized */, &packagePath); | 
 |             if (err != OK) return err; | 
 |  | 
 |             *path = mOutputPath + packageRootPath + packagePath + fileName; | 
 |         } break; | 
 |         default: { CHECK(false) << "Invalid location: " << static_cast<size_t>(location); } | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | void Coordinator::onFileAccess(const std::string& path, const std::string& mode) const { | 
 |     if (mode == "r") { | 
 |         // This is a global list. It's not cleared when a second fqname is processed for | 
 |         // two reasons: | 
 |         // 1). If there is a bug in hidl-gen, the dependencies on the first project from | 
 |         //     the second would be required to recover correctly when the bug is fixed. | 
 |         // 2). This option is never used in Android builds. | 
 |         mReadFiles.insert(makeRelative(path)); | 
 |     } | 
 |  | 
 |     if (!mVerbose) { | 
 |         return; | 
 |     } | 
 |  | 
 |     fprintf(stderr, | 
 |             "VERBOSE: file access %s %s\n", path.c_str(), mode.c_str()); | 
 | } | 
 |  | 
 | status_t Coordinator::writeDepFile(const std::string& forFile) const { | 
 |     // No dep file requested | 
 |     if (mDepFile.empty()) return OK; | 
 |  | 
 |     onFileAccess(mDepFile, "w"); | 
 |  | 
 |     FILE* file = fopen(mDepFile.c_str(), "w"); | 
 |     if (file == nullptr) { | 
 |         fprintf(stderr, "ERROR: could not open dep file at %s.\n", mDepFile.c_str()); | 
 |         return UNKNOWN_ERROR; | 
 |     } | 
 |  | 
 |     Formatter out(file, 2 /* spacesPerIndent */); | 
 |     out << StringHelper::LTrim(forFile, mOutputPath) << ": \\\n"; | 
 |     out.indent([&] { | 
 |         for (const std::string& file : mReadFiles) { | 
 |             out << makeRelative(file) << " \\\n"; | 
 |         } | 
 |     }); | 
 |     return OK; | 
 | } | 
 |  | 
 | AST* Coordinator::parse(const FQName& fqName, std::set<AST*>* parsedASTs, | 
 |                         Enforce enforcement) const { | 
 |     AST* ret; | 
 |     status_t err = parseOptional(fqName, &ret, parsedASTs, enforcement); | 
 |     if (err != OK) CHECK(ret == nullptr);  // internal consistency | 
 |  | 
 |     // only in a handful of places do we want to distinguish between | 
 |     // a missing file and a bad AST. Everywhere else, we just want to | 
 |     // throw an error if we expect an AST to be present but it is not. | 
 |     return ret; | 
 | } | 
 |  | 
 | status_t Coordinator::parseOptional(const FQName& fqName, AST** ast, std::set<AST*>* parsedASTs, | 
 |                                     Enforce enforcement) const { | 
 |     CHECK(fqName.isFullyQualified()); | 
 |  | 
 |     auto it = mCache.find(fqName); | 
 |     if (it != mCache.end()) { | 
 |         *ast = (*it).second; | 
 |  | 
 |         if (*ast != nullptr && parsedASTs != nullptr) { | 
 |             parsedASTs->insert(*ast); | 
 |         } | 
 |  | 
 |         if (*ast == nullptr) { | 
 |             // circular import OR that AST has errors in it | 
 |             return UNKNOWN_ERROR; | 
 |         } | 
 |  | 
 |         return OK; | 
 |     } | 
 |  | 
 |     // Add this to the cache immediately, so we can discover circular imports. | 
 |     mCache[fqName] = nullptr; | 
 |  | 
 |     std::string packagePath; | 
 |     status_t err = | 
 |         getPackagePath(fqName, false /* relative */, false /* sanitized */, &packagePath); | 
 |     if (err != OK) return err; | 
 |  | 
 |     const std::string path = makeAbsolute(packagePath + fqName.name() + ".hal"); | 
 |  | 
 |     *ast = new AST(this, &Hash::getHash(path)); | 
 |  | 
 |     if (fqName.name() != "types") { | 
 |         // 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)->addImplicitImport(fqName.getTypesForPackage()); | 
 |     } | 
 |  | 
 |     std::unique_ptr<FILE, std::function<void(FILE*)>> file(fopen(path.c_str(), "rb"), fclose); | 
 |  | 
 |     if (file == nullptr) { | 
 |         mCache.erase(fqName);  // nullptr in cache is used to find circular imports | 
 |         delete *ast; | 
 |         *ast = nullptr; | 
 |         return OK;  // File does not exist, nullptr AST* == file doesn't exist. | 
 |     } | 
 |  | 
 |     onFileAccess(path, "r"); | 
 |  | 
 |     // parse file takes ownership of file | 
 |     if (parseFile(*ast, std::move(file)) != OK || (*ast)->postParse() != OK) { | 
 |         delete *ast; | 
 |         *ast = nullptr; | 
 |         return UNKNOWN_ERROR; | 
 |     } | 
 |  | 
 |     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 { | 
 |         if ((*ast)->isInterface()) { | 
 |             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(), (*ast)->getInterface()->definedName().c_str()); | 
 |  | 
 |                 err = UNKNOWN_ERROR; | 
 |             } else if ((*ast)->getInterface()->definedName() != 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)->definesInterfaces()) { | 
 |             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 err; | 
 |     } | 
 |  | 
 |     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, enforcement); | 
 |     if (err != OK) { | 
 |         mCache[fqName] = nullptr; | 
 |         delete *ast; | 
 |         *ast = nullptr; | 
 |         return err; | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | const Coordinator::PackageRoot* Coordinator::findPackageRoot(const FQName& fqName) const { | 
 |     CHECK(!fqName.package().empty()) << fqName.string(); | 
 |  | 
 |     // 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 ret = mPackageRoots.end(); | 
 |     for (auto it = mPackageRoots.begin(); it != mPackageRoots.end(); it++) { | 
 |         if (!fqName.inPackage(it->root.package())) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         if (ret != mPackageRoots.end()) { | 
 |             std::cerr << "ERROR: Multiple package roots found for " << fqName.string() << " (" | 
 |                       << it->root.package() << " and " << ret->root.package() << ")\n"; | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         ret = it; | 
 |     } | 
 |  | 
 |     if (ret == mPackageRoots.end()) { | 
 |         std::cerr << "ERROR: Package root not specified for " << fqName.string() << "\n"; | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     return &(*ret); | 
 | } | 
 |  | 
 | std::string Coordinator::makeAbsolute(const std::string& path) const { | 
 |     if (StringHelper::StartsWith(path, "/") || mRootPath.empty()) { | 
 |         return path; | 
 |     } | 
 |  | 
 |     return mRootPath + path; | 
 | } | 
 |  | 
 | std::string Coordinator::makeRelative(const std::string& filename) const { | 
 |     return StringHelper::LTrim(filename, mRootPath); | 
 | } | 
 |  | 
 | status_t Coordinator::getPackageRoot(const FQName& fqName, std::string* root) const { | 
 |     const PackageRoot* packageRoot = findPackageRoot(fqName); | 
 |     if (packageRoot == nullptr) { | 
 |         return UNKNOWN_ERROR; | 
 |     } | 
 |     *root = packageRoot->root.package(); | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::getPackageRootPath(const FQName& fqName, std::string* path) const { | 
 |     const PackageRoot* packageRoot = findPackageRoot(fqName); | 
 |     if (packageRoot == nullptr) { | 
 |         return UNKNOWN_ERROR; | 
 |     } | 
 |     *path = packageRoot->path; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::getPackagePath(const FQName& fqName, bool relative, bool sanitized, | 
 |                                      std::string* path) const { | 
 |     const PackageRoot* packageRoot = findPackageRoot(fqName); | 
 |     if (packageRoot == nullptr) return UNKNOWN_ERROR; | 
 |  | 
 |     // Given FQName of "android.hardware.nfc.test@1.0::IFoo" and a prefix | 
 |     // "android.hardware", the suffix is "nfc.test". | 
 |     std::string suffix = StringHelper::LTrim(fqName.package(), packageRoot->root.package()); | 
 |     suffix = StringHelper::LTrim(suffix, "."); | 
 |  | 
 |     std::vector<std::string> suffixComponents; | 
 |     StringHelper::SplitString(suffix, '.', &suffixComponents); | 
 |  | 
 |     std::vector<std::string> components; | 
 |     if (!relative) { | 
 |         components.push_back(StringHelper::RTrimAll(packageRoot->path, "/")); | 
 |     } | 
 |     components.insert(components.end(), suffixComponents.begin(), suffixComponents.end()); | 
 |     components.push_back(sanitized ? fqName.sanitizedVersion() : fqName.version()); | 
 |  | 
 |     *path = StringHelper::JoinStrings(components, "/") + "/"; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::getPackageInterfaceFiles( | 
 |         const FQName &package, | 
 |         std::vector<std::string> *fileNames) const { | 
 |     if (fileNames) fileNames->clear(); | 
 |  | 
 |     std::string packagePath; | 
 |     status_t err = | 
 |         getPackagePath(package, false /* relative */, false /* sanitized */, &packagePath); | 
 |     if (err != OK) return err; | 
 |  | 
 |     const std::string path = makeAbsolute(packagePath); | 
 |     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(path.c_str()), closedir); | 
 |  | 
 |     if (dir == nullptr) { | 
 |         fprintf(stderr, "ERROR: Could not open package path %s for package %s:\n%s\n", | 
 |                 packagePath.c_str(), package.string().c_str(), path.c_str()); | 
 |         return -errno; | 
 |     } | 
 |  | 
 |     if (fileNames == nullptr) { | 
 |         return OK; | 
 |     } | 
 |  | 
 |     struct dirent *ent; | 
 |     while ((ent = readdir(dir.get())) != nullptr) { | 
 |         // filesystems may not support d_type and return DT_UNKNOWN | 
 |         if (ent->d_type == DT_UNKNOWN) { | 
 |             struct stat sb; | 
 |             const auto filename = packagePath + std::string(ent->d_name); | 
 |             if (stat(filename.c_str(), &sb) == -1) { | 
 |                 fprintf(stderr, "ERROR: Could not stat %s\n", filename.c_str()); | 
 |                 return -errno; | 
 |             } | 
 |             if ((sb.st_mode & S_IFMT) != S_IFREG) { | 
 |                 continue; | 
 |             } | 
 |         } else 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)); | 
 |     } | 
 |  | 
 |     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.version(), fileName); | 
 |         packageInterfaces->push_back(subFQName); | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::convertPackageRootToPath(const FQName& fqName, std::string* path) const { | 
 |     std::string packageRoot; | 
 |     status_t err = getPackageRoot(fqName, &packageRoot); | 
 |     if (err != OK) return err; | 
 |  | 
 |     if (*(packageRoot.end()--) != '.') { | 
 |         packageRoot += '.'; | 
 |     } | 
 |  | 
 |     std::replace(packageRoot.begin(), packageRoot.end(), '.', '/'); | 
 |  | 
 |     *path = packageRoot;  // now converted to a path | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::isTypesOnlyPackage(const FQName& package, bool* result) const { | 
 |     std::vector<FQName> packageInterfaces; | 
 |  | 
 |     status_t err = appendPackageInterfacesToVector(package, &packageInterfaces); | 
 |  | 
 |     if (err != OK) { | 
 |         *result = false; | 
 |         return err; | 
 |     } | 
 |  | 
 |     *result = packageInterfaces.size() == 1 && packageInterfaces[0].name() == "types"; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::addUnreferencedTypes(const std::vector<FQName>& packageInterfaces, | 
 |                                            std::set<FQName>* unreferencedDefinitions, | 
 |                                            std::set<FQName>* unreferencedImports) const { | 
 |     CHECK(unreferencedDefinitions != nullptr); | 
 |     CHECK(unreferencedImports != nullptr); | 
 |  | 
 |     std::set<FQName> packageDefinedTypes; | 
 |     std::set<FQName> packageReferencedTypes; | 
 |     std::set<FQName> packageImportedTypes; | 
 |     std::set<FQName> typesDefinedTypes;  // only types.hal types | 
 |  | 
 |     for (const auto& fqName : packageInterfaces) { | 
 |         AST* ast = parse(fqName, nullptr /*imported*/, Coordinator::Enforce::NONE); | 
 |         if (!ast) { | 
 |             std::cerr << "ERROR: Could not parse " << fqName.string() << ". Aborting." << std::endl; | 
 |  | 
 |             return UNKNOWN_ERROR; | 
 |         } | 
 |  | 
 |         ast->addDefinedTypes(&packageDefinedTypes); | 
 |         ast->addReferencedTypes(&packageReferencedTypes); | 
 |         ast->getAllImportedNamesGranular(&packageImportedTypes); | 
 |  | 
 |         if (fqName.name() == "types") { | 
 |             ast->addDefinedTypes(&typesDefinedTypes); | 
 |         } | 
 |     } | 
 |  | 
 | #if 0 | 
 |     for (const auto &fqName : packageDefinedTypes) { | 
 |         std::cout << "VERBOSE: DEFINED " << fqName.string() << std::endl; | 
 |     } | 
 |  | 
 |     for (const auto &fqName : packageImportedTypes) { | 
 |         std::cout << "VERBOSE: IMPORTED " << fqName.string() << std::endl; | 
 |     } | 
 |  | 
 |     for (const auto &fqName : packageReferencedTypes) { | 
 |         std::cout << "VERBOSE: REFERENCED " << fqName.string() << std::endl; | 
 |     } | 
 |  | 
 |     for (const auto &fqName : typesDefinedTypes) { | 
 |         std::cout << "VERBOSE: DEFINED in types.hal " << fqName.string() << std::endl; | 
 |     } | 
 | #endif | 
 |  | 
 |     for (const auto& fqName : packageReferencedTypes) { | 
 |         packageDefinedTypes.erase(fqName); | 
 |         packageImportedTypes.erase(fqName); | 
 |     } | 
 |  | 
 |     // A package implicitly imports its own types.hal, only track them in one set. | 
 |     for (const auto& fqName : typesDefinedTypes) { | 
 |         packageImportedTypes.erase(fqName); | 
 |     } | 
 |  | 
 |     // defined but not referenced | 
 |     unreferencedDefinitions->insert(packageDefinedTypes.begin(), packageDefinedTypes.end()); | 
 |     // imported but not referenced | 
 |     unreferencedImports->insert(packageImportedTypes.begin(), packageImportedTypes.end()); | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::enforceRestrictionsOnPackage(const FQName& fqName, | 
 |                                                    Enforce enforcement) const { | 
 |     CHECK(enforcement == Enforce::FULL || enforcement == Enforce::NO_HASH || | 
 |           enforcement == Enforce::NONE); | 
 |  | 
 |     // need fqName to be something like android.hardware.foo@1.0. | 
 |     // name and valueName is ignored. | 
 |     if (fqName.package().empty() || fqName.version().empty()) { | 
 |         std::cerr << "ERROR: Cannot enforce restrictions on package " << fqName.string() | 
 |                   << ": package or version is missing." << std::endl; | 
 |         return BAD_VALUE; | 
 |     } | 
 |  | 
 |     if (enforcement == Enforce::NONE) { | 
 |         return OK; | 
 |     } | 
 |  | 
 |     FQName package = fqName.getPackageAndVersion(); | 
 |     // look up cache. | 
 |     if (mPackagesEnforced.find(package) != mPackagesEnforced.end()) { | 
 |         return OK; | 
 |     } | 
 |  | 
 |     // enforce all rules. | 
 |     status_t err; | 
 |  | 
 |     err = enforceMinorVersionUprevs(package, enforcement); | 
 |     if (err != OK) { | 
 |         return err; | 
 |     } | 
 |  | 
 |     if (enforcement != Enforce::NO_HASH) { | 
 |         err = enforceHashes(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::packageExists(const FQName& package, bool* result) const { | 
 |     std::string packagePath; | 
 |     status_t err = | 
 |             getPackagePath(package, false /* relative */, false /* sanitized */, &packagePath); | 
 |     if (err != OK) return err; | 
 |  | 
 |     if (existdir(makeAbsolute(packagePath).c_str())) { | 
 |         *result = true; | 
 |         return OK; | 
 |     } | 
 |  | 
 |     *result = false; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::enforceMinorVersionUprevs(const FQName& currentPackage, | 
 |                                                 Enforce enforcement) const { | 
 |     if(!currentPackage.hasVersion()) { | 
 |         std::cerr << "ERROR: Cannot enforce minor version uprevs for " << currentPackage.string() | 
 |                   << ": missing version." << std::endl; | 
 |         return UNKNOWN_ERROR; | 
 |     } | 
 |  | 
 |     if (currentPackage.getPackageMinorVersion() == 0) { | 
 |         return OK; // ignore for @x.0 | 
 |     } | 
 |  | 
 |     bool hasPrevPackage = false; | 
 |     FQName prevPackage = currentPackage; | 
 |     while (prevPackage.getPackageMinorVersion() > 0) { | 
 |         prevPackage = prevPackage.downRev(); | 
 |  | 
 |         bool result; | 
 |         status_t err = packageExists(prevPackage, &result); | 
 |         if (err != OK) return err; | 
 |  | 
 |         if (result) { | 
 |             hasPrevPackage = true; | 
 |             break; | 
 |         } | 
 |     } | 
 |     if (!hasPrevPackage) { | 
 |         // no @x.z, where z < y, exist. | 
 |         return OK; | 
 |     } | 
 |  | 
 |     if (prevPackage != currentPackage.downRev()) { | 
 |         std::cerr << "ERROR: Cannot enforce minor version uprevs for " << currentPackage.string() | 
 |                   << ": Found package " << prevPackage.string() << " but missing " | 
 |                   << currentPackage.downRev().string() << "; you cannot skip a minor version." | 
 |                   << std::endl; | 
 |         return UNKNOWN_ERROR; | 
 |     } | 
 |  | 
 |     bool prevIsTypesOnly; | 
 |     status_t err = isTypesOnlyPackage(prevPackage, &prevIsTypesOnly); | 
 |     if (err != OK) return err; | 
 |  | 
 |     if (prevIsTypesOnly) { | 
 |         // A types only package can be extended in any way. | 
 |         return OK; | 
 |     } | 
 |  | 
 |     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 | 
 |         } | 
 |  | 
 |         const Interface *iface = nullptr; | 
 |         AST* currentAST = parse(currentFQName, nullptr /* parsedASTs */, enforcement); | 
 |         if (currentAST != nullptr) { | 
 |             iface = currentAST->getInterface(); | 
 |         } | 
 |         if (iface == nullptr) { | 
 |             if (currentAST == nullptr) { | 
 |                 std::cerr << "WARNING: Skipping " << currentFQName.string() | 
 |                           << " because it could not be found or parsed" | 
 |                           << " or " << currentPackage.string() << " doesn't pass all requirements." | 
 |                           << std::endl; | 
 |             } else { | 
 |                 std::cerr << "WARNING: Skipping " << currentFQName.string() | 
 |                           << " because the file might contain more than one interface." | 
 |                           << std::endl; | 
 |             } | 
 |             continue; | 
 |         } | 
 |  | 
 |         if (iface->superType() == nullptr) { | 
 |             CHECK(iface->isIBase()); | 
 |             continue; | 
 |         } | 
 |  | 
 |         // Assume that currentFQName == android.hardware.foo@2.2::IFoo. | 
 |         FQName lastFQName(prevPackage.package(), prevPackage.version(), | 
 |                 currentFQName.name()); | 
 |         AST *lastAST = parse(lastFQName); | 
 |  | 
 |         for (; lastFQName.getPackageMinorVersion() > 0 && | 
 |                (lastAST == nullptr || lastAST->getInterface() == nullptr) | 
 |              ; lastFQName = lastFQName.downRev(), lastAST = parse(lastFQName)) { | 
 |             // nothing | 
 |         } | 
 |  | 
 |         // Then lastFQName == android.hardware.foo@2.1::IFoo or | 
 |         //      lastFQName == android.hardware.foo@2.0::IFoo if 2.1 doesn't exist. | 
 |  | 
 |         bool lastFQNameExists = lastAST != nullptr && lastAST->getInterface() != nullptr; | 
 |  | 
 |         if (!lastFQNameExists) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         if (iface->superType()->fqName() != lastFQName) { | 
 |             std::cerr << "ERROR: Cannot enforce minor version uprevs for " | 
 |                       << currentPackage.string() << ": " << iface->fqName().string() << " extends " | 
 |                       << iface->superType()->fqName().string() | 
 |                       << ", which is not allowed. It must extend " << lastFQName.string() | 
 |                       << std::endl; | 
 |             return UNKNOWN_ERROR; | 
 |         } | 
 |  | 
 |         // at least one interface must extend the previous version | 
 |         // @2.0::IFoo does not work. It must be @2.1::IFoo for at least one interface. | 
 |         if (lastFQName.getPackageAndVersion() == prevPackage.getPackageAndVersion()) { | 
 |             extendedInterface = true; | 
 |         } | 
 |  | 
 |         if (mVerbose) { | 
 |             std::cout << "VERBOSE: EnforceMinorVersionUprevs: " << currentFQName.string() | 
 |                       << " passes." << std::endl; | 
 |         } | 
 |     } | 
 |  | 
 |     if (!extendedInterface) { | 
 |         // No interface extends the interface with the same name in @x.(y-1). | 
 |         std::cerr << "ERROR: " << currentPackage.string() | 
 |                   << " doesn't pass minor version uprev requirement. " | 
 |                   << "Requires at least one interface to extend an interface with the same name " | 
 |                   << "from " << prevPackage.string() << "." << std::endl; | 
 |         return UNKNOWN_ERROR; | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | Coordinator::HashStatus Coordinator::checkHash(const FQName& fqName) const { | 
 |     AST* ast = parse(fqName); | 
 |     if (ast == nullptr) return HashStatus::ERROR; | 
 |  | 
 |     std::string rootPath; | 
 |     status_t err = getPackageRootPath(fqName, &rootPath); | 
 |     if (err != OK) return HashStatus::ERROR; | 
 |  | 
 |     std::string hashPath = makeAbsolute(rootPath) + "/current.txt"; | 
 |     std::string error; | 
 |     bool fileExists; | 
 |     std::vector<std::string> frozen = | 
 |         Hash::lookupHash(hashPath, fqName.string(), &error, &fileExists); | 
 |     if (fileExists) onFileAccess(hashPath, "r"); | 
 |  | 
 |     if (error.size() > 0) { | 
 |         std::cerr << "ERROR: " << error << std::endl; | 
 |         return HashStatus::ERROR; | 
 |     } | 
 |  | 
 |     // hash not defined, interface not frozen | 
 |     if (frozen.size() == 0) { | 
 |         if (isVerbose()) { | 
 |             std::cerr << "VERBOSE: clearing runtime hash for " << fqName.string() | 
 |                       << " because it is not frozen and so its hash cannot be depended upon as an " | 
 |                          "indication of stability." | 
 |                       << std::endl; | 
 |         } | 
 |         // This ensures that it can be detected. | 
 |         Hash::clearHash(ast->getFilename()); | 
 |  | 
 |         if (mRequireFrozen) { | 
 |             std::cerr << "ERROR: Unfrozen " << fqName.string() | 
 |                       << " cannot be referenced from context specifying -F (require_frozen)." | 
 |                       << std::endl; | 
 |             return HashStatus::ERROR; | 
 |         } | 
 |  | 
 |         return HashStatus::UNFROZEN; | 
 |     } | 
 |  | 
 |     std::string currentHash = ast->getFileHash()->hexString(); | 
 |  | 
 |     if (std::find(frozen.begin(), frozen.end(), currentHash) == frozen.end()) { | 
 |         std::cerr << "ERROR: " << fqName.string() << " has hash " << currentHash | 
 |                   << " which does not match hash on record. This interface has " | 
 |                   << "been frozen. Do not change it!" << std::endl; | 
 |         return HashStatus::CHANGED; | 
 |     } | 
 |  | 
 |     return HashStatus::FROZEN; | 
 | } | 
 |  | 
 | status_t Coordinator::getUnfrozenDependencies(const FQName& fqName, | 
 |                                               std::set<FQName>* result) const { | 
 |     CHECK(result != nullptr); | 
 |  | 
 |     AST* ast = parse(fqName); | 
 |     if (ast == nullptr) return UNKNOWN_ERROR; | 
 |  | 
 |     std::set<FQName> imported; | 
 |     ast->getImportedPackages(&imported); | 
 |  | 
 |     // consider current package as dependency for types.hal and also to make | 
 |     // sure that if anything is frozen in a package, everything is. | 
 |     imported.insert(fqName.getPackageAndVersion()); | 
 |  | 
 |     // no circular dependency is already guaranteed by parsing | 
 |     // indirect dependencies will be checked when the imported interface frozen checks are done | 
 |     for (const FQName& importedPackage : imported) { | 
 |         std::vector<FQName> packageInterfaces; | 
 |         status_t err = appendPackageInterfacesToVector(importedPackage, &packageInterfaces); | 
 |         if (err != OK) { | 
 |             return err; | 
 |         } | 
 |  | 
 |         for (const FQName& importedName : packageInterfaces) { | 
 |             HashStatus status = checkHash(importedName); | 
 |             switch (status) { | 
 |                 case HashStatus::CHANGED: | 
 |                 case HashStatus::ERROR: | 
 |                     return UNKNOWN_ERROR; | 
 |                 case HashStatus::FROZEN: | 
 |                     continue; | 
 |                 case HashStatus::UNFROZEN: | 
 |                     result->insert(importedName); | 
 |                     continue; | 
 |                 default: | 
 |                     LOG(FATAL) << static_cast<uint64_t>(status); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t Coordinator::enforceHashes(const FQName& currentPackage) const { | 
 |     std::vector<FQName> packageInterfaces; | 
 |     status_t err = appendPackageInterfacesToVector(currentPackage, &packageInterfaces); | 
 |     if (err != OK) { | 
 |         return err; | 
 |     } | 
 |  | 
 |     for (const FQName& currentFQName : packageInterfaces) { | 
 |         HashStatus status = checkHash(currentFQName); | 
 |         switch (status) { | 
 |             case HashStatus::CHANGED: | 
 |             case HashStatus::ERROR: | 
 |                 return UNKNOWN_ERROR; | 
 |             case HashStatus::FROZEN: { | 
 |                 std::set<FQName> unfrozenDependencies; | 
 |                 err = getUnfrozenDependencies(currentFQName, &unfrozenDependencies); | 
 |                 if (err != OK) return err; | 
 |  | 
 |                 if (!unfrozenDependencies.empty()) { | 
 |                     std::cerr << "ERROR: Frozen interface " << currentFQName.string() | 
 |                               << " cannot depend or be adjacent to unfrozen thing(s):" << std::endl; | 
 |                     for (const FQName& name : unfrozenDependencies) { | 
 |                         std::cerr << " (unfrozen) " << name.string() << std::endl; | 
 |                     } | 
 |                     return UNKNOWN_ERROR; | 
 |                 } | 
 |             } | 
 |                 continue; | 
 |             case HashStatus::UNFROZEN: | 
 |                 continue; | 
 |             default: | 
 |                 LOG(FATAL) << static_cast<uint64_t>(status); | 
 |         } | 
 |     } | 
 |  | 
 |     return err; | 
 | } | 
 |  | 
 | 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; | 
 | } | 
 |  | 
 | void Coordinator::emitOptionsUsageString(Formatter& out) { | 
 |     out << "[-p <root path>] (-r <interface root>)+ [-R] [-F] [-v] [-d <depfile>]"; | 
 | } | 
 |  | 
 | void Coordinator::emitOptionsDetailString(Formatter& out) { | 
 |     out << "-p <root path>: Android build root, defaults to $ANDROID_BUILD_TOP or pwd.\n" | 
 |         << "-R: Do not add default package roots if not specified in -r.\n" | 
 |         << "-F: Require all referenced ASTs to be frozen.\n" | 
 |         << "-r <package:path root>: E.g., android.hardware:hardware/interfaces.\n" | 
 |         << "-v: verbose output.\n" | 
 |         << "-d <depfile>: location of depfile to write to.\n"; | 
 | } | 
 |  | 
 | void Coordinator::parseOptions(int argc, char** argv, const std::string& options, | 
 |                                const HandleArg& handleArg) { | 
 |     // reset global state for getopt | 
 |     optind = 1; | 
 |  | 
 |     bool suppressDefaultPackagePaths = false; | 
 |  | 
 |     int res; | 
 |     std::string optstr = options + "p:r:RFvd:"; | 
 |     while ((res = getopt(argc, argv, optstr.c_str())) >= 0) { | 
 |         switch (res) { | 
 |             case 'v': { | 
 |                 setVerbose(true); | 
 |                 break; | 
 |             } | 
 |             case 'd': { | 
 |                 setDepFile(optarg); | 
 |                 break; | 
 |             } | 
 |             case 'p': { | 
 |                 if (!getRootPath().empty()) { | 
 |                     fprintf(stderr, "ERROR: -p <root path> can only be specified once.\n"); | 
 |                     exit(1); | 
 |                 } | 
 |                 setRootPath(optarg); | 
 |                 break; | 
 |             } | 
 |             case 'r': { | 
 |                 std::string val(optarg); | 
 |                 auto index = val.find_first_of(':'); | 
 |                 if (index == std::string::npos) { | 
 |                     fprintf(stderr, "ERROR: -r option must contain ':': %s\n", val.c_str()); | 
 |                     exit(1); | 
 |                 } | 
 |  | 
 |                 auto root = val.substr(0, index); | 
 |                 auto path = val.substr(index + 1); | 
 |  | 
 |                 std::string error; | 
 |                 status_t err = addPackagePath(root, path, &error); | 
 |                 if (err != OK) { | 
 |                     fprintf(stderr, "%s\n", error.c_str()); | 
 |                     exit(1); | 
 |                 } | 
 |  | 
 |                 break; | 
 |             } | 
 |             case 'R': { | 
 |                 suppressDefaultPackagePaths = true; | 
 |                 break; | 
 |             } | 
 |             case 'F': { | 
 |                 setRequireFrozen(true); | 
 |                 break; | 
 |             } | 
 |             // something downstream should handle these cases | 
 |             default: { handleArg(res, optarg); } | 
 |         } | 
 |     } | 
 |  | 
 |     if (getRootPath().empty()) { | 
 |         const char* ANDROID_BUILD_TOP = getenv("ANDROID_BUILD_TOP"); | 
 |         if (ANDROID_BUILD_TOP != nullptr) { | 
 |             setRootPath(ANDROID_BUILD_TOP); | 
 |         } | 
 |     } | 
 |  | 
 |     if (!suppressDefaultPackagePaths) { | 
 |         addDefaultPackagePath("android.hardware", "hardware/interfaces"); | 
 |         addDefaultPackagePath("android.hidl", "system/libhidl/transport"); | 
 |         addDefaultPackagePath("android.frameworks", "frameworks/hardware/interfaces"); | 
 |         addDefaultPackagePath("android.system", "system/hardware/interfaces"); | 
 |     } | 
 | } | 
 |  | 
 | }  // namespace android | 
 |  |