Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 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 <ctype.h> |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 18 | #include <dirent.h> |
Iliyan Malchev | 3ea902f | 2015-05-01 14:05:04 -0700 | [diff] [blame] | 19 | #include <errno.h> |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 20 | #include <stdio.h> |
| 21 | #include <stdlib.h> |
| 22 | #include <string.h> |
| 23 | #include <sys/mount.h> |
Iliyan Malchev | 3ea902f | 2015-05-01 14:05:04 -0700 | [diff] [blame] | 24 | #include <unistd.h> |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 25 | |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 26 | #include <algorithm> |
Tom Cherry | 7e34f75 | 2019-01-22 15:37:08 -0800 | [diff] [blame] | 27 | #include <array> |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 28 | #include <utility> |
| 29 | #include <vector> |
| 30 | |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 31 | #include <android-base/file.h> |
Jaegeuk Kim | 2aedc82 | 2018-11-20 13:27:06 -0800 | [diff] [blame] | 32 | #include <android-base/parseint.h> |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 33 | #include <android-base/stringprintf.h> |
| 34 | #include <android-base/strings.h> |
David Anderson | 0e330f1 | 2019-01-03 18:16:56 -0800 | [diff] [blame] | 35 | #include <libgsi/libgsi.h> |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 36 | |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 37 | #include "fs_mgr_priv.h" |
| 38 | |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 39 | using android::base::EndsWith; |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 40 | using android::base::ParseByteCount; |
| 41 | using android::base::ParseInt; |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 42 | using android::base::ReadFileToString; |
Nikita Ioffe | 5110628 | 2019-11-21 21:40:03 +0000 | [diff] [blame] | 43 | using android::base::Readlink; |
Tom Cherry | 6bbe947 | 2018-12-13 10:58:37 -0800 | [diff] [blame] | 44 | using android::base::Split; |
Tom Marshall | 66f6a6d | 2018-03-06 16:19:18 +0100 | [diff] [blame] | 45 | using android::base::StartsWith; |
| 46 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 47 | namespace android { |
| 48 | namespace fs_mgr { |
| 49 | namespace { |
| 50 | |
Vic Yang | 5caa3e9 | 2019-08-06 14:15:33 -0700 | [diff] [blame] | 51 | constexpr char kDefaultAndroidDtDir[] = "/proc/device-tree/firmware/android"; |
Yu Ning | c01022a | 2017-07-26 17:54:08 +0800 | [diff] [blame] | 52 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 53 | struct FlagList { |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 54 | const char *name; |
Bowgo Tsai | 4c80edf | 2018-12-12 23:21:13 +0800 | [diff] [blame] | 55 | uint64_t flag; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 56 | }; |
| 57 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 58 | FlagList kMountFlagsList[] = { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 59 | {"noatime", MS_NOATIME}, |
| 60 | {"noexec", MS_NOEXEC}, |
| 61 | {"nosuid", MS_NOSUID}, |
| 62 | {"nodev", MS_NODEV}, |
| 63 | {"nodiratime", MS_NODIRATIME}, |
| 64 | {"ro", MS_RDONLY}, |
| 65 | {"rw", 0}, |
Jaegeuk Kim | 5ffa970 | 2019-06-05 17:43:47 -0700 | [diff] [blame] | 66 | {"sync", MS_SYNCHRONOUS}, |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 67 | {"remount", MS_REMOUNT}, |
| 68 | {"bind", MS_BIND}, |
| 69 | {"rec", MS_REC}, |
| 70 | {"unbindable", MS_UNBINDABLE}, |
| 71 | {"private", MS_PRIVATE}, |
| 72 | {"slave", MS_SLAVE}, |
| 73 | {"shared", MS_SHARED}, |
Daniel Rosenberg | 4c93b25 | 2018-08-28 01:41:18 -0700 | [diff] [blame] | 74 | {"defaults", 0}, |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 75 | }; |
| 76 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 77 | off64_t CalculateZramSize(int percentage) { |
Tom Cherry | fafbb64 | 2018-11-29 13:24:09 -0800 | [diff] [blame] | 78 | off64_t total; |
Iliyan Malchev | 3ea902f | 2015-05-01 14:05:04 -0700 | [diff] [blame] | 79 | |
| 80 | total = sysconf(_SC_PHYS_PAGES); |
| 81 | total *= percentage; |
| 82 | total /= 100; |
| 83 | |
| 84 | total *= sysconf(_SC_PAGESIZE); |
| 85 | |
| 86 | return total; |
| 87 | } |
| 88 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 89 | // Fills 'dt_value' with the underlying device tree value string without the trailing '\0'. |
| 90 | // Returns true if 'dt_value' has a valid string, 'false' otherwise. |
| 91 | bool ReadDtFile(const std::string& file_name, std::string* dt_value) { |
Sandeep Patil | 4cd9a46 | 2017-02-24 13:03:39 -0800 | [diff] [blame] | 92 | if (android::base::ReadFileToString(file_name, dt_value)) { |
| 93 | if (!dt_value->empty()) { |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 94 | // Trim the trailing '\0' out, otherwise the comparison will produce false-negatives. |
Sandeep Patil | 4cd9a46 | 2017-02-24 13:03:39 -0800 | [diff] [blame] | 95 | dt_value->resize(dt_value->size() - 1); |
| 96 | return true; |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | return false; |
| 101 | } |
| 102 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 103 | void ParseFileEncryption(const std::string& arg, FstabEntry* entry) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 104 | entry->fs_mgr_flags.file_encryption = true; |
Paul Crowley | 4fa8413 | 2019-10-24 23:33:12 -0700 | [diff] [blame] | 105 | entry->encryption_options = arg; |
Tom Cherry | 7e34f75 | 2019-01-22 15:37:08 -0800 | [diff] [blame] | 106 | } |
| 107 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 108 | bool SetMountFlag(const std::string& flag, FstabEntry* entry) { |
| 109 | for (const auto& [name, value] : kMountFlagsList) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 110 | if (flag == name) { |
| 111 | entry->flags |= value; |
| 112 | return true; |
| 113 | } |
| 114 | } |
| 115 | return false; |
| 116 | } |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 117 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 118 | void ParseMountFlags(const std::string& flags, FstabEntry* entry) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 119 | std::string fs_options; |
| 120 | for (const auto& flag : Split(flags, ",")) { |
| 121 | if (!SetMountFlag(flag, entry)) { |
| 122 | // Unknown flag, so it must be a filesystem specific option. |
| 123 | if (!fs_options.empty()) { |
| 124 | fs_options.append(","); // appends a comma if not the first |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 125 | } |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 126 | fs_options.append(flag); |
Jaegeuk Kim | f07d07b | 2019-06-13 17:37:01 -0700 | [diff] [blame] | 127 | |
| 128 | if (entry->fs_type == "f2fs" && StartsWith(flag, "reserve_root=")) { |
| 129 | std::string arg; |
| 130 | if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) { |
| 131 | arg = flag.substr(equal_sign + 1); |
| 132 | } |
| 133 | if (!ParseInt(arg, &entry->reserved_size)) { |
| 134 | LWARNING << "Warning: reserve_root= flag malformed: " << arg; |
| 135 | } else { |
| 136 | entry->reserved_size <<= 12; |
| 137 | } |
| 138 | } |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 139 | } |
| 140 | } |
| 141 | entry->fs_options = std::move(fs_options); |
| 142 | } |
| 143 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 144 | void ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 145 | for (const auto& flag : Split(flags, ",")) { |
Mark Salyzyn | c0d09a2 | 2019-02-05 07:30:23 -0800 | [diff] [blame] | 146 | if (flag.empty() || flag == "defaults") continue; |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 147 | std::string arg; |
| 148 | if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) { |
| 149 | arg = flag.substr(equal_sign + 1); |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 150 | } |
| 151 | |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 152 | // First handle flags that simply set a boolean. |
| 153 | #define CheckFlag(flag_name, value) \ |
| 154 | if (flag == flag_name) { \ |
| 155 | entry->fs_mgr_flags.value = true; \ |
| 156 | continue; \ |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 157 | } |
| 158 | |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 159 | CheckFlag("wait", wait); |
| 160 | CheckFlag("check", check); |
| 161 | CheckFlag("nonremovable", nonremovable); |
| 162 | CheckFlag("recoveryonly", recovery_only); |
| 163 | CheckFlag("noemulatedsd", no_emulated_sd); |
| 164 | CheckFlag("notrim", no_trim); |
| 165 | CheckFlag("verify", verify); |
| 166 | CheckFlag("formattable", formattable); |
| 167 | CheckFlag("slotselect", slot_select); |
| 168 | CheckFlag("latemount", late_mount); |
| 169 | CheckFlag("nofail", no_fail); |
| 170 | CheckFlag("verifyatboot", verify_at_boot); |
| 171 | CheckFlag("quota", quota); |
| 172 | CheckFlag("avb", avb); |
| 173 | CheckFlag("logical", logical); |
| 174 | CheckFlag("checkpoint=block", checkpoint_blk); |
| 175 | CheckFlag("checkpoint=fs", checkpoint_fs); |
| 176 | CheckFlag("first_stage_mount", first_stage_mount); |
| 177 | CheckFlag("slotselect_other", slot_select_other); |
| 178 | CheckFlag("fsverity", fs_verity); |
| 179 | |
| 180 | #undef CheckFlag |
| 181 | |
| 182 | // Then handle flags that take an argument. |
| 183 | if (StartsWith(flag, "encryptable=")) { |
| 184 | // The encryptable flag is followed by an = and the location of the keys. |
| 185 | entry->fs_mgr_flags.crypt = true; |
| 186 | entry->key_loc = arg; |
| 187 | } else if (StartsWith(flag, "voldmanaged=")) { |
| 188 | // The voldmanaged flag is followed by an = and the label, a colon and the partition |
| 189 | // number or the word "auto", e.g. voldmanaged=sdcard:3 |
| 190 | entry->fs_mgr_flags.vold_managed = true; |
| 191 | auto parts = Split(arg, ":"); |
| 192 | if (parts.size() != 2) { |
| 193 | LWARNING << "Warning: voldmanaged= flag malformed: " << arg; |
| 194 | continue; |
| 195 | } |
| 196 | |
| 197 | entry->label = std::move(parts[0]); |
| 198 | if (parts[1] == "auto") { |
| 199 | entry->partnum = -1; |
| 200 | } else { |
| 201 | if (!ParseInt(parts[1], &entry->partnum)) { |
| 202 | entry->partnum = -1; |
| 203 | LWARNING << "Warning: voldmanaged= flag malformed: " << arg; |
| 204 | continue; |
| 205 | } |
| 206 | } |
| 207 | } else if (StartsWith(flag, "length=")) { |
| 208 | // The length flag is followed by an = and the size of the partition. |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 209 | if (!ParseInt(arg, &entry->length)) { |
| 210 | LWARNING << "Warning: length= flag malformed: " << arg; |
| 211 | } |
| 212 | } else if (StartsWith(flag, "swapprio=")) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 213 | if (!ParseInt(arg, &entry->swap_prio)) { |
Felix | 92ed423 | 2020-02-12 17:29:54 +0100 | [diff] [blame] | 214 | LWARNING << "Warning: swapprio= flag malformed: " << arg; |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 215 | } |
| 216 | } else if (StartsWith(flag, "zramsize=")) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 217 | if (!arg.empty() && arg.back() == '%') { |
| 218 | arg.pop_back(); |
| 219 | int val; |
| 220 | if (ParseInt(arg, &val, 0, 100)) { |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 221 | entry->zram_size = CalculateZramSize(val); |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 222 | } else { |
| 223 | LWARNING << "Warning: zramsize= flag malformed: " << arg; |
| 224 | } |
| 225 | } else { |
| 226 | if (!ParseInt(arg, &entry->zram_size)) { |
| 227 | LWARNING << "Warning: zramsize= flag malformed: " << arg; |
| 228 | } |
| 229 | } |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 230 | } else if (StartsWith(flag, "forceencrypt=")) { |
| 231 | // The forceencrypt flag is followed by an = and the location of the keys. |
| 232 | entry->fs_mgr_flags.force_crypt = true; |
| 233 | entry->key_loc = arg; |
| 234 | } else if (StartsWith(flag, "fileencryption=")) { |
| 235 | ParseFileEncryption(arg, entry); |
| 236 | } else if (StartsWith(flag, "forcefdeorfbe=")) { |
| 237 | // The forcefdeorfbe flag is followed by an = and the location of the keys. Get it and |
| 238 | // return it. |
| 239 | entry->fs_mgr_flags.force_fde_or_fbe = true; |
| 240 | entry->key_loc = arg; |
Paul Crowley | 4fa8413 | 2019-10-24 23:33:12 -0700 | [diff] [blame] | 241 | entry->encryption_options = "aes-256-xts:aes-256-cts"; |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 242 | } else if (StartsWith(flag, "max_comp_streams=")) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 243 | if (!ParseInt(arg, &entry->max_comp_streams)) { |
| 244 | LWARNING << "Warning: max_comp_streams= flag malformed: " << arg; |
| 245 | } |
| 246 | } else if (StartsWith(flag, "reservedsize=")) { |
| 247 | // The reserved flag is followed by an = and the reserved size of the partition. |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 248 | uint64_t size; |
| 249 | if (!ParseByteCount(arg, &size)) { |
| 250 | LWARNING << "Warning: reservedsize= flag malformed: " << arg; |
| 251 | } else { |
| 252 | entry->reserved_size = static_cast<off64_t>(size); |
| 253 | } |
| 254 | } else if (StartsWith(flag, "eraseblk=")) { |
| 255 | // The erase block size flag is followed by an = and the flash erase block size. Get it, |
| 256 | // check that it is a power of 2 and at least 4096, and return it. |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 257 | off64_t val; |
| 258 | if (!ParseInt(arg, &val) || val < 4096 || (val & (val - 1)) != 0) { |
| 259 | LWARNING << "Warning: eraseblk= flag malformed: " << arg; |
| 260 | } else { |
| 261 | entry->erase_blk_size = val; |
| 262 | } |
| 263 | } else if (StartsWith(flag, "logicalblk=")) { |
| 264 | // The logical block size flag is followed by an = and the flash logical block size. Get |
| 265 | // it, check that it is a power of 2 and at least 4096, and return it. |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 266 | off64_t val; |
| 267 | if (!ParseInt(arg, &val) || val < 4096 || (val & (val - 1)) != 0) { |
| 268 | LWARNING << "Warning: logicalblk= flag malformed: " << arg; |
| 269 | } else { |
| 270 | entry->logical_blk_size = val; |
| 271 | } |
Bowgo Tsai | d214b40 | 2019-02-27 22:56:56 +0800 | [diff] [blame] | 272 | } else if (StartsWith(flag, "avb_keys=")) { // must before the following "avb" |
| 273 | entry->avb_keys = arg; |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 274 | } else if (StartsWith(flag, "avb")) { |
| 275 | entry->fs_mgr_flags.avb = true; |
| 276 | entry->vbmeta_partition = arg; |
| 277 | } else if (StartsWith(flag, "keydirectory=")) { |
| 278 | // The metadata flag is followed by an = and the directory for the keys. |
Paul Crowley | 7823e32 | 2020-01-30 16:03:45 -0800 | [diff] [blame] | 279 | entry->metadata_key_dir = arg; |
Barani Muthukumaran | 2ca1d83 | 2020-02-06 23:46:37 -0800 | [diff] [blame^] | 280 | } else if (StartsWith(flag, "metadata_encryption=")) { |
| 281 | // Specify the cipher and flags to use for metadata encryption |
| 282 | entry->metadata_encryption = arg; |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 283 | } else if (StartsWith(flag, "sysfs_path=")) { |
| 284 | // The path to trigger device gc by idle-maint of vold. |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 285 | entry->sysfs_path = arg; |
| 286 | } else if (StartsWith(flag, "zram_loopback_path=")) { |
| 287 | // The path to use loopback for zram. |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 288 | entry->zram_loopback_path = arg; |
| 289 | } else if (StartsWith(flag, "zram_loopback_size=")) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 290 | if (!ParseByteCount(arg, &entry->zram_loopback_size)) { |
| 291 | LWARNING << "Warning: zram_loopback_size= flag malformed: " << arg; |
| 292 | } |
| 293 | } else if (StartsWith(flag, "zram_backing_dev_path=")) { |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 294 | entry->zram_backing_dev_path = arg; |
| 295 | } else { |
| 296 | LWARNING << "Warning: unknown flag: " << flag; |
| 297 | } |
| 298 | } |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 299 | } |
| 300 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 301 | std::string InitAndroidDtDir() { |
Yu Ning | c01022a | 2017-07-26 17:54:08 +0800 | [diff] [blame] | 302 | std::string android_dt_dir; |
| 303 | // The platform may specify a custom Android DT path in kernel cmdline |
| 304 | if (!fs_mgr_get_boot_config_from_kernel_cmdline("android_dt_dir", &android_dt_dir)) { |
| 305 | // Fall back to the standard procfs-based path |
| 306 | android_dt_dir = kDefaultAndroidDtDir; |
| 307 | } |
| 308 | return android_dt_dir; |
| 309 | } |
| 310 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 311 | bool IsDtFstabCompatible() { |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 312 | std::string dt_value; |
Yu Ning | c01022a | 2017-07-26 17:54:08 +0800 | [diff] [blame] | 313 | std::string file_name = get_android_dt_dir() + "/fstab/compatible"; |
Tom Cherry | 1eb0456 | 2018-11-06 10:42:09 -0800 | [diff] [blame] | 314 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 315 | if (ReadDtFile(file_name, &dt_value) && dt_value == "android,fstab") { |
Tom Cherry | 1eb0456 | 2018-11-06 10:42:09 -0800 | [diff] [blame] | 316 | // If there's no status property or its set to "ok" or "okay", then we use the DT fstab. |
| 317 | std::string status_value; |
| 318 | std::string status_file_name = get_android_dt_dir() + "/fstab/status"; |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 319 | return !ReadDtFile(status_file_name, &status_value) || status_value == "ok" || |
Tom Cherry | 1eb0456 | 2018-11-06 10:42:09 -0800 | [diff] [blame] | 320 | status_value == "okay"; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 321 | } |
| 322 | |
| 323 | return false; |
| 324 | } |
| 325 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 326 | std::string ReadFstabFromDt() { |
| 327 | if (!is_dt_compatible() || !IsDtFstabCompatible()) { |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 328 | return {}; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 329 | } |
| 330 | |
Yu Ning | c01022a | 2017-07-26 17:54:08 +0800 | [diff] [blame] | 331 | std::string fstabdir_name = get_android_dt_dir() + "/fstab"; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 332 | std::unique_ptr<DIR, int (*)(DIR*)> fstabdir(opendir(fstabdir_name.c_str()), closedir); |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 333 | if (!fstabdir) return {}; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 334 | |
| 335 | dirent* dp; |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 336 | // Each element in fstab_dt_entries is <mount point, the line format in fstab file>. |
| 337 | std::vector<std::pair<std::string, std::string>> fstab_dt_entries; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 338 | while ((dp = readdir(fstabdir.get())) != NULL) { |
Bowgo Tsai | 06ed613 | 2017-06-08 10:43:41 +0800 | [diff] [blame] | 339 | // skip over name, compatible and . |
| 340 | if (dp->d_type != DT_DIR || dp->d_name[0] == '.') continue; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 341 | |
| 342 | // create <dev> <mnt_point> <type> <mnt_flags> <fsmgr_flags>\n |
| 343 | std::vector<std::string> fstab_entry; |
| 344 | std::string file_name; |
| 345 | std::string value; |
Sandeep Patil | be4302b | 2017-05-26 18:09:06 -0700 | [diff] [blame] | 346 | // skip a partition entry if the status property is present and not set to ok |
| 347 | file_name = android::base::StringPrintf("%s/%s/status", fstabdir_name.c_str(), dp->d_name); |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 348 | if (ReadDtFile(file_name, &value)) { |
Sandeep Patil | be4302b | 2017-05-26 18:09:06 -0700 | [diff] [blame] | 349 | if (value != "okay" && value != "ok") { |
| 350 | LINFO << "dt_fstab: Skip disabled entry for partition " << dp->d_name; |
| 351 | continue; |
| 352 | } |
| 353 | } |
| 354 | |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 355 | file_name = android::base::StringPrintf("%s/%s/dev", fstabdir_name.c_str(), dp->d_name); |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 356 | if (!ReadDtFile(file_name, &value)) { |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 357 | LERROR << "dt_fstab: Failed to find device for partition " << dp->d_name; |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 358 | return {}; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 359 | } |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 360 | fstab_entry.push_back(value); |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 361 | |
| 362 | std::string mount_point; |
| 363 | file_name = |
| 364 | android::base::StringPrintf("%s/%s/mnt_point", fstabdir_name.c_str(), dp->d_name); |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 365 | if (ReadDtFile(file_name, &value)) { |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 366 | LINFO << "dt_fstab: Using a specified mount point " << value << " for " << dp->d_name; |
| 367 | mount_point = value; |
| 368 | } else { |
| 369 | mount_point = android::base::StringPrintf("/%s", dp->d_name); |
| 370 | } |
| 371 | fstab_entry.push_back(mount_point); |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 372 | |
| 373 | file_name = android::base::StringPrintf("%s/%s/type", fstabdir_name.c_str(), dp->d_name); |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 374 | if (!ReadDtFile(file_name, &value)) { |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 375 | LERROR << "dt_fstab: Failed to find type for partition " << dp->d_name; |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 376 | return {}; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 377 | } |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 378 | fstab_entry.push_back(value); |
| 379 | |
| 380 | file_name = android::base::StringPrintf("%s/%s/mnt_flags", fstabdir_name.c_str(), dp->d_name); |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 381 | if (!ReadDtFile(file_name, &value)) { |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 382 | LERROR << "dt_fstab: Failed to find type for partition " << dp->d_name; |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 383 | return {}; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 384 | } |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 385 | fstab_entry.push_back(value); |
| 386 | |
| 387 | file_name = android::base::StringPrintf("%s/%s/fsmgr_flags", fstabdir_name.c_str(), dp->d_name); |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 388 | if (!ReadDtFile(file_name, &value)) { |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 389 | LERROR << "dt_fstab: Failed to find type for partition " << dp->d_name; |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 390 | return {}; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 391 | } |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 392 | fstab_entry.push_back(value); |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 393 | // Adds a fstab_entry to fstab_dt_entries, to be sorted by mount_point later. |
| 394 | fstab_dt_entries.emplace_back(mount_point, android::base::Join(fstab_entry, " ")); |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 395 | } |
| 396 | |
Bowgo Tsai | 8028189 | 2017-12-12 00:47:43 +0800 | [diff] [blame] | 397 | // Sort fstab_dt entries, to ensure /vendor is mounted before /vendor/abc is attempted. |
| 398 | std::sort(fstab_dt_entries.begin(), fstab_dt_entries.end(), |
| 399 | [](const auto& a, const auto& b) { return a.first < b.first; }); |
| 400 | |
| 401 | std::string fstab_result; |
| 402 | for (const auto& [_, dt_entry] : fstab_dt_entries) { |
| 403 | fstab_result += dt_entry + "\n"; |
| 404 | } |
| 405 | return fstab_result; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 406 | } |
| 407 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 408 | // Identify path to fstab file. Lookup is based on pattern fstab.<hardware>, |
| 409 | // fstab.<hardware.platform> in folders /odm/etc, vendor/etc, or /. |
| 410 | std::string GetFstabPath() { |
| 411 | for (const char* prop : {"hardware", "hardware.platform"}) { |
| 412 | std::string hw; |
| 413 | |
| 414 | if (!fs_mgr_get_boot_config(prop, &hw)) continue; |
| 415 | |
| 416 | for (const char* prefix : {"/odm/etc/fstab.", "/vendor/etc/fstab.", "/fstab."}) { |
| 417 | std::string fstab_path = prefix + hw; |
| 418 | if (access(fstab_path.c_str(), F_OK) == 0) { |
| 419 | return fstab_path; |
| 420 | } |
Sandeep Patil | e396c60 | 2017-02-24 11:04:49 -0800 | [diff] [blame] | 421 | } |
| 422 | } |
| 423 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 424 | return ""; |
Sandeep Patil | e396c60 | 2017-02-24 11:04:49 -0800 | [diff] [blame] | 425 | } |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 426 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 427 | bool ReadFstabFile(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out) { |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 428 | ssize_t len; |
| 429 | size_t alloc_len = 0; |
| 430 | char *line = NULL; |
| 431 | const char *delim = " \t"; |
| 432 | char *save_ptr, *p; |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 433 | Fstab fstab; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 434 | |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 435 | while ((len = getline(&line, &alloc_len, fstab_file)) != -1) { |
| 436 | /* if the last character is a newline, shorten the string by 1 byte */ |
| 437 | if (line[len - 1] == '\n') { |
| 438 | line[len - 1] = '\0'; |
| 439 | } |
| 440 | |
| 441 | /* Skip any leading whitespace */ |
| 442 | p = line; |
| 443 | while (isspace(*p)) { |
| 444 | p++; |
| 445 | } |
| 446 | /* ignore comments or empty lines */ |
| 447 | if (*p == '#' || *p == '\0') |
| 448 | continue; |
| 449 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 450 | FstabEntry entry; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 451 | |
| 452 | if (!(p = strtok_r(line, delim, &save_ptr))) { |
bowgotsai | 47878de | 2017-01-23 14:04:34 +0800 | [diff] [blame] | 453 | LERROR << "Error parsing mount source"; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 454 | goto err; |
| 455 | } |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 456 | entry.blk_device = p; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 457 | |
| 458 | if (!(p = strtok_r(NULL, delim, &save_ptr))) { |
bowgotsai | 47878de | 2017-01-23 14:04:34 +0800 | [diff] [blame] | 459 | LERROR << "Error parsing mount_point"; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 460 | goto err; |
| 461 | } |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 462 | entry.mount_point = p; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 463 | |
| 464 | if (!(p = strtok_r(NULL, delim, &save_ptr))) { |
bowgotsai | 47878de | 2017-01-23 14:04:34 +0800 | [diff] [blame] | 465 | LERROR << "Error parsing fs_type"; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 466 | goto err; |
| 467 | } |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 468 | entry.fs_type = p; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 469 | |
| 470 | if (!(p = strtok_r(NULL, delim, &save_ptr))) { |
bowgotsai | 47878de | 2017-01-23 14:04:34 +0800 | [diff] [blame] | 471 | LERROR << "Error parsing mount_flags"; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 472 | goto err; |
| 473 | } |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 474 | |
| 475 | ParseMountFlags(p, &entry); |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 476 | |
Mark Salyzyn | eba4706 | 2018-06-20 13:07:28 -0700 | [diff] [blame] | 477 | // For /proc/mounts, ignore everything after mnt_freq and mnt_passno |
| 478 | if (proc_mounts) { |
| 479 | p += strlen(p); |
| 480 | } else if (!(p = strtok_r(NULL, delim, &save_ptr))) { |
bowgotsai | 47878de | 2017-01-23 14:04:34 +0800 | [diff] [blame] | 481 | LERROR << "Error parsing fs_mgr_options"; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 482 | goto err; |
| 483 | } |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 484 | |
Tom Cherry | 151a17f | 2019-01-21 16:49:13 -0800 | [diff] [blame] | 485 | ParseFsMgrFlags(p, &entry); |
| 486 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 487 | if (entry.fs_mgr_flags.logical) { |
| 488 | entry.logical_partition_name = entry.blk_device; |
David Anderson | 62e5b20 | 2018-05-01 17:09:17 -0700 | [diff] [blame] | 489 | } |
| 490 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 491 | fstab.emplace_back(std::move(entry)); |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 492 | } |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 493 | |
| 494 | if (fstab.empty()) { |
| 495 | LERROR << "No entries found in fstab"; |
| 496 | goto err; |
| 497 | } |
| 498 | |
David Zeuthen | 227ef3c | 2015-09-03 12:23:12 -0400 | [diff] [blame] | 499 | /* If an A/B partition, modify block device to be the real block device */ |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 500 | if (!fs_mgr_update_for_slotselect(&fstab)) { |
bowgotsai | 47878de | 2017-01-23 14:04:34 +0800 | [diff] [blame] | 501 | LERROR << "Error updating for slotselect"; |
David Zeuthen | 744a8f8 | 2015-09-09 18:03:13 -0400 | [diff] [blame] | 502 | goto err; |
David Zeuthen | 227ef3c | 2015-09-03 12:23:12 -0400 | [diff] [blame] | 503 | } |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 504 | free(line); |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 505 | *fstab_out = std::move(fstab); |
| 506 | return true; |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 507 | |
| 508 | err: |
Colin Cross | 5edee2a | 2014-01-23 14:22:48 -0800 | [diff] [blame] | 509 | free(line); |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 510 | return false; |
Bowgo Tsai | 47d3427 | 2017-03-06 22:22:07 +0800 | [diff] [blame] | 511 | } |
| 512 | |
Bowgo Tsai | 8eec38f | 2018-05-16 18:33:44 +0800 | [diff] [blame] | 513 | /* Extracts <device>s from the by-name symlinks specified in a fstab: |
| 514 | * /dev/block/<type>/<device>/by-name/<partition> |
| 515 | * |
| 516 | * <type> can be: platform, pci or vbd. |
| 517 | * |
| 518 | * For example, given the following entries in the input fstab: |
| 519 | * /dev/block/platform/soc/1da4000.ufshc/by-name/system |
| 520 | * /dev/block/pci/soc.0/f9824900.sdhci/by-name/vendor |
| 521 | * it returns a set { "soc/1da4000.ufshc", "soc.0/f9824900.sdhci" }. |
| 522 | */ |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 523 | std::set<std::string> ExtraBootDevices(const Fstab& fstab) { |
Bowgo Tsai | 8eec38f | 2018-05-16 18:33:44 +0800 | [diff] [blame] | 524 | std::set<std::string> boot_devices; |
| 525 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 526 | for (const auto& entry : fstab) { |
| 527 | std::string blk_device = entry.blk_device; |
Bowgo Tsai | 8eec38f | 2018-05-16 18:33:44 +0800 | [diff] [blame] | 528 | // Skips blk_device that doesn't conform to the format. |
| 529 | if (!android::base::StartsWith(blk_device, "/dev/block") || |
| 530 | android::base::StartsWith(blk_device, "/dev/block/by-name") || |
| 531 | android::base::StartsWith(blk_device, "/dev/block/bootdevice/by-name")) { |
| 532 | continue; |
| 533 | } |
| 534 | // Skips non-by_name blk_device. |
| 535 | // /dev/block/<type>/<device>/by-name/<partition> |
| 536 | // ^ slash_by_name |
| 537 | auto slash_by_name = blk_device.find("/by-name"); |
| 538 | if (slash_by_name == std::string::npos) continue; |
| 539 | blk_device.erase(slash_by_name); // erases /by-name/<partition> |
| 540 | |
| 541 | // Erases /dev/block/, now we have <type>/<device> |
| 542 | blk_device.erase(0, std::string("/dev/block/").size()); |
| 543 | |
| 544 | // <type>/<device> |
| 545 | // ^ first_slash |
| 546 | auto first_slash = blk_device.find('/'); |
| 547 | if (first_slash == std::string::npos) continue; |
| 548 | |
| 549 | auto boot_device = blk_device.substr(first_slash + 1); |
| 550 | if (!boot_device.empty()) boot_devices.insert(std::move(boot_device)); |
| 551 | } |
| 552 | |
| 553 | return boot_devices; |
| 554 | } |
| 555 | |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 556 | FstabEntry BuildDsuUserdataFstabEntry() { |
David Anderson | 0e330f1 | 2019-01-03 18:16:56 -0800 | [diff] [blame] | 557 | constexpr uint32_t kFlags = MS_NOATIME | MS_NOSUID | MS_NODEV; |
| 558 | |
| 559 | FstabEntry userdata = { |
| 560 | .blk_device = "userdata_gsi", |
| 561 | .mount_point = "/data", |
| 562 | .fs_type = "ext4", |
| 563 | .flags = kFlags, |
| 564 | .reserved_size = 128 * 1024 * 1024, |
| 565 | }; |
| 566 | userdata.fs_mgr_flags.wait = true; |
| 567 | userdata.fs_mgr_flags.check = true; |
| 568 | userdata.fs_mgr_flags.logical = true; |
| 569 | userdata.fs_mgr_flags.quota = true; |
| 570 | userdata.fs_mgr_flags.late_mount = true; |
David Anderson | c45026f | 2019-01-24 11:45:52 -0800 | [diff] [blame] | 571 | userdata.fs_mgr_flags.formattable = true; |
David Anderson | 88045ae | 2019-02-04 19:02:19 -0800 | [diff] [blame] | 572 | return userdata; |
| 573 | } |
| 574 | |
Bowgo Tsai | 9bbaa7b | 2019-02-18 17:56:58 +0800 | [diff] [blame] | 575 | bool EraseFstabEntry(Fstab* fstab, const std::string& mount_point) { |
David Anderson | 88045ae | 2019-02-04 19:02:19 -0800 | [diff] [blame] | 576 | auto iter = std::remove_if(fstab->begin(), fstab->end(), |
| 577 | [&](const auto& entry) { return entry.mount_point == mount_point; }); |
Bowgo Tsai | 9bbaa7b | 2019-02-18 17:56:58 +0800 | [diff] [blame] | 578 | if (iter != fstab->end()) { |
| 579 | fstab->erase(iter, fstab->end()); |
| 580 | return true; |
| 581 | } |
| 582 | return false; |
David Anderson | 88045ae | 2019-02-04 19:02:19 -0800 | [diff] [blame] | 583 | } |
| 584 | |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 585 | } // namespace |
| 586 | |
| 587 | void TransformFstabForDsu(Fstab* fstab, const std::vector<std::string>& dsu_partitions) { |
Bowgo Tsai | a2ac846 | 2019-12-11 15:03:00 +0800 | [diff] [blame] | 588 | static constexpr char kDsuKeysDir[] = "/avb"; |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 589 | // Convert userdata |
David Anderson | 88045ae | 2019-02-04 19:02:19 -0800 | [diff] [blame] | 590 | // Inherit fstab properties for userdata. |
| 591 | FstabEntry userdata; |
| 592 | if (FstabEntry* entry = GetEntryForMountPoint(fstab, "/data")) { |
| 593 | userdata = *entry; |
| 594 | userdata.blk_device = "userdata_gsi"; |
| 595 | userdata.fs_mgr_flags.logical = true; |
| 596 | userdata.fs_mgr_flags.formattable = true; |
Paul Crowley | 7823e32 | 2020-01-30 16:03:45 -0800 | [diff] [blame] | 597 | if (!userdata.metadata_key_dir.empty()) { |
| 598 | userdata.metadata_key_dir += "/gsi"; |
David Anderson | 88045ae | 2019-02-04 19:02:19 -0800 | [diff] [blame] | 599 | } |
| 600 | } else { |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 601 | userdata = BuildDsuUserdataFstabEntry(); |
Bowgo Tsai | 9bbaa7b | 2019-02-18 17:56:58 +0800 | [diff] [blame] | 602 | } |
David Anderson | 88045ae | 2019-02-04 19:02:19 -0800 | [diff] [blame] | 603 | |
Bowgo Tsai | 9bbaa7b | 2019-02-18 17:56:58 +0800 | [diff] [blame] | 604 | if (EraseFstabEntry(fstab, "/data")) { |
| 605 | fstab->emplace_back(userdata); |
| 606 | } |
David Anderson | 0e330f1 | 2019-01-03 18:16:56 -0800 | [diff] [blame] | 607 | |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 608 | // Convert others |
| 609 | for (auto&& partition : dsu_partitions) { |
| 610 | if (!EndsWith(partition, gsi::kDsuPostfix)) { |
| 611 | continue; |
| 612 | } |
| 613 | // userdata has been handled |
| 614 | if (StartsWith(partition, "user")) { |
| 615 | continue; |
| 616 | } |
| 617 | // dsu_partition_name = corresponding_partition_name + kDsuPostfix |
| 618 | // e.g. |
| 619 | // system_gsi for system |
| 620 | // product_gsi for product |
| 621 | // vendor_gsi for vendor |
| 622 | std::string lp_name = partition.substr(0, partition.length() - strlen(gsi::kDsuPostfix)); |
| 623 | std::string mount_point = "/" + lp_name; |
| 624 | std::vector<FstabEntry*> entries = GetEntriesForMountPoint(fstab, mount_point); |
| 625 | if (entries.empty()) { |
| 626 | FstabEntry entry = { |
| 627 | .blk_device = partition, |
Nick Desaulniers | 8e04844 | 2019-10-11 10:52:16 -0700 | [diff] [blame] | 628 | // .logical_partition_name is required to look up AVB Hashtree descriptors. |
| 629 | .logical_partition_name = "system", |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 630 | .mount_point = mount_point, |
| 631 | .fs_type = "ext4", |
| 632 | .flags = MS_RDONLY, |
| 633 | .fs_options = "barrier=1", |
Bowgo Tsai | a2ac846 | 2019-12-11 15:03:00 +0800 | [diff] [blame] | 634 | .avb_keys = kDsuKeysDir, |
Nick Desaulniers | 8e04844 | 2019-10-11 10:52:16 -0700 | [diff] [blame] | 635 | }; |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 636 | entry.fs_mgr_flags.wait = true; |
| 637 | entry.fs_mgr_flags.logical = true; |
| 638 | entry.fs_mgr_flags.first_stage_mount = true; |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 639 | } else { |
| 640 | // If the corresponding partition exists, transform all its Fstab |
| 641 | // by pointing .blk_device to the DSU partition. |
| 642 | for (auto&& entry : entries) { |
| 643 | entry->blk_device = partition; |
Bowgo Tsai | a2ac846 | 2019-12-11 15:03:00 +0800 | [diff] [blame] | 644 | // AVB keys for DSU should always be under kDsuKeysDir. |
| 645 | entry->avb_keys += kDsuKeysDir; |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 646 | } |
| 647 | // Make sure the ext4 is included to support GSI. |
| 648 | auto partition_ext4 = |
| 649 | std::find_if(fstab->begin(), fstab->end(), [&](const auto& entry) { |
| 650 | return entry.mount_point == mount_point && entry.fs_type == "ext4"; |
| 651 | }); |
| 652 | if (partition_ext4 == fstab->end()) { |
| 653 | auto new_entry = *GetEntryForMountPoint(fstab, mount_point); |
| 654 | new_entry.fs_type = "ext4"; |
| 655 | fstab->emplace_back(new_entry); |
| 656 | } |
| 657 | } |
| 658 | } |
| 659 | } |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 660 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 661 | bool ReadFstabFromFile(const std::string& path, Fstab* fstab) { |
| 662 | auto fstab_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose}; |
Hung-ying Tyan | 99c4a8a | 2016-02-01 15:07:40 +0800 | [diff] [blame] | 663 | if (!fstab_file) { |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 664 | PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'"; |
| 665 | return false; |
| 666 | } |
| 667 | |
David Anderson | 0e330f1 | 2019-01-03 18:16:56 -0800 | [diff] [blame] | 668 | bool is_proc_mounts = path == "/proc/mounts"; |
| 669 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 670 | if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, fstab)) { |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 671 | LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'"; |
| 672 | return false; |
| 673 | } |
David Anderson | 0e330f1 | 2019-01-03 18:16:56 -0800 | [diff] [blame] | 674 | if (!is_proc_mounts && !access(android::gsi::kGsiBootedIndicatorFile, F_OK)) { |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 675 | std::string lp_names; |
| 676 | ReadFileToString(gsi::kGsiLpNamesFile, &lp_names); |
| 677 | TransformFstabForDsu(fstab, Split(lp_names, ",")); |
David Anderson | 0e330f1 | 2019-01-03 18:16:56 -0800 | [diff] [blame] | 678 | } |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 679 | |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 680 | SkipMountingPartitions(fstab); |
| 681 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 682 | return true; |
| 683 | } |
| 684 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 685 | // Returns fstab entries parsed from the device tree if they exist |
Mark Salyzyn | f98d6c4 | 2019-01-25 09:08:42 -0800 | [diff] [blame] | 686 | bool ReadFstabFromDt(Fstab* fstab, bool log) { |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 687 | std::string fstab_buf = ReadFstabFromDt(); |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 688 | if (fstab_buf.empty()) { |
Mark Salyzyn | f98d6c4 | 2019-01-25 09:08:42 -0800 | [diff] [blame] | 689 | if (log) LINFO << __FUNCTION__ << "(): failed to read fstab from dt"; |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 690 | return false; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 691 | } |
| 692 | |
| 693 | std::unique_ptr<FILE, decltype(&fclose)> fstab_file( |
| 694 | fmemopen(static_cast<void*>(const_cast<char*>(fstab_buf.c_str())), |
| 695 | fstab_buf.length(), "r"), fclose); |
| 696 | if (!fstab_file) { |
Mark Salyzyn | f98d6c4 | 2019-01-25 09:08:42 -0800 | [diff] [blame] | 697 | if (log) PERROR << __FUNCTION__ << "(): failed to create a file stream for fstab dt"; |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 698 | return false; |
| 699 | } |
| 700 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 701 | if (!ReadFstabFile(fstab_file.get(), false, fstab)) { |
Mark Salyzyn | f98d6c4 | 2019-01-25 09:08:42 -0800 | [diff] [blame] | 702 | if (log) { |
| 703 | LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl |
| 704 | << fstab_buf; |
| 705 | } |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 706 | return false; |
| 707 | } |
| 708 | |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 709 | SkipMountingPartitions(fstab); |
| 710 | |
| 711 | return true; |
| 712 | } |
| 713 | |
Justin Yun | 7eaf9b5 | 2019-06-28 14:28:00 +0900 | [diff] [blame] | 714 | // For GSI to skip mounting /product and /system_ext, until there are well-defined interfaces |
| 715 | // between them and /system. Otherwise, the GSI flashed on /system might not be able to work with |
Bowgo Tsai | 79f875f | 2019-09-04 22:20:01 +0800 | [diff] [blame] | 716 | // device-specific /product and /system_ext. skip_mount.cfg belongs to system_ext partition because |
| 717 | // only common files for all targets can be put into system partition. It is under |
| 718 | // /system/system_ext because GSI is a single system.img that includes the contents of system_ext |
| 719 | // partition and product partition under /system/system_ext and /system/product, respectively. |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 720 | bool SkipMountingPartitions(Fstab* fstab) { |
Bowgo Tsai | 79f875f | 2019-09-04 22:20:01 +0800 | [diff] [blame] | 721 | constexpr const char kSkipMountConfig[] = "/system/system_ext/etc/init/config/skip_mount.cfg"; |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 722 | |
| 723 | std::string skip_config; |
Mark Salyzyn | 98a0128 | 2019-05-13 09:14:55 -0700 | [diff] [blame] | 724 | auto save_errno = errno; |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 725 | if (!ReadFileToString(kSkipMountConfig, &skip_config)) { |
Mark Salyzyn | 98a0128 | 2019-05-13 09:14:55 -0700 | [diff] [blame] | 726 | errno = save_errno; // missing file is expected |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 727 | return true; |
| 728 | } |
| 729 | |
| 730 | for (const auto& skip_mount_point : Split(skip_config, "\n")) { |
| 731 | if (skip_mount_point.empty()) { |
| 732 | continue; |
| 733 | } |
| 734 | auto it = std::remove_if(fstab->begin(), fstab->end(), |
| 735 | [&skip_mount_point](const auto& entry) { |
| 736 | return entry.mount_point == skip_mount_point; |
| 737 | }); |
bohu | 42b28b0 | 2019-06-05 10:53:43 -0700 | [diff] [blame] | 738 | if (it == fstab->end()) continue; |
SzuWei Lin | 77c2847 | 2019-04-22 17:27:52 +0800 | [diff] [blame] | 739 | fstab->erase(it, fstab->end()); |
| 740 | LOG(INFO) << "Skip mounting partition: " << skip_mount_point; |
| 741 | } |
| 742 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 743 | return true; |
| 744 | } |
| 745 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 746 | // Loads the fstab file and combines with fstab entries passed in from device tree. |
| 747 | bool ReadDefaultFstab(Fstab* fstab) { |
| 748 | Fstab dt_fstab; |
Mark Salyzyn | f98d6c4 | 2019-01-25 09:08:42 -0800 | [diff] [blame] | 749 | ReadFstabFromDt(&dt_fstab, false); |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 750 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 751 | *fstab = std::move(dt_fstab); |
| 752 | |
| 753 | std::string default_fstab_path; |
Bowgo Tsai | d05a2f7 | 2017-03-28 01:28:29 +0800 | [diff] [blame] | 754 | // Use different fstab paths for normal boot and recovery boot, respectively |
Jerry Zhang | 5de2be5 | 2018-07-03 16:03:28 -0700 | [diff] [blame] | 755 | if (access("/system/bin/recovery", F_OK) == 0) { |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 756 | default_fstab_path = "/etc/recovery.fstab"; |
Oleg Matcovschi | 018d7f6 | 2017-10-30 19:26:32 -0700 | [diff] [blame] | 757 | } else { // normal boot |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 758 | default_fstab_path = GetFstabPath(); |
Oleg Matcovschi | 018d7f6 | 2017-10-30 19:26:32 -0700 | [diff] [blame] | 759 | } |
| 760 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 761 | Fstab default_fstab; |
| 762 | if (!default_fstab_path.empty()) { |
| 763 | ReadFstabFromFile(default_fstab_path, &default_fstab); |
Chris Morin | b183e05 | 2018-01-04 17:59:45 -0800 | [diff] [blame] | 764 | } else { |
| 765 | LINFO << __FUNCTION__ << "(): failed to find device default fstab"; |
Sandeep Patil | c20c0c2 | 2017-02-23 16:09:34 -0800 | [diff] [blame] | 766 | } |
| 767 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 768 | for (auto&& entry : default_fstab) { |
| 769 | fstab->emplace_back(std::move(entry)); |
| 770 | } |
Chris Morin | b183e05 | 2018-01-04 17:59:45 -0800 | [diff] [blame] | 771 | |
Tom Cherry | d0be7a5 | 2018-11-29 13:04:52 -0800 | [diff] [blame] | 772 | return !fstab->empty(); |
| 773 | } |
| 774 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 775 | FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string& path) { |
| 776 | if (fstab == nullptr) { |
| 777 | return nullptr; |
| 778 | } |
| 779 | |
| 780 | for (auto& entry : *fstab) { |
| 781 | if (entry.mount_point == path) { |
| 782 | return &entry; |
| 783 | } |
| 784 | } |
| 785 | |
| 786 | return nullptr; |
| 787 | } |
| 788 | |
Howard Chen | 1b09493 | 2019-09-11 18:22:01 +0800 | [diff] [blame] | 789 | std::vector<FstabEntry*> GetEntriesForMountPoint(Fstab* fstab, const std::string& path) { |
| 790 | std::vector<FstabEntry*> entries; |
| 791 | if (fstab == nullptr) { |
| 792 | return entries; |
| 793 | } |
| 794 | |
| 795 | for (auto& entry : *fstab) { |
| 796 | if (entry.mount_point == path) { |
| 797 | entries.emplace_back(&entry); |
| 798 | } |
| 799 | } |
| 800 | |
| 801 | return entries; |
| 802 | } |
| 803 | |
Nikita Ioffe | 5110628 | 2019-11-21 21:40:03 +0000 | [diff] [blame] | 804 | static std::string ResolveBlockDevice(const std::string& block_device) { |
| 805 | if (!StartsWith(block_device, "/dev/block/")) { |
| 806 | LWARNING << block_device << " is not a block device"; |
| 807 | return block_device; |
| 808 | } |
| 809 | std::string name = block_device.substr(5); |
| 810 | if (!StartsWith(name, "block/dm-")) { |
| 811 | // Not a dm-device, but might be a symlink. Optimistically try to readlink. |
| 812 | std::string result; |
| 813 | if (Readlink(block_device, &result)) { |
| 814 | return result; |
| 815 | } else if (errno == EINVAL) { |
| 816 | // After all, it wasn't a symlink. |
| 817 | return block_device; |
| 818 | } else { |
| 819 | LERROR << "Failed to readlink " << block_device; |
| 820 | return ""; |
| 821 | } |
| 822 | } |
| 823 | // It's a dm-device, let's find what's inside! |
| 824 | std::string sys_dir = "/sys/" + name; |
| 825 | while (true) { |
| 826 | std::string slaves_dir = sys_dir + "/slaves"; |
| 827 | std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(slaves_dir.c_str()), closedir); |
| 828 | if (!dir) { |
| 829 | LERROR << "Failed to open " << slaves_dir; |
| 830 | return ""; |
| 831 | } |
| 832 | std::string sub_device_name = ""; |
| 833 | for (auto entry = readdir(dir.get()); entry; entry = readdir(dir.get())) { |
| 834 | if (entry->d_type != DT_LNK) continue; |
| 835 | if (!sub_device_name.empty()) { |
| 836 | LERROR << "Too many slaves in " << slaves_dir; |
| 837 | return ""; |
| 838 | } |
| 839 | sub_device_name = entry->d_name; |
| 840 | } |
| 841 | if (sub_device_name.empty()) { |
| 842 | LERROR << "No slaves in " << slaves_dir; |
| 843 | return ""; |
| 844 | } |
| 845 | if (!StartsWith(sub_device_name, "dm-")) { |
| 846 | // Not a dm-device! We can stop now. |
| 847 | return "/dev/block/" + sub_device_name; |
| 848 | } |
| 849 | // Still a dm-device, keep digging. |
| 850 | sys_dir = "/sys/block/" + sub_device_name; |
| 851 | } |
| 852 | } |
| 853 | |
| 854 | FstabEntry* GetMountedEntryForUserdata(Fstab* fstab) { |
| 855 | Fstab mounts; |
| 856 | if (!ReadFstabFromFile("/proc/mounts", &mounts)) { |
| 857 | LERROR << "Failed to read /proc/mounts"; |
| 858 | return nullptr; |
| 859 | } |
| 860 | auto mounted_entry = GetEntryForMountPoint(&mounts, "/data"); |
| 861 | if (mounted_entry == nullptr) { |
| 862 | LWARNING << "/data is not mounted"; |
| 863 | return nullptr; |
| 864 | } |
| 865 | std::string resolved_block_device = ResolveBlockDevice(mounted_entry->blk_device); |
| 866 | if (resolved_block_device.empty()) { |
| 867 | return nullptr; |
| 868 | } |
| 869 | LINFO << "/data is mounted on " << resolved_block_device; |
| 870 | for (auto& entry : *fstab) { |
| 871 | if (entry.mount_point != "/data") { |
| 872 | continue; |
| 873 | } |
| 874 | std::string block_device; |
| 875 | if (!Readlink(entry.blk_device, &block_device)) { |
| 876 | LWARNING << "Failed to readlink " << entry.blk_device; |
| 877 | block_device = entry.blk_device; |
| 878 | } |
| 879 | if (block_device == resolved_block_device) { |
| 880 | return &entry; |
| 881 | } |
| 882 | } |
| 883 | LERROR << "Didn't find entry that was used to mount /data"; |
| 884 | return nullptr; |
| 885 | } |
| 886 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 887 | std::set<std::string> GetBootDevices() { |
| 888 | // First check the kernel commandline, then try the device tree otherwise |
| 889 | std::string dt_file_name = get_android_dt_dir() + "/boot_devices"; |
| 890 | std::string value; |
| 891 | if (fs_mgr_get_boot_config_from_kernel_cmdline("boot_devices", &value) || |
| 892 | ReadDtFile(dt_file_name, &value)) { |
| 893 | auto boot_devices = Split(value, ","); |
| 894 | return std::set<std::string>(boot_devices.begin(), boot_devices.end()); |
| 895 | } |
| 896 | |
| 897 | // Fallback to extract boot devices from fstab. |
| 898 | Fstab fstab; |
| 899 | if (!ReadDefaultFstab(&fstab)) { |
| 900 | return {}; |
| 901 | } |
| 902 | |
| 903 | return ExtraBootDevices(fstab); |
| 904 | } |
| 905 | |
David Anderson | 7082f68 | 2019-03-18 19:17:11 -0700 | [diff] [blame] | 906 | std::string GetVerityDeviceName(const FstabEntry& entry) { |
| 907 | std::string base_device; |
| 908 | if (entry.mount_point == "/") { |
David Anderson | 9655c7b | 2019-05-09 17:55:18 -0700 | [diff] [blame] | 909 | // When using system-as-root, the device name is fixed as "vroot". |
| 910 | if (entry.fs_mgr_flags.avb) { |
| 911 | return "vroot"; |
| 912 | } |
| 913 | base_device = "system"; |
David Anderson | 7082f68 | 2019-03-18 19:17:11 -0700 | [diff] [blame] | 914 | } else { |
| 915 | base_device = android::base::Basename(entry.mount_point); |
| 916 | } |
| 917 | return base_device + "-verity"; |
| 918 | } |
| 919 | |
Tom Cherry | a3530e6 | 2019-01-30 13:25:35 -0800 | [diff] [blame] | 920 | } // namespace fs_mgr |
| 921 | } // namespace android |
| 922 | |
| 923 | // FIXME: The same logic is duplicated in system/core/init/ |
| 924 | const std::string& get_android_dt_dir() { |
| 925 | // Set once and saves time for subsequent calls to this function |
| 926 | static const std::string kAndroidDtDir = android::fs_mgr::InitAndroidDtDir(); |
| 927 | return kAndroidDtDir; |
| 928 | } |
| 929 | |
| 930 | bool is_dt_compatible() { |
| 931 | std::string file_name = get_android_dt_dir() + "/compatible"; |
| 932 | std::string dt_value; |
| 933 | if (android::fs_mgr::ReadDtFile(file_name, &dt_value)) { |
| 934 | if (dt_value == "android,firmware") { |
| 935 | return true; |
| 936 | } |
| 937 | } |
| 938 | |
| 939 | return false; |
| 940 | } |