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: 69 explicit BlockDevice(const fs::path& path) { name = path.filename(); } 70 71 BlockDeviceType GetType() const { 72 if (StartsWith(name, "loop")) return LoopDevice; 73 if (StartsWith(name, "dm-")) return DeviceMapperDevice; 74 return UnknownDevice; 75 } 76 77 fs::path SysPath() const { return kSysBlock / name; } 78 79 fs::path DevPath() const { return kDevBlock / name; } 80 81 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 90 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 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 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 127 bool IsActiveMountPoint(const std::string& mount_point) { 128 return (mount_point.find('@') == std::string::npos); 129 } 130 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. 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 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. 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