blob: 03e1c2679fa46c21883bf8ebbdd0361dff59830a [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
21#include <base/files/file_path.h>
22#include <base/files/file_util.h>
23#include <base/strings/string_util.h>
Alex Deymob17327c2015-09-04 10:29:00 -070024#include <chromeos/make_unique_ptr.h>
Alex Deymo763e7db2015-08-27 21:08:08 -070025#include <rootdev/rootdev.h>
26
27extern "C" {
28#include <vboot/vboot_host.h>
29}
30
Alex Deymob17327c2015-09-04 10:29:00 -070031#include "update_engine/boot_control.h"
Alex Deymo763e7db2015-08-27 21:08:08 -070032#include "update_engine/utils.h"
33
34using std::string;
35
36namespace {
37
38const char* kChromeOSPartitionNameKernel = "kernel";
39const char* kChromeOSPartitionNameRoot = "root";
40const char* kAndroidPartitionNameKernel = "boot";
41const char* kAndroidPartitionNameRoot = "system";
42
43// Returns the currently booted rootfs partition. "/dev/sda3", for example.
44string GetBootDevice() {
45 char boot_path[PATH_MAX];
46 // Resolve the boot device path fully, including dereferencing through
47 // dm-verity.
48 int ret = rootdev(boot_path, sizeof(boot_path), true, false);
49 if (ret < 0) {
50 LOG(ERROR) << "rootdev failed to find the root device";
51 return "";
52 }
53 LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
54
55 // This local variable is used to construct the return string and is not
56 // passed around after use.
57 return boot_path;
58}
59
60} // namespace
61
62namespace chromeos_update_engine {
63
Alex Deymob17327c2015-09-04 10:29:00 -070064namespace boot_control {
65
66// Factory defined in boot_control.h.
67std::unique_ptr<BootControlInterface> CreateBootControl() {
68 std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
69 new BootControlChromeOS());
70 if (!boot_control_chromeos->Init()) {
71 LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
72 }
73 return chromeos::make_unique_ptr(boot_control_chromeos.release());
74}
75
76} // namespace boot_control
77
Alex Deymo763e7db2015-08-27 21:08:08 -070078bool BootControlChromeOS::Init() {
79 string boot_device = GetBootDevice();
80 if (boot_device.empty())
81 return false;
82
83 int partition_num;
84 if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
85 return false;
86
87 // All installed Chrome OS devices have two slots. We don't update removable
88 // devices, so we will pretend we have only one slot in that case.
89 if (IsRemovableDevice(boot_disk_name_)) {
90 LOG(INFO)
91 << "Booted from a removable device, pretending we have only one slot.";
92 num_slots_ = 1;
93 } else {
94 // TODO(deymo): Look at the actual number of slots reported in the GPT.
95 num_slots_ = 2;
96 }
97
98 // Search through the slots to see which slot has the partition_num we booted
99 // from. This should map to one of the existing slots, otherwise something is
100 // very wrong.
101 current_slot_ = 0;
102 while (current_slot_ < num_slots_ &&
103 partition_num !=
104 GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
105 current_slot_++;
106 }
107 if (current_slot_ >= num_slots_) {
108 LOG(ERROR) << "Couldn't find the slot number corresponding to the "
109 "partition " << boot_device
110 << ", number of slots: " << num_slots_
111 << ". This device is not updateable.";
112 num_slots_ = 1;
113 current_slot_ = BootControlInterface::kInvalidSlot;
114 return false;
115 }
116
117 LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
118 << BootControlInterface::SlotName(current_slot_) << ") of "
119 << num_slots_ << " slots present on disk " << boot_disk_name_;
120 return true;
121}
122
123unsigned int BootControlChromeOS::GetNumSlots() const {
124 return num_slots_;
125}
126
127BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
128 return current_slot_;
129}
130
131bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
132 unsigned int slot,
133 string* device) const {
134 int partition_num = GetPartitionNumber(partition_name, slot);
135 if (partition_num < 0)
136 return false;
137
138 string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
139 if (part_device.empty())
140 return false;
141
142 *device = part_device;
143 return true;
144}
145
146bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
147 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
148 if (partition_num < 0)
149 return false;
150
151 CgptAddParams params;
152 memset(&params, '\0', sizeof(params));
153 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
154 params.partition = partition_num;
155
156 int retval = CgptGetPartitionDetails(&params);
157 if (retval != CGPT_OK)
158 return false;
159
160 return params.successful || params.tries > 0;
161}
162
163bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
164 LOG(INFO) << "Marking slot " << BootControlInterface::SlotName(slot)
165 << " unbootable";
166
167 if (slot == current_slot_) {
168 LOG(ERROR) << "Refusing to mark current slot as unbootable.";
169 return false;
170 }
171
172 int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
173 if (partition_num < 0)
174 return false;
175
176 CgptAddParams params;
177 memset(&params, 0, sizeof(params));
178
179 params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
180 params.partition = partition_num;
181
182 params.successful = false;
183 params.set_successful = true;
184
185 params.tries = 0;
186 params.set_tries = true;
187
188 int retval = CgptSetAttributes(&params);
189 if (retval != CGPT_OK) {
190 LOG(ERROR) << "Marking kernel unbootable failed.";
191 return false;
192 }
193
194 return true;
195}
196
197// static
198string BootControlChromeOS::SysfsBlockDevice(const string& device) {
199 base::FilePath device_path(device);
200 if (device_path.DirName().value() != "/dev") {
201 return "";
202 }
203 return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
204}
205
206// static
207bool BootControlChromeOS::IsRemovableDevice(const string& device) {
208 string sysfs_block = SysfsBlockDevice(device);
209 string removable;
210 if (sysfs_block.empty() ||
211 !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
212 &removable)) {
213 return false;
214 }
215 base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
216 return removable == "1";
217}
218
219int BootControlChromeOS::GetPartitionNumber(
220 const string partition_name,
221 BootControlInterface::Slot slot) const {
222 if (slot >= num_slots_) {
223 LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
224 << num_slots_ << " slot(s)";
225 return -1;
226 }
227
228 // In Chrome OS, the partition numbers are hard-coded:
229 // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
230 // To help compatibility between different we accept both lowercase and
231 // uppercase names in the ChromeOS or Brillo standard names.
232 // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
233 string partition_lower = base::StringToLowerASCII(partition_name);
234 int base_part_num = 2 + 2 * slot;
235 if (partition_lower == kChromeOSPartitionNameKernel ||
236 partition_lower == kAndroidPartitionNameKernel)
237 return base_part_num + 0;
238 if (partition_lower == kChromeOSPartitionNameRoot ||
239 partition_lower == kAndroidPartitionNameRoot)
240 return base_part_num + 1;
241 LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
242 return -1;
243}
244
245} // namespace chromeos_update_engine