blob: a82efc71199f6e3a65506940865effea046af0d6 [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
Yifan Hongc96589a2018-09-24 16:53:24 -070019#include <memory>
Alex Deymo763e7db2015-08-27 21:08:08 -070020#include <string>
Yifan Hongc96589a2018-09-24 16:53:24 -070021#include <utility>
Alex Deymo763e7db2015-08-27 21:08:08 -070022
Alex Deymoaa26f622015-09-16 18:21:27 -070023#include <base/bind.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070024#include <base/files/file_path.h>
25#include <base/files/file_util.h>
26#include <base/strings/string_util.h>
27#include <rootdev/rootdev.h>
28
29extern "C" {
30#include <vboot/vboot_host.h>
31}
32
Alex Deymo39910dc2015-11-09 17:04:30 -080033#include "update_engine/common/boot_control.h"
34#include "update_engine/common/subprocess.h"
35#include "update_engine/common/utils.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070036
37using std::string;
38
39namespace {
40
41const char* kChromeOSPartitionNameKernel = "kernel";
42const char* kChromeOSPartitionNameRoot = "root";
43const char* kAndroidPartitionNameKernel = "boot";
44const char* kAndroidPartitionNameRoot = "system";
45
46// Returns the currently booted rootfs partition. "/dev/sda3", for example.
47string GetBootDevice() {
48 char boot_path[PATH_MAX];
49 // Resolve the boot device path fully, including dereferencing through
50 // dm-verity.
51 int ret = rootdev(boot_path, sizeof(boot_path), true, false);
52 if (ret < 0) {
53 LOG(ERROR) << "rootdev failed to find the root device";
54 return "";
55 }
56 LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
57
58 // This local variable is used to construct the return string and is not
59 // passed around after use.
60 return boot_path;
61}
62
Alex Deymoaa26f622015-09-16 18:21:27 -070063// ExecCallback called when the execution of setgoodkernel finishes. Notifies
64// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
65// result.
66void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
67 int return_code,
68 const string& output) {
69 callback.Run(return_code == 0);
70}
71
Alex Deymo763e7db2015-08-27 21:08:08 -070072} // namespace
73
74namespace chromeos_update_engine {
75
Alex Deymob17327c2015-09-04 10:29:00 -070076namespace boot_control {
77
78// Factory defined in boot_control.h.
79std::unique_ptr<BootControlInterface> CreateBootControl() {
80 std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
81 new BootControlChromeOS());
82 if (!boot_control_chromeos->Init()) {
83 LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
84 }
Alex Vakulenkoce8c8ee2016-04-08 08:59:26 -070085 return std::move(boot_control_chromeos);
Alex Deymob17327c2015-09-04 10:29:00 -070086}
87
88} // namespace boot_control
89
Alex Deymo763e7db2015-08-27 21:08:08 -070090bool BootControlChromeOS::Init() {
91 string boot_device = GetBootDevice();
92 if (boot_device.empty())
93 return false;
94
95 int partition_num;
96 if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
97 return false;
98
99 // All installed Chrome OS devices have two slots. We don't update removable
100 // devices, so we will pretend we have only one slot in that case.
101 if (IsRemovableDevice(boot_disk_name_)) {
102 LOG(INFO)
103 << "Booted from a removable device, pretending we have only one slot.";
104 num_slots_ = 1;
105 } else {
106 // TODO(deymo): Look at the actual number of slots reported in the GPT.
107 num_slots_ = 2;
108 }
109
110 // Search through the slots to see which slot has the partition_num we booted
111 // from. This should map to one of the existing slots, otherwise something is
112 // very wrong.
113 current_slot_ = 0;
114 while (current_slot_ < num_slots_ &&
115 partition_num !=
116 GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
117 current_slot_++;
118 }
119 if (current_slot_ >= num_slots_) {
120 LOG(ERROR) << "Couldn't find the slot number corresponding to the "
121 "partition " << boot_device
122 << ", number of slots: " << num_slots_
123 << ". This device is not updateable.";
124 num_slots_ = 1;
125 current_slot_ = BootControlInterface::kInvalidSlot;
126 return false;
127 }
128
129 LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
Alex Deymo31d95ac2015-09-17 11:56:18 -0700130 << SlotName(current_slot_) << ") of " << num_slots_
131 << " slots present on disk " << boot_disk_name_;
Alex Deymo763e7db2015-08-27 21:08:08 -0700132 return true;
133}
134
135unsigned int BootControlChromeOS::GetNumSlots() const {
136 return num_slots_;
137}
138
139BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
140 return current_slot_;
141}
142
143bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
144 unsigned int slot,
145 string* device) const {
146 int partition_num = GetPartitionNumber(partition_name, slot);
147 if (partition_num < 0)
148 return false;
149
150 string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
151 if (part_device.empty())
152 return false;
153
154 *device = part_device;
155 return true;
156}
157
158bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
159 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
160 if (partition_num < 0)
161 return false;
162
163 CgptAddParams params;
164 memset(&params, '\0', sizeof(params));
165 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
166 params.partition = partition_num;
167
168 int retval = CgptGetPartitionDetails(&params);
169 if (retval != CGPT_OK)
170 return false;
171
172 return params.successful || params.tries > 0;
173}
174
175bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
Alex Deymo31d95ac2015-09-17 11:56:18 -0700176 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
Alex Deymo763e7db2015-08-27 21:08:08 -0700177
178 if (slot == current_slot_) {
179 LOG(ERROR) << "Refusing to mark current slot as unbootable.";
180 return false;
181 }
182
183 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
184 if (partition_num < 0)
185 return false;
186
187 CgptAddParams params;
188 memset(&params, 0, sizeof(params));
189
190 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
191 params.partition = partition_num;
192
193 params.successful = false;
194 params.set_successful = true;
195
196 params.tries = 0;
197 params.set_tries = true;
198
199 int retval = CgptSetAttributes(&params);
200 if (retval != CGPT_OK) {
201 LOG(ERROR) << "Marking kernel unbootable failed.";
202 return false;
203 }
204
205 return true;
206}
207
Alex Deymo31d95ac2015-09-17 11:56:18 -0700208bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
209 LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
210
211 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
212 if (partition_num < 0)
213 return false;
214
215 CgptPrioritizeParams prio_params;
216 memset(&prio_params, 0, sizeof(prio_params));
217
218 prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
219 prio_params.set_partition = partition_num;
220
221 prio_params.max_priority = 0;
222
223 int retval = CgptPrioritize(&prio_params);
224 if (retval != CGPT_OK) {
225 LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
226 << " (partition " << partition_num << ").";
227 return false;
228 }
229
230 CgptAddParams add_params;
231 memset(&add_params, 0, sizeof(add_params));
232
233 add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
234 add_params.partition = partition_num;
235
236 add_params.tries = 6;
237 add_params.set_tries = true;
238
239 retval = CgptSetAttributes(&add_params);
240 if (retval != CGPT_OK) {
241 LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
242 << " for slot " << SlotName(slot) << " (partition "
243 << partition_num << ").";
244 return false;
245 }
246
247 return true;
248}
249
Alex Deymoaa26f622015-09-16 18:21:27 -0700250bool BootControlChromeOS::MarkBootSuccessfulAsync(
251 base::Callback<void(bool)> callback) {
252 return Subprocess::Get().Exec(
253 {"/usr/sbin/chromeos-setgoodkernel"},
254 base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
255}
256
Alex Deymo763e7db2015-08-27 21:08:08 -0700257// static
258string BootControlChromeOS::SysfsBlockDevice(const string& device) {
259 base::FilePath device_path(device);
260 if (device_path.DirName().value() != "/dev") {
261 return "";
262 }
263 return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
264}
265
266// static
267bool BootControlChromeOS::IsRemovableDevice(const string& device) {
268 string sysfs_block = SysfsBlockDevice(device);
269 string removable;
270 if (sysfs_block.empty() ||
271 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
272 &removable)) {
273 return false;
274 }
275 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
276 return removable == "1";
277}
278
279int BootControlChromeOS::GetPartitionNumber(
280 const string partition_name,
281 BootControlInterface::Slot slot) const {
282 if (slot >= num_slots_) {
283 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
284 << num_slots_ << " slot(s)";
285 return -1;
286 }
287
288 // In Chrome OS, the partition numbers are hard-coded:
289 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
290 // To help compatibility between different we accept both lowercase and
291 // uppercase names in the ChromeOS or Brillo standard names.
292 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
Alex Vakulenko0103c362016-01-20 07:56:15 -0800293 string partition_lower = base::ToLowerASCII(partition_name);
Alex Deymo763e7db2015-08-27 21:08:08 -0700294 int base_part_num = 2 + 2 * slot;
295 if (partition_lower == kChromeOSPartitionNameKernel ||
296 partition_lower == kAndroidPartitionNameKernel)
297 return base_part_num + 0;
298 if (partition_lower == kChromeOSPartitionNameRoot ||
299 partition_lower == kAndroidPartitionNameRoot)
300 return base_part_num + 1;
301 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
302 return -1;
303}
304
Yifan Hong537802d2018-08-15 13:15:42 -0700305bool BootControlChromeOS::InitPartitionMetadata(
Yifan Hong6b0a9842018-10-16 16:54:18 -0700306 Slot slot, const PartitionMetadata& partition_metadata) {
Yifan Hong537802d2018-08-15 13:15:42 -0700307 return true;
308}
309
310void BootControlChromeOS::Cleanup() {}
311
Alex Deymo763e7db2015-08-27 21:08:08 -0700312} // namespace chromeos_update_engine