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