• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <string>
20 
21 #include <base/bind.h>
22 #include <base/files/file_path.h>
23 #include <base/files/file_util.h>
24 #include <base/strings/string_util.h>
25 #include <brillo/make_unique_ptr.h>
26 #include <rootdev/rootdev.h>
27 
28 extern "C" {
29 #include <vboot/vboot_host.h>
30 }
31 
32 #include "update_engine/common/boot_control.h"
33 #include "update_engine/common/subprocess.h"
34 #include "update_engine/common/utils.h"
35 
36 using std::string;
37 
38 namespace {
39 
40 const char* kChromeOSPartitionNameKernel = "kernel";
41 const char* kChromeOSPartitionNameRoot = "root";
42 const char* kAndroidPartitionNameKernel = "boot";
43 const char* kAndroidPartitionNameRoot = "system";
44 
45 // Returns the currently booted rootfs partition. "/dev/sda3", for example.
GetBootDevice()46 string GetBootDevice() {
47   char boot_path[PATH_MAX];
48   // Resolve the boot device path fully, including dereferencing through
49   // dm-verity.
50   int ret = rootdev(boot_path, sizeof(boot_path), true, false);
51   if (ret < 0) {
52     LOG(ERROR) << "rootdev failed to find the root device";
53     return "";
54   }
55   LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
56 
57   // This local variable is used to construct the return string and is not
58   // passed around after use.
59   return boot_path;
60 }
61 
62 // ExecCallback called when the execution of setgoodkernel finishes. Notifies
63 // the caller of MarkBootSuccessfullAsync() by calling |callback| with the
64 // result.
OnMarkBootSuccessfulDone(base::Callback<void (bool)> callback,int return_code,const string & output)65 void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
66                               int return_code,
67                               const string& output) {
68   callback.Run(return_code == 0);
69 }
70 
71 }  // namespace
72 
73 namespace chromeos_update_engine {
74 
75 namespace boot_control {
76 
77 // Factory defined in boot_control.h.
CreateBootControl()78 std::unique_ptr<BootControlInterface> CreateBootControl() {
79   std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
80       new BootControlChromeOS());
81   if (!boot_control_chromeos->Init()) {
82     LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
83   }
84   return brillo::make_unique_ptr(boot_control_chromeos.release());
85 }
86 
87 }  // namespace boot_control
88 
Init()89 bool BootControlChromeOS::Init() {
90   string boot_device = GetBootDevice();
91   if (boot_device.empty())
92     return false;
93 
94   int partition_num;
95   if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
96     return false;
97 
98   // All installed Chrome OS devices have two slots. We don't update removable
99   // devices, so we will pretend we have only one slot in that case.
100   if (IsRemovableDevice(boot_disk_name_)) {
101     LOG(INFO)
102         << "Booted from a removable device, pretending we have only one slot.";
103     num_slots_ = 1;
104   } else {
105     // TODO(deymo): Look at the actual number of slots reported in the GPT.
106     num_slots_ = 2;
107   }
108 
109   // Search through the slots to see which slot has the partition_num we booted
110   // from. This should map to one of the existing slots, otherwise something is
111   // very wrong.
112   current_slot_ = 0;
113   while (current_slot_ < num_slots_ &&
114          partition_num !=
115              GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
116     current_slot_++;
117   }
118   if (current_slot_ >= num_slots_) {
119     LOG(ERROR) << "Couldn't find the slot number corresponding to the "
120                   "partition " << boot_device
121                << ", number of slots: " << num_slots_
122                << ". This device is not updateable.";
123     num_slots_ = 1;
124     current_slot_ = BootControlInterface::kInvalidSlot;
125     return false;
126   }
127 
128   LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
129             << SlotName(current_slot_) << ") of " << num_slots_
130             << " slots present on disk " << boot_disk_name_;
131   return true;
132 }
133 
GetNumSlots() const134 unsigned int BootControlChromeOS::GetNumSlots() const {
135   return num_slots_;
136 }
137 
GetCurrentSlot() const138 BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
139   return current_slot_;
140 }
141 
GetPartitionDevice(const string & partition_name,unsigned int slot,string * device) const142 bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
143                                              unsigned int slot,
144                                              string* device) const {
145   int partition_num = GetPartitionNumber(partition_name, slot);
146   if (partition_num < 0)
147     return false;
148 
149   string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
150   if (part_device.empty())
151     return false;
152 
153   *device = part_device;
154   return true;
155 }
156 
IsSlotBootable(Slot slot) const157 bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
158   int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
159   if (partition_num < 0)
160     return false;
161 
162   CgptAddParams params;
163   memset(&params, '\0', sizeof(params));
164   params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
165   params.partition = partition_num;
166 
167   int retval = CgptGetPartitionDetails(&params);
168   if (retval != CGPT_OK)
169     return false;
170 
171   return params.successful || params.tries > 0;
172 }
173 
MarkSlotUnbootable(Slot slot)174 bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
175   LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
176 
177   if (slot == current_slot_) {
178     LOG(ERROR) << "Refusing to mark current slot as unbootable.";
179     return false;
180   }
181 
182   int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
183   if (partition_num < 0)
184     return false;
185 
186   CgptAddParams params;
187   memset(&params, 0, sizeof(params));
188 
189   params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
190   params.partition = partition_num;
191 
192   params.successful = false;
193   params.set_successful = true;
194 
195   params.tries = 0;
196   params.set_tries = true;
197 
198   int retval = CgptSetAttributes(&params);
199   if (retval != CGPT_OK) {
200     LOG(ERROR) << "Marking kernel unbootable failed.";
201     return false;
202   }
203 
204   return true;
205 }
206 
SetActiveBootSlot(Slot slot)207 bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
208   LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
209 
210   int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
211   if (partition_num < 0)
212     return false;
213 
214   CgptPrioritizeParams prio_params;
215   memset(&prio_params, 0, sizeof(prio_params));
216 
217   prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
218   prio_params.set_partition = partition_num;
219 
220   prio_params.max_priority = 0;
221 
222   int retval = CgptPrioritize(&prio_params);
223   if (retval != CGPT_OK) {
224     LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
225                << " (partition " << partition_num << ").";
226     return false;
227   }
228 
229   CgptAddParams add_params;
230   memset(&add_params, 0, sizeof(add_params));
231 
232   add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
233   add_params.partition = partition_num;
234 
235   add_params.tries = 6;
236   add_params.set_tries = true;
237 
238   retval = CgptSetAttributes(&add_params);
239   if (retval != CGPT_OK) {
240     LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
241                << " for slot " << SlotName(slot) << " (partition "
242                << partition_num << ").";
243     return false;
244   }
245 
246   return true;
247 }
248 
MarkBootSuccessfulAsync(base::Callback<void (bool)> callback)249 bool BootControlChromeOS::MarkBootSuccessfulAsync(
250     base::Callback<void(bool)> callback) {
251   return Subprocess::Get().Exec(
252              {"/usr/sbin/chromeos-setgoodkernel"},
253              base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
254 }
255 
256 // static
SysfsBlockDevice(const string & device)257 string BootControlChromeOS::SysfsBlockDevice(const string& device) {
258   base::FilePath device_path(device);
259   if (device_path.DirName().value() != "/dev") {
260     return "";
261   }
262   return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
263 }
264 
265 // static
IsRemovableDevice(const string & device)266 bool BootControlChromeOS::IsRemovableDevice(const string& device) {
267   string sysfs_block = SysfsBlockDevice(device);
268   string removable;
269   if (sysfs_block.empty() ||
270       !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
271                               &removable)) {
272     return false;
273   }
274   base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
275   return removable == "1";
276 }
277 
GetPartitionNumber(const string partition_name,BootControlInterface::Slot slot) const278 int BootControlChromeOS::GetPartitionNumber(
279     const string partition_name,
280     BootControlInterface::Slot slot) const {
281   if (slot >= num_slots_) {
282     LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
283                << num_slots_ << " slot(s)";
284     return -1;
285   }
286 
287   // In Chrome OS, the partition numbers are hard-coded:
288   //   KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
289   // To help compatibility between different we accept both lowercase and
290   // uppercase names in the ChromeOS or Brillo standard names.
291   // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
292   string partition_lower = base::ToLowerASCII(partition_name);
293   int base_part_num = 2 + 2 * slot;
294   if (partition_lower == kChromeOSPartitionNameKernel ||
295       partition_lower == kAndroidPartitionNameKernel)
296     return base_part_num + 0;
297   if (partition_lower == kChromeOSPartitionNameRoot ||
298       partition_lower == kAndroidPartitionNameRoot)
299     return base_part_num + 1;
300   LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
301   return -1;
302 }
303 
304 }  // namespace chromeos_update_engine
305