Home | History | Annotate | Download | only in update_engine
      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(&params, '\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(&params);
    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(&params, 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(&params);
    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