blob: d027f95c8d7a2f75a2e0d8ab1811d7882c8c300c [file] [log] [blame]
Jooyung Han7dca50c2019-04-12 04:52:42 +09001/*
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"
Jooyung Han7dca50c2019-04-12 04:52:42 +090023#include "string_log.h"
24
25#include <android-base/file.h>
26#include <android-base/logging.h>
27#include <android-base/parseint.h>
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +010028#include <android-base/result.h>
Jooyung Han7dca50c2019-04-12 04:52:42 +090029#include <android-base/strings.h>
30
31#include <filesystem>
32#include <fstream>
33#include <string>
34#include <unordered_map>
35#include <utility>
36
Nikita Ioffe78d2bce2020-05-02 01:28:30 +010037using android::base::ConsumeSuffix;
Jooyung Han7dca50c2019-04-12 04:52:42 +090038using android::base::ParseInt;
39using android::base::ReadFileToString;
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +010040using android::base::Result;
Jooyung Han7dca50c2019-04-12 04:52:42 +090041using android::base::Split;
42using android::base::StartsWith;
43using android::base::Trim;
44
45namespace fs = std::filesystem;
46
47namespace android {
48namespace apex {
49
50namespace {
51
52using MountedApexData = MountedApexDatabase::MountedApexData;
53
Jooyung Han7dca50c2019-04-12 04:52:42 +090054enum BlockDeviceType {
55 UnknownDevice,
56 LoopDevice,
57 DeviceMapperDevice,
58};
59
60const fs::path kDevBlock = "/dev/block";
61const fs::path kSysBlock = "/sys/block";
62
63class BlockDevice {
64 std::string name; // loopN, dm-N, ...
65 public:
66 explicit BlockDevice(const fs::path& path) { name = path.filename(); }
67
68 BlockDeviceType GetType() const {
69 if (StartsWith(name, "loop")) return LoopDevice;
70 if (StartsWith(name, "dm-")) return DeviceMapperDevice;
71 return UnknownDevice;
72 }
73
74 fs::path SysPath() const { return kSysBlock / name; }
75
76 fs::path DevPath() const { return kDevBlock / name; }
77
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +010078 Result<std::string> GetProperty(const std::string& property) const {
Jooyung Han7dca50c2019-04-12 04:52:42 +090079 auto propertyFile = SysPath() / property;
80 std::string propertyValue;
81 if (!ReadFileToString(propertyFile, &propertyValue)) {
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +010082 return ErrnoError() << "Fail to read";
Jooyung Han7dca50c2019-04-12 04:52:42 +090083 }
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +010084 return Trim(propertyValue);
Jooyung Han7dca50c2019-04-12 04:52:42 +090085 }
86
87 std::vector<BlockDevice> GetSlaves() const {
88 std::vector<BlockDevice> slaves;
Jooyung Han80d8c862019-05-03 00:42:31 +090089 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);
Jooyung Han7dca50c2019-04-12 04:52:42 +090094 }
Jooyung Han80d8c862019-05-03 00:42:31 +090095 });
Bernie Innocentid04d5d02020-02-06 22:01:51 +090096 if (!status.ok()) {
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +010097 LOG(WARNING) << status.error();
Jooyung Han7dca50c2019-04-12 04:52:42 +090098 }
99 return slaves;
100 }
101};
102
103std::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
111std::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
124bool isActiveMountPoint(const std::string& mountPoint) {
125 return (mountPoint.find('@') == std::string::npos);
126}
127
Nikita Ioffed3169802020-01-02 02:44:44 +0000128Result<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");
Bernie Innocentid04d5d02020-02-06 22:01:51 +0900142 if (!backing_file.ok()) {
Nikita Ioffed3169802020-01-02 02:44:44 +0000143 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
Nikita Ioffe78d2bce2020-05-02 01:28:30 +0100172// This is not the right place to do this normalization, but proper solution
173// will require some refactoring first. :(
174// TODO(ioffe): introduce MountedApexDataBuilder and delegate all
175// building/normalization logic to it.
176void 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
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +0100191Result<MountedApexData> resolveMountInfo(const BlockDevice& block,
192 const std::string& mountPoint) {
Jooyung Han341cab02019-05-05 02:09:48 +0900193 // Now, see if it is dm-verity or loop mounted
Jooyung Han7dca50c2019-04-12 04:52:42 +0900194 switch (block.GetType()) {
195 case LoopDevice: {
196 auto backingFile = block.GetProperty("loop/backing_file");
Bernie Innocentid04d5d02020-02-06 22:01:51 +0900197 if (!backingFile.ok()) {
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +0100198 return backingFile.error();
Jooyung Han7dca50c2019-04-12 04:52:42 +0900199 }
Nikita Ioffe78d2bce2020-05-02 01:28:30 +0100200 auto result = MountedApexData(block.DevPath(), *backingFile, mountPoint,
201 /* device_name= */ "",
202 /* hashtree_loop_name= */ "");
203 NormalizeIfDeleted(&result);
204 return result;
Jooyung Han7dca50c2019-04-12 04:52:42 +0900205 }
206 case DeviceMapperDevice: {
207 auto name = block.GetProperty("dm/name");
Bernie Innocentid04d5d02020-02-06 22:01:51 +0900208 if (!name.ok()) {
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +0100209 return name.error();
Jooyung Han7dca50c2019-04-12 04:52:42 +0900210 }
Nikita Ioffed3169802020-01-02 02:44:44 +0000211 MountedApexData result;
212 result.mount_point = mountPoint;
213 result.device_name = *name;
Bernie Innocentid04d5d02020-02-06 22:01:51 +0900214 if (auto status = PopulateLoopInfo(block, &result); !status.ok()) {
Nikita Ioffed3169802020-01-02 02:44:44 +0000215 return status.error();
Jooyung Han7dca50c2019-04-12 04:52:42 +0900216 }
Nikita Ioffe78d2bce2020-05-02 01:28:30 +0100217 NormalizeIfDeleted(&result);
Nikita Ioffed3169802020-01-02 02:44:44 +0000218 return result;
Jooyung Han7dca50c2019-04-12 04:52:42 +0900219 }
220 case UnknownDevice: {
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +0100221 return Errorf("Can't resolve {}", block.DevPath().string());
Jooyung Han7dca50c2019-04-12 04:52:42 +0900222 }
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
Jooyung Han7dca50c2019-04-12 04:52:42 +0900233
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
Jooyung Han7dca50c2019-04-12 04:52:42 +0900246// 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.
249void MountedApexDatabase::PopulateFromMounts() {
250 LOG(INFO) << "Populating APEX database from mounts...";
251
252 std::unordered_map<std::string, int> activeVersions;
Jooyung Han7dca50c2019-04-12 04:52:42 +0900253
254 std::ifstream mounts("/proc/mounts");
255 std::string line;
256 while (std::getline(mounts, line)) {
257 auto [block, mountPoint] = parseMountInfo(line);
258 // TODO(jooyung): ignore tmp mount?
Jooyung Hanb0de1672019-05-03 01:39:57 +0900259 if (fs::path(mountPoint).parent_path() != kApexRoot) {
Jooyung Han7dca50c2019-04-12 04:52:42 +0900260 continue;
261 }
262 if (isActiveMountPoint(mountPoint)) {
263 continue;
264 }
265
Jiyong Park8f55a212019-06-03 20:48:15 +0900266 auto mountData = resolveMountInfo(BlockDevice(block), mountPoint);
Bernie Innocentid04d5d02020-02-06 22:01:51 +0900267 if (!mountData.ok()) {
Mohammad Samiul Islambd6ab0f2019-06-20 15:55:27 +0100268 LOG(WARNING) << "Can't resolve mount info " << mountData.error();
Jooyung Han7dca50c2019-04-12 04:52:42 +0900269 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 }
Nikita Ioffe78d2bce2020-05-02 01:28:30 +0100280 LOG(INFO) << "Found " << mountPoint << " backed by"
281 << (mountData->deleted ? " deleted " : " ") << "file "
282 << mountData->full_path;
Jooyung Han7dca50c2019-04-12 04:52:42 +0900283 }
284
285 LOG(INFO) << mounted_apexes_.size() << " packages restored.";
286}
287
288} // namespace apex
289} // namespace android