1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <getopt.h> 18 #include <sysexits.h> 19 20 #include <algorithm> 21 #include <map> 22 23 #include <android-base/file.h> 24 #include <android-base/logging.h> 25 #include <android-base/strings.h> 26 #include <vintf/Dirmap.h> 27 #include <vintf/HostFileSystem.h> 28 #include <vintf/VintfFm.h> 29 #include <vintf/VintfObject.h> 30 #include <vintf/parse_string.h> 31 #include <vintf/parse_xml.h> 32 33 #include "utils.h" 34 35 namespace android::vintf { 36 37 namespace { 38 39 int usage() { 40 LOG(ERROR) << R"( 41 vintffm: Utility to deprecate framework manifest. 42 usage: 43 vintffm <-c|--check> <--dirmap /system:system_dir> frozen_dir 44 Check framework manifest under system_root against frozen dir. root is the 45 root directory of the device, e.g. $ANDROID_PRODUCT_OUT. 46 vintffm <-u|--update> <--dirmap /system:system_dir> <-l|--level>=current_level output_frozen_dir 47 Update ${output_frozen_dir}/${current_level}.xml using framework manifest. 48 vintffm <-h|--help> 49 Print help message. 50 51 Example: 52 53 # Freeze a framework manifest for Android R. 54 m check-vintf-all # Build framework manifest. 55 vintffm --update --dirmap /system:$ANDROID_PRODUCT_OUT/system --level 5 \ 56 system/libhidl/vintfdata/frozen 57 58 # Check that the framework manifest is aligned with the frozen data. 59 vintffm --check --dirmap /system:$ANDROID_PRODUCT_OUT/system \ 60 system/libhidl/vintfdata/frozen 61 )"; 62 return EX_USAGE; 63 } 64 65 class WritableFileSystemImpl : public WritableFileSystem { 66 public: 67 status_t fetch(const std::string& path, std::string* fetched, 68 std::string* error) const override { 69 return mRoFileSystem.fetch(path, fetched, error); 70 } 71 status_t listFiles(const std::string& path, std::vector<std::string>* out, 72 std::string* error) const override { 73 return mRoFileSystem.listFiles(path, out, error); 74 } 75 status_t write(const std::string& path, const std::string& content, 76 std::string* error) const override { 77 if (!android::base::WriteStringToFile(content, path)) { 78 int saved_errno = errno; 79 if (error) { 80 *error = "Can't write to " + path + ": " + strerror(saved_errno); 81 } 82 return saved_errno == 0 ? UNKNOWN_ERROR : -saved_errno; 83 } 84 return OK; 85 } 86 status_t deleteFile(const std::string& path, std::string* error) const override { 87 if (unlink(path.c_str()) == -1) { 88 int saved_errno = errno; 89 if (error) { 90 *error = "Can't unlink " + path + ": " + strerror(saved_errno); 91 } 92 return saved_errno == 0 ? UNKNOWN_ERROR : -saved_errno; 93 } 94 return OK; 95 } 96 97 private: 98 details::FileSystemImpl mRoFileSystem; 99 }; 100 101 } // namespace 102 103 namespace details { 104 105 // A VintfObject with a proper framework manifest and a fake device manifest with 106 // only targetFcmVersion. 107 class FmOnlyVintfObject : public VintfObject { 108 public: 109 FmOnlyVintfObject(std::unique_ptr<FileSystem>&& fs, Level targetFcmVersion) 110 : mFs(std::move(fs)) { 111 mDeviceManifest = std::make_shared<HalManifest>(); 112 mDeviceManifest->mLevel = targetFcmVersion; 113 } 114 115 std::shared_ptr<const HalManifest> getDeviceHalManifest() override { return mDeviceManifest; } 116 std::shared_ptr<const CompatibilityMatrix> getFrameworkCompatibilityMatrix() override { 117 return nullptr; 118 } 119 std::shared_ptr<const CompatibilityMatrix> getDeviceCompatibilityMatrix() override { 120 return nullptr; 121 } 122 123 protected: 124 const std::unique_ptr<FileSystem>& getFileSystem() override { return mFs; } 125 // Set environment to empty to prevent accidentally reading other things. 126 const std::unique_ptr<PropertyFetcher>& getPropertyFetcher() override { return mNoOpProp; } 127 128 private: 129 std::unique_ptr<FileSystem> mFs; 130 std::shared_ptr<HalManifest> mDeviceManifest; 131 std::unique_ptr<PropertyFetcher> mNoOpProp = std::make_unique<details::PropertyFetcherNoOp>(); 132 }; 133 134 } // namespace details 135 136 VintfFm::VintfFm() : VintfFm(std::make_unique<WritableFileSystemImpl>()) {} 137 138 int VintfFm::main(int argc, char** argv) { 139 // clang-format off 140 const struct option longopts[] = { 141 {"check", no_argument, nullptr, 'c'}, 142 {"dirmap", required_argument, nullptr, 'd'}, 143 {"help", no_argument, nullptr, 'h'}, 144 {"level", required_argument, nullptr, 'l'}, 145 {"update", no_argument, nullptr, 'u'}, 146 {0, 0, 0, 0}}; 147 // clang-format on 148 149 bool checking = false; 150 bool updating = false; 151 Level current = Level::UNSPECIFIED; 152 std::vector<std::string> dirmapVec; 153 154 int res; 155 optind = 1; 156 while ((res = getopt_long(argc, argv, "cdhlu", longopts, nullptr)) >= 0) { 157 switch (res) { 158 case 'c': { 159 checking = true; 160 } break; 161 162 case 'd': { 163 dirmapVec.push_back(optarg); 164 } break; 165 166 case 'l': { 167 if (!parse(optarg, ¤t)) { 168 LOG(ERROR) << "Unable to parse '" << optarg << "' as level."; 169 return usage(); 170 } 171 } break; 172 173 case 'u': { 174 updating = true; 175 } break; 176 177 case 'h': 178 default: { 179 return usage(); 180 } break; 181 } 182 } 183 184 if ((checking + updating) != 1) { 185 LOG(ERROR) << "Exactly one of --check or --update must be set."; 186 return usage(); 187 } 188 189 auto dirmap = details::getDirmap(dirmapVec); 190 auto vintfFsFactory = [&] { 191 return std::make_unique<details::HostFileSystem>(dirmap, NAME_NOT_FOUND, mFs.get()); 192 }; 193 194 argc -= optind; 195 argv += optind; 196 197 if (argc != 1) { 198 LOG(ERROR) << "There must be exactly 1 positional arguments."; 199 return usage(); 200 } 201 auto dir = argv[0]; 202 203 if (updating) { 204 return update(vintfFsFactory, dir, current); 205 } 206 return check(vintfFsFactory, dir); 207 } 208 209 int VintfFm::update(const FsFactory& vintfFsFactory, const std::string& dir, Level level) { 210 if (level == Level::UNSPECIFIED) { 211 LOG(ERROR) << "Must specify last frozen level with --level for --update option."; 212 return usage(); 213 } 214 215 auto manifest = getManifestForLevel(vintfFsFactory, level); 216 if (manifest == nullptr) { 217 LOG(ERROR) << "Unable to determine manifests for level " << level; 218 return EX_SOFTWARE; 219 } 220 221 if (!dumpMatrix(*manifest, dir, level)) { 222 return EX_SOFTWARE; 223 } 224 225 return EX_OK; 226 } 227 228 int VintfFm::check(const FsFactory& vintfFsFactory, const std::string& dir) { 229 auto frozenMatrices = loadMatrices(dir); 230 if (!frozenMatrices.has_value()) { 231 return EX_SOFTWARE; 232 } 233 for (const auto& [level, matrix] : *frozenMatrices) { 234 auto manifest = getManifestForLevel(vintfFsFactory, level); 235 if (manifest == nullptr) { 236 LOG(ERROR) << "Unable to determine manifests for level " << level; 237 return EX_SOFTWARE; 238 } 239 std::string error; 240 if (!manifest->checkCompatibility(matrix, &error)) { 241 LOG(ERROR) << "Framework manifest is incompatible with frozen matrix at level " << level 242 << ": " << error; 243 return EX_SOFTWARE; 244 } 245 } 246 return OK; 247 } 248 249 std::shared_ptr<const HalManifest> VintfFm::getManifestForLevel(const FsFactory& vintfFsFactory, 250 Level level) { 251 auto vintfObject = std::make_unique<details::FmOnlyVintfObject>(vintfFsFactory(), level); 252 auto frameworkManifest = vintfObject->getFrameworkHalManifest(); 253 if (frameworkManifest == nullptr) { 254 LOG(ERROR) << "Unable to get framework HAL manifest for target FCM version " << level; 255 } 256 return frameworkManifest; 257 } 258 259 bool VintfFm::dumpMatrix(const HalManifest& frameworkManifest, const std::string& dir, 260 Level level) { 261 auto matrix = frameworkManifest.generateCompatibleMatrix(false /*optional*/); 262 std::string path = dir + "/" + to_string(level) + ".xml"; 263 std::string error; 264 if (OK != mFs->write(path, toXml(matrix), &error)) { 265 LOG(ERROR) << "Unable to dump matrix to " << path << ": " << error; 266 return false; 267 } 268 return true; 269 } 270 271 std::optional<VintfFm::FrozenMatrices> VintfFm::loadMatrices(const std::string& dir) { 272 std::string error; 273 std::vector<std::string> allFiles; 274 if (OK != mFs->listFiles(dir, &allFiles, &error)) { 275 LOG(ERROR) << "Unable to list files under " << dir << ": " << error; 276 return std::nullopt; 277 } 278 if (allFiles.empty()) { 279 LOG(ERROR) << "Unable to load frozen interfaces under " << dir << ": directory is empty."; 280 return std::nullopt; 281 } 282 auto ret = std::make_optional<FrozenMatrices>(); 283 for (const auto& filename : allFiles) { 284 std::string path = dir + "/" + filename; 285 std::string xmlString; 286 if (OK != mFs->fetch(path, &xmlString, &error)) { 287 LOG(ERROR) << "Unable to read " << path << ": " << error; 288 return std::nullopt; 289 } 290 CompatibilityMatrix matrix; 291 if (!fromXml(&matrix, xmlString, &error)) { 292 LOG(ERROR) << "Unable to parse " << path << ": " << error; 293 return std::nullopt; 294 } 295 std::string_view filenameSv{filename}; 296 (void)android::base::ConsumeSuffix(&filenameSv, ".xml"); 297 std::string levelString{filenameSv}; 298 Level matrixLevel; 299 if (!parse(levelString, &matrixLevel)) { 300 LOG(ERROR) << "Unable to parse " << path << ": " << levelString << " is not a level."; 301 return std::nullopt; 302 } 303 if (ret->find(matrixLevel) != ret->end()) { 304 LOG(ERROR) << "Duplicated level " << matrixLevel << ", second one is at " << path; 305 return std::nullopt; 306 } 307 ret->emplace(matrixLevel, std::move(matrix)); 308 } 309 return ret; 310 } 311 312 } // namespace android::vintf 313