• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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