1 /* 2 * Copyright (C) 2017 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 #include <unistd.h> 20 21 #include <iostream> 22 #include <map> 23 #include <optional> 24 25 #include <android-base/file.h> 26 #include <android-base/logging.h> 27 #include <android-base/parseint.h> 28 #include <android-base/result.h> 29 #include <android-base/strings.h> 30 #include <hidl/metadata.h> 31 #include <utils/Errors.h> 32 #include <vintf/KernelConfigParser.h> 33 #include <vintf/VintfObject.h> 34 #include <vintf/parse_string.h> 35 #include <vintf/parse_xml.h> 36 #include "utils.h" 37 38 namespace android { 39 namespace vintf { 40 namespace details { 41 42 // fake sysprops 43 using Properties = std::map<std::string, std::string>; 44 45 using Dirmap = std::map<std::string, std::string>; 46 47 enum Option : int { 48 // Modes 49 HELP, 50 DUMP_FILE_LIST = 1, 51 CHECK_COMPAT, 52 CHECK_ONE, 53 54 // Options 55 ROOTDIR, 56 PROPERTY, 57 DIR_MAP, 58 KERNEL, 59 }; 60 // command line arguments 61 using Args = std::multimap<Option, std::string>; 62 63 class HostFileSystem : public details::FileSystemImpl { 64 public: 65 HostFileSystem(const Dirmap& dirmap, status_t missingError) 66 : mDirMap(dirmap), mMissingError(missingError) {} 67 status_t fetch(const std::string& path, std::string* fetched, 68 std::string* error) const override { 69 auto resolved = resolve(path, error); 70 if (resolved.empty()) { 71 return mMissingError; 72 } 73 status_t status = details::FileSystemImpl::fetch(resolved, fetched, error); 74 LOG(INFO) << "Fetch '" << resolved << "': " << toString(status); 75 return status; 76 } 77 status_t listFiles(const std::string& path, std::vector<std::string>* out, 78 std::string* error) const override { 79 auto resolved = resolve(path, error); 80 if (resolved.empty()) { 81 return mMissingError; 82 } 83 status_t status = details::FileSystemImpl::listFiles(resolved, out, error); 84 LOG(INFO) << "List '" << resolved << "': " << toString(status); 85 return status; 86 } 87 88 private: 89 static std::string toString(status_t status) { 90 return status == OK ? "SUCCESS" : strerror(-status); 91 } 92 std::string resolve(const std::string& path, std::string* error) const { 93 for (auto [prefix, mappedPath] : mDirMap) { 94 if (path == prefix) { 95 return mappedPath; 96 } 97 if (android::base::StartsWith(path, prefix + "/")) { 98 return mappedPath + "/" + path.substr(prefix.size() + 1); 99 } 100 } 101 if (error) { 102 *error = "Cannot resolve path " + path; 103 } else { 104 LOG(mMissingError == NAME_NOT_FOUND ? INFO : ERROR) << "Cannot resolve path " << path; 105 } 106 return ""; 107 } 108 109 Dirmap mDirMap; 110 status_t mMissingError; 111 }; 112 113 class PresetPropertyFetcher : public PropertyFetcher { 114 public: 115 std::string getProperty(const std::string& key, 116 const std::string& defaultValue) const override { 117 auto it = mProps.find(key); 118 if (it == mProps.end()) { 119 LOG(INFO) << "Sysprop " << key << " is missing, default to '" << defaultValue << "'"; 120 return defaultValue; 121 } 122 LOG(INFO) << "Sysprop " << key << "=" << it->second; 123 return it->second; 124 } 125 uint64_t getUintProperty(const std::string& key, uint64_t defaultValue, 126 uint64_t max) const override { 127 uint64_t result; 128 std::string value = getProperty(key, ""); 129 if (!value.empty() && android::base::ParseUint(value, &result, max)) return result; 130 return defaultValue; 131 } 132 bool getBoolProperty(const std::string& key, bool defaultValue) const override { 133 std::string value = getProperty(key, ""); 134 if (value == "1" || value == "true") { 135 return true; 136 } else if (value == "0" || value == "false") { 137 return false; 138 } 139 return defaultValue; 140 } 141 void setProperties(const Properties& props) { mProps.insert(props.begin(), props.end()); } 142 143 private: 144 std::map<std::string, std::string> mProps; 145 }; 146 147 struct StaticRuntimeInfo : public RuntimeInfo { 148 KernelVersion kernelVersion; 149 std::string kernelConfigFile; 150 151 status_t fetchAllInformation(FetchFlags flags) override { 152 if (flags & RuntimeInfo::FetchFlag::CPU_VERSION) { 153 mKernel.mVersion = kernelVersion; 154 LOG(INFO) << "fetched kernel version " << kernelVersion; 155 } 156 if (flags & RuntimeInfo::FetchFlag::CONFIG_GZ) { 157 std::string content; 158 if (!android::base::ReadFileToString(kernelConfigFile, &content)) { 159 LOG(ERROR) << "Cannot read " << kernelConfigFile; 160 return UNKNOWN_ERROR; 161 } 162 KernelConfigParser parser; 163 auto status = parser.processAndFinish(content); 164 if (status != OK) { 165 return status; 166 } 167 mKernel.mConfigs = std::move(parser.configs()); 168 LOG(INFO) << "read kernel configs from " << kernelConfigFile; 169 } 170 if (flags & RuntimeInfo::FetchFlag::POLICYVERS) { 171 mKernelSepolicyVersion = SIZE_MAX; 172 } 173 return OK; 174 } 175 }; 176 177 struct StubRuntimeInfo : public RuntimeInfo { 178 status_t fetchAllInformation(FetchFlags) override { return UNKNOWN_ERROR; } 179 }; 180 181 struct StaticRuntimeInfoFactory : public ObjectFactory<RuntimeInfo> { 182 std::shared_ptr<RuntimeInfo> info; 183 StaticRuntimeInfoFactory(std::shared_ptr<RuntimeInfo> i) : info(i) {} 184 std::shared_ptr<RuntimeInfo> make_shared() const override { 185 if (info) return info; 186 return std::make_shared<StubRuntimeInfo>(); 187 } 188 }; 189 190 // helper functions 191 template <typename T> 192 std::unique_ptr<T> readObject(FileSystem* fileSystem, const std::string& path, 193 const XmlConverter<T>& converter) { 194 std::string xml; 195 std::string error; 196 status_t err = fileSystem->fetch(path, &xml, &error); 197 if (err != OK) { 198 LOG(ERROR) << "Cannot read '" << path << "' (" << strerror(-err) << "): " << error; 199 return nullptr; 200 } 201 auto ret = std::make_unique<T>(); 202 if (!converter(ret.get(), xml, &error)) { 203 LOG(ERROR) << "Cannot parse '" << path << "': " << error; 204 return nullptr; 205 } 206 return ret; 207 } 208 209 int checkCompatibilityForFiles(const std::string& manifestPath, const std::string& matrixPath) { 210 auto fileSystem = std::make_unique<FileSystemImpl>(); 211 auto manifest = readObject(fileSystem.get(), manifestPath, gHalManifestConverter); 212 auto matrix = readObject(fileSystem.get(), matrixPath, gCompatibilityMatrixConverter); 213 if (manifest == nullptr || matrix == nullptr) { 214 return -1; 215 } 216 217 std::string error; 218 if (!manifest->checkCompatibility(*matrix, &error)) { 219 LOG(ERROR) << "Incompatible: " << error; 220 std::cout << "false" << std::endl; 221 return 1; 222 } 223 224 std::cout << "true" << std::endl; 225 return 0; 226 } 227 228 Args parseArgs(int argc, char** argv) { 229 int longOptFlag; 230 int optionIndex; 231 Args ret; 232 std::vector<struct option> longopts{ 233 // Modes 234 {"help", no_argument, &longOptFlag, HELP}, 235 {"dump-file-list", no_argument, &longOptFlag, DUMP_FILE_LIST}, 236 {"check-compat", no_argument, &longOptFlag, CHECK_COMPAT}, 237 {"check-one", no_argument, &longOptFlag, CHECK_ONE}, 238 // Options 239 {"rootdir", required_argument, &longOptFlag, ROOTDIR}, 240 {"property", required_argument, &longOptFlag, PROPERTY}, 241 {"dirmap", required_argument, &longOptFlag, DIR_MAP}, 242 {"kernel", required_argument, &longOptFlag, KERNEL}, 243 {0, 0, 0, 0}}; 244 std::map<int, Option> shortopts{ 245 {'h', HELP}, {'D', PROPERTY}, {'c', CHECK_COMPAT}, 246 }; 247 for (;;) { 248 int c = getopt_long(argc, argv, "hcD:", longopts.data(), &optionIndex); 249 if (c == -1) { 250 break; 251 } 252 std::string argValue = optarg ? optarg : std::string{}; 253 if (c == 0) { 254 ret.emplace(static_cast<Option>(longOptFlag), std::move(argValue)); 255 } else { 256 ret.emplace(shortopts[c], std::move(argValue)); 257 } 258 } 259 if (optind < argc) { 260 // see non option 261 LOG(ERROR) << "unrecognized option `" << argv[optind] << "'"; 262 return {{HELP, ""}}; 263 } 264 return ret; 265 } 266 267 template <typename T> 268 std::map<std::string, std::string> splitArgs(const T& args, char split) { 269 std::map<std::string, std::string> ret; 270 for (const auto& arg : args) { 271 auto pos = arg.find(split); 272 auto key = arg.substr(0, pos); 273 auto value = pos == std::string::npos ? std::string{} : arg.substr(pos + 1); 274 ret[key] = value; 275 } 276 return ret; 277 } 278 template <typename T> 279 Properties getProperties(const T& args) { 280 return splitArgs(args, '='); 281 } 282 283 template <typename T> 284 Dirmap getDirmap(const T& args) { 285 return splitArgs(args, ':'); 286 } 287 288 template <typename T> 289 std::shared_ptr<StaticRuntimeInfo> getRuntimeInfo(const T& args) { 290 auto ret = std::make_shared<StaticRuntimeInfo>(); 291 if (std::distance(args.begin(), args.end()) > 1) { 292 LOG(ERROR) << "Can't have multiple --kernel options"; 293 return nullptr; 294 } 295 auto pair = android::base::Split(*args.begin(), ":"); 296 if (pair.size() != 2) { 297 LOG(ERROR) << "Invalid --kernel"; 298 return nullptr; 299 } 300 if (!parse(pair[0], &ret->kernelVersion)) { 301 LOG(ERROR) << "Cannot parse " << pair[0] << " as kernel version"; 302 return nullptr; 303 } 304 ret->kernelConfigFile = std::move(pair[1]); 305 return ret; 306 } 307 308 int usage(const char* me) { 309 LOG(ERROR) 310 << me << ": check VINTF metadata." << std::endl 311 << " Modes:" << std::endl 312 << " --dump-file-list: Dump a list of directories / files on device" << std::endl 313 << " that is required to be used by --check-compat." << std::endl 314 << " -c, --check-compat: check compatibility for files under the root" << std::endl 315 << " directory specified by --root-dir." << std::endl 316 << " --check-one: check consistency of VINTF metadata for a single partition." 317 << std::endl 318 << std::endl 319 << " Options:" << std::endl 320 << " --rootdir=<dir>: specify root directory for all metadata. Same as " << std::endl 321 << " --dirmap /:<dir>" << std::endl 322 << " -D, --property <key>=<value>: specify sysprops." << std::endl 323 << " --dirmap </system:/dir/to/system> [--dirmap </vendor:/dir/to/vendor>[...]]" 324 << std::endl 325 << " Map partitions to directories. Cannot be specified with --rootdir." 326 << " --kernel <x.y.z:path/to/config>" << std::endl 327 << " Use the given kernel version and config to check. If" << std::endl 328 << " unspecified, kernel requirements are skipped." << std::endl 329 << std::endl 330 << " --help: show this message." << std::endl 331 << std::endl 332 << " Example:" << std::endl 333 << " # Get the list of required files." << std::endl 334 << " " << me << " --dump-file-list > /tmp/files.txt" << std::endl 335 << " # Pull from ADB, or use your own command to extract files from images" 336 << std::endl 337 << " ROOTDIR=/tmp/device/" << std::endl 338 << " cat /tmp/files.txt | xargs -I{} bash -c \"mkdir -p $ROOTDIR`dirname {}` && adb " 339 "pull {} $ROOTDIR{}\"" 340 << std::endl 341 << " # Check compatibility." << std::endl 342 << " " << me << " --check-compat --rootdir=$ROOTDIR \\" << std::endl 343 << " --property ro.product.first_api_level=`adb shell getprop " 344 "ro.product.first_api_level` \\" 345 << std::endl 346 << " --property ro.boot.product.hardware.sku=`adb shell getprop " 347 "ro.boot.product.hardware.sku`"; 348 return EX_USAGE; 349 } 350 351 // If |result| is already an error, don't do anything. Otherwise, set it to 352 // an error with |errorCode|. Return reference to Error object for appending 353 // additional error messages. 354 android::base::Error& SetErrorCode(std::optional<android::base::Error>* retError, 355 int errorCode = 0) { 356 if (!retError->has_value()) { 357 retError->emplace(errorCode); 358 } else { 359 // Use existing error code. 360 // There should already been an error message appended. Add a new line char for 361 // additional messages. 362 (**retError) << "\n"; 363 } 364 return **retError; 365 } 366 367 // If |other| is an error, add it to |retError|. 368 template <typename T> 369 void AddResult(std::optional<android::base::Error>* retError, 370 const android::base::Result<T>& other) { 371 if (other.ok()) return; 372 SetErrorCode(retError, other.error().code()) << other.error(); 373 } 374 375 android::base::Result<void> checkAllFiles(const Dirmap& dirmap, const Properties& props, 376 std::shared_ptr<StaticRuntimeInfo> runtimeInfo) { 377 auto hostPropertyFetcher = std::make_unique<PresetPropertyFetcher>(); 378 hostPropertyFetcher->setProperties(props); 379 380 CheckFlags::Type flags = CheckFlags::DEFAULT; 381 if (!runtimeInfo) flags = flags.disableRuntimeInfo(); 382 383 auto vintfObject = 384 VintfObject::Builder() 385 .setFileSystem(std::make_unique<HostFileSystem>(dirmap, UNKNOWN_ERROR)) 386 .setPropertyFetcher(std::move(hostPropertyFetcher)) 387 .setRuntimeInfoFactory(std::make_unique<StaticRuntimeInfoFactory>(runtimeInfo)) 388 .build(); 389 390 std::optional<android::base::Error> retError = std::nullopt; 391 392 std::string compatibleError; 393 int compatibleResult = vintfObject->checkCompatibility(&compatibleError, flags); 394 if (compatibleResult == INCOMPATIBLE) { 395 SetErrorCode(&retError) << compatibleError; 396 } else if (compatibleResult != COMPATIBLE) { 397 SetErrorCode(&retError, -compatibleResult) << compatibleError; 398 } 399 400 auto hidlMetadata = HidlInterfaceMetadata::all(); 401 402 std::string deprecateError; 403 int deprecateResult = vintfObject->checkDeprecation(hidlMetadata, &deprecateError); 404 if (deprecateResult == DEPRECATED) { 405 SetErrorCode(&retError) << deprecateError; 406 } else if (deprecateResult != NO_DEPRECATED_HALS) { 407 SetErrorCode(&retError, -deprecateResult) << deprecateError; 408 } 409 410 auto hasFcmExt = vintfObject->hasFrameworkCompatibilityMatrixExtensions(); 411 AddResult(&retError, hasFcmExt); 412 413 auto deviceManifest = vintfObject->getDeviceHalManifest(); 414 Level targetFcm = Level::UNSPECIFIED; 415 if (deviceManifest == nullptr) { 416 SetErrorCode(&retError, -NAME_NOT_FOUND) << "No device HAL manifest"; 417 } else { 418 targetFcm = deviceManifest->level(); 419 } 420 421 if (hasFcmExt.value_or(false) || (targetFcm != Level::UNSPECIFIED && targetFcm >= Level::R)) { 422 AddResult(&retError, vintfObject->checkUnusedHals(hidlMetadata)); 423 } else { 424 LOG(INFO) << "Skip checking unused HALs."; 425 } 426 427 if (retError.has_value()) { 428 return *retError; 429 } else { 430 return {}; 431 } 432 } 433 434 int checkDirmaps(const Dirmap& dirmap, const Properties& props) { 435 auto hostPropertyFetcher = std::make_unique<PresetPropertyFetcher>(); 436 hostPropertyFetcher->setProperties(props); 437 auto exitCode = EX_OK; 438 for (auto&& [prefix, mappedPath] : dirmap) { 439 auto vintfObject = 440 VintfObject::Builder() 441 .setFileSystem(std::make_unique<HostFileSystem>(dirmap, NAME_NOT_FOUND)) 442 .setPropertyFetcher(std::move(hostPropertyFetcher)) 443 .setRuntimeInfoFactory(std::make_unique<StaticRuntimeInfoFactory>(nullptr)) 444 .build(); 445 446 if (android::base::StartsWith(prefix, "/system")) { 447 LOG(INFO) << "Checking system manifest."; 448 auto manifest = vintfObject->getFrameworkHalManifest(); 449 if (!manifest) { 450 LOG(ERROR) << "Cannot fetch system manifest."; 451 exitCode = EX_SOFTWARE; 452 } 453 LOG(INFO) << "Checking system matrix."; 454 auto matrix = vintfObject->getFrameworkCompatibilityMatrix(); 455 if (!matrix) { 456 LOG(ERROR) << "Cannot fetch system matrix."; 457 exitCode = EX_SOFTWARE; 458 } 459 continue; 460 } 461 462 if (android::base::StartsWith(prefix, "/vendor")) { 463 LOG(INFO) << "Checking vendor manifest."; 464 auto manifest = vintfObject->getDeviceHalManifest(); 465 if (!manifest) { 466 LOG(ERROR) << "Cannot fetch vendor manifest."; 467 exitCode = EX_SOFTWARE; 468 } 469 LOG(INFO) << "Checking vendor matrix."; 470 auto matrix = vintfObject->getDeviceCompatibilityMatrix(); 471 if (!matrix) { 472 LOG(ERROR) << "Cannot fetch vendor matrix."; 473 exitCode = EX_SOFTWARE; 474 } 475 continue; 476 } 477 478 LOG(ERROR) << "--check-one does not work with --dirmap " << prefix; 479 exitCode = EX_SOFTWARE; 480 } 481 return exitCode; 482 } 483 484 } // namespace details 485 } // namespace vintf 486 } // namespace android 487 488 int main(int argc, char** argv) { 489 android::base::SetLogger(android::base::StderrLogger); 490 491 using namespace android::vintf; 492 using namespace android::vintf::details; 493 // legacy usage: check_vintf <manifest.xml> <matrix.xml> 494 if (argc == 3 && *argv[1] != '-' && *argv[2] != '-') { 495 int ret = checkCompatibilityForFiles(argv[1], argv[2]); 496 if (ret >= 0) return ret; 497 } 498 499 Args args = parseArgs(argc, argv); 500 501 if (!iterateValues(args, HELP).empty()) { 502 return usage(argv[0]); 503 } 504 505 if (!iterateValues(args, DUMP_FILE_LIST).empty()) { 506 for (const auto& file : dumpFileList()) { 507 std::cout << file << std::endl; 508 } 509 return 0; 510 } 511 512 auto dirmap = getDirmap(iterateValues(args, DIR_MAP)); 513 auto properties = getProperties(iterateValues(args, PROPERTY)); 514 515 if (!iterateValues(args, CHECK_ONE).empty()) { 516 return checkDirmaps(dirmap, properties); 517 } 518 519 auto checkCompat = iterateValues(args, CHECK_COMPAT); 520 if (checkCompat.empty()) { 521 return usage(argv[0]); 522 } 523 524 auto rootdirs = iterateValues(args, ROOTDIR); 525 if (!rootdirs.empty()) { 526 if (std::distance(rootdirs.begin(), rootdirs.end()) > 1) { 527 LOG(ERROR) << "Can't have multiple --rootdir options"; 528 return usage(argv[0]); 529 } 530 args.emplace(DIR_MAP, "/:" + *rootdirs.begin()); 531 } 532 533 std::shared_ptr<StaticRuntimeInfo> runtimeInfo; 534 auto kernelArgs = iterateValues(args, KERNEL); 535 if (!kernelArgs.empty()) { 536 runtimeInfo = getRuntimeInfo(kernelArgs); 537 if (runtimeInfo == nullptr) { 538 return usage(argv[0]); 539 } 540 } 541 542 if (dirmap.empty()) { 543 LOG(ERROR) << "Missing --rootdir or --dirmap option."; 544 return usage(argv[0]); 545 } 546 547 auto compat = checkAllFiles(dirmap, properties, runtimeInfo); 548 549 if (compat.ok()) { 550 std::cout << "COMPATIBLE" << std::endl; 551 return EX_OK; 552 } 553 if (compat.error().code() == 0) { 554 LOG(ERROR) << "files are incompatible: " << compat.error(); 555 std::cout << "INCOMPATIBLE" << std::endl; 556 return EX_DATAERR; 557 } 558 LOG(ERROR) << strerror(compat.error().code()) << ": " << compat.error(); 559 return EX_SOFTWARE; 560 } 561