boot_control_chromeos.cc revision 31d95ac85d294b2b1bfa293835013e66c010fbcf
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 <chromeos/make_unique_ptr.h>
26#include <rootdev/rootdev.h>
27
28extern "C" {
29#include <vboot/vboot_host.h>
30}
31
32#include "update_engine/boot_control.h"
33#include "update_engine/subprocess.h"
34#include "update_engine/utils.h"
35
36using std::string;
37
38namespace {
39
40const char* kChromeOSPartitionNameKernel = "kernel";
41const char* kChromeOSPartitionNameRoot = "root";
42const char* kAndroidPartitionNameKernel = "boot";
43const char* kAndroidPartitionNameRoot = "system";
44
45// Returns the currently booted rootfs partition. "/dev/sda3", for example.
46string 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.
65void 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
73namespace chromeos_update_engine {
74
75namespace boot_control {
76
77// Factory defined in boot_control.h.
78std::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 chromeos::make_unique_ptr(boot_control_chromeos.release());
85}
86
87}  // namespace boot_control
88
89bool 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
134unsigned int BootControlChromeOS::GetNumSlots() const {
135  return num_slots_;
136}
137
138BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
139  return current_slot_;
140}
141
142bool 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
157bool 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
174bool 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
207bool 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
249bool 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
257string 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
266bool 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
278int 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::StringToLowerASCII(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