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 
usage()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:
fetch(const std::string & path,std::string * fetched,std::string * error) const67     status_t fetch(const std::string& path, std::string* fetched,
68                    std::string* error) const override {
69         return mRoFileSystem.fetch(path, fetched, error);
70     }
listFiles(const std::string & path,std::vector<std::string> * out,std::string * error) const71     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     }
modifiedTime(const std::string & path,timespec * out,std::string * error) const75     status_t modifiedTime(const std::string& path, timespec* out,
76                           std::string* error) const override {
77         return mRoFileSystem.modifiedTime(path, out, error);
78     }
write(const std::string & path,const std::string & content,std::string * error) const79     status_t write(const std::string& path, const std::string& content,
80                    std::string* error) const override {
81         if (!android::base::WriteStringToFile(content, path)) {
82             int saved_errno = errno;
83             if (error) {
84                 *error = "Can't write to " + path + ": " + strerror(saved_errno);
85             }
86             return saved_errno == 0 ? UNKNOWN_ERROR : -saved_errno;
87         }
88         return OK;
89     }
deleteFile(const std::string & path,std::string * error) const90     status_t deleteFile(const std::string& path, std::string* error) const override {
91         if (unlink(path.c_str()) == -1) {
92             int saved_errno = errno;
93             if (error) {
94                 *error = "Can't unlink " + path + ": " + strerror(saved_errno);
95             }
96             return saved_errno == 0 ? UNKNOWN_ERROR : -saved_errno;
97         }
98         return OK;
99     }
100 
101    private:
102     details::FileSystemImpl mRoFileSystem;
103 };
104 
105 }  // namespace
106 
107 namespace details {
108 
109 // A VintfObject with a proper framework manifest and a fake device manifest with
110 // only targetFcmVersion.
111 class FmOnlyVintfObject : public VintfObject {
112    public:
FmOnlyVintfObject(std::unique_ptr<FileSystem> && fs,Level targetFcmVersion)113     FmOnlyVintfObject(std::unique_ptr<FileSystem>&& fs, Level targetFcmVersion)
114         : mFs(std::move(fs)) {
115         mDeviceManifest = std::make_shared<HalManifest>();
116         mDeviceManifest->mLevel = targetFcmVersion;
117     }
118 
getDeviceHalManifest()119     std::shared_ptr<const HalManifest> getDeviceHalManifest() override { return mDeviceManifest; }
getFrameworkCompatibilityMatrix()120     std::shared_ptr<const CompatibilityMatrix> getFrameworkCompatibilityMatrix() override {
121         return nullptr;
122     }
getDeviceCompatibilityMatrix()123     std::shared_ptr<const CompatibilityMatrix> getDeviceCompatibilityMatrix() override {
124         return nullptr;
125     }
126 
127    protected:
getFileSystem()128     const std::unique_ptr<FileSystem>& getFileSystem() override { return mFs; }
129     // Set environment to empty to prevent accidentally reading other things.
getPropertyFetcher()130     const std::unique_ptr<PropertyFetcher>& getPropertyFetcher() override { return mNoOpProp; }
131 
132    private:
133     std::unique_ptr<FileSystem> mFs;
134     std::shared_ptr<HalManifest> mDeviceManifest;
135     std::unique_ptr<PropertyFetcher> mNoOpProp = std::make_unique<details::PropertyFetcherNoOp>();
136 };
137 
138 }  // namespace details
139 
VintfFm()140 VintfFm::VintfFm() : VintfFm(std::make_unique<WritableFileSystemImpl>()) {}
141 
main(int argc,char ** argv)142 int VintfFm::main(int argc, char** argv) {
143     // clang-format off
144     const struct option longopts[] = {
145         {"check", no_argument, nullptr, 'c'},
146         {"dirmap", required_argument, nullptr, 'd'},
147         {"help", no_argument, nullptr, 'h'},
148         {"level", required_argument, nullptr, 'l'},
149         {"update", no_argument, nullptr, 'u'},
150         {0, 0, 0, 0}};
151     // clang-format on
152 
153     bool checking = false;
154     bool updating = false;
155     Level current = Level::UNSPECIFIED;
156     std::vector<std::string> dirmapVec;
157 
158     int res;
159     optind = 1;
160     while ((res = getopt_long(argc, argv, "cdhlu", longopts, nullptr)) >= 0) {
161         switch (res) {
162             case 'c': {
163                 checking = true;
164             } break;
165 
166             case 'd': {
167                 dirmapVec.push_back(optarg);
168             } break;
169 
170             case 'l': {
171                 if (!parse(optarg, &current)) {
172                     LOG(ERROR) << "Unable to parse '" << optarg << "' as level.";
173                     return usage();
174                 }
175             } break;
176 
177             case 'u': {
178                 updating = true;
179             } break;
180 
181             case 'h':
182             default: {
183                 return usage();
184             } break;
185         }
186     }
187 
188     if ((checking + updating) != 1) {
189         LOG(ERROR) << "Exactly one of --check or --update must be set.";
190         return usage();
191     }
192 
193     auto dirmap = details::getDirmap(dirmapVec);
194     auto vintfFsFactory = [&] {
195         return std::make_unique<details::HostFileSystem>(dirmap, NAME_NOT_FOUND, mFs.get());
196     };
197 
198     argc -= optind;
199     argv += optind;
200 
201     if (argc != 1) {
202         LOG(ERROR) << "There must be exactly 1 positional arguments.";
203         return usage();
204     }
205     auto dir = argv[0];
206 
207     if (updating) {
208         return update(vintfFsFactory, dir, current);
209     }
210     return check(vintfFsFactory, dir);
211 }
212 
update(const FsFactory & vintfFsFactory,const std::string & dir,Level level)213 int VintfFm::update(const FsFactory& vintfFsFactory, const std::string& dir, Level level) {
214     if (level == Level::UNSPECIFIED) {
215         LOG(ERROR) << "Must specify last frozen level with --level for --update option.";
216         return usage();
217     }
218 
219     auto manifest = getManifestForLevel(vintfFsFactory, level);
220     if (manifest == nullptr) {
221         LOG(ERROR) << "Unable to determine manifests for level " << level;
222         return EX_SOFTWARE;
223     }
224 
225     if (!dumpMatrix(*manifest, dir, level)) {
226         return EX_SOFTWARE;
227     }
228 
229     return EX_OK;
230 }
231 
check(const FsFactory & vintfFsFactory,const std::string & dir)232 int VintfFm::check(const FsFactory& vintfFsFactory, const std::string& dir) {
233     auto frozenMatrices = loadMatrices(dir);
234     if (!frozenMatrices.has_value()) {
235         return EX_SOFTWARE;
236     }
237     for (const auto& [level, matrix] : *frozenMatrices) {
238         auto manifest = getManifestForLevel(vintfFsFactory, level);
239         if (manifest == nullptr) {
240             LOG(ERROR) << "Unable to determine manifests for level " << level;
241             return EX_SOFTWARE;
242         }
243         std::string error;
244         if (!manifest->checkCompatibility(matrix, &error)) {
245             LOG(ERROR) << "Framework manifest is incompatible with frozen matrix at level " << level
246                        << ": " << error;
247             return EX_SOFTWARE;
248         }
249     }
250     return OK;
251 }
252 
getManifestForLevel(const FsFactory & vintfFsFactory,Level level)253 std::shared_ptr<const HalManifest> VintfFm::getManifestForLevel(const FsFactory& vintfFsFactory,
254                                                                 Level level) {
255     auto vintfObject = std::make_unique<details::FmOnlyVintfObject>(vintfFsFactory(), level);
256     auto frameworkManifest = vintfObject->getFrameworkHalManifest();
257     if (frameworkManifest == nullptr) {
258         LOG(ERROR) << "Unable to get framework HAL manifest for target FCM version " << level;
259     }
260     return frameworkManifest;
261 }
262 
dumpMatrix(const HalManifest & frameworkManifest,const std::string & dir,Level level)263 bool VintfFm::dumpMatrix(const HalManifest& frameworkManifest, const std::string& dir,
264                          Level level) {
265     auto matrix = frameworkManifest.generateCompatibleMatrix(false /*optional*/);
266     std::string path = dir + "/" + to_string(level) + ".xml";
267     std::string error;
268     if (OK != mFs->write(path, toXml(matrix), &error)) {
269         LOG(ERROR) << "Unable to dump matrix to " << path << ": " << error;
270         return false;
271     }
272     return true;
273 }
274 
loadMatrices(const std::string & dir)275 std::optional<VintfFm::FrozenMatrices> VintfFm::loadMatrices(const std::string& dir) {
276     std::string error;
277     std::vector<std::string> allFiles;
278     if (OK != mFs->listFiles(dir, &allFiles, &error)) {
279         LOG(ERROR) << "Unable to list files under " << dir << ": " << error;
280         return std::nullopt;
281     }
282     if (allFiles.empty()) {
283         LOG(ERROR) << "Unable to load frozen interfaces under " << dir << ": directory is empty.";
284         return std::nullopt;
285     }
286     auto ret = std::make_optional<FrozenMatrices>();
287     for (const auto& filename : allFiles) {
288         std::string path = dir + "/" + filename;
289         std::string xmlString;
290         if (OK != mFs->fetch(path, &xmlString, &error)) {
291             LOG(ERROR) << "Unable to read " << path << ": " << error;
292             return std::nullopt;
293         }
294         CompatibilityMatrix matrix;
295         if (!fromXml(&matrix, xmlString, &error)) {
296             LOG(ERROR) << "Unable to parse " << path << ": " << error;
297             return std::nullopt;
298         }
299         std::string_view filenameSv{filename};
300         (void)android::base::ConsumeSuffix(&filenameSv, ".xml");
301         std::string levelString{filenameSv};
302         Level matrixLevel;
303         if (!parse(levelString, &matrixLevel)) {
304             LOG(ERROR) << "Unable to parse " << path << ": " << levelString << " is not a level.";
305             return std::nullopt;
306         }
307         if (ret->find(matrixLevel) != ret->end()) {
308             LOG(ERROR) << "Duplicated level " << matrixLevel << ", second one is at " << path;
309             return std::nullopt;
310         }
311         ret->emplace(matrixLevel, std::move(matrix));
312     }
313     return ret;
314 }
315 
316 }  // namespace android::vintf
317