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