1 // 2 // Copyright (C) 2015 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 "update_engine/boot_control_chromeos.h" 18 19 #include <memory> 20 #include <string> 21 #include <utility> 22 23 #include <base/bind.h> 24 #include <base/files/file_path.h> 25 #include <base/files/file_util.h> 26 #include <base/strings/string_util.h> 27 #include <rootdev/rootdev.h> 28 29 extern "C" { 30 #include <vboot/vboot_host.h> 31 } 32 33 #include "update_engine/common/boot_control.h" 34 #include "update_engine/common/dynamic_partition_control_stub.h" 35 #include "update_engine/common/subprocess.h" 36 #include "update_engine/common/utils.h" 37 38 using std::string; 39 40 namespace { 41 42 const char* kChromeOSPartitionNameKernel = "kernel"; 43 const char* kChromeOSPartitionNameRoot = "root"; 44 const char* kAndroidPartitionNameKernel = "boot"; 45 const char* kAndroidPartitionNameRoot = "system"; 46 47 const char kDlcInstallRootDirectoryEncrypted[] = "/home/chronos/dlc"; 48 const char kPartitionNamePrefixDlc[] = "dlc_"; 49 const char kPartitionNameDlcA[] = "dlc_a"; 50 const char kPartitionNameDlcB[] = "dlc_b"; 51 const char kPartitionNameDlcImage[] = "dlc.img"; 52 53 // Returns the currently booted rootfs partition. "/dev/sda3", for example. 54 string GetBootDevice() { 55 char boot_path[PATH_MAX]; 56 // Resolve the boot device path fully, including dereferencing through 57 // dm-verity. 58 int ret = rootdev(boot_path, sizeof(boot_path), true, false); 59 if (ret < 0) { 60 LOG(ERROR) << "rootdev failed to find the root device"; 61 return ""; 62 } 63 LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node"; 64 65 // This local variable is used to construct the return string and is not 66 // passed around after use. 67 return boot_path; 68 } 69 70 // ExecCallback called when the execution of setgoodkernel finishes. Notifies 71 // the caller of MarkBootSuccessfullAsync() by calling |callback| with the 72 // result. 73 void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback, 74 int return_code, 75 const string& output) { 76 callback.Run(return_code == 0); 77 } 78 79 } // namespace 80 81 namespace chromeos_update_engine { 82 83 namespace boot_control { 84 85 // Factory defined in boot_control.h. 86 std::unique_ptr<BootControlInterface> CreateBootControl() { 87 std::unique_ptr<BootControlChromeOS> boot_control_chromeos( 88 new BootControlChromeOS()); 89 if (!boot_control_chromeos->Init()) { 90 LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates."; 91 } 92 return std::move(boot_control_chromeos); 93 } 94 95 } // namespace boot_control 96 97 bool BootControlChromeOS::Init() { 98 string boot_device = GetBootDevice(); 99 if (boot_device.empty()) 100 return false; 101 102 int partition_num; 103 if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num)) 104 return false; 105 106 // All installed Chrome OS devices have two slots. We don't update removable 107 // devices, so we will pretend we have only one slot in that case. 108 if (IsRemovableDevice(boot_disk_name_)) { 109 LOG(INFO) 110 << "Booted from a removable device, pretending we have only one slot."; 111 num_slots_ = 1; 112 } else { 113 // TODO(deymo): Look at the actual number of slots reported in the GPT. 114 num_slots_ = 2; 115 } 116 117 // Search through the slots to see which slot has the partition_num we booted 118 // from. This should map to one of the existing slots, otherwise something is 119 // very wrong. 120 current_slot_ = 0; 121 while (current_slot_ < num_slots_ && 122 partition_num != 123 GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) { 124 current_slot_++; 125 } 126 if (current_slot_ >= num_slots_) { 127 LOG(ERROR) << "Couldn't find the slot number corresponding to the " 128 << "partition " << boot_device << ", number of slots: " 129 << num_slots_ << ". This device is not updateable."; 130 num_slots_ = 1; 131 current_slot_ = BootControlInterface::kInvalidSlot; 132 return false; 133 } 134 135 dynamic_partition_control_.reset(new DynamicPartitionControlStub()); 136 137 LOG(INFO) << "Booted from slot " << current_slot_ << " (slot " 138 << SlotName(current_slot_) << ") of " << num_slots_ 139 << " slots present on disk " << boot_disk_name_; 140 return true; 141 } 142 143 unsigned int BootControlChromeOS::GetNumSlots() const { 144 return num_slots_; 145 } 146 147 BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const { 148 return current_slot_; 149 } 150 151 bool BootControlChromeOS::GetPartitionDevice(const string& partition_name, 152 unsigned int slot, 153 string* device) const { 154 // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module. 155 if (base::StartsWith(partition_name, 156 kPartitionNamePrefixDlc, 157 base::CompareCase::SENSITIVE)) { 158 // Extract DLC module ID from partition_name (DLC module ID is the string 159 // after |kPartitionNamePrefixDlc| in partition_name). 160 const auto dlc_module_id = 161 partition_name.substr(strlen(kPartitionNamePrefixDlc)); 162 if (dlc_module_id.empty()) { 163 LOG(ERROR) << " partition name does not contain DLC module ID:" 164 << partition_name; 165 return false; 166 } 167 *device = base::FilePath(kDlcInstallRootDirectoryEncrypted) 168 .Append(dlc_module_id) 169 .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB) 170 .Append(kPartitionNameDlcImage) 171 .value(); 172 return true; 173 } 174 int partition_num = GetPartitionNumber(partition_name, slot); 175 if (partition_num < 0) 176 return false; 177 178 string part_device = utils::MakePartitionName(boot_disk_name_, partition_num); 179 if (part_device.empty()) 180 return false; 181 182 *device = part_device; 183 return true; 184 } 185 186 bool BootControlChromeOS::IsSlotBootable(Slot slot) const { 187 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot); 188 if (partition_num < 0) 189 return false; 190 191 CgptAddParams params; 192 memset(¶ms, '\0', sizeof(params)); 193 params.drive_name = const_cast<char*>(boot_disk_name_.c_str()); 194 params.partition = partition_num; 195 196 int retval = CgptGetPartitionDetails(¶ms); 197 if (retval != CGPT_OK) 198 return false; 199 200 return params.successful || params.tries > 0; 201 } 202 203 bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) { 204 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable"; 205 206 if (slot == current_slot_) { 207 LOG(ERROR) << "Refusing to mark current slot as unbootable."; 208 return false; 209 } 210 211 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot); 212 if (partition_num < 0) 213 return false; 214 215 CgptAddParams params; 216 memset(¶ms, 0, sizeof(params)); 217 218 params.drive_name = const_cast<char*>(boot_disk_name_.c_str()); 219 params.partition = partition_num; 220 221 params.successful = false; 222 params.set_successful = true; 223 224 params.tries = 0; 225 params.set_tries = true; 226 227 int retval = CgptSetAttributes(¶ms); 228 if (retval != CGPT_OK) { 229 LOG(ERROR) << "Marking kernel unbootable failed."; 230 return false; 231 } 232 233 return true; 234 } 235 236 bool BootControlChromeOS::SetActiveBootSlot(Slot slot) { 237 LOG(INFO) << "Marking slot " << SlotName(slot) << " active."; 238 239 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot); 240 if (partition_num < 0) 241 return false; 242 243 CgptPrioritizeParams prio_params; 244 memset(&prio_params, 0, sizeof(prio_params)); 245 246 prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str()); 247 prio_params.set_partition = partition_num; 248 249 prio_params.max_priority = 0; 250 251 int retval = CgptPrioritize(&prio_params); 252 if (retval != CGPT_OK) { 253 LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot) 254 << " (partition " << partition_num << ")."; 255 return false; 256 } 257 258 CgptAddParams add_params; 259 memset(&add_params, 0, sizeof(add_params)); 260 261 add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str()); 262 add_params.partition = partition_num; 263 264 add_params.tries = 6; 265 add_params.set_tries = true; 266 267 retval = CgptSetAttributes(&add_params); 268 if (retval != CGPT_OK) { 269 LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries 270 << " for slot " << SlotName(slot) << " (partition " 271 << partition_num << ")."; 272 return false; 273 } 274 275 return true; 276 } 277 278 bool BootControlChromeOS::MarkBootSuccessfulAsync( 279 base::Callback<void(bool)> callback) { 280 return Subprocess::Get().Exec( 281 {"/usr/sbin/chromeos-setgoodkernel"}, 282 base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0; 283 } 284 285 // static 286 string BootControlChromeOS::SysfsBlockDevice(const string& device) { 287 base::FilePath device_path(device); 288 if (device_path.DirName().value() != "/dev") { 289 return ""; 290 } 291 return base::FilePath("/sys/block").Append(device_path.BaseName()).value(); 292 } 293 294 // static 295 bool BootControlChromeOS::IsRemovableDevice(const string& device) { 296 string sysfs_block = SysfsBlockDevice(device); 297 string removable; 298 if (sysfs_block.empty() || 299 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"), 300 &removable)) { 301 return false; 302 } 303 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable); 304 return removable == "1"; 305 } 306 307 int BootControlChromeOS::GetPartitionNumber( 308 const string partition_name, BootControlInterface::Slot slot) const { 309 if (slot >= num_slots_) { 310 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have " 311 << num_slots_ << " slot(s)"; 312 return -1; 313 } 314 315 // In Chrome OS, the partition numbers are hard-coded: 316 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ... 317 // To help compatibility between different we accept both lowercase and 318 // uppercase names in the ChromeOS or Brillo standard names. 319 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format 320 string partition_lower = base::ToLowerASCII(partition_name); 321 int base_part_num = 2 + 2 * slot; 322 if (partition_lower == kChromeOSPartitionNameKernel || 323 partition_lower == kAndroidPartitionNameKernel) 324 return base_part_num + 0; 325 if (partition_lower == kChromeOSPartitionNameRoot || 326 partition_lower == kAndroidPartitionNameRoot) 327 return base_part_num + 1; 328 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\""; 329 return -1; 330 } 331 332 bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const { 333 LOG(ERROR) << __func__ << " not supported."; 334 return false; 335 } 336 337 DynamicPartitionControlInterface* 338 BootControlChromeOS::GetDynamicPartitionControl() { 339 return dynamic_partition_control_.get(); 340 } 341 342 } // namespace chromeos_update_engine 343