blob: aa94d3c42690f259a2dfd52fca9311b873375a34 [file] [log] [blame]
Alex Deymo763e7db2015-08-27 21:08:08 -07001//
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
Alex Deymo1b03f9f2015-12-09 00:38:36 -080017#include "update_engine/boot_control_chromeos.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070018
19#include <string>
20
Alex Deymoaa26f622015-09-16 18:21:27 -070021#include <base/bind.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070022#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
27extern "C" {
28#include <vboot/vboot_host.h>
29}
30
Alex Deymo39910dc2015-11-09 17:04:30 -080031#include "update_engine/common/boot_control.h"
32#include "update_engine/common/subprocess.h"
33#include "update_engine/common/utils.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070034
35using std::string;
36
37namespace {
38
39const char* kChromeOSPartitionNameKernel = "kernel";
40const char* kChromeOSPartitionNameRoot = "root";
41const char* kAndroidPartitionNameKernel = "boot";
42const char* kAndroidPartitionNameRoot = "system";
43
44// Returns the currently booted rootfs partition. "/dev/sda3", for example.
45string 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
Alex Deymoaa26f622015-09-16 18:21:27 -070061// ExecCallback called when the execution of setgoodkernel finishes. Notifies
62// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
63// result.
64void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
65 int return_code,
66 const string& output) {
67 callback.Run(return_code == 0);
68}
69
Alex Deymo763e7db2015-08-27 21:08:08 -070070} // namespace
71
72namespace chromeos_update_engine {
73
Alex Deymob17327c2015-09-04 10:29:00 -070074namespace boot_control {
75
76// Factory defined in boot_control.h.
77std::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 }
Alex Vakulenkoce8c8ee2016-04-08 08:59:26 -070083 return std::move(boot_control_chromeos);
Alex Deymob17327c2015-09-04 10:29:00 -070084}
85
86} // namespace boot_control
87
Alex Deymo763e7db2015-08-27 21:08:08 -070088bool 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 "
Alex Deymo31d95ac2015-09-17 11:56:18 -0700128 << SlotName(current_slot_) << ") of " << num_slots_
129 << " slots present on disk " << boot_disk_name_;
Alex Deymo763e7db2015-08-27 21:08:08 -0700130 return true;
131}
132
133unsigned int BootControlChromeOS::GetNumSlots() const {
134 return num_slots_;
135}
136
137BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
138 return current_slot_;
139}
140
141bool 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
156bool 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
173bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
Alex Deymo31d95ac2015-09-17 11:56:18 -0700174 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
Alex Deymo763e7db2015-08-27 21:08:08 -0700175
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
Alex Deymo31d95ac2015-09-17 11:56:18 -0700206bool 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
Alex Deymoaa26f622015-09-16 18:21:27 -0700248bool 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
Alex Deymo763e7db2015-08-27 21:08:08 -0700255// static
256string 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
265bool 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
277int 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
Alex Vakulenko0103c362016-01-20 07:56:15 -0800291 string partition_lower = base::ToLowerASCII(partition_name);
Alex Deymo763e7db2015-08-27 21:08:08 -0700292 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