blob: ccba316c7bb91512c1adebfbc5fbe2f6b732057a [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
Xiaochu Liu0d692202018-10-18 10:52:20 -070046const char kDlcInstallRootDirectoryEncrypted[] = "/home/chronos/dlc";
47const char kPartitionNamePrefixDlc[] = "dlc_";
48const char kPartitionNameDlcA[] = "dlc_a";
49const char kPartitionNameDlcB[] = "dlc_b";
50const char kPartitionNameDlcImage[] = "dlc.img";
51
Alex Deymo763e7db2015-08-27 21:08:08 -070052// Returns the currently booted rootfs partition. "/dev/sda3", for example.
53string GetBootDevice() {
54 char boot_path[PATH_MAX];
55 // Resolve the boot device path fully, including dereferencing through
56 // dm-verity.
57 int ret = rootdev(boot_path, sizeof(boot_path), true, false);
58 if (ret < 0) {
59 LOG(ERROR) << "rootdev failed to find the root device";
60 return "";
61 }
62 LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
63
64 // This local variable is used to construct the return string and is not
65 // passed around after use.
66 return boot_path;
67}
68
Alex Deymoaa26f622015-09-16 18:21:27 -070069// ExecCallback called when the execution of setgoodkernel finishes. Notifies
70// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
71// result.
72void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
73 int return_code,
74 const string& output) {
75 callback.Run(return_code == 0);
76}
77
Alex Deymo763e7db2015-08-27 21:08:08 -070078} // namespace
79
80namespace chromeos_update_engine {
81
Alex Deymob17327c2015-09-04 10:29:00 -070082namespace boot_control {
83
84// Factory defined in boot_control.h.
85std::unique_ptr<BootControlInterface> CreateBootControl() {
86 std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
87 new BootControlChromeOS());
88 if (!boot_control_chromeos->Init()) {
89 LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
90 }
Alex Vakulenkoce8c8ee2016-04-08 08:59:26 -070091 return std::move(boot_control_chromeos);
Alex Deymob17327c2015-09-04 10:29:00 -070092}
93
94} // namespace boot_control
95
Alex Deymo763e7db2015-08-27 21:08:08 -070096bool BootControlChromeOS::Init() {
97 string boot_device = GetBootDevice();
98 if (boot_device.empty())
99 return false;
100
101 int partition_num;
102 if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
103 return false;
104
105 // All installed Chrome OS devices have two slots. We don't update removable
106 // devices, so we will pretend we have only one slot in that case.
107 if (IsRemovableDevice(boot_disk_name_)) {
108 LOG(INFO)
109 << "Booted from a removable device, pretending we have only one slot.";
110 num_slots_ = 1;
111 } else {
112 // TODO(deymo): Look at the actual number of slots reported in the GPT.
113 num_slots_ = 2;
114 }
115
116 // Search through the slots to see which slot has the partition_num we booted
117 // from. This should map to one of the existing slots, otherwise something is
118 // very wrong.
119 current_slot_ = 0;
120 while (current_slot_ < num_slots_ &&
121 partition_num !=
122 GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
123 current_slot_++;
124 }
125 if (current_slot_ >= num_slots_) {
126 LOG(ERROR) << "Couldn't find the slot number corresponding to the "
Amin Hassani7cc8bb02019-01-14 16:29:47 -0800127 << "partition " << boot_device << ", number of slots: "
128 << num_slots_ << ". This device is not updateable.";
Alex Deymo763e7db2015-08-27 21:08:08 -0700129 num_slots_ = 1;
130 current_slot_ = BootControlInterface::kInvalidSlot;
131 return false;
132 }
133
134 LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
Alex Deymo31d95ac2015-09-17 11:56:18 -0700135 << SlotName(current_slot_) << ") of " << num_slots_
136 << " slots present on disk " << boot_disk_name_;
Alex Deymo763e7db2015-08-27 21:08:08 -0700137 return true;
138}
139
140unsigned int BootControlChromeOS::GetNumSlots() const {
141 return num_slots_;
142}
143
144BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
145 return current_slot_;
146}
147
148bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
149 unsigned int slot,
150 string* device) const {
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800151 // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
Xiaochu Liu88d90382018-08-29 16:09:11 -0700152 if (base::StartsWith(partition_name,
Xiaochu Liu0d692202018-10-18 10:52:20 -0700153 kPartitionNamePrefixDlc,
Xiaochu Liu88d90382018-08-29 16:09:11 -0700154 base::CompareCase::SENSITIVE)) {
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800155 // Extract DLC module ID from partition_name (DLC module ID is the string
156 // after |kPartitionNamePrefixDlc| in partition_name).
157 const auto dlc_module_id =
158 partition_name.substr(strlen(kPartitionNamePrefixDlc));
159 if (dlc_module_id.empty()) {
160 LOG(ERROR) << " partition name does not contain DLC module ID:"
Xiaochu Liu88d90382018-08-29 16:09:11 -0700161 << partition_name;
162 return false;
163 }
Xiaochu Liu0d692202018-10-18 10:52:20 -0700164 *device = base::FilePath(kDlcInstallRootDirectoryEncrypted)
Xiaochu Liuf53a5d32018-11-26 13:48:59 -0800165 .Append(dlc_module_id)
Xiaochu Liu0d692202018-10-18 10:52:20 -0700166 .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
167 .Append(kPartitionNameDlcImage)
168 .value();
Xiaochu Liu88d90382018-08-29 16:09:11 -0700169 return true;
170 }
Alex Deymo763e7db2015-08-27 21:08:08 -0700171 int partition_num = GetPartitionNumber(partition_name, slot);
172 if (partition_num < 0)
173 return false;
174
175 string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
176 if (part_device.empty())
177 return false;
178
179 *device = part_device;
180 return true;
181}
182
183bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
184 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
185 if (partition_num < 0)
186 return false;
187
188 CgptAddParams params;
189 memset(&params, '\0', sizeof(params));
190 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
191 params.partition = partition_num;
192
193 int retval = CgptGetPartitionDetails(&params);
194 if (retval != CGPT_OK)
195 return false;
196
197 return params.successful || params.tries > 0;
198}
199
200bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
Alex Deymo31d95ac2015-09-17 11:56:18 -0700201 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
Alex Deymo763e7db2015-08-27 21:08:08 -0700202
203 if (slot == current_slot_) {
204 LOG(ERROR) << "Refusing to mark current slot as unbootable.";
205 return false;
206 }
207
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
215 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
216 params.partition = partition_num;
217
218 params.successful = false;
219 params.set_successful = true;
220
221 params.tries = 0;
222 params.set_tries = true;
223
224 int retval = CgptSetAttributes(&params);
225 if (retval != CGPT_OK) {
226 LOG(ERROR) << "Marking kernel unbootable failed.";
227 return false;
228 }
229
230 return true;
231}
232
Alex Deymo31d95ac2015-09-17 11:56:18 -0700233bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
234 LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
235
236 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
237 if (partition_num < 0)
238 return false;
239
240 CgptPrioritizeParams prio_params;
241 memset(&prio_params, 0, sizeof(prio_params));
242
243 prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
244 prio_params.set_partition = partition_num;
245
246 prio_params.max_priority = 0;
247
248 int retval = CgptPrioritize(&prio_params);
249 if (retval != CGPT_OK) {
250 LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
251 << " (partition " << partition_num << ").";
252 return false;
253 }
254
255 CgptAddParams add_params;
256 memset(&add_params, 0, sizeof(add_params));
257
258 add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
259 add_params.partition = partition_num;
260
261 add_params.tries = 6;
262 add_params.set_tries = true;
263
264 retval = CgptSetAttributes(&add_params);
265 if (retval != CGPT_OK) {
266 LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
267 << " for slot " << SlotName(slot) << " (partition "
268 << partition_num << ").";
269 return false;
270 }
271
272 return true;
273}
274
Alex Deymoaa26f622015-09-16 18:21:27 -0700275bool BootControlChromeOS::MarkBootSuccessfulAsync(
276 base::Callback<void(bool)> callback) {
277 return Subprocess::Get().Exec(
278 {"/usr/sbin/chromeos-setgoodkernel"},
279 base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
280}
281
Alex Deymo763e7db2015-08-27 21:08:08 -0700282// static
283string BootControlChromeOS::SysfsBlockDevice(const string& device) {
284 base::FilePath device_path(device);
285 if (device_path.DirName().value() != "/dev") {
286 return "";
287 }
288 return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
289}
290
291// static
292bool BootControlChromeOS::IsRemovableDevice(const string& device) {
293 string sysfs_block = SysfsBlockDevice(device);
294 string removable;
295 if (sysfs_block.empty() ||
296 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
297 &removable)) {
298 return false;
299 }
300 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
301 return removable == "1";
302}
303
304int BootControlChromeOS::GetPartitionNumber(
Amin Hassani7cc8bb02019-01-14 16:29:47 -0800305 const string partition_name, BootControlInterface::Slot slot) const {
Alex Deymo763e7db2015-08-27 21:08:08 -0700306 if (slot >= num_slots_) {
307 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
308 << num_slots_ << " slot(s)";
309 return -1;
310 }
311
312 // In Chrome OS, the partition numbers are hard-coded:
313 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
314 // To help compatibility between different we accept both lowercase and
315 // uppercase names in the ChromeOS or Brillo standard names.
316 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
Alex Vakulenko0103c362016-01-20 07:56:15 -0800317 string partition_lower = base::ToLowerASCII(partition_name);
Alex Deymo763e7db2015-08-27 21:08:08 -0700318 int base_part_num = 2 + 2 * slot;
319 if (partition_lower == kChromeOSPartitionNameKernel ||
320 partition_lower == kAndroidPartitionNameKernel)
321 return base_part_num + 0;
322 if (partition_lower == kChromeOSPartitionNameRoot ||
323 partition_lower == kAndroidPartitionNameRoot)
324 return base_part_num + 1;
325 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
326 return -1;
327}
328
Yifan Hong537802d2018-08-15 13:15:42 -0700329bool BootControlChromeOS::InitPartitionMetadata(
Tao Bao3406c772019-01-02 15:34:35 -0800330 Slot slot,
331 const PartitionMetadata& partition_metadata,
332 bool update_metadata) {
Yifan Hong537802d2018-08-15 13:15:42 -0700333 return true;
334}
335
336void BootControlChromeOS::Cleanup() {}
337
Alex Deymo763e7db2015-08-27 21:08:08 -0700338} // namespace chromeos_update_engine