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