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 "string_log.h"
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 
31 #include <filesystem>
32 #include <fstream>
33 #include <string>
34 #include <unordered_map>
35 #include <utility>
36 
37 using android::base::ConsumeSuffix;
38 using android::base::ParseInt;
39 using android::base::ReadFileToString;
40 using android::base::Result;
41 using android::base::Split;
42 using android::base::StartsWith;
43 using android::base::Trim;
44 
45 namespace fs = std::filesystem;
46 
47 namespace android {
48 namespace apex {
49 
50 namespace {
51 
52 using MountedApexData = MountedApexDatabase::MountedApexData;
53 
54 enum BlockDeviceType {
55   UnknownDevice,
56   LoopDevice,
57   DeviceMapperDevice,
58 };
59 
60 const fs::path kDevBlock = "/dev/block";
61 const fs::path kSysBlock = "/sys/block";
62 
63 class BlockDevice {
64   std::string name;  // loopN, dm-N, ...
65  public:
BlockDevice(const fs::path & path)66   explicit BlockDevice(const fs::path& path) { name = path.filename(); }
67 
GetType() const68   BlockDeviceType GetType() const {
69     if (StartsWith(name, "loop")) return LoopDevice;
70     if (StartsWith(name, "dm-")) return DeviceMapperDevice;
71     return UnknownDevice;
72   }
73 
SysPath() const74   fs::path SysPath() const { return kSysBlock / name; }
75 
DevPath() const76   fs::path DevPath() const { return kDevBlock / name; }
77 
GetProperty(const std::string & property) const78   Result<std::string> GetProperty(const std::string& property) const {
79     auto propertyFile = SysPath() / property;
80     std::string propertyValue;
81     if (!ReadFileToString(propertyFile, &propertyValue)) {
82       return ErrnoError() << "Fail to read";
83     }
84     return Trim(propertyValue);
85   }
86 
GetSlaves() const87   std::vector<BlockDevice> GetSlaves() const {
88     std::vector<BlockDevice> slaves;
89     std::error_code ec;
90     auto status = WalkDir(SysPath() / "slaves", [&](const auto& entry) {
91       BlockDevice dev(entry);
92       if (fs::is_block_file(dev.DevPath(), ec)) {
93         slaves.push_back(dev);
94       }
95     });
96     if (!status.ok()) {
97       LOG(WARNING) << status.error();
98     }
99     return slaves;
100   }
101 };
102 
parseMountInfo(const std::string & mountInfo)103 std::pair<fs::path, fs::path> parseMountInfo(const std::string& mountInfo) {
104   const auto& tokens = Split(mountInfo, " ");
105   if (tokens.size() < 2) {
106     return std::make_pair("", "");
107   }
108   return std::make_pair(tokens[0], tokens[1]);
109 }
110 
parseMountPoint(const std::string & mountPoint)111 std::pair<std::string, int> parseMountPoint(const std::string& mountPoint) {
112   auto packageId = fs::path(mountPoint).filename();
113   auto split = Split(packageId, "@");
114   if (split.size() == 2) {
115     int version;
116     if (!ParseInt(split[1], &version)) {
117       version = -1;
118     }
119     return std::make_pair(split[0], version);
120   }
121   return std::make_pair(packageId, -1);
122 }
123 
isActiveMountPoint(const std::string & mountPoint)124 bool isActiveMountPoint(const std::string& mountPoint) {
125   return (mountPoint.find('@') == std::string::npos);
126 }
127 
PopulateLoopInfo(const BlockDevice & top_device,MountedApexData * apex_data)128 Result<void> PopulateLoopInfo(const BlockDevice& top_device,
129                               MountedApexData* apex_data) {
130   std::vector<BlockDevice> slaves = top_device.GetSlaves();
131   if (slaves.size() != 1 && slaves.size() != 2) {
132     return Error() << "dm device " << top_device.DevPath()
133                    << " has unexpected number of slaves : " << slaves.size();
134   }
135   std::vector<std::string> backing_files;
136   backing_files.reserve(slaves.size());
137   for (const auto& dev : slaves) {
138     if (dev.GetType() != LoopDevice) {
139       return Error() << dev.DevPath() << " is not a loop device";
140     }
141     auto backing_file = dev.GetProperty("loop/backing_file");
142     if (!backing_file.ok()) {
143       return backing_file.error();
144     }
145     backing_files.push_back(std::move(*backing_file));
146   }
147   // Enforce following invariant:
148   //  * slaves[0] always represents a data loop device
149   //  * if size = 2 then slaves[1] represents an external hashtree loop device
150   if (slaves.size() == 2) {
151     if (!StartsWith(backing_files[0], kActiveApexPackagesDataDir)) {
152       std::swap(slaves[0], slaves[1]);
153       std::swap(backing_files[0], backing_files[1]);
154     }
155   }
156   if (!StartsWith(backing_files[0], kActiveApexPackagesDataDir)) {
157     return Error() << "Data loop device " << slaves[0].DevPath()
158                    << " has unexpected backing file " << backing_files[0];
159   }
160   if (slaves.size() == 2) {
161     if (!StartsWith(backing_files[1], kApexHashTreeDir)) {
162       return Error() << "Hashtree loop device " << slaves[1].DevPath()
163                      << " has unexpected backing file " << backing_files[1];
164     }
165     apex_data->hashtree_loop_name = slaves[1].DevPath();
166   }
167   apex_data->loop_name = slaves[0].DevPath();
168   apex_data->full_path = backing_files[0];
169   return {};
170 }
171 
172 // This is not the right place to do this normalization, but proper solution
173 // will require some refactoring first. :(
174 // TODO(b/158469911): introduce MountedApexDataBuilder and delegate all
175 //  building/normalization logic to it.
NormalizeIfDeleted(MountedApexData * apex_data)176 void NormalizeIfDeleted(MountedApexData* apex_data) {
177   std::string_view full_path = apex_data->full_path;
178   if (ConsumeSuffix(&full_path, "(deleted)")) {
179     apex_data->deleted = true;
180     auto it = full_path.rbegin();
181     while (it != full_path.rend() && isspace(*it)) {
182       it++;
183     }
184     full_path.remove_suffix(it - full_path.rbegin());
185   } else {
186     apex_data->deleted = false;
187   }
188   apex_data->full_path = full_path;
189 }
190 
resolveMountInfo(const BlockDevice & block,const std::string & mountPoint)191 Result<MountedApexData> resolveMountInfo(const BlockDevice& block,
192                                          const std::string& mountPoint) {
193   // Now, see if it is dm-verity or loop mounted
194   switch (block.GetType()) {
195     case LoopDevice: {
196       auto backingFile = block.GetProperty("loop/backing_file");
197       if (!backingFile.ok()) {
198         return backingFile.error();
199       }
200       auto result = MountedApexData(block.DevPath(), *backingFile, mountPoint,
201                                     /* device_name= */ "",
202                                     /* hashtree_loop_name= */ "");
203       NormalizeIfDeleted(&result);
204       return result;
205     }
206     case DeviceMapperDevice: {
207       auto name = block.GetProperty("dm/name");
208       if (!name.ok()) {
209         return name.error();
210       }
211       MountedApexData result;
212       result.mount_point = mountPoint;
213       result.device_name = *name;
214       if (auto status = PopulateLoopInfo(block, &result); !status.ok()) {
215         return status.error();
216       }
217       NormalizeIfDeleted(&result);
218       return result;
219     }
220     case UnknownDevice: {
221       return Errorf("Can't resolve {}", block.DevPath().string());
222     }
223   }
224 }
225 
226 }  // namespace
227 
228 // On startup, APEX database is populated from /proc/mounts.
229 
230 // /apex/<package-id> can be mounted from
231 // - /dev/block/loopX : loop device
232 // - /dev/block/dm-X : dm-verity
233 
234 // In case of loop device, it is from a non-flattened
235 // APEX file. This original APEX file can be tracked
236 // by /sys/block/loopX/loop/backing_file.
237 
238 // In case of dm-verity, it is mapped to a loop device.
239 // This mapped loop device can be traced by
240 // /sys/block/dm-X/slaves/ directory which contains
241 // a symlink to /sys/block/loopY, which leads to
242 // the original APEX file.
243 // Device name can be retrieved from
244 // /sys/block/dm-Y/dm/name.
245 
246 // By synchronizing the mounts info with Database on startup,
247 // Apexd serves the correct package list even on the devices
248 // which are not ro.apex.updatable.
PopulateFromMounts()249 void MountedApexDatabase::PopulateFromMounts() {
250   LOG(INFO) << "Populating APEX database from mounts...";
251 
252   std::unordered_map<std::string, int> activeVersions;
253 
254   std::ifstream mounts("/proc/mounts");
255   std::string line;
256   while (std::getline(mounts, line)) {
257     auto [block, mountPoint] = parseMountInfo(line);
258     // TODO(b/158469914): distinguish between temp and non-temp mounts
259     if (fs::path(mountPoint).parent_path() != kApexRoot) {
260       continue;
261     }
262     if (isActiveMountPoint(mountPoint)) {
263       continue;
264     }
265 
266     auto mountData = resolveMountInfo(BlockDevice(block), mountPoint);
267     if (!mountData.ok()) {
268       LOG(WARNING) << "Can't resolve mount info " << mountData.error();
269       continue;
270     }
271 
272     auto [package, version] = parseMountPoint(mountPoint);
273     AddMountedApex(package, false, *mountData);
274 
275     auto active = activeVersions[package] < version;
276     if (active) {
277       activeVersions[package] = version;
278       SetLatest(package, mountData->full_path);
279     }
280     LOG(INFO) << "Found " << mountPoint << " backed by"
281               << (mountData->deleted ? " deleted " : " ") << "file "
282               << mountData->full_path;
283   }
284 
285   LOG(INFO) << mounted_apexes_.size() << " packages restored.";
286 }
287 
288 }  // namespace apex
289 }  // namespace android
290