1 /*
2 * Copyright (C) 2019 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 #define LOG_TAG "apexd"
18
19 #include "apex_database.h"
20 #include "apex_constants.h"
21 #include "apex_file.h"
22 #include "apexd_utils.h"
23 #include "status_or.h"
24 #include "string_log.h"
25
26 #include <android-base/file.h>
27 #include <android-base/logging.h>
28 #include <android-base/parseint.h>
29 #include <android-base/strings.h>
30
31 #include <filesystem>
32 #include <fstream>
33 #include <string>
34 #include <unordered_map>
35 #include <utility>
36
37 using android::base::EndsWith;
38 using android::base::ParseInt;
39 using android::base::ReadFileToString;
40 using android::base::Split;
41 using android::base::StartsWith;
42 using android::base::Trim;
43
44 namespace fs = std::filesystem;
45
46 namespace android {
47 namespace apex {
48
49 namespace {
50
51 using MountedApexData = MountedApexDatabase::MountedApexData;
52
53 // from art/runtime/class_linker.cc
hash_combine(size_t seed,size_t val)54 inline size_t hash_combine(size_t seed, size_t val) {
55 return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2));
56 }
57
58 typedef std::pair<dev_t, ino_t> inode_t;
59 struct inode_hash {
operator ()android::apex::__anonc3d8388d0111::inode_hash60 size_t operator()(const inode_t& inode) const {
61 auto h1 = std::hash<dev_t>{}(inode.first);
62 auto h2 = std::hash<ino_t>{}(inode.second);
63 return hash_combine(h1, h2);
64 }
65 };
66 typedef std::unordered_map<inode_t, std::string, inode_hash> inode_map;
67
68 enum BlockDeviceType {
69 UnknownDevice,
70 LoopDevice,
71 DeviceMapperDevice,
72 };
73
74 const fs::path kDevBlock = "/dev/block";
75 const fs::path kSysBlock = "/sys/block";
76
77 class BlockDevice {
78 std::string name; // loopN, dm-N, ...
79 public:
BlockDevice(const fs::path & path)80 explicit BlockDevice(const fs::path& path) { name = path.filename(); }
81
GetType() const82 BlockDeviceType GetType() const {
83 if (StartsWith(name, "loop")) return LoopDevice;
84 if (StartsWith(name, "dm-")) return DeviceMapperDevice;
85 return UnknownDevice;
86 }
87
SysPath() const88 fs::path SysPath() const { return kSysBlock / name; }
89
DevPath() const90 fs::path DevPath() const { return kDevBlock / name; }
91
GetProperty(const std::string & property) const92 StatusOr<std::string> GetProperty(const std::string& property) const {
93 auto propertyFile = SysPath() / property;
94 std::string propertyValue;
95 if (!ReadFileToString(propertyFile, &propertyValue)) {
96 return StatusOr<std::string>::MakeError(PStringLog() << "Fail to read");
97 }
98 return StatusOr<std::string>(Trim(propertyValue));
99 }
100
GetSlaves() const101 std::vector<BlockDevice> GetSlaves() const {
102 std::vector<BlockDevice> slaves;
103 std::error_code ec;
104 auto status = WalkDir(SysPath() / "slaves", [&](const auto& entry) {
105 BlockDevice dev(entry);
106 if (fs::is_block_file(dev.DevPath(), ec)) {
107 slaves.push_back(dev);
108 }
109 });
110 if (!status.Ok()) {
111 LOG(WARNING) << status.ErrorMessage();
112 }
113 return slaves;
114 }
115 };
116
parseMountInfo(const std::string & mountInfo)117 std::pair<fs::path, fs::path> parseMountInfo(const std::string& mountInfo) {
118 const auto& tokens = Split(mountInfo, " ");
119 if (tokens.size() < 2) {
120 return std::make_pair("", "");
121 }
122 return std::make_pair(tokens[0], tokens[1]);
123 }
124
parseMountPoint(const std::string & mountPoint)125 std::pair<std::string, int> parseMountPoint(const std::string& mountPoint) {
126 auto packageId = fs::path(mountPoint).filename();
127 auto split = Split(packageId, "@");
128 if (split.size() == 2) {
129 int version;
130 if (!ParseInt(split[1], &version)) {
131 version = -1;
132 }
133 return std::make_pair(split[0], version);
134 }
135 return std::make_pair(packageId, -1);
136 }
137
isActiveMountPoint(const std::string & mountPoint)138 bool isActiveMountPoint(const std::string& mountPoint) {
139 return (mountPoint.find('@') == std::string::npos);
140 }
141
inodeFor(const std::string & path)142 StatusOr<inode_t> inodeFor(const std::string& path) {
143 struct stat buf;
144 if (stat(path.c_str(), &buf)) {
145 return StatusOr<inode_t>::MakeError(PStringLog() << "stat failed");
146 }
147 return StatusOr<inode_t>(buf.st_dev, buf.st_ino);
148 }
149
150 // Flattened packages from builtin APEX dirs(/system/apex, /product/apex, ...)
scanFlattendedPackages()151 inode_map scanFlattendedPackages() {
152 inode_map map;
153
154 for (const auto& dir : kApexPackageBuiltinDirs) {
155 auto status = WalkDir(dir, [&](const fs::directory_entry& entry) {
156 const auto& path = entry.path();
157 if (isFlattenedApex(path)) {
158 auto inode = inodeFor(path);
159 if (inode.Ok()) {
160 map[*inode] = path;
161 }
162 }
163 });
164 if (!status.Ok()) {
165 LOG(ERROR) << "Failed to walk " << dir << " : " << status.ErrorMessage();
166 }
167 }
168
169 return map;
170 }
171
resolveMountInfo(const BlockDevice & block,const std::string & mountPoint,const inode_map & inodeMap)172 StatusOr<MountedApexData> resolveMountInfo(const BlockDevice& block,
173 const std::string& mountPoint,
174 const inode_map& inodeMap) {
175 auto Error = [](auto e) { return StatusOr<MountedApexData>::MakeError(e); };
176
177 // First, see if it is bind-mount'ed to a flattened APEX
178 // This is checked first since flattened APEXes can be located in any stacked
179 // filesystem. (e.g. if / is mounted via /dev/loop1, then /proc/mounts shows
180 // that loop device as associated block device. But it is not related to APEX
181 // activation.) In any cases, comparing (dev,inode) pair with scanned
182 // flattened APEXes must identify bind-mounted APEX properly.
183 // See b/131924899.
184 auto inode = inodeFor(mountPoint);
185 if (inode.Ok()) {
186 auto iter = inodeMap.find(*inode);
187 if (iter != inodeMap.end()) {
188 return StatusOr<MountedApexData>("", iter->second, mountPoint, "");
189 }
190 }
191
192 // Now, see if it is dm-verity or loop mounted
193 switch (block.GetType()) {
194 case LoopDevice: {
195 auto backingFile = block.GetProperty("loop/backing_file");
196 if (!backingFile.Ok()) {
197 return Error(backingFile.ErrorStatus());
198 }
199 return StatusOr<MountedApexData>(block.DevPath(), *backingFile,
200 mountPoint, "");
201 }
202 case DeviceMapperDevice: {
203 auto name = block.GetProperty("dm/name");
204 if (!name.Ok()) {
205 return Error(name.ErrorStatus());
206 }
207 auto slaves = block.GetSlaves();
208 if (slaves.empty() || slaves[0].GetType() != LoopDevice) {
209 return Error("DeviceMapper device with no loop devices");
210 }
211 // TODO(jooyung): handle multiple loop devices when hash tree is
212 // externalized
213 auto slave = slaves[0];
214 auto backingFile = slave.GetProperty("loop/backing_file");
215 if (!backingFile.Ok()) {
216 return Error(backingFile.ErrorStatus());
217 }
218 return StatusOr<MountedApexData>(slave.DevPath(), *backingFile,
219 mountPoint, *name);
220 }
221 case UnknownDevice: {
222 return Error("Can't resolve " + block.DevPath().string());
223 }
224 }
225 }
226
227 } // namespace
228
229 // On startup, APEX database is populated from /proc/mounts.
230
231 // /apex/<package-id> can be mounted from
232 // - /dev/block/loopX : loop device
233 // - /dev/block/dm-X : dm-verity
234 // - <flattened> : bind-mount
235
236 // In case of loop device, it is from a non-flattened
237 // APEX file. This original APEX file can be tracked
238 // by /sys/block/loopX/loop/backing_file.
239
240 // In case of dm-verity, it is mapped to a loop device.
241 // This mapped loop device can be traced by
242 // /sys/block/dm-X/slaves/ directory which contains
243 // a symlink to /sys/block/loopY, which leads to
244 // the original APEX file.
245 // Device name can be retrieved from
246 // /sys/block/dm-Y/dm/name.
247
248 // In case of <flattened>, it is --bind mounted to a flattened
249 // APEX directory. This is allowed only for system/product
250 // partitions. So, original APEX directory can be found
251 // by comparing dev/inode pair with candidates.
252
253 // By synchronizing the mounts info with Database on startup,
254 // Apexd serves the correct package list even on the devices
255 // which are not ro.apex.updatable.
PopulateFromMounts()256 void MountedApexDatabase::PopulateFromMounts() {
257 LOG(INFO) << "Populating APEX database from mounts...";
258
259 std::unordered_map<std::string, int> activeVersions;
260 inode_map inodeToFlattendApexMap = scanFlattendedPackages();
261
262 std::ifstream mounts("/proc/mounts");
263 std::string line;
264 while (std::getline(mounts, line)) {
265 auto [block, mountPoint] = parseMountInfo(line);
266 // TODO(jooyung): ignore tmp mount?
267 if (fs::path(mountPoint).parent_path() != kApexRoot) {
268 continue;
269 }
270 if (isActiveMountPoint(mountPoint)) {
271 continue;
272 }
273
274 auto mountData = resolveMountInfo(BlockDevice(block), mountPoint,
275 inodeToFlattendApexMap);
276 if (!mountData.Ok()) {
277 LOG(WARNING) << "Can't resolve mount info " << mountData.ErrorMessage();
278 continue;
279 }
280
281 auto [package, version] = parseMountPoint(mountPoint);
282 AddMountedApex(package, false, *mountData);
283
284 auto active = activeVersions[package] < version;
285 if (active) {
286 activeVersions[package] = version;
287 SetLatest(package, mountData->full_path);
288 }
289 LOG(INFO) << "Found " << mountPoint;
290 }
291
292 LOG(INFO) << mounted_apexes_.size() << " packages restored.";
293 }
294
295 } // namespace apex
296 } // namespace android
297