blob: 5f8f74e5ba2b2ce47e33122c5bbe0270941a21ef [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
17#include "update_engine/boot_control_chromeos.h"
18
19#include <string>
20
Alex Deymoaa26f622015-09-16 18:21:27 -070021#include <base/bind.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070022#include <base/files/file_path.h>
23#include <base/files/file_util.h>
24#include <base/strings/string_util.h>
Alex Deymob17327c2015-09-04 10:29:00 -070025#include <chromeos/make_unique_ptr.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070026#include <rootdev/rootdev.h>
27
28extern "C" {
29#include <vboot/vboot_host.h>
30}
31
Alex Deymob17327c2015-09-04 10:29:00 -070032#include "update_engine/boot_control.h"
Alex Deymoaa26f622015-09-16 18:21:27 -070033#include "update_engine/subprocess.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070034#include "update_engine/utils.h"
35
36using std::string;
37
38namespace {
39
40const char* kChromeOSPartitionNameKernel = "kernel";
41const char* kChromeOSPartitionNameRoot = "root";
42const char* kAndroidPartitionNameKernel = "boot";
43const char* kAndroidPartitionNameRoot = "system";
44
45// Returns the currently booted rootfs partition. "/dev/sda3", for example.
46string 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
Alex Deymoaa26f622015-09-16 18:21:27 -070062// ExecCallback called when the execution of setgoodkernel finishes. Notifies
63// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
64// result.
65void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
66 int return_code,
67 const string& output) {
68 callback.Run(return_code == 0);
69}
70
Alex Deymo763e7db2015-08-27 21:08:08 -070071} // namespace
72
73namespace chromeos_update_engine {
74
Alex Deymob17327c2015-09-04 10:29:00 -070075namespace boot_control {
76
77// Factory defined in boot_control.h.
78std::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 chromeos::make_unique_ptr(boot_control_chromeos.release());
85}
86
87} // namespace boot_control
88
Alex Deymo763e7db2015-08-27 21:08:08 -070089bool 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 << BootControlInterface::SlotName(current_slot_) << ") of "
130 << num_slots_ << " slots present on disk " << boot_disk_name_;
131 return true;
132}
133
134unsigned int BootControlChromeOS::GetNumSlots() const {
135 return num_slots_;
136}
137
138BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
139 return current_slot_;
140}
141
142bool 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
157bool 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
174bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
175 LOG(INFO) << "Marking slot " << BootControlInterface::SlotName(slot)
176 << " unbootable";
177
178 if (slot == current_slot_) {
179 LOG(ERROR) << "Refusing to mark current slot as unbootable.";
180 return false;
181 }
182
183 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
184 if (partition_num < 0)
185 return false;
186
187 CgptAddParams params;
188 memset(&params, 0, sizeof(params));
189
190 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
191 params.partition = partition_num;
192
193 params.successful = false;
194 params.set_successful = true;
195
196 params.tries = 0;
197 params.set_tries = true;
198
199 int retval = CgptSetAttributes(&params);
200 if (retval != CGPT_OK) {
201 LOG(ERROR) << "Marking kernel unbootable failed.";
202 return false;
203 }
204
205 return true;
206}
207
Alex Deymoaa26f622015-09-16 18:21:27 -0700208bool BootControlChromeOS::MarkBootSuccessfulAsync(
209 base::Callback<void(bool)> callback) {
210 return Subprocess::Get().Exec(
211 {"/usr/sbin/chromeos-setgoodkernel"},
212 base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
213}
214
Alex Deymo763e7db2015-08-27 21:08:08 -0700215// static
216string BootControlChromeOS::SysfsBlockDevice(const string& device) {
217 base::FilePath device_path(device);
218 if (device_path.DirName().value() != "/dev") {
219 return "";
220 }
221 return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
222}
223
224// static
225bool BootControlChromeOS::IsRemovableDevice(const string& device) {
226 string sysfs_block = SysfsBlockDevice(device);
227 string removable;
228 if (sysfs_block.empty() ||
229 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
230 &removable)) {
231 return false;
232 }
233 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
234 return removable == "1";
235}
236
237int BootControlChromeOS::GetPartitionNumber(
238 const string partition_name,
239 BootControlInterface::Slot slot) const {
240 if (slot >= num_slots_) {
241 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
242 << num_slots_ << " slot(s)";
243 return -1;
244 }
245
246 // In Chrome OS, the partition numbers are hard-coded:
247 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
248 // To help compatibility between different we accept both lowercase and
249 // uppercase names in the ChromeOS or Brillo standard names.
250 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
251 string partition_lower = base::StringToLowerASCII(partition_name);
252 int base_part_num = 2 + 2 * slot;
253 if (partition_lower == kChromeOSPartitionNameKernel ||
254 partition_lower == kAndroidPartitionNameKernel)
255 return base_part_num + 0;
256 if (partition_lower == kChromeOSPartitionNameRoot ||
257 partition_lower == kAndroidPartitionNameRoot)
258 return base_part_num + 1;
259 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
260 return -1;
261}
262
263} // namespace chromeos_update_engine