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