Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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" |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 23 | #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 Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 28 | #include <android-base/result.h> |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 29 | #include <android-base/strings.h> |
| 30 | |
| 31 | #include <filesystem> |
| 32 | #include <fstream> |
| 33 | #include <string> |
| 34 | #include <unordered_map> |
| 35 | #include <utility> |
| 36 | |
Nikita Ioffe | 78d2bce | 2020-05-02 01:28:30 +0100 | [diff] [blame] | 37 | using android::base::ConsumeSuffix; |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 38 | using android::base::ParseInt; |
| 39 | using android::base::ReadFileToString; |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 40 | using android::base::Result; |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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 | |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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: |
| 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 Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 78 | Result<std::string> GetProperty(const std::string& property) const { |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 79 | auto propertyFile = SysPath() / property; |
| 80 | std::string propertyValue; |
| 81 | if (!ReadFileToString(propertyFile, &propertyValue)) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 82 | return ErrnoError() << "Fail to read"; |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 83 | } |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 84 | return Trim(propertyValue); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 85 | } |
| 86 | |
| 87 | std::vector<BlockDevice> GetSlaves() const { |
| 88 | std::vector<BlockDevice> slaves; |
Jooyung Han | 80d8c86 | 2019-05-03 00:42:31 +0900 | [diff] [blame] | 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); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 94 | } |
Jooyung Han | 80d8c86 | 2019-05-03 00:42:31 +0900 | [diff] [blame] | 95 | }); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 96 | if (!status.ok()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 97 | LOG(WARNING) << status.error(); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 98 | } |
| 99 | return slaves; |
| 100 | } |
| 101 | }; |
| 102 | |
| 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 | |
| 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 | |
| 124 | bool isActiveMountPoint(const std::string& mountPoint) { |
| 125 | return (mountPoint.find('@') == std::string::npos); |
| 126 | } |
| 127 | |
Nikita Ioffe | d316980 | 2020-01-02 02:44:44 +0000 | [diff] [blame] | 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"); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 142 | if (!backing_file.ok()) { |
Nikita Ioffe | d316980 | 2020-01-02 02:44:44 +0000 | [diff] [blame] | 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 | |
Nikita Ioffe | 78d2bce | 2020-05-02 01:28:30 +0100 | [diff] [blame] | 172 | // 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. |
| 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 | |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 191 | Result<MountedApexData> resolveMountInfo(const BlockDevice& block, |
| 192 | const std::string& mountPoint) { |
Jooyung Han | 341cab0 | 2019-05-05 02:09:48 +0900 | [diff] [blame] | 193 | // Now, see if it is dm-verity or loop mounted |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 194 | switch (block.GetType()) { |
| 195 | case LoopDevice: { |
| 196 | auto backingFile = block.GetProperty("loop/backing_file"); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 197 | if (!backingFile.ok()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 198 | return backingFile.error(); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 199 | } |
Nikita Ioffe | 78d2bce | 2020-05-02 01:28:30 +0100 | [diff] [blame] | 200 | auto result = MountedApexData(block.DevPath(), *backingFile, mountPoint, |
| 201 | /* device_name= */ "", |
| 202 | /* hashtree_loop_name= */ ""); |
| 203 | NormalizeIfDeleted(&result); |
| 204 | return result; |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 205 | } |
| 206 | case DeviceMapperDevice: { |
| 207 | auto name = block.GetProperty("dm/name"); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 208 | if (!name.ok()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 209 | return name.error(); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 210 | } |
Nikita Ioffe | d316980 | 2020-01-02 02:44:44 +0000 | [diff] [blame] | 211 | MountedApexData result; |
| 212 | result.mount_point = mountPoint; |
| 213 | result.device_name = *name; |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 214 | if (auto status = PopulateLoopInfo(block, &result); !status.ok()) { |
Nikita Ioffe | d316980 | 2020-01-02 02:44:44 +0000 | [diff] [blame] | 215 | return status.error(); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 216 | } |
Nikita Ioffe | 78d2bce | 2020-05-02 01:28:30 +0100 | [diff] [blame] | 217 | NormalizeIfDeleted(&result); |
Nikita Ioffe | d316980 | 2020-01-02 02:44:44 +0000 | [diff] [blame] | 218 | return result; |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 219 | } |
| 220 | case UnknownDevice: { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 221 | return Errorf("Can't resolve {}", block.DevPath().string()); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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 |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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 | |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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. |
| 249 | void MountedApexDatabase::PopulateFromMounts() { |
| 250 | LOG(INFO) << "Populating APEX database from mounts..."; |
| 251 | |
| 252 | std::unordered_map<std::string, int> activeVersions; |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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(jooyung): ignore tmp mount? |
Jooyung Han | b0de167 | 2019-05-03 01:39:57 +0900 | [diff] [blame] | 259 | if (fs::path(mountPoint).parent_path() != kApexRoot) { |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 260 | continue; |
| 261 | } |
| 262 | if (isActiveMountPoint(mountPoint)) { |
| 263 | continue; |
| 264 | } |
| 265 | |
Jiyong Park | 8f55a21 | 2019-06-03 20:48:15 +0900 | [diff] [blame] | 266 | auto mountData = resolveMountInfo(BlockDevice(block), mountPoint); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 267 | if (!mountData.ok()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 268 | LOG(WARNING) << "Can't resolve mount info " << mountData.error(); |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 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 | } |
Nikita Ioffe | 78d2bce | 2020-05-02 01:28:30 +0100 | [diff] [blame] | 280 | LOG(INFO) << "Found " << mountPoint << " backed by" |
| 281 | << (mountData->deleted ? " deleted " : " ") << "file " |
| 282 | << mountData->full_path; |
Jooyung Han | 7dca50c | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 283 | } |
| 284 | |
| 285 | LOG(INFO) << mounted_apexes_.size() << " packages restored."; |
| 286 | } |
| 287 | |
| 288 | } // namespace apex |
| 289 | } // namespace android |