1 //
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
17 #include "update_engine/boot_control_chromeos.h"
18
19 #include <memory>
20 #include <string>
21 #include <utility>
22
23 #include <base/bind.h>
24 #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
29 extern "C" {
30 #include <vboot/vboot_host.h>
31 }
32
33 #include "update_engine/common/boot_control.h"
34 #include "update_engine/common/dynamic_partition_control_stub.h"
35 #include "update_engine/common/subprocess.h"
36 #include "update_engine/common/utils.h"
37
38 using std::string;
39
40 namespace {
41
42 const char* kChromeOSPartitionNameKernel = "kernel";
43 const char* kChromeOSPartitionNameRoot = "root";
44 const char* kAndroidPartitionNameKernel = "boot";
45 const char* kAndroidPartitionNameRoot = "system";
46
47 const char kDlcInstallRootDirectoryEncrypted[] = "/home/chronos/dlc";
48 const char kPartitionNamePrefixDlc[] = "dlc_";
49 const char kPartitionNameDlcA[] = "dlc_a";
50 const char kPartitionNameDlcB[] = "dlc_b";
51 const char kPartitionNameDlcImage[] = "dlc.img";
52
53 // Returns the currently booted rootfs partition. "/dev/sda3", for example.
GetBootDevice()54 string 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
70 // ExecCallback called when the execution of setgoodkernel finishes. Notifies
71 // the caller of MarkBootSuccessfullAsync() by calling |callback| with the
72 // result.
OnMarkBootSuccessfulDone(base::Callback<void (bool)> callback,int return_code,const string & output)73 void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
74 int return_code,
75 const string& output) {
76 callback.Run(return_code == 0);
77 }
78
79 } // namespace
80
81 namespace chromeos_update_engine {
82
83 namespace boot_control {
84
85 // Factory defined in boot_control.h.
CreateBootControl()86 std::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 }
92 return std::move(boot_control_chromeos);
93 }
94
95 } // namespace boot_control
96
Init()97 bool 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 "
128 << "partition " << boot_device << ", number of slots: "
129 << num_slots_ << ". This device is not updateable.";
130 num_slots_ = 1;
131 current_slot_ = BootControlInterface::kInvalidSlot;
132 return false;
133 }
134
135 dynamic_partition_control_.reset(new DynamicPartitionControlStub());
136
137 LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
138 << SlotName(current_slot_) << ") of " << num_slots_
139 << " slots present on disk " << boot_disk_name_;
140 return true;
141 }
142
GetNumSlots() const143 unsigned int BootControlChromeOS::GetNumSlots() const {
144 return num_slots_;
145 }
146
GetCurrentSlot() const147 BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
148 return current_slot_;
149 }
150
GetPartitionDevice(const string & partition_name,unsigned int slot,string * device) const151 bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
152 unsigned int slot,
153 string* device) const {
154 // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
155 if (base::StartsWith(partition_name,
156 kPartitionNamePrefixDlc,
157 base::CompareCase::SENSITIVE)) {
158 // 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:"
164 << partition_name;
165 return false;
166 }
167 *device = base::FilePath(kDlcInstallRootDirectoryEncrypted)
168 .Append(dlc_module_id)
169 .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
170 .Append(kPartitionNameDlcImage)
171 .value();
172 return true;
173 }
174 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
IsSlotBootable(Slot slot) const186 bool 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(¶ms, '\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(¶ms);
197 if (retval != CGPT_OK)
198 return false;
199
200 return params.successful || params.tries > 0;
201 }
202
MarkSlotUnbootable(Slot slot)203 bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
204 LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
205
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(¶ms, 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(¶ms);
228 if (retval != CGPT_OK) {
229 LOG(ERROR) << "Marking kernel unbootable failed.";
230 return false;
231 }
232
233 return true;
234 }
235
SetActiveBootSlot(Slot slot)236 bool 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
MarkBootSuccessfulAsync(base::Callback<void (bool)> callback)278 bool 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
285 // static
SysfsBlockDevice(const string & device)286 string 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
IsRemovableDevice(const string & device)295 bool 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
GetPartitionNumber(const string partition_name,BootControlInterface::Slot slot) const307 int BootControlChromeOS::GetPartitionNumber(
308 const string partition_name, BootControlInterface::Slot slot) const {
309 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
320 string partition_lower = base::ToLowerASCII(partition_name);
321 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
IsSlotMarkedSuccessful(Slot slot) const332 bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const {
333 LOG(ERROR) << __func__ << " not supported.";
334 return false;
335 }
336
337 DynamicPartitionControlInterface*
GetDynamicPartitionControl()338 BootControlChromeOS::GetDynamicPartitionControl() {
339 return dynamic_partition_control_.get();
340 }
341
342 } // namespace chromeos_update_engine
343