/* * Copyright (C) 2020 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" namespace android::vintf { namespace { int usage() { LOG(ERROR) << R"( vintffm: Utility to deprecate framework manifest. usage: vintffm <-c|--check> <--dirmap /system:system_dir> frozen_dir Check framework manifest under system_root against frozen dir. root is the root directory of the device, e.g. $ANDROID_PRODUCT_OUT. vintffm <-u|--update> <--dirmap /system:system_dir> <-l|--level>=current_level output_frozen_dir Update ${output_frozen_dir}/${current_level}.xml using framework manifest. vintffm <-h|--help> Print help message. Example: # Freeze a framework manifest for Android R. m check-vintf-all # Build framework manifest. vintffm --update --dirmap /system:$ANDROID_PRODUCT_OUT/system --level 5 \ system/libhidl/vintfdata/frozen # Check that the framework manifest is aligned with the frozen data. vintffm --check --dirmap /system:$ANDROID_PRODUCT_OUT/system \ system/libhidl/vintfdata/frozen )"; return EX_USAGE; } class WritableFileSystemImpl : public WritableFileSystem { public: status_t fetch(const std::string& path, std::string* fetched, std::string* error) const override { return mRoFileSystem.fetch(path, fetched, error); } status_t listFiles(const std::string& path, std::vector* out, std::string* error) const override { return mRoFileSystem.listFiles(path, out, error); } status_t modifiedTime(const std::string& path, timespec* out, std::string* error) const override { return mRoFileSystem.modifiedTime(path, out, error); } status_t write(const std::string& path, const std::string& content, std::string* error) const override { if (!android::base::WriteStringToFile(content, path)) { int saved_errno = errno; if (error) { *error = "Can't write to " + path + ": " + strerror(saved_errno); } return saved_errno == 0 ? UNKNOWN_ERROR : -saved_errno; } return OK; } status_t deleteFile(const std::string& path, std::string* error) const override { if (unlink(path.c_str()) == -1) { int saved_errno = errno; if (error) { *error = "Can't unlink " + path + ": " + strerror(saved_errno); } return saved_errno == 0 ? UNKNOWN_ERROR : -saved_errno; } return OK; } private: details::FileSystemImpl mRoFileSystem; }; } // namespace namespace details { // A VintfObject with a proper framework manifest and a fake device manifest with // only targetFcmVersion. class FmOnlyVintfObject : public VintfObject { public: FmOnlyVintfObject(std::unique_ptr&& fs, Level targetFcmVersion) : mFs(std::move(fs)) { mDeviceManifest = std::make_shared(); mDeviceManifest->mLevel = targetFcmVersion; } std::shared_ptr getDeviceHalManifest() override { return mDeviceManifest; } std::shared_ptr getFrameworkCompatibilityMatrix() override { return nullptr; } std::shared_ptr getDeviceCompatibilityMatrix() override { return nullptr; } protected: const std::unique_ptr& getFileSystem() override { return mFs; } // Set environment to empty to prevent accidentally reading other things. const std::unique_ptr& getPropertyFetcher() override { return mNoOpProp; } private: std::unique_ptr mFs; std::shared_ptr mDeviceManifest; std::unique_ptr mNoOpProp = std::make_unique(); }; } // namespace details VintfFm::VintfFm() : VintfFm(std::make_unique()) {} int VintfFm::main(int argc, char** argv) { // clang-format off const struct option longopts[] = { {"check", no_argument, nullptr, 'c'}, {"dirmap", required_argument, nullptr, 'd'}, {"help", no_argument, nullptr, 'h'}, {"level", required_argument, nullptr, 'l'}, {"update", no_argument, nullptr, 'u'}, {0, 0, 0, 0}}; // clang-format on bool checking = false; bool updating = false; Level current = Level::UNSPECIFIED; std::vector dirmapVec; int res; optind = 1; while ((res = getopt_long(argc, argv, "cdhlu", longopts, nullptr)) >= 0) { switch (res) { case 'c': { checking = true; } break; case 'd': { dirmapVec.push_back(optarg); } break; case 'l': { if (!parse(optarg, ¤t)) { LOG(ERROR) << "Unable to parse '" << optarg << "' as level."; return usage(); } } break; case 'u': { updating = true; } break; case 'h': default: { return usage(); } break; } } if ((checking + updating) != 1) { LOG(ERROR) << "Exactly one of --check or --update must be set."; return usage(); } auto dirmap = details::getDirmap(dirmapVec); auto vintfFsFactory = [&] { return std::make_unique(dirmap, NAME_NOT_FOUND, mFs.get()); }; argc -= optind; argv += optind; if (argc != 1) { LOG(ERROR) << "There must be exactly 1 positional arguments."; return usage(); } auto dir = argv[0]; if (updating) { return update(vintfFsFactory, dir, current); } return check(vintfFsFactory, dir); } int VintfFm::update(const FsFactory& vintfFsFactory, const std::string& dir, Level level) { if (level == Level::UNSPECIFIED) { LOG(ERROR) << "Must specify last frozen level with --level for --update option."; return usage(); } auto manifest = getManifestForLevel(vintfFsFactory, level); if (manifest == nullptr) { LOG(ERROR) << "Unable to determine manifests for level " << level; return EX_SOFTWARE; } if (!dumpMatrix(*manifest, dir, level)) { return EX_SOFTWARE; } return EX_OK; } int VintfFm::check(const FsFactory& vintfFsFactory, const std::string& dir) { auto frozenMatrices = loadMatrices(dir); if (!frozenMatrices.has_value()) { return EX_SOFTWARE; } for (const auto& [level, matrix] : *frozenMatrices) { auto manifest = getManifestForLevel(vintfFsFactory, level); if (manifest == nullptr) { LOG(ERROR) << "Unable to determine manifests for level " << level; return EX_SOFTWARE; } std::string error; if (!manifest->checkCompatibility(matrix, &error)) { LOG(ERROR) << "Framework manifest is incompatible with frozen matrix at level " << level << ": " << error; return EX_SOFTWARE; } } return OK; } std::shared_ptr VintfFm::getManifestForLevel(const FsFactory& vintfFsFactory, Level level) { auto vintfObject = std::make_unique(vintfFsFactory(), level); auto frameworkManifest = vintfObject->getFrameworkHalManifest(); if (frameworkManifest == nullptr) { LOG(ERROR) << "Unable to get framework HAL manifest for target FCM version " << level; } return frameworkManifest; } bool VintfFm::dumpMatrix(const HalManifest& frameworkManifest, const std::string& dir, Level level) { auto matrix = frameworkManifest.generateCompatibleMatrix(false /*optional*/); std::string path = dir + "/" + to_string(level) + ".xml"; std::string error; if (OK != mFs->write(path, toXml(matrix), &error)) { LOG(ERROR) << "Unable to dump matrix to " << path << ": " << error; return false; } return true; } std::optional VintfFm::loadMatrices(const std::string& dir) { std::string error; std::vector allFiles; if (OK != mFs->listFiles(dir, &allFiles, &error)) { LOG(ERROR) << "Unable to list files under " << dir << ": " << error; return std::nullopt; } if (allFiles.empty()) { LOG(ERROR) << "Unable to load frozen interfaces under " << dir << ": directory is empty."; return std::nullopt; } auto ret = std::make_optional(); for (const auto& filename : allFiles) { std::string path = dir + "/" + filename; std::string xmlString; if (OK != mFs->fetch(path, &xmlString, &error)) { LOG(ERROR) << "Unable to read " << path << ": " << error; return std::nullopt; } CompatibilityMatrix matrix; if (!fromXml(&matrix, xmlString, &error)) { LOG(ERROR) << "Unable to parse " << path << ": " << error; return std::nullopt; } std::string_view filenameSv{filename}; (void)android::base::ConsumeSuffix(&filenameSv, ".xml"); std::string levelString{filenameSv}; Level matrixLevel; if (!parse(levelString, &matrixLevel)) { LOG(ERROR) << "Unable to parse " << path << ": " << levelString << " is not a level."; return std::nullopt; } if (ret->find(matrixLevel) != ret->end()) { LOG(ERROR) << "Duplicated level " << matrixLevel << ", second one is at " << path; return std::nullopt; } ret->emplace(matrixLevel, std::move(matrix)); } return ret; } } // namespace android::vintf