Jooyung Han | 451cc34 | 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" |
| 23 | #include "status_or.h" |
| 24 | #include "string_log.h" |
| 25 | |
| 26 | #include <android-base/file.h> |
| 27 | #include <android-base/logging.h> |
| 28 | #include <android-base/parseint.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::EndsWith; |
| 38 | using android::base::ParseInt; |
| 39 | using android::base::ReadFileToString; |
| 40 | using android::base::Split; |
| 41 | using android::base::StartsWith; |
| 42 | using android::base::Trim; |
| 43 | |
| 44 | namespace fs = std::filesystem; |
| 45 | |
| 46 | namespace android { |
| 47 | namespace apex { |
| 48 | |
| 49 | namespace { |
| 50 | |
| 51 | using MountedApexData = MountedApexDatabase::MountedApexData; |
| 52 | |
| 53 | // from art/runtime/class_linker.cc |
| 54 | inline size_t hash_combine(size_t seed, size_t val) { |
| 55 | return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2)); |
| 56 | } |
| 57 | |
| 58 | typedef std::pair<dev_t, ino_t> inode_t; |
| 59 | struct inode_hash { |
| 60 | size_t operator()(const inode_t& inode) const { |
| 61 | auto h1 = std::hash<dev_t>{}(inode.first); |
| 62 | auto h2 = std::hash<ino_t>{}(inode.second); |
| 63 | return hash_combine(h1, h2); |
| 64 | } |
| 65 | }; |
| 66 | typedef std::unordered_map<inode_t, std::string, inode_hash> inode_map; |
| 67 | |
| 68 | enum BlockDeviceType { |
| 69 | UnknownDevice, |
| 70 | LoopDevice, |
| 71 | DeviceMapperDevice, |
| 72 | }; |
| 73 | |
| 74 | const fs::path kDevBlock = "/dev/block"; |
| 75 | const fs::path kSysBlock = "/sys/block"; |
| 76 | |
| 77 | class BlockDevice { |
| 78 | std::string name; // loopN, dm-N, ... |
| 79 | public: |
| 80 | explicit BlockDevice(const fs::path& path) { name = path.filename(); } |
| 81 | |
| 82 | BlockDeviceType GetType() const { |
| 83 | if (StartsWith(name, "loop")) return LoopDevice; |
| 84 | if (StartsWith(name, "dm-")) return DeviceMapperDevice; |
| 85 | return UnknownDevice; |
| 86 | } |
| 87 | |
| 88 | fs::path SysPath() const { return kSysBlock / name; } |
| 89 | |
| 90 | fs::path DevPath() const { return kDevBlock / name; } |
| 91 | |
| 92 | StatusOr<std::string> GetProperty(const std::string& property) const { |
| 93 | auto propertyFile = SysPath() / property; |
| 94 | std::string propertyValue; |
| 95 | if (!ReadFileToString(propertyFile, &propertyValue)) { |
| 96 | return StatusOr<std::string>::MakeError(PStringLog() << "Fail to read"); |
| 97 | } |
| 98 | return StatusOr<std::string>(Trim(propertyValue)); |
| 99 | } |
| 100 | |
| 101 | std::vector<BlockDevice> GetSlaves() const { |
| 102 | std::vector<BlockDevice> slaves; |
Jooyung Han | aed3a03 | 2019-05-03 00:42:31 +0900 | [diff] [blame] | 103 | std::error_code ec; |
| 104 | auto status = WalkDir(SysPath() / "slaves", [&](const auto& entry) { |
| 105 | BlockDevice dev(entry); |
| 106 | if (fs::is_block_file(dev.DevPath(), ec)) { |
| 107 | slaves.push_back(dev); |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 108 | } |
Jooyung Han | aed3a03 | 2019-05-03 00:42:31 +0900 | [diff] [blame] | 109 | }); |
| 110 | if (!status.Ok()) { |
| 111 | LOG(WARNING) << status.ErrorMessage(); |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 112 | } |
| 113 | return slaves; |
| 114 | } |
| 115 | }; |
| 116 | |
| 117 | std::pair<fs::path, fs::path> parseMountInfo(const std::string& mountInfo) { |
| 118 | const auto& tokens = Split(mountInfo, " "); |
| 119 | if (tokens.size() < 2) { |
| 120 | return std::make_pair("", ""); |
| 121 | } |
| 122 | return std::make_pair(tokens[0], tokens[1]); |
| 123 | } |
| 124 | |
| 125 | std::pair<std::string, int> parseMountPoint(const std::string& mountPoint) { |
| 126 | auto packageId = fs::path(mountPoint).filename(); |
| 127 | auto split = Split(packageId, "@"); |
| 128 | if (split.size() == 2) { |
| 129 | int version; |
| 130 | if (!ParseInt(split[1], &version)) { |
| 131 | version = -1; |
| 132 | } |
| 133 | return std::make_pair(split[0], version); |
| 134 | } |
| 135 | return std::make_pair(packageId, -1); |
| 136 | } |
| 137 | |
| 138 | bool isActiveMountPoint(const std::string& mountPoint) { |
| 139 | return (mountPoint.find('@') == std::string::npos); |
| 140 | } |
| 141 | |
| 142 | StatusOr<inode_t> inodeFor(const std::string& path) { |
| 143 | struct stat buf; |
| 144 | if (stat(path.c_str(), &buf)) { |
| 145 | return StatusOr<inode_t>::MakeError(PStringLog() << "stat failed"); |
| 146 | } |
| 147 | return StatusOr<inode_t>(buf.st_dev, buf.st_ino); |
| 148 | } |
| 149 | |
Jooyung Han | aed3a03 | 2019-05-03 00:42:31 +0900 | [diff] [blame] | 150 | // Flattened packages from builtin APEX dirs(/system/apex, /product/apex, ...) |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 151 | inode_map scanFlattendedPackages() { |
| 152 | inode_map map; |
| 153 | |
Nikita Ioffe | e44c58a | 2019-04-29 14:07:11 +0100 | [diff] [blame] | 154 | for (const auto& dir : kApexPackageBuiltinDirs) { |
Nikita Ioffe | 8a8a356 | 2019-06-21 01:21:13 +0100 | [diff] [blame] | 155 | auto status = WalkDir(dir, [&](const fs::directory_entry& entry) { |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 156 | const auto& path = entry.path(); |
| 157 | if (isFlattenedApex(path)) { |
| 158 | auto inode = inodeFor(path); |
| 159 | if (inode.Ok()) { |
| 160 | map[*inode] = path; |
| 161 | } |
| 162 | } |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 163 | }); |
Nikita Ioffe | 8a8a356 | 2019-06-21 01:21:13 +0100 | [diff] [blame] | 164 | if (!status.Ok()) { |
| 165 | LOG(ERROR) << "Failed to walk " << dir << " : " << status.ErrorMessage(); |
| 166 | } |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 167 | } |
| 168 | |
| 169 | return map; |
| 170 | } |
| 171 | |
| 172 | StatusOr<MountedApexData> resolveMountInfo(const BlockDevice& block, |
| 173 | const std::string& mountPoint, |
| 174 | const inode_map& inodeMap) { |
| 175 | auto Error = [](auto e) { return StatusOr<MountedApexData>::MakeError(e); }; |
| 176 | |
Jooyung Han | 3775131 | 2019-05-05 02:09:48 +0900 | [diff] [blame] | 177 | // First, see if it is bind-mount'ed to a flattened APEX |
| 178 | // This is checked first since flattened APEXes can be located in any stacked |
| 179 | // filesystem. (e.g. if / is mounted via /dev/loop1, then /proc/mounts shows |
| 180 | // that loop device as associated block device. But it is not related to APEX |
| 181 | // activation.) In any cases, comparing (dev,inode) pair with scanned |
| 182 | // flattened APEXes must identify bind-mounted APEX properly. |
| 183 | // See b/131924899. |
| 184 | auto inode = inodeFor(mountPoint); |
| 185 | if (inode.Ok()) { |
| 186 | auto iter = inodeMap.find(*inode); |
| 187 | if (iter != inodeMap.end()) { |
| 188 | return StatusOr<MountedApexData>("", iter->second, mountPoint, ""); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | // Now, see if it is dm-verity or loop mounted |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 193 | switch (block.GetType()) { |
| 194 | case LoopDevice: { |
| 195 | auto backingFile = block.GetProperty("loop/backing_file"); |
| 196 | if (!backingFile.Ok()) { |
| 197 | return Error(backingFile.ErrorStatus()); |
| 198 | } |
| 199 | return StatusOr<MountedApexData>(block.DevPath(), *backingFile, |
| 200 | mountPoint, ""); |
| 201 | } |
| 202 | case DeviceMapperDevice: { |
| 203 | auto name = block.GetProperty("dm/name"); |
| 204 | if (!name.Ok()) { |
| 205 | return Error(name.ErrorStatus()); |
| 206 | } |
| 207 | auto slaves = block.GetSlaves(); |
| 208 | if (slaves.empty() || slaves[0].GetType() != LoopDevice) { |
| 209 | return Error("DeviceMapper device with no loop devices"); |
| 210 | } |
| 211 | // TODO(jooyung): handle multiple loop devices when hash tree is |
| 212 | // externalized |
| 213 | auto slave = slaves[0]; |
| 214 | auto backingFile = slave.GetProperty("loop/backing_file"); |
| 215 | if (!backingFile.Ok()) { |
| 216 | return Error(backingFile.ErrorStatus()); |
| 217 | } |
| 218 | return StatusOr<MountedApexData>(slave.DevPath(), *backingFile, |
| 219 | mountPoint, *name); |
| 220 | } |
| 221 | case UnknownDevice: { |
Jooyung Han | 3775131 | 2019-05-05 02:09:48 +0900 | [diff] [blame] | 222 | return Error("Can't resolve " + block.DevPath().string()); |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 223 | } |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | } // namespace |
| 228 | |
| 229 | // On startup, APEX database is populated from /proc/mounts. |
| 230 | |
| 231 | // /apex/<package-id> can be mounted from |
| 232 | // - /dev/block/loopX : loop device |
| 233 | // - /dev/block/dm-X : dm-verity |
| 234 | // - <flattened> : bind-mount |
| 235 | |
| 236 | // In case of loop device, it is from a non-flattened |
| 237 | // APEX file. This original APEX file can be tracked |
| 238 | // by /sys/block/loopX/loop/backing_file. |
| 239 | |
| 240 | // In case of dm-verity, it is mapped to a loop device. |
| 241 | // This mapped loop device can be traced by |
| 242 | // /sys/block/dm-X/slaves/ directory which contains |
| 243 | // a symlink to /sys/block/loopY, which leads to |
| 244 | // the original APEX file. |
| 245 | // Device name can be retrieved from |
| 246 | // /sys/block/dm-Y/dm/name. |
| 247 | |
| 248 | // In case of <flattened>, it is --bind mounted to a flattened |
| 249 | // APEX directory. This is allowed only for system/product |
| 250 | // partitions. So, original APEX directory can be found |
| 251 | // by comparing dev/inode pair with candidates. |
| 252 | |
| 253 | // By synchronizing the mounts info with Database on startup, |
| 254 | // Apexd serves the correct package list even on the devices |
| 255 | // which are not ro.apex.updatable. |
| 256 | void MountedApexDatabase::PopulateFromMounts() { |
| 257 | LOG(INFO) << "Populating APEX database from mounts..."; |
| 258 | |
| 259 | std::unordered_map<std::string, int> activeVersions; |
| 260 | inode_map inodeToFlattendApexMap = scanFlattendedPackages(); |
| 261 | |
| 262 | std::ifstream mounts("/proc/mounts"); |
| 263 | std::string line; |
| 264 | while (std::getline(mounts, line)) { |
| 265 | auto [block, mountPoint] = parseMountInfo(line); |
| 266 | // TODO(jooyung): ignore tmp mount? |
Jooyung Han | 3d55078 | 2019-05-03 01:39:57 +0900 | [diff] [blame] | 267 | if (fs::path(mountPoint).parent_path() != kApexRoot) { |
Jooyung Han | 451cc34 | 2019-04-12 04:52:42 +0900 | [diff] [blame] | 268 | continue; |
| 269 | } |
| 270 | if (isActiveMountPoint(mountPoint)) { |
| 271 | continue; |
| 272 | } |
| 273 | |
| 274 | auto mountData = resolveMountInfo(BlockDevice(block), mountPoint, |
| 275 | inodeToFlattendApexMap); |
| 276 | if (!mountData.Ok()) { |
| 277 | LOG(WARNING) << "Can't resolve mount info " << mountData.ErrorMessage(); |
| 278 | continue; |
| 279 | } |
| 280 | |
| 281 | auto [package, version] = parseMountPoint(mountPoint); |
| 282 | AddMountedApex(package, false, *mountData); |
| 283 | |
| 284 | auto active = activeVersions[package] < version; |
| 285 | if (active) { |
| 286 | activeVersions[package] = version; |
| 287 | SetLatest(package, mountData->full_path); |
| 288 | } |
| 289 | LOG(INFO) << "Found " << mountPoint; |
| 290 | } |
| 291 | |
| 292 | LOG(INFO) << mounted_apexes_.size() << " packages restored."; |
| 293 | } |
| 294 | |
| 295 | } // namespace apex |
| 296 | } // namespace android |