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 <rootdev/rootdev.h>
     26 
     27 extern "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 
     35 using std::string;
     36 
     37 namespace {
     38 
     39 const char* kChromeOSPartitionNameKernel = "kernel";
     40 const char* kChromeOSPartitionNameRoot = "root";
     41 const char* kAndroidPartitionNameKernel = "boot";
     42 const char* kAndroidPartitionNameRoot = "system";
     43 
     44 // Returns the currently booted rootfs partition. "/dev/sda3", for example.
     45 string 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.
     64 void 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 
     72 namespace chromeos_update_engine {
     73 
     74 namespace boot_control {
     75 
     76 // Factory defined in boot_control.h.
     77 std::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 
     88 bool 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 
    133 unsigned int BootControlChromeOS::GetNumSlots() const {
    134   return num_slots_;
    135 }
    136 
    137 BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
    138   return current_slot_;
    139 }
    140 
    141 bool 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 
    156 bool 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(&params, '\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(&params);
    167   if (retval != CGPT_OK)
    168     return false;
    169 
    170   return params.successful || params.tries > 0;
    171 }
    172 
    173 bool 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(&params, 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(&params);
    198   if (retval != CGPT_OK) {
    199     LOG(ERROR) << "Marking kernel unbootable failed.";
    200     return false;
    201   }
    202 
    203   return true;
    204 }
    205 
    206 bool 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 
    248 bool 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
    256 string 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
    265 bool 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 
    277 int 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