blob: 0f471696c7d0ca0d261ef71e00cf4e38099cc8aa [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"
Yifan Hongdaac7322019-11-07 10:48:26 -080034#include "update_engine/common/dynamic_partition_control_stub.h"
Alex Deymo39910dc2015-11-09 17:04:30 -080035#include "update_engine/common/subprocess.h"
36#include "update_engine/common/utils.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070037
38using std::string;
39
40namespace {
41
42const char* kChromeOSPartitionNameKernel = "kernel";
43const char* kChromeOSPartitionNameRoot = "root";
44const char* kAndroidPartitionNameKernel = "boot";
45const char* kAndroidPartitionNameRoot = "system";
46
Xiaochu Liu0d692202018-10-18 10:52:20 -070047const char kDlcInstallRootDirectoryEncrypted[] = "/home/chronos/dlc";
48const char kPartitionNamePrefixDlc[] = "dlc_";
49const char kPartitionNameDlcA[] = "dlc_a";
50const char kPartitionNameDlcB[] = "dlc_b";
51const char kPartitionNameDlcImage[] = "dlc.img";
52
Alex Deymo763e7db2015-08-27 21:08:08 -070053// Returns the currently booted rootfs partition. "/dev/sda3", for example.
54string GetBootDevice() {
55 char boot_path[PATH_MAX];
56 // Resolve the boot device path fully, including dereferencing through
57 // dm-verity.
58 int ret = rootdev(boot_path, sizeof(boot_path), true, false);
59 if (ret < 0) {
60 LOG(ERROR) << "rootdev failed to find the root device";
61 return "";
62 }
63 LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
64
65 // This local variable is used to construct the return string and is not
66 // passed around after use.
67 return boot_path;
68}
69
Alex Deymoaa26f622015-09-16 18:21:27 -070070// ExecCallback called when the execution of setgoodkernel finishes. Notifies
71// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
72// result.
73void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
74 int return_code,
75 const string& output) {
76 callback.Run(return_code == 0);
77}
78
Alex Deymo763e7db2015-08-27 21:08:08 -070079} // namespace
80
81namespace chromeos_update_engine {
82
Alex Deymob17327c2015-09-04 10:29:00 -070083namespace boot_control {
84
85// Factory defined in boot_control.h.
86std::unique_ptr<BootControlInterface> CreateBootControl() {
87 std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
88 new BootControlChromeOS());
89 if (!boot_control_chromeos->Init()) {
90 LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
91 }
Alex Vakulenkoce8c8ee2016-04-08 08:59:26 -070092 return std::move(boot_control_chromeos);
Alex Deymob17327c2015-09-04 10:29:00 -070093}
94
95} // namespace boot_control
96
Alex Deymo763e7db2015-08-27 21:08:08 -070097bool BootControlChromeOS::Init() {
98 string boot_device = GetBootDevice();
99 if (boot_device.empty())
100 return false;
101
102 int partition_num;
103 if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
104 return false;
105
106 // All installed Chrome OS devices have two slots. We don't update removable
107 // devices, so we will pretend we have only one slot in that case.
108 if (IsRemovableDevice(boot_disk_name_)) {
109 LOG(INFO)
110 << "Booted from a removable device, pretending we have only one slot.";
111 num_slots_ = 1;
112 } else {
113 // TODO(deymo): Look at the actual number of slots reported in the GPT.
114 num_slots_ = 2;
115 }
116
117 // Search through the slots to see which slot has the partition_num we booted
118 // from. This should map to one of the existing slots, otherwise something is
119 // very wrong.
120 current_slot_ = 0;
121 while (current_slot_ < num_slots_ &&
122 partition_num !=
123 GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
124 current_slot_++;
125 }
126 if (current_slot_ >= num_slots_) {
127 LOG(ERROR) << "Couldn't find the slot number corresponding to the "
Amin Hassani7cc8bb02019-01-14 16:29:47 -0800128 << "partition " << boot_device << ", number of slots: "
129 << num_slots_ << ". This device is not updateable.";
Alex Deymo763e7db2015-08-27 21:08:08 -0700130 num_slots_ = 1;
131 current_slot_ = BootControlInterface::kInvalidSlot;
132 return false;
133 }
134
Yifan Hongdaac7322019-11-07 10:48:26 -0800135 dynamic_partition_control_.reset(new DynamicPartitionControlStub());
136
Alex Deymo763e7db2015-08-27 21:08:08 -0700137 LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
Alex Deymo31d95ac2015-09-17 11:56:18 -0700138 << SlotName(current_slot_) << ") of " << num_slots_
139 << " slots present on disk " << boot_disk_name_;
Alex Deymo763e7db2015-08-27 21:08:08 -0700140 return true;
141}
142
143unsigned int BootControlChromeOS::GetNumSlots() const {
144 return num_slots_;
145}
146
147BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
148 return current_slot_;
149}
150
151bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
152 unsigned int slot,
153 string* device) const {
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800154 // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
Xiaochu Liu88d90382018-08-29 16:09:11 -0700155 if (base::StartsWith(partition_name,
Xiaochu Liu0d692202018-10-18 10:52:20 -0700156 kPartitionNamePrefixDlc,
Xiaochu Liu88d90382018-08-29 16:09:11 -0700157 base::CompareCase::SENSITIVE)) {
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800158 // Extract DLC module ID from partition_name (DLC module ID is the string
159 // after |kPartitionNamePrefixDlc| in partition_name).
160 const auto dlc_module_id =
161 partition_name.substr(strlen(kPartitionNamePrefixDlc));
162 if (dlc_module_id.empty()) {
163 LOG(ERROR) << " partition name does not contain DLC module ID:"
Xiaochu Liu88d90382018-08-29 16:09:11 -0700164 << partition_name;
165 return false;
166 }
Xiaochu Liu0d692202018-10-18 10:52:20 -0700167 *device = base::FilePath(kDlcInstallRootDirectoryEncrypted)
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800168 .Append(dlc_module_id)
Xiaochu Liu0d692202018-10-18 10:52:20 -0700169 .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
170 .Append(kPartitionNameDlcImage)
171 .value();
Xiaochu Liu88d90382018-08-29 16:09:11 -0700172 return true;
173 }
Alex Deymo763e7db2015-08-27 21:08:08 -0700174 int partition_num = GetPartitionNumber(partition_name, slot);
175 if (partition_num < 0)
176 return false;
177
178 string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
179 if (part_device.empty())
180 return false;
181
182 *device = part_device;
183 return true;
184}
185
186bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
187 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
188 if (partition_num < 0)
189 return false;
190
191 CgptAddParams params;
192 memset(&params, '\0', sizeof(params));
193 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
194 params.partition = partition_num;
195
196 int retval = CgptGetPartitionDetails(&params);
197 if (retval != CGPT_OK)
198 return false;
199
200 return params.successful || params.tries > 0;
201}
202
203bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
Alex Deymo31d95ac2015-09-17 11:56:18 -0700204 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
Alex Deymo763e7db2015-08-27 21:08:08 -0700205
206 if (slot == current_slot_) {
207 LOG(ERROR) << "Refusing to mark current slot as unbootable.";
208 return false;
209 }
210
211 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
212 if (partition_num < 0)
213 return false;
214
215 CgptAddParams params;
216 memset(&params, 0, sizeof(params));
217
218 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
219 params.partition = partition_num;
220
221 params.successful = false;
222 params.set_successful = true;
223
224 params.tries = 0;
225 params.set_tries = true;
226
227 int retval = CgptSetAttributes(&params);
228 if (retval != CGPT_OK) {
229 LOG(ERROR) << "Marking kernel unbootable failed.";
230 return false;
231 }
232
233 return true;
234}
235
Alex Deymo31d95ac2015-09-17 11:56:18 -0700236bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
237 LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
238
239 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
240 if (partition_num < 0)
241 return false;
242
243 CgptPrioritizeParams prio_params;
244 memset(&prio_params, 0, sizeof(prio_params));
245
246 prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
247 prio_params.set_partition = partition_num;
248
249 prio_params.max_priority = 0;
250
251 int retval = CgptPrioritize(&prio_params);
252 if (retval != CGPT_OK) {
253 LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
254 << " (partition " << partition_num << ").";
255 return false;
256 }
257
258 CgptAddParams add_params;
259 memset(&add_params, 0, sizeof(add_params));
260
261 add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
262 add_params.partition = partition_num;
263
264 add_params.tries = 6;
265 add_params.set_tries = true;
266
267 retval = CgptSetAttributes(&add_params);
268 if (retval != CGPT_OK) {
269 LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
270 << " for slot " << SlotName(slot) << " (partition "
271 << partition_num << ").";
272 return false;
273 }
274
275 return true;
276}
277
Alex Deymoaa26f622015-09-16 18:21:27 -0700278bool BootControlChromeOS::MarkBootSuccessfulAsync(
279 base::Callback<void(bool)> callback) {
280 return Subprocess::Get().Exec(
281 {"/usr/sbin/chromeos-setgoodkernel"},
282 base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
283}
284
Alex Deymo763e7db2015-08-27 21:08:08 -0700285// static
286string BootControlChromeOS::SysfsBlockDevice(const string& device) {
287 base::FilePath device_path(device);
288 if (device_path.DirName().value() != "/dev") {
289 return "";
290 }
291 return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
292}
293
294// static
295bool BootControlChromeOS::IsRemovableDevice(const string& device) {
296 string sysfs_block = SysfsBlockDevice(device);
297 string removable;
298 if (sysfs_block.empty() ||
299 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
300 &removable)) {
301 return false;
302 }
303 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
304 return removable == "1";
305}
306
307int BootControlChromeOS::GetPartitionNumber(
Amin Hassani7cc8bb02019-01-14 16:29:47 -0800308 const string partition_name, BootControlInterface::Slot slot) const {
Alex Deymo763e7db2015-08-27 21:08:08 -0700309 if (slot >= num_slots_) {
310 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
311 << num_slots_ << " slot(s)";
312 return -1;
313 }
314
315 // In Chrome OS, the partition numbers are hard-coded:
316 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
317 // To help compatibility between different we accept both lowercase and
318 // uppercase names in the ChromeOS or Brillo standard names.
319 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
Alex Vakulenko0103c362016-01-20 07:56:15 -0800320 string partition_lower = base::ToLowerASCII(partition_name);
Alex Deymo763e7db2015-08-27 21:08:08 -0700321 int base_part_num = 2 + 2 * slot;
322 if (partition_lower == kChromeOSPartitionNameKernel ||
323 partition_lower == kAndroidPartitionNameKernel)
324 return base_part_num + 0;
325 if (partition_lower == kChromeOSPartitionNameRoot ||
326 partition_lower == kAndroidPartitionNameRoot)
327 return base_part_num + 1;
328 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
329 return -1;
330}
331
Yifan Hongf1415942020-02-24 18:34:49 -0800332bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const {
333 LOG(ERROR) << __func__ << " not supported.";
334 return false;
335}
336
Yifan Hongdaac7322019-11-07 10:48:26 -0800337DynamicPartitionControlInterface*
338BootControlChromeOS::GetDynamicPartitionControl() {
339 return dynamic_partition_control_.get();
340}
341
Alex Deymo763e7db2015-08-27 21:08:08 -0700342} // namespace chromeos_update_engine