You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

379 lines
12 KiB

//
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "update_engine/cros/boot_control_chromeos.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <chromeos/constants/imageloader.h>
#include <rootdev/rootdev.h>
extern "C" {
#include <vboot/vboot_host.h>
}
#include "update_engine/common/boot_control.h"
#include "update_engine/common/dynamic_partition_control_stub.h"
#include "update_engine/common/subprocess.h"
#include "update_engine/common/utils.h"
using std::string;
using std::vector;
namespace {
const char* kChromeOSPartitionNameKernel = "kernel";
const char* kChromeOSPartitionNameRoot = "root";
const char* kAndroidPartitionNameKernel = "boot";
const char* kAndroidPartitionNameRoot = "system";
const char kPartitionNamePrefixDlc[] = "dlc";
const char kPartitionNameDlcA[] = "dlc_a";
const char kPartitionNameDlcB[] = "dlc_b";
const char kPartitionNameDlcImage[] = "dlc.img";
// Returns the currently booted rootfs partition. "/dev/sda3", for example.
string GetBootDevice() {
char boot_path[PATH_MAX];
// Resolve the boot device path fully, including dereferencing through
// dm-verity.
int ret = rootdev(boot_path, sizeof(boot_path), true, false);
if (ret < 0) {
LOG(ERROR) << "rootdev failed to find the root device";
return "";
}
LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
// This local variable is used to construct the return string and is not
// passed around after use.
return boot_path;
}
// ExecCallback called when the execution of setgoodkernel finishes. Notifies
// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
// result.
void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
int return_code,
const string& output) {
callback.Run(return_code == 0);
}
} // namespace
namespace chromeos_update_engine {
namespace boot_control {
// Factory defined in boot_control.h.
std::unique_ptr<BootControlInterface> CreateBootControl() {
std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
new BootControlChromeOS());
if (!boot_control_chromeos->Init()) {
LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
}
return std::move(boot_control_chromeos);
}
} // namespace boot_control
bool BootControlChromeOS::Init() {
string boot_device = GetBootDevice();
if (boot_device.empty())
return false;
int partition_num;
if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
return false;
// All installed Chrome OS devices have two slots. We don't update removable
// devices, so we will pretend we have only one slot in that case.
if (IsRemovableDevice(boot_disk_name_)) {
LOG(INFO)
<< "Booted from a removable device, pretending we have only one slot.";
num_slots_ = 1;
} else {
// TODO(deymo): Look at the actual number of slots reported in the GPT.
num_slots_ = 2;
}
// Search through the slots to see which slot has the partition_num we booted
// from. This should map to one of the existing slots, otherwise something is
// very wrong.
current_slot_ = 0;
while (current_slot_ < num_slots_ &&
partition_num !=
GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
current_slot_++;
}
if (current_slot_ >= num_slots_) {
LOG(ERROR) << "Couldn't find the slot number corresponding to the "
<< "partition " << boot_device
<< ", number of slots: " << num_slots_
<< ". This device is not updateable.";
num_slots_ = 1;
current_slot_ = BootControlInterface::kInvalidSlot;
return false;
}
dynamic_partition_control_.reset(new DynamicPartitionControlStub());
LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
<< SlotName(current_slot_) << ") of " << num_slots_
<< " slots present on disk " << boot_disk_name_;
return true;
}
unsigned int BootControlChromeOS::GetNumSlots() const {
return num_slots_;
}
BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
return current_slot_;
}
bool BootControlChromeOS::ParseDlcPartitionName(
const std::string partition_name,
std::string* dlc_id,
std::string* dlc_package) const {
CHECK_NE(dlc_id, nullptr);
CHECK_NE(dlc_package, nullptr);
vector<string> tokens = base::SplitString(
partition_name, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (tokens.size() != 3 || tokens[0] != kPartitionNamePrefixDlc) {
LOG(ERROR) << "DLC partition name (" << partition_name
<< ") is not well formatted.";
return false;
}
if (tokens[1].empty() || tokens[2].empty()) {
LOG(ERROR) << " partition name does not contain valid DLC ID (" << tokens[1]
<< ") or package (" << tokens[2] << ")";
return false;
}
*dlc_id = tokens[1];
*dlc_package = tokens[2];
return true;
}
bool BootControlChromeOS::GetPartitionDevice(const std::string& partition_name,
BootControlInterface::Slot slot,
bool not_in_payload,
std::string* device,
bool* is_dynamic) const {
// Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
if (base::StartsWith(partition_name,
kPartitionNamePrefixDlc,
base::CompareCase::SENSITIVE)) {
string dlc_id, dlc_package;
if (!ParseDlcPartitionName(partition_name, &dlc_id, &dlc_package))
return false;
*device = base::FilePath(imageloader::kDlcImageRootpath)
.Append(dlc_id)
.Append(dlc_package)
.Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
.Append(kPartitionNameDlcImage)
.value();
return true;
}
int partition_num = GetPartitionNumber(partition_name, slot);
if (partition_num < 0)
return false;
string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
if (part_device.empty())
return false;
*device = part_device;
if (is_dynamic) {
*is_dynamic = false;
}
return true;
}
bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
BootControlInterface::Slot slot,
string* device) const {
return GetPartitionDevice(partition_name, slot, false, device, nullptr);
}
bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
if (partition_num < 0)
return false;
CgptAddParams params;
memset(&params, '\0', sizeof(params));
params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
params.partition = partition_num;
int retval = CgptGetPartitionDetails(&params);
if (retval != CGPT_OK)
return false;
return params.successful || params.tries > 0;
}
bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
if (slot == current_slot_) {
LOG(ERROR) << "Refusing to mark current slot as unbootable.";
return false;
}
int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
if (partition_num < 0)
return false;
CgptAddParams params;
memset(&params, 0, sizeof(params));
params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
params.partition = partition_num;
params.successful = false;
params.set_successful = true;
params.tries = 0;
params.set_tries = true;
int retval = CgptSetAttributes(&params);
if (retval != CGPT_OK) {
LOG(ERROR) << "Marking kernel unbootable failed.";
return false;
}
return true;
}
bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
if (partition_num < 0)
return false;
CgptPrioritizeParams prio_params;
memset(&prio_params, 0, sizeof(prio_params));
prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
prio_params.set_partition = partition_num;
prio_params.max_priority = 0;
int retval = CgptPrioritize(&prio_params);
if (retval != CGPT_OK) {
LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
<< " (partition " << partition_num << ").";
return false;
}
CgptAddParams add_params;
memset(&add_params, 0, sizeof(add_params));
add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
add_params.partition = partition_num;
add_params.tries = 6;
add_params.set_tries = true;
retval = CgptSetAttributes(&add_params);
if (retval != CGPT_OK) {
LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
<< " for slot " << SlotName(slot) << " (partition "
<< partition_num << ").";
return false;
}
return true;
}
bool BootControlChromeOS::MarkBootSuccessfulAsync(
base::Callback<void(bool)> callback) {
return Subprocess::Get().Exec(
{"/usr/sbin/chromeos-setgoodkernel"},
base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
}
// static
string BootControlChromeOS::SysfsBlockDevice(const string& device) {
base::FilePath device_path(device);
if (device_path.DirName().value() != "/dev") {
return "";
}
return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
}
// static
bool BootControlChromeOS::IsRemovableDevice(const string& device) {
string sysfs_block = SysfsBlockDevice(device);
string removable;
if (sysfs_block.empty() ||
!base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
&removable)) {
return false;
}
base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
return removable == "1";
}
int BootControlChromeOS::GetPartitionNumber(
const string partition_name, BootControlInterface::Slot slot) const {
if (slot >= num_slots_) {
LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
<< num_slots_ << " slot(s)";
return -1;
}
// In Chrome OS, the partition numbers are hard-coded:
// KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
// To help compatibility between different we accept both lowercase and
// uppercase names in the ChromeOS or Brillo standard names.
// See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
string partition_lower = base::ToLowerASCII(partition_name);
int base_part_num = 2 + 2 * slot;
if (partition_lower == kChromeOSPartitionNameKernel ||
partition_lower == kAndroidPartitionNameKernel)
return base_part_num + 0;
if (partition_lower == kChromeOSPartitionNameRoot ||
partition_lower == kAndroidPartitionNameRoot)
return base_part_num + 1;
LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
return -1;
}
bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const {
LOG(ERROR) << __func__ << " not supported.";
return false;
}
DynamicPartitionControlInterface*
BootControlChromeOS::GetDynamicPartitionControl() {
return dynamic_partition_control_.get();
}
} // namespace chromeos_update_engine