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/cros/boot_control_chromeos.h"
18 
19 #include <memory>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include <base/bind.h>
25 #include <base/files/file_path.h>
26 #include <base/files/file_util.h>
27 #include <base/strings/string_split.h>
28 #include <base/strings/string_util.h>
29 #include <chromeos/constants/imageloader.h>
30 #include <rootdev/rootdev.h>
31 
32 extern "C" {
33 #include <vboot/vboot_host.h>
34 }
35 
36 #include "update_engine/common/boot_control.h"
37 #include "update_engine/common/dynamic_partition_control_stub.h"
38 #include "update_engine/common/subprocess.h"
39 #include "update_engine/common/utils.h"
40 
41 using std::string;
42 using std::vector;
43 
44 namespace {
45 
46 const char* kChromeOSPartitionNameKernel = "kernel";
47 const char* kChromeOSPartitionNameRoot = "root";
48 const char* kAndroidPartitionNameKernel = "boot";
49 const char* kAndroidPartitionNameRoot = "system";
50 
51 const char kPartitionNamePrefixDlc[] = "dlc";
52 const char kPartitionNameDlcA[] = "dlc_a";
53 const char kPartitionNameDlcB[] = "dlc_b";
54 const char kPartitionNameDlcImage[] = "dlc.img";
55 
56 // Returns the currently booted rootfs partition. "/dev/sda3", for example.
GetBootDevice()57 string 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 
73 // ExecCallback called when the execution of setgoodkernel finishes. Notifies
74 // the caller of MarkBootSuccessfullAsync() by calling |callback| with the
75 // result.
OnMarkBootSuccessfulDone(base::Callback<void (bool)> callback,int return_code,const string & output)76 void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
77                               int return_code,
78                               const string& output) {
79   callback.Run(return_code == 0);
80 }
81 
82 }  // namespace
83 
84 namespace chromeos_update_engine {
85 
86 namespace boot_control {
87 
88 // Factory defined in boot_control.h.
CreateBootControl()89 std::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   }
95   return std::move(boot_control_chromeos);
96 }
97 
98 }  // namespace boot_control
99 
Init()100 bool 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 "
131                << "partition " << boot_device
132                << ", number of slots: " << num_slots_
133                << ". This device is not updateable.";
134     num_slots_ = 1;
135     current_slot_ = BootControlInterface::kInvalidSlot;
136     return false;
137   }
138 
139   dynamic_partition_control_.reset(new DynamicPartitionControlStub());
140 
141   LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
142             << SlotName(current_slot_) << ") of " << num_slots_
143             << " slots present on disk " << boot_disk_name_;
144   return true;
145 }
146 
GetNumSlots() const147 unsigned int BootControlChromeOS::GetNumSlots() const {
148   return num_slots_;
149 }
150 
GetCurrentSlot() const151 BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
152   return current_slot_;
153 }
154 
ParseDlcPartitionName(const std::string partition_name,std::string * dlc_id,std::string * dlc_package) const155 bool 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 
GetPartitionDevice(const std::string & partition_name,BootControlInterface::Slot slot,bool not_in_payload,std::string * device,bool * is_dynamic) const180 bool 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 {
185   // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
186   if (base::StartsWith(partition_name,
187                        kPartitionNamePrefixDlc,
188                        base::CompareCase::SENSITIVE)) {
189     string dlc_id, dlc_package;
190     if (!ParseDlcPartitionName(partition_name, &dlc_id, &dlc_package))
191       return false;
192 
193     *device = base::FilePath(imageloader::kDlcImageRootpath)
194                   .Append(dlc_id)
195                   .Append(dlc_package)
196                   .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
197                   .Append(kPartitionNameDlcImage)
198                   .value();
199     return true;
200   }
201   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;
210   if (is_dynamic) {
211     *is_dynamic = false;
212   }
213   return true;
214 }
215 
GetPartitionDevice(const string & partition_name,BootControlInterface::Slot slot,string * device) const216 bool 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 
IsSlotBootable(Slot slot) const222 bool 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 
MarkSlotUnbootable(Slot slot)239 bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
240   LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
241 
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 
SetActiveBootSlot(Slot slot)272 bool 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 
MarkBootSuccessfulAsync(base::Callback<void (bool)> callback)314 bool 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 
321 // static
SysfsBlockDevice(const string & device)322 string 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
IsRemovableDevice(const string & device)331 bool 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 
GetPartitionNumber(const string partition_name,BootControlInterface::Slot slot) const343 int BootControlChromeOS::GetPartitionNumber(
344     const string partition_name, BootControlInterface::Slot slot) const {
345   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
356   string partition_lower = base::ToLowerASCII(partition_name);
357   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 
IsSlotMarkedSuccessful(Slot slot) const368 bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const {
369   LOG(ERROR) << __func__ << " not supported.";
370   return false;
371 }
372 
373 DynamicPartitionControlInterface*
GetDynamicPartitionControl()374 BootControlChromeOS::GetDynamicPartitionControl() {
375   return dynamic_partition_control_.get();
376 }
377 
378 }  // namespace chromeos_update_engine
379