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.
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.
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.
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 
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 
143 unsigned int BootControlChromeOS::GetNumSlots() const {
144   return num_slots_;
145 }
146 
147 BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
148   return current_slot_;
149 }
150 
151 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 
186 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(&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 
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(&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 
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 
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
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
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 
307 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 
332 bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const {
333   LOG(ERROR) << __func__ << " not supported.";
334   return false;
335 }
336 
337 DynamicPartitionControlInterface*
338 BootControlChromeOS::GetDynamicPartitionControl() {
339   return dynamic_partition_control_.get();
340 }
341 
342 }  // namespace chromeos_update_engine
343