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