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