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