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