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 <algorithm> 22 #include <functional> 23 #include <iostream> 24 #include <map> 25 #include <optional> 26 27 #include <aidl/metadata.h> 28 #include <android-base/file.h> 29 #include <android-base/logging.h> 30 #include <android-base/parseint.h> 31 #include <android-base/result.h> 32 #include <android-base/strings.h> 33 #include <hidl/metadata.h> 34 #include <kver/kernel_release.h> 35 #include <utils/Errors.h> 36 #include <vintf/Dirmap.h> 37 #include <vintf/HostFileSystem.h> 38 #include <vintf/KernelConfigParser.h> 39 #include <vintf/VintfObject.h> 40 #include <vintf/fcm_exclude.h> 41 #include <vintf/parse_string.h> 42 #include <vintf/parse_xml.h> 43 #include "utils.h" 44 45 using android::kver::KernelRelease; 46 47 namespace android { 48 namespace vintf { 49 namespace details { 50 51 // fake sysprops 52 using Properties = std::map<std::string, std::string>; 53 54 enum Option : int { 55 // Modes 56 HELP, 57 DUMP_FILE_LIST = 1, 58 CHECK_COMPAT, 59 CHECK_ONE, 60 61 // Options 62 ROOTDIR, 63 PROPERTY, 64 DIR_MAP, 65 KERNEL, 66 }; 67 // command line arguments 68 using Args = std::multimap<Option, std::string>; 69 70 class PresetPropertyFetcher : public PropertyFetcher { 71 public: 72 std::string getProperty(const std::string& key, 73 const std::string& defaultValue) const override { 74 auto it = mProps.find(key); 75 if (it == mProps.end()) { 76 LOG(INFO) << "Sysprop " << key << " is missing, default to '" << defaultValue << "'"; 77 return defaultValue; 78 } 79 LOG(INFO) << "Sysprop " << key << "=" << it->second; 80 return it->second; 81 } 82 uint64_t getUintProperty(const std::string& key, uint64_t defaultValue, 83 uint64_t max) const override { 84 uint64_t result; 85 std::string value = getProperty(key, ""); 86 if (!value.empty() && android::base::ParseUint(value, &result, max)) return result; 87 return defaultValue; 88 } 89 bool getBoolProperty(const std::string& key, bool defaultValue) const override { 90 std::string value = getProperty(key, ""); 91 if (value == "1" || value == "true") { 92 return true; 93 } else if (value == "0" || value == "false") { 94 return false; 95 } 96 return defaultValue; 97 } 98 void setProperties(const Properties& props) { mProps.insert(props.begin(), props.end()); } 99 100 private: 101 std::map<std::string, std::string> mProps; 102 }; 103 104 struct StaticRuntimeInfo : public RuntimeInfo { 105 KernelVersion kernelVersion; 106 Level kernelLevel = Level::UNSPECIFIED; 107 std::string kernelConfigFile; 108 109 status_t fetchAllInformation(FetchFlags flags) override { 110 if (flags & RuntimeInfo::FetchFlag::CPU_VERSION) { 111 mKernel.mVersion = kernelVersion; 112 LOG(INFO) << "fetched kernel version " << kernelVersion; 113 } 114 if (flags & RuntimeInfo::FetchFlag::KERNEL_FCM) { 115 mKernel.mLevel = kernelLevel; 116 LOG(INFO) << "fetched kernel level from RuntimeInfo '" << kernelLevel << "'"; 117 } 118 if (flags & RuntimeInfo::FetchFlag::CONFIG_GZ) { 119 std::string content; 120 if (!android::base::ReadFileToString(kernelConfigFile, &content)) { 121 LOG(ERROR) << "Cannot read " << kernelConfigFile; 122 return UNKNOWN_ERROR; 123 } 124 KernelConfigParser parser; 125 auto status = parser.processAndFinish(content); 126 if (status != OK) { 127 return status; 128 } 129 mKernel.mConfigs = std::move(parser.configs()); 130 LOG(INFO) << "read kernel configs from " << kernelConfigFile; 131 } 132 if (flags & RuntimeInfo::FetchFlag::POLICYVERS) { 133 mKernelSepolicyVersion = SIZE_MAX; 134 } 135 return OK; 136 } 137 }; 138 139 struct StubRuntimeInfo : public RuntimeInfo { 140 status_t fetchAllInformation(FetchFlags) override { return UNKNOWN_ERROR; } 141 }; 142 143 struct StaticRuntimeInfoFactory : public ObjectFactory<RuntimeInfo> { 144 std::shared_ptr<RuntimeInfo> info; 145 StaticRuntimeInfoFactory(std::shared_ptr<RuntimeInfo> i) : info(i) {} 146 std::shared_ptr<RuntimeInfo> make_shared() const override { 147 if (info) return info; 148 return std::make_shared<StubRuntimeInfo>(); 149 } 150 }; 151 152 // helper functions 153 template <typename T> 154 std::unique_ptr<T> readObject(FileSystem* fileSystem, const std::string& path) { 155 std::string xml; 156 std::string error; 157 status_t err = fileSystem->fetch(path, &xml, &error); 158 if (err != OK) { 159 LOG(ERROR) << "Cannot read '" << path << "' (" << strerror(-err) << "): " << error; 160 return nullptr; 161 } 162 auto ret = std::make_unique<T>(); 163 ret->setFileName(path); 164 if (!fromXml(ret.get(), xml, &error)) { 165 LOG(ERROR) << "Cannot parse '" << path << "': " << error; 166 return nullptr; 167 } 168 return ret; 169 } 170 171 int checkCompatibilityForFiles(const std::string& manifestPath, const std::string& matrixPath) { 172 auto fileSystem = std::make_unique<FileSystemImpl>(); 173 auto manifest = readObject<HalManifest>(fileSystem.get(), manifestPath); 174 auto matrix = readObject<CompatibilityMatrix>(fileSystem.get(), matrixPath); 175 if (manifest == nullptr || matrix == nullptr) { 176 return -1; 177 } 178 179 std::string error; 180 if (!manifest->checkCompatibility(*matrix, &error)) { 181 LOG(ERROR) << "Incompatible: " << error; 182 std::cout << "false" << std::endl; 183 return 1; 184 } 185 186 std::cout << "true" << std::endl; 187 return 0; 188 } 189 190 Args parseArgs(int argc, char** argv) { 191 int longOptFlag; 192 int optionIndex; 193 Args ret; 194 std::vector<struct option> longopts{ 195 // Modes 196 {"help", no_argument, &longOptFlag, HELP}, 197 {"dump-file-list", no_argument, &longOptFlag, DUMP_FILE_LIST}, 198 {"check-compat", no_argument, &longOptFlag, CHECK_COMPAT}, 199 {"check-one", no_argument, &longOptFlag, CHECK_ONE}, 200 // Options 201 {"rootdir", required_argument, &longOptFlag, ROOTDIR}, 202 {"property", required_argument, &longOptFlag, PROPERTY}, 203 {"dirmap", required_argument, &longOptFlag, DIR_MAP}, 204 {"kernel", required_argument, &longOptFlag, KERNEL}, 205 {0, 0, 0, 0}}; 206 std::map<int, Option> shortopts{ 207 {'h', HELP}, {'D', PROPERTY}, {'c', CHECK_COMPAT}, 208 }; 209 for (;;) { 210 int c = getopt_long(argc, argv, "hcD:", longopts.data(), &optionIndex); 211 if (c == -1) { 212 break; 213 } 214 std::string argValue = optarg ? optarg : std::string{}; 215 if (c == 0) { 216 ret.emplace(static_cast<Option>(longOptFlag), std::move(argValue)); 217 } else { 218 ret.emplace(shortopts[c], std::move(argValue)); 219 } 220 } 221 if (optind < argc) { 222 // see non option 223 LOG(ERROR) << "unrecognized option `" << argv[optind] << "'"; 224 return {{HELP, ""}}; 225 } 226 return ret; 227 } 228 229 template <typename T> 230 Properties getProperties(const T& args) { 231 return splitArgs(args, '='); 232 } 233 234 // Parse a kernel version or a GKI kernel release. 235 bool parseKernelVersionOrRelease(const std::string& s, StaticRuntimeInfo* ret) { 236 // 5.4.42 237 if (parse(s, &ret->kernelVersion)) { 238 ret->kernelLevel = Level::UNSPECIFIED; 239 return true; 240 } 241 LOG(INFO) << "Cannot parse \"" << s << "\" as kernel version, parsing as GKI kernel release."; 242 243 // 5.4.42-android12-0-something 244 auto kernelRelease = KernelRelease::Parse(s, true /* allow suffix */); 245 if (kernelRelease.has_value()) { 246 ret->kernelVersion = KernelVersion{kernelRelease->version(), kernelRelease->patch_level(), 247 kernelRelease->sub_level()}; 248 ret->kernelLevel = RuntimeInfo::gkiAndroidReleaseToLevel(kernelRelease->android_release()); 249 return true; 250 } 251 LOG(INFO) << "Cannot parse \"" << s << "\" as GKI kernel release, parsing as kernel release"; 252 253 // 5.4.42-something 254 auto pos = s.find_first_not_of("0123456789."); 255 // substr handles pos == npos case 256 if (parse(s.substr(0, pos), &ret->kernelVersion)) { 257 ret->kernelLevel = Level::UNSPECIFIED; 258 return true; 259 } 260 261 LOG(INFO) << "Cannot parse \"" << s << "\" as kernel release"; 262 return false; 263 } 264 265 // Parse the first half of --kernel. |s| can either be a kernel version, a GKI kernel release, 266 // or a file that contains either of them. 267 bool parseKernelArgFirstHalf(const std::string& s, StaticRuntimeInfo* ret) { 268 if (parseKernelVersionOrRelease(s, ret)) { 269 LOG(INFO) << "Successfully parsed \"" << s << "\""; 270 return true; 271 } 272 std::string content; 273 if (!android::base::ReadFileToString(s, &content)) { 274 PLOG(INFO) << "Cannot read file " << s; 275 return false; 276 } 277 if (parseKernelVersionOrRelease(content, ret)) { 278 LOG(INFO) << "Successfully parsed content of " << s << ": " << content; 279 return true; 280 } 281 LOG(ERROR) << "Cannot parse content of " << s << ": " << content; 282 return false; 283 } 284 285 template <typename T> 286 std::shared_ptr<StaticRuntimeInfo> getRuntimeInfo(const T& args) { 287 auto ret = std::make_shared<StaticRuntimeInfo>(); 288 if (std::distance(args.begin(), args.end()) > 1) { 289 LOG(ERROR) << "Can't have multiple --kernel options"; 290 return nullptr; 291 } 292 const auto& arg = *args.begin(); 293 auto colonPos = arg.rfind(":"); 294 if (colonPos == std::string::npos) { 295 LOG(ERROR) << "Invalid --kernel"; 296 return nullptr; 297 } 298 299 if (!parseKernelArgFirstHalf(arg.substr(0, colonPos), ret.get())) { 300 return nullptr; 301 } 302 303 ret->kernelConfigFile = arg.substr(colonPos + 1); 304 return ret; 305 } 306 307 int usage(const char* me) { 308 LOG(ERROR) 309 << me << ": check VINTF metadata." << std::endl 310 << " Modes:" << std::endl 311 << " --dump-file-list: Dump a list of directories / files on device" << std::endl 312 << " that is required to be used by --check-compat." << std::endl 313 << " -c, --check-compat: check compatibility for files under the root" << std::endl 314 << " directory specified by --root-dir." << std::endl 315 << " --check-one: check consistency of VINTF metadata for a single partition." 316 << std::endl 317 << std::endl 318 << " Options:" << std::endl 319 << " --rootdir=<dir>: specify root directory for all metadata. Same as " << std::endl 320 << " --dirmap /:<dir>" << std::endl 321 << " -D, --property <key>=<value>: specify sysprops." << std::endl 322 << " --dirmap </system:/dir/to/system> [--dirmap </vendor:/dir/to/vendor>[...]]" 323 << std::endl 324 << " Map partitions to directories. Cannot be specified with --rootdir." 325 << " --kernel <version:path/to/config>" << std::endl 326 << " Use the given kernel version and config to check. If" << std::endl 327 << " unspecified, kernel requirements are skipped." << std::endl 328 << " The first half, version, can be just x.y.z, or a file " << std::endl 329 << " containing the full kernel release string x.y.z-something." << 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 class CheckVintfUtils { 352 public: 353 // Print HALs in the device manifest that are not declared in FCMs <= target FCM version. 354 static void logHalsFromNewFcms(VintfObject* vintfObject, 355 const std::vector<HidlInterfaceMetadata>& hidlMetadata) { 356 auto deviceManifest = vintfObject->getDeviceHalManifest(); 357 if (deviceManifest == nullptr) { 358 LOG(WARNING) << "Unable to print HALs from new FCMs: no device HAL manifest."; 359 return; 360 } 361 std::vector<CompatibilityMatrix> matrixFragments; 362 std::string error; 363 auto status = vintfObject->getAllFrameworkMatrixLevels(&matrixFragments, &error); 364 if (status != OK || matrixFragments.empty()) { 365 LOG(WARNING) << "Unable to print HALs from new FCMs: " << statusToString(status) << ": " 366 << error; 367 return; 368 } 369 auto it = std::remove_if(matrixFragments.begin(), matrixFragments.end(), 370 [&](const CompatibilityMatrix& matrix) { 371 return matrix.level() != Level::UNSPECIFIED && 372 matrix.level() > deviceManifest->level(); 373 }); 374 matrixFragments.erase(it, matrixFragments.end()); 375 auto combined = 376 CompatibilityMatrix::combine(deviceManifest->level(), &matrixFragments, &error); 377 if (combined == nullptr) { 378 LOG(WARNING) << "Unable to print HALs from new FCMs: unable to combine matrix " 379 "fragments <= level " 380 << deviceManifest->level() << ": " << error; 381 } 382 auto unused = deviceManifest->checkUnusedHals(*combined, hidlMetadata); 383 if (unused.empty()) { 384 LOG(INFO) << "All HALs in device manifest are declared in FCM <= level " 385 << deviceManifest->level(); 386 return; 387 } 388 LOG(INFO) << "The following HALs in device manifest are not declared in FCM <= level " 389 << deviceManifest->level() << ": "; 390 for (const auto& hal : unused) { 391 LOG(INFO) << " " << hal; 392 } 393 } 394 }; 395 396 // If |result| is already an error, don't do anything. Otherwise, set it to 397 // an error with |errorCode|. Return reference to Error object for appending 398 // additional error messages. 399 android::base::Error& SetErrorCode(std::optional<android::base::Error>* retError, 400 int errorCode = 0) { 401 if (!retError->has_value()) { 402 retError->emplace(errorCode); 403 } else { 404 // Use existing error code. 405 // There should already been an error message appended. Add a new line char for 406 // additional messages. 407 (**retError) << "\n"; 408 } 409 return **retError; 410 } 411 412 // If |other| is an error, add it to |retError|. 413 template <typename T> 414 void AddResult(std::optional<android::base::Error>* retError, const android::base::Result<T>& other, 415 const char* additionalMessage = "") { 416 if (other.ok()) return; 417 SetErrorCode(retError, other.error().code()) << other.error() << additionalMessage; 418 } 419 420 static constexpr const char* gCheckMissingHalsSuggestion{ 421 "\n- If this is a new package, add it to the latest framework compatibility matrix." 422 "\n- If no interface should be added to the framework compatibility matrix (e.g. " 423 "types-only package), add it to the exempt list in libvintf_fcm_exclude."}; 424 425 android::base::Result<void> checkAllFiles(const Dirmap& dirmap, const Properties& props, 426 std::shared_ptr<StaticRuntimeInfo> runtimeInfo) { 427 auto hostPropertyFetcher = std::make_unique<PresetPropertyFetcher>(); 428 hostPropertyFetcher->setProperties(props); 429 430 CheckFlags::Type flags = CheckFlags::DEFAULT; 431 if (!runtimeInfo) flags = flags.disableRuntimeInfo(); 432 433 auto vintfObject = 434 VintfObject::Builder() 435 .setFileSystem(std::make_unique<HostFileSystem>(dirmap, UNKNOWN_ERROR)) 436 .setPropertyFetcher(std::move(hostPropertyFetcher)) 437 .setRuntimeInfoFactory(std::make_unique<StaticRuntimeInfoFactory>(runtimeInfo)) 438 .build(); 439 440 std::optional<android::base::Error> retError = std::nullopt; 441 442 std::string compatibleError; 443 int compatibleResult = vintfObject->checkCompatibility(&compatibleError, flags); 444 if (compatibleResult == INCOMPATIBLE) { 445 SetErrorCode(&retError) << compatibleError; 446 } else if (compatibleResult != COMPATIBLE) { 447 SetErrorCode(&retError, -compatibleResult) << compatibleError; 448 } 449 450 auto hidlMetadata = HidlInterfaceMetadata::all(); 451 452 std::string deprecateError; 453 int deprecateResult = vintfObject->checkDeprecation(hidlMetadata, &deprecateError); 454 if (deprecateResult == DEPRECATED) { 455 SetErrorCode(&retError) << deprecateError; 456 } else if (deprecateResult != NO_DEPRECATED_HALS) { 457 SetErrorCode(&retError, -deprecateResult) << deprecateError; 458 } 459 460 auto hasFcmExt = vintfObject->hasFrameworkCompatibilityMatrixExtensions(); 461 AddResult(&retError, hasFcmExt); 462 463 auto deviceManifest = vintfObject->getDeviceHalManifest(); 464 Level targetFcm = Level::UNSPECIFIED; 465 if (deviceManifest == nullptr) { 466 SetErrorCode(&retError, -NAME_NOT_FOUND) << "No device HAL manifest"; 467 } else { 468 targetFcm = deviceManifest->level(); 469 } 470 471 if (hasFcmExt.value_or(false) || (targetFcm != Level::UNSPECIFIED && targetFcm >= Level::R)) { 472 AddResult(&retError, vintfObject->checkUnusedHals(hidlMetadata)); 473 } else { 474 LOG(INFO) << "Skip checking unused HALs."; 475 } 476 477 CheckVintfUtils::logHalsFromNewFcms(vintfObject.get(), hidlMetadata); 478 479 if (retError.has_value()) { 480 return *retError; 481 } else { 482 return {}; 483 } 484 } 485 486 int checkDirmaps(const Dirmap& dirmap, const Properties& props) { 487 auto hostPropertyFetcher = std::make_unique<PresetPropertyFetcher>(); 488 hostPropertyFetcher->setProperties(props); 489 auto exitCode = EX_OK; 490 for (auto&& [prefix, mappedPath] : dirmap) { 491 auto vintfObject = 492 VintfObject::Builder() 493 .setFileSystem(std::make_unique<HostFileSystem>(dirmap, NAME_NOT_FOUND)) 494 .setPropertyFetcher(std::move(hostPropertyFetcher)) 495 .setRuntimeInfoFactory(std::make_unique<StaticRuntimeInfoFactory>(nullptr)) 496 .build(); 497 498 if (android::base::StartsWith(prefix, "/system")) { 499 LOG(INFO) << "Checking system manifest."; 500 auto manifest = vintfObject->getFrameworkHalManifest(); 501 if (!manifest) { 502 LOG(ERROR) << "Cannot fetch system manifest."; 503 exitCode = EX_SOFTWARE; 504 } 505 LOG(INFO) << "Checking system matrix."; 506 auto matrix = vintfObject->getFrameworkCompatibilityMatrix(); 507 if (!matrix) { 508 LOG(ERROR) << "Cannot fetch system matrix."; 509 exitCode = EX_SOFTWARE; 510 } 511 auto res = vintfObject->checkMissingHalsInMatrices(HidlInterfaceMetadata::all(), 512 AidlInterfaceMetadata::all(), 513 ShouldCheckMissingHalsInFcm); 514 if (!res.ok()) { 515 LOG(ERROR) << res.error() << gCheckMissingHalsSuggestion; 516 exitCode = EX_SOFTWARE; 517 } 518 519 res = vintfObject->checkMatrixHalsHasDefinition(HidlInterfaceMetadata::all(), 520 AidlInterfaceMetadata::all()); 521 if (!res.ok()) { 522 LOG(ERROR) << res.error(); 523 exitCode = EX_SOFTWARE; 524 } 525 continue; 526 } 527 528 if (android::base::StartsWith(prefix, "/vendor")) { 529 LOG(INFO) << "Checking vendor manifest."; 530 auto manifest = vintfObject->getDeviceHalManifest(); 531 if (!manifest) { 532 LOG(ERROR) << "Cannot fetch vendor manifest."; 533 exitCode = EX_SOFTWARE; 534 } 535 LOG(INFO) << "Checking vendor matrix."; 536 auto matrix = vintfObject->getDeviceCompatibilityMatrix(); 537 if (!matrix) { 538 LOG(ERROR) << "Cannot fetch vendor matrix."; 539 exitCode = EX_SOFTWARE; 540 } 541 continue; 542 } 543 544 LOG(ERROR) << "--check-one does not work with --dirmap " << prefix; 545 exitCode = EX_SOFTWARE; 546 } 547 return exitCode; 548 } 549 550 } // namespace details 551 } // namespace vintf 552 } // namespace android 553 554 int main(int argc, char** argv) { 555 android::base::SetLogger(android::base::StderrLogger); 556 557 using namespace android::vintf; 558 using namespace android::vintf::details; 559 // legacy usage: check_vintf <manifest.xml> <matrix.xml> 560 if (argc == 3 && *argv[1] != '-' && *argv[2] != '-') { 561 int ret = checkCompatibilityForFiles(argv[1], argv[2]); 562 if (ret >= 0) return ret; 563 } 564 565 Args args = parseArgs(argc, argv); 566 567 if (!iterateValues(args, HELP).empty()) { 568 return usage(argv[0]); 569 } 570 571 if (!iterateValues(args, DUMP_FILE_LIST).empty()) { 572 for (const auto& file : dumpFileList()) { 573 std::cout << file << std::endl; 574 } 575 return 0; 576 } 577 578 auto dirmap = getDirmap(iterateValues(args, DIR_MAP)); 579 auto properties = getProperties(iterateValues(args, PROPERTY)); 580 581 if (!iterateValues(args, CHECK_ONE).empty()) { 582 return checkDirmaps(dirmap, properties); 583 } 584 585 auto checkCompat = iterateValues(args, CHECK_COMPAT); 586 if (checkCompat.empty()) { 587 return usage(argv[0]); 588 } 589 590 auto rootdirs = iterateValues(args, ROOTDIR); 591 if (!rootdirs.empty()) { 592 if (std::distance(rootdirs.begin(), rootdirs.end()) > 1) { 593 LOG(ERROR) << "Can't have multiple --rootdir options"; 594 return usage(argv[0]); 595 } 596 args.emplace(DIR_MAP, "/:" + *rootdirs.begin()); 597 } 598 599 std::shared_ptr<StaticRuntimeInfo> runtimeInfo; 600 auto kernelArgs = iterateValues(args, KERNEL); 601 if (!kernelArgs.empty()) { 602 runtimeInfo = getRuntimeInfo(kernelArgs); 603 if (runtimeInfo == nullptr) { 604 return usage(argv[0]); 605 } 606 } 607 608 if (dirmap.empty()) { 609 LOG(ERROR) << "Missing --rootdir or --dirmap option."; 610 return usage(argv[0]); 611 } 612 613 auto compat = checkAllFiles(dirmap, properties, runtimeInfo); 614 615 if (compat.ok()) { 616 std::cout << "COMPATIBLE" << std::endl; 617 return EX_OK; 618 } 619 if (compat.error().code() == 0) { 620 LOG(ERROR) << "files are incompatible: " << compat.error(); 621 std::cout << "INCOMPATIBLE" << std::endl; 622 return EX_DATAERR; 623 } 624 LOG(ERROR) << strerror(compat.error().code()) << ": " << compat.error(); 625 return EX_SOFTWARE; 626 } 627