blob: 17659ae794464f80006bb9a9cd5a379e2a83b60c [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
Amin Hassaniec7bc112020-10-29 16:47:58 -070017#include "update_engine/cros/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>
Amin Hassani73733a02019-03-18 14:20:46 -070022#include <vector>
Alex Deymo763e7db2015-08-27 21:08:08 -070023
Alex Deymoaa26f622015-09-16 18:21:27 -070024#include <base/bind.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070025#include <base/files/file_path.h>
26#include <base/files/file_util.h>
Amin Hassani73733a02019-03-18 14:20:46 -070027#include <base/strings/string_split.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070028#include <base/strings/string_util.h>
Xiaochu Liub9cf5012019-01-25 11:05:58 -080029#include <chromeos/constants/imageloader.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070030#include <rootdev/rootdev.h>
31
32extern "C" {
33#include <vboot/vboot_host.h>
34}
35
Alex Deymo39910dc2015-11-09 17:04:30 -080036#include "update_engine/common/boot_control.h"
Yifan Hongdaac7322019-11-07 10:48:26 -080037#include "update_engine/common/dynamic_partition_control_stub.h"
Alex Deymo39910dc2015-11-09 17:04:30 -080038#include "update_engine/common/subprocess.h"
39#include "update_engine/common/utils.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070040
41using std::string;
Amin Hassani73733a02019-03-18 14:20:46 -070042using std::vector;
Alex Deymo763e7db2015-08-27 21:08:08 -070043
44namespace {
45
46const char* kChromeOSPartitionNameKernel = "kernel";
47const char* kChromeOSPartitionNameRoot = "root";
48const char* kAndroidPartitionNameKernel = "boot";
49const char* kAndroidPartitionNameRoot = "system";
50
Amin Hassani73733a02019-03-18 14:20:46 -070051const char kPartitionNamePrefixDlc[] = "dlc";
Xiaochu Liu0d692202018-10-18 10:52:20 -070052const char kPartitionNameDlcA[] = "dlc_a";
53const char kPartitionNameDlcB[] = "dlc_b";
54const char kPartitionNameDlcImage[] = "dlc.img";
55
Alex Deymo763e7db2015-08-27 21:08:08 -070056// Returns the currently booted rootfs partition. "/dev/sda3", for example.
57string GetBootDevice() {
58 char boot_path[PATH_MAX];
59 // Resolve the boot device path fully, including dereferencing through
60 // dm-verity.
61 int ret = rootdev(boot_path, sizeof(boot_path), true, false);
62 if (ret < 0) {
63 LOG(ERROR) << "rootdev failed to find the root device";
64 return "";
65 }
66 LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
67
68 // This local variable is used to construct the return string and is not
69 // passed around after use.
70 return boot_path;
71}
72
Alex Deymoaa26f622015-09-16 18:21:27 -070073// ExecCallback called when the execution of setgoodkernel finishes. Notifies
74// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
75// result.
76void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
77 int return_code,
78 const string& output) {
79 callback.Run(return_code == 0);
80}
81
Alex Deymo763e7db2015-08-27 21:08:08 -070082} // namespace
83
84namespace chromeos_update_engine {
85
Alex Deymob17327c2015-09-04 10:29:00 -070086namespace boot_control {
87
88// Factory defined in boot_control.h.
89std::unique_ptr<BootControlInterface> CreateBootControl() {
90 std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
91 new BootControlChromeOS());
92 if (!boot_control_chromeos->Init()) {
93 LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
94 }
Alex Vakulenkoce8c8ee2016-04-08 08:59:26 -070095 return std::move(boot_control_chromeos);
Alex Deymob17327c2015-09-04 10:29:00 -070096}
97
98} // namespace boot_control
99
Alex Deymo763e7db2015-08-27 21:08:08 -0700100bool BootControlChromeOS::Init() {
101 string boot_device = GetBootDevice();
102 if (boot_device.empty())
103 return false;
104
105 int partition_num;
106 if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
107 return false;
108
109 // All installed Chrome OS devices have two slots. We don't update removable
110 // devices, so we will pretend we have only one slot in that case.
111 if (IsRemovableDevice(boot_disk_name_)) {
112 LOG(INFO)
113 << "Booted from a removable device, pretending we have only one slot.";
114 num_slots_ = 1;
115 } else {
116 // TODO(deymo): Look at the actual number of slots reported in the GPT.
117 num_slots_ = 2;
118 }
119
120 // Search through the slots to see which slot has the partition_num we booted
121 // from. This should map to one of the existing slots, otherwise something is
122 // very wrong.
123 current_slot_ = 0;
124 while (current_slot_ < num_slots_ &&
125 partition_num !=
126 GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
127 current_slot_++;
128 }
129 if (current_slot_ >= num_slots_) {
130 LOG(ERROR) << "Couldn't find the slot number corresponding to the "
Vyshu852f57d2020-10-09 17:35:14 +0000131 << "partition " << boot_device
132 << ", number of slots: " << num_slots_
133 << ". This device is not updateable.";
Alex Deymo763e7db2015-08-27 21:08:08 -0700134 num_slots_ = 1;
135 current_slot_ = BootControlInterface::kInvalidSlot;
136 return false;
137 }
138
Yifan Hongdaac7322019-11-07 10:48:26 -0800139 dynamic_partition_control_.reset(new DynamicPartitionControlStub());
140
Alex Deymo763e7db2015-08-27 21:08:08 -0700141 LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
Alex Deymo31d95ac2015-09-17 11:56:18 -0700142 << SlotName(current_slot_) << ") of " << num_slots_
143 << " slots present on disk " << boot_disk_name_;
Alex Deymo763e7db2015-08-27 21:08:08 -0700144 return true;
145}
146
147unsigned int BootControlChromeOS::GetNumSlots() const {
148 return num_slots_;
149}
150
151BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
152 return current_slot_;
153}
154
Amin Hassani73733a02019-03-18 14:20:46 -0700155bool BootControlChromeOS::ParseDlcPartitionName(
156 const std::string partition_name,
157 std::string* dlc_id,
158 std::string* dlc_package) const {
159 CHECK_NE(dlc_id, nullptr);
160 CHECK_NE(dlc_package, nullptr);
161
162 vector<string> tokens = base::SplitString(
163 partition_name, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
164 if (tokens.size() != 3 || tokens[0] != kPartitionNamePrefixDlc) {
165 LOG(ERROR) << "DLC partition name (" << partition_name
166 << ") is not well formatted.";
167 return false;
168 }
169 if (tokens[1].empty() || tokens[2].empty()) {
170 LOG(ERROR) << " partition name does not contain valid DLC ID (" << tokens[1]
171 << ") or package (" << tokens[2] << ")";
172 return false;
173 }
174
175 *dlc_id = tokens[1];
176 *dlc_package = tokens[2];
177 return true;
178}
179
Tianjie51a5a392020-06-03 14:39:32 -0700180bool BootControlChromeOS::GetPartitionDevice(const std::string& partition_name,
181 BootControlInterface::Slot slot,
182 bool not_in_payload,
183 std::string* device,
184 bool* is_dynamic) const {
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800185 // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
Xiaochu Liu88d90382018-08-29 16:09:11 -0700186 if (base::StartsWith(partition_name,
Xiaochu Liu0d692202018-10-18 10:52:20 -0700187 kPartitionNamePrefixDlc,
Xiaochu Liu88d90382018-08-29 16:09:11 -0700188 base::CompareCase::SENSITIVE)) {
Amin Hassani73733a02019-03-18 14:20:46 -0700189 string dlc_id, dlc_package;
190 if (!ParseDlcPartitionName(partition_name, &dlc_id, &dlc_package))
Xiaochu Liu88d90382018-08-29 16:09:11 -0700191 return false;
Amin Hassani73733a02019-03-18 14:20:46 -0700192
Xiaochu Liub9cf5012019-01-25 11:05:58 -0800193 *device = base::FilePath(imageloader::kDlcImageRootpath)
Amin Hassani73733a02019-03-18 14:20:46 -0700194 .Append(dlc_id)
195 .Append(dlc_package)
Xiaochu Liu0d692202018-10-18 10:52:20 -0700196 .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
197 .Append(kPartitionNameDlcImage)
198 .value();
Xiaochu Liu88d90382018-08-29 16:09:11 -0700199 return true;
200 }
Alex Deymo763e7db2015-08-27 21:08:08 -0700201 int partition_num = GetPartitionNumber(partition_name, slot);
202 if (partition_num < 0)
203 return false;
204
205 string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
206 if (part_device.empty())
207 return false;
208
209 *device = part_device;
Tianjie51a5a392020-06-03 14:39:32 -0700210 if (is_dynamic) {
211 *is_dynamic = false;
212 }
Alex Deymo763e7db2015-08-27 21:08:08 -0700213 return true;
214}
215
Tianjie51a5a392020-06-03 14:39:32 -0700216bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
217 BootControlInterface::Slot slot,
218 string* device) const {
219 return GetPartitionDevice(partition_name, slot, false, device, nullptr);
220}
221
Alex Deymo763e7db2015-08-27 21:08:08 -0700222bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
223 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
224 if (partition_num < 0)
225 return false;
226
227 CgptAddParams params;
228 memset(&params, '\0', sizeof(params));
229 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
230 params.partition = partition_num;
231
232 int retval = CgptGetPartitionDetails(&params);
233 if (retval != CGPT_OK)
234 return false;
235
236 return params.successful || params.tries > 0;
237}
238
239bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
Alex Deymo31d95ac2015-09-17 11:56:18 -0700240 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
Alex Deymo763e7db2015-08-27 21:08:08 -0700241
242 if (slot == current_slot_) {
243 LOG(ERROR) << "Refusing to mark current slot as unbootable.";
244 return false;
245 }
246
247 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
248 if (partition_num < 0)
249 return false;
250
251 CgptAddParams params;
252 memset(&params, 0, sizeof(params));
253
254 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
255 params.partition = partition_num;
256
257 params.successful = false;
258 params.set_successful = true;
259
260 params.tries = 0;
261 params.set_tries = true;
262
263 int retval = CgptSetAttributes(&params);
264 if (retval != CGPT_OK) {
265 LOG(ERROR) << "Marking kernel unbootable failed.";
266 return false;
267 }
268
269 return true;
270}
271
Alex Deymo31d95ac2015-09-17 11:56:18 -0700272bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
273 LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
274
275 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
276 if (partition_num < 0)
277 return false;
278
279 CgptPrioritizeParams prio_params;
280 memset(&prio_params, 0, sizeof(prio_params));
281
282 prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
283 prio_params.set_partition = partition_num;
284
285 prio_params.max_priority = 0;
286
287 int retval = CgptPrioritize(&prio_params);
288 if (retval != CGPT_OK) {
289 LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
290 << " (partition " << partition_num << ").";
291 return false;
292 }
293
294 CgptAddParams add_params;
295 memset(&add_params, 0, sizeof(add_params));
296
297 add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
298 add_params.partition = partition_num;
299
300 add_params.tries = 6;
301 add_params.set_tries = true;
302
303 retval = CgptSetAttributes(&add_params);
304 if (retval != CGPT_OK) {
305 LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
306 << " for slot " << SlotName(slot) << " (partition "
307 << partition_num << ").";
308 return false;
309 }
310
311 return true;
312}
313
Alex Deymoaa26f622015-09-16 18:21:27 -0700314bool BootControlChromeOS::MarkBootSuccessfulAsync(
315 base::Callback<void(bool)> callback) {
316 return Subprocess::Get().Exec(
317 {"/usr/sbin/chromeos-setgoodkernel"},
318 base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
319}
320
Alex Deymo763e7db2015-08-27 21:08:08 -0700321// static
322string BootControlChromeOS::SysfsBlockDevice(const string& device) {
323 base::FilePath device_path(device);
324 if (device_path.DirName().value() != "/dev") {
325 return "";
326 }
327 return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
328}
329
330// static
331bool BootControlChromeOS::IsRemovableDevice(const string& device) {
332 string sysfs_block = SysfsBlockDevice(device);
333 string removable;
334 if (sysfs_block.empty() ||
335 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
336 &removable)) {
337 return false;
338 }
339 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
340 return removable == "1";
341}
342
343int BootControlChromeOS::GetPartitionNumber(
Amin Hassani7cc8bb02019-01-14 16:29:47 -0800344 const string partition_name, BootControlInterface::Slot slot) const {
Alex Deymo763e7db2015-08-27 21:08:08 -0700345 if (slot >= num_slots_) {
346 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
347 << num_slots_ << " slot(s)";
348 return -1;
349 }
350
351 // In Chrome OS, the partition numbers are hard-coded:
352 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
353 // To help compatibility between different we accept both lowercase and
354 // uppercase names in the ChromeOS or Brillo standard names.
355 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
Alex Vakulenko0103c362016-01-20 07:56:15 -0800356 string partition_lower = base::ToLowerASCII(partition_name);
Alex Deymo763e7db2015-08-27 21:08:08 -0700357 int base_part_num = 2 + 2 * slot;
358 if (partition_lower == kChromeOSPartitionNameKernel ||
359 partition_lower == kAndroidPartitionNameKernel)
360 return base_part_num + 0;
361 if (partition_lower == kChromeOSPartitionNameRoot ||
362 partition_lower == kAndroidPartitionNameRoot)
363 return base_part_num + 1;
364 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
365 return -1;
366}
367
Yifan Hongf1415942020-02-24 18:34:49 -0800368bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const {
369 LOG(ERROR) << __func__ << " not supported.";
370 return false;
371}
372
Yifan Hongdaac7322019-11-07 10:48:26 -0800373DynamicPartitionControlInterface*
374BootControlChromeOS::GetDynamicPartitionControl() {
375 return dynamic_partition_control_.get();
376}
377
Alex Deymo763e7db2015-08-27 21:08:08 -0700378} // namespace chromeos_update_engine