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