blob: 3f1eac441c85a8ec80af92a5b4fe026dab2dc7d5 [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>
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"
37#include "update_engine/common/subprocess.h"
38#include "update_engine/common/utils.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070039
40using std::string;
Amin Hassani73733a02019-03-18 14:20:46 -070041using std::vector;
Alex Deymo763e7db2015-08-27 21:08:08 -070042
43namespace {
44
45const char* kChromeOSPartitionNameKernel = "kernel";
46const char* kChromeOSPartitionNameRoot = "root";
47const char* kAndroidPartitionNameKernel = "boot";
48const char* kAndroidPartitionNameRoot = "system";
49
Amin Hassani73733a02019-03-18 14:20:46 -070050const char kPartitionNamePrefixDlc[] = "dlc";
Xiaochu Liu0d692202018-10-18 10:52:20 -070051const char kPartitionNameDlcA[] = "dlc_a";
52const char kPartitionNameDlcB[] = "dlc_b";
53const char kPartitionNameDlcImage[] = "dlc.img";
54
Alex Deymo763e7db2015-08-27 21:08:08 -070055// Returns the currently booted rootfs partition. "/dev/sda3", for example.
56string GetBootDevice() {
57 char boot_path[PATH_MAX];
58 // Resolve the boot device path fully, including dereferencing through
59 // dm-verity.
60 int ret = rootdev(boot_path, sizeof(boot_path), true, false);
61 if (ret < 0) {
62 LOG(ERROR) << "rootdev failed to find the root device";
63 return "";
64 }
65 LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
66
67 // This local variable is used to construct the return string and is not
68 // passed around after use.
69 return boot_path;
70}
71
Alex Deymoaa26f622015-09-16 18:21:27 -070072// ExecCallback called when the execution of setgoodkernel finishes. Notifies
73// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
74// result.
75void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
76 int return_code,
77 const string& output) {
78 callback.Run(return_code == 0);
79}
80
Alex Deymo763e7db2015-08-27 21:08:08 -070081} // namespace
82
83namespace chromeos_update_engine {
84
Alex Deymob17327c2015-09-04 10:29:00 -070085namespace boot_control {
86
87// Factory defined in boot_control.h.
88std::unique_ptr<BootControlInterface> CreateBootControl() {
89 std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
90 new BootControlChromeOS());
91 if (!boot_control_chromeos->Init()) {
92 LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
93 }
Alex Vakulenkoce8c8ee2016-04-08 08:59:26 -070094 return std::move(boot_control_chromeos);
Alex Deymob17327c2015-09-04 10:29:00 -070095}
96
97} // namespace boot_control
98
Alex Deymo763e7db2015-08-27 21:08:08 -070099bool BootControlChromeOS::Init() {
100 string boot_device = GetBootDevice();
101 if (boot_device.empty())
102 return false;
103
104 int partition_num;
105 if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
106 return false;
107
108 // All installed Chrome OS devices have two slots. We don't update removable
109 // devices, so we will pretend we have only one slot in that case.
110 if (IsRemovableDevice(boot_disk_name_)) {
111 LOG(INFO)
112 << "Booted from a removable device, pretending we have only one slot.";
113 num_slots_ = 1;
114 } else {
115 // TODO(deymo): Look at the actual number of slots reported in the GPT.
116 num_slots_ = 2;
117 }
118
119 // Search through the slots to see which slot has the partition_num we booted
120 // from. This should map to one of the existing slots, otherwise something is
121 // very wrong.
122 current_slot_ = 0;
123 while (current_slot_ < num_slots_ &&
124 partition_num !=
125 GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
126 current_slot_++;
127 }
128 if (current_slot_ >= num_slots_) {
129 LOG(ERROR) << "Couldn't find the slot number corresponding to the "
Amin Hassani7cc8bb02019-01-14 16:29:47 -0800130 << "partition " << boot_device << ", number of slots: "
131 << num_slots_ << ". This device is not updateable.";
Alex Deymo763e7db2015-08-27 21:08:08 -0700132 num_slots_ = 1;
133 current_slot_ = BootControlInterface::kInvalidSlot;
134 return false;
135 }
136
137 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
Amin Hassani73733a02019-03-18 14:20:46 -0700151bool BootControlChromeOS::ParseDlcPartitionName(
152 const std::string partition_name,
153 std::string* dlc_id,
154 std::string* dlc_package) const {
155 CHECK_NE(dlc_id, nullptr);
156 CHECK_NE(dlc_package, nullptr);
157
158 vector<string> tokens = base::SplitString(
159 partition_name, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
160 if (tokens.size() != 3 || tokens[0] != kPartitionNamePrefixDlc) {
161 LOG(ERROR) << "DLC partition name (" << partition_name
162 << ") is not well formatted.";
163 return false;
164 }
165 if (tokens[1].empty() || tokens[2].empty()) {
166 LOG(ERROR) << " partition name does not contain valid DLC ID (" << tokens[1]
167 << ") or package (" << tokens[2] << ")";
168 return false;
169 }
170
171 *dlc_id = tokens[1];
172 *dlc_package = tokens[2];
173 return true;
174}
175
Alex Deymo763e7db2015-08-27 21:08:08 -0700176bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
177 unsigned int slot,
178 string* device) const {
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800179 // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
Xiaochu Liu88d90382018-08-29 16:09:11 -0700180 if (base::StartsWith(partition_name,
Xiaochu Liu0d692202018-10-18 10:52:20 -0700181 kPartitionNamePrefixDlc,
Xiaochu Liu88d90382018-08-29 16:09:11 -0700182 base::CompareCase::SENSITIVE)) {
Amin Hassani73733a02019-03-18 14:20:46 -0700183 string dlc_id, dlc_package;
184 if (!ParseDlcPartitionName(partition_name, &dlc_id, &dlc_package))
Xiaochu Liu88d90382018-08-29 16:09:11 -0700185 return false;
Amin Hassani73733a02019-03-18 14:20:46 -0700186
Xiaochu Liub9cf5012019-01-25 11:05:58 -0800187 *device = base::FilePath(imageloader::kDlcImageRootpath)
Amin Hassani73733a02019-03-18 14:20:46 -0700188 .Append(dlc_id)
189 .Append(dlc_package)
Xiaochu Liu0d692202018-10-18 10:52:20 -0700190 .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
191 .Append(kPartitionNameDlcImage)
192 .value();
Xiaochu Liu88d90382018-08-29 16:09:11 -0700193 return true;
194 }
Alex Deymo763e7db2015-08-27 21:08:08 -0700195 int partition_num = GetPartitionNumber(partition_name, slot);
196 if (partition_num < 0)
197 return false;
198
199 string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
200 if (part_device.empty())
201 return false;
202
203 *device = part_device;
204 return true;
205}
206
207bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
208 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
209 if (partition_num < 0)
210 return false;
211
212 CgptAddParams params;
213 memset(&params, '\0', sizeof(params));
214 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
215 params.partition = partition_num;
216
217 int retval = CgptGetPartitionDetails(&params);
218 if (retval != CGPT_OK)
219 return false;
220
221 return params.successful || params.tries > 0;
222}
223
224bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
Alex Deymo31d95ac2015-09-17 11:56:18 -0700225 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
Alex Deymo763e7db2015-08-27 21:08:08 -0700226
227 if (slot == current_slot_) {
228 LOG(ERROR) << "Refusing to mark current slot as unbootable.";
229 return false;
230 }
231
232 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
233 if (partition_num < 0)
234 return false;
235
236 CgptAddParams params;
237 memset(&params, 0, sizeof(params));
238
239 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
240 params.partition = partition_num;
241
242 params.successful = false;
243 params.set_successful = true;
244
245 params.tries = 0;
246 params.set_tries = true;
247
248 int retval = CgptSetAttributes(&params);
249 if (retval != CGPT_OK) {
250 LOG(ERROR) << "Marking kernel unbootable failed.";
251 return false;
252 }
253
254 return true;
255}
256
Alex Deymo31d95ac2015-09-17 11:56:18 -0700257bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
258 LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
259
260 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
261 if (partition_num < 0)
262 return false;
263
264 CgptPrioritizeParams prio_params;
265 memset(&prio_params, 0, sizeof(prio_params));
266
267 prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
268 prio_params.set_partition = partition_num;
269
270 prio_params.max_priority = 0;
271
272 int retval = CgptPrioritize(&prio_params);
273 if (retval != CGPT_OK) {
274 LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
275 << " (partition " << partition_num << ").";
276 return false;
277 }
278
279 CgptAddParams add_params;
280 memset(&add_params, 0, sizeof(add_params));
281
282 add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
283 add_params.partition = partition_num;
284
285 add_params.tries = 6;
286 add_params.set_tries = true;
287
288 retval = CgptSetAttributes(&add_params);
289 if (retval != CGPT_OK) {
290 LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
291 << " for slot " << SlotName(slot) << " (partition "
292 << partition_num << ").";
293 return false;
294 }
295
296 return true;
297}
298
Alex Deymoaa26f622015-09-16 18:21:27 -0700299bool BootControlChromeOS::MarkBootSuccessfulAsync(
300 base::Callback<void(bool)> callback) {
301 return Subprocess::Get().Exec(
302 {"/usr/sbin/chromeos-setgoodkernel"},
303 base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
304}
305
Alex Deymo763e7db2015-08-27 21:08:08 -0700306// static
307string BootControlChromeOS::SysfsBlockDevice(const string& device) {
308 base::FilePath device_path(device);
309 if (device_path.DirName().value() != "/dev") {
310 return "";
311 }
312 return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
313}
314
315// static
316bool BootControlChromeOS::IsRemovableDevice(const string& device) {
317 string sysfs_block = SysfsBlockDevice(device);
318 string removable;
319 if (sysfs_block.empty() ||
320 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
321 &removable)) {
322 return false;
323 }
324 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
325 return removable == "1";
326}
327
328int BootControlChromeOS::GetPartitionNumber(
Amin Hassani7cc8bb02019-01-14 16:29:47 -0800329 const string partition_name, BootControlInterface::Slot slot) const {
Alex Deymo763e7db2015-08-27 21:08:08 -0700330 if (slot >= num_slots_) {
331 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
332 << num_slots_ << " slot(s)";
333 return -1;
334 }
335
336 // In Chrome OS, the partition numbers are hard-coded:
337 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
338 // To help compatibility between different we accept both lowercase and
339 // uppercase names in the ChromeOS or Brillo standard names.
340 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
Alex Vakulenko0103c362016-01-20 07:56:15 -0800341 string partition_lower = base::ToLowerASCII(partition_name);
Alex Deymo763e7db2015-08-27 21:08:08 -0700342 int base_part_num = 2 + 2 * slot;
343 if (partition_lower == kChromeOSPartitionNameKernel ||
344 partition_lower == kAndroidPartitionNameKernel)
345 return base_part_num + 0;
346 if (partition_lower == kChromeOSPartitionNameRoot ||
347 partition_lower == kAndroidPartitionNameRoot)
348 return base_part_num + 1;
349 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
350 return -1;
351}
352
Yifan Hong537802d2018-08-15 13:15:42 -0700353bool BootControlChromeOS::InitPartitionMetadata(
Tao Bao3406c772019-01-02 15:34:35 -0800354 Slot slot,
355 const PartitionMetadata& partition_metadata,
356 bool update_metadata) {
Yifan Hong537802d2018-08-15 13:15:42 -0700357 return true;
358}
359
360void BootControlChromeOS::Cleanup() {}
361
Alex Deymo763e7db2015-08-27 21:08:08 -0700362} // namespace chromeos_update_engine