/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <fs_mgr.h>
#include <hardware/hardware.h>
#include <hardware/boot_control.h>
#include "bootinfo.h"
void module_init(boot_control_module_t *module)
{
}
unsigned module_getNumberSlots(boot_control_module_t *module)
{
return 2;
}
static bool get_dev_t_for_partition(const char *name, dev_t *out_device)
{
int fd;
struct stat statbuf;
fd = boot_info_open_partition(name, NULL, O_RDONLY);
if (fd == -1)
return false;
if (fstat(fd, &statbuf) != 0) {
fprintf(stderr, "WARNING: Error getting information about part %s: %s\n",
name, strerror(errno));
close(fd);
return false;
}
close(fd);
*out_device = statbuf.st_rdev;
return true;
}
unsigned module_getCurrentSlot(boot_control_module_t *module)
{
struct stat statbuf;
dev_t system_a_dev, system_b_dev;
if (stat("/system", &statbuf) != 0) {
fprintf(stderr, "WARNING: Error getting information about /system: %s\n",
strerror(errno));
return 0;
}
if (!get_dev_t_for_partition("system_a", &system_a_dev) ||
!get_dev_t_for_partition("system_b", &system_b_dev))
return 0;
if (statbuf.st_dev == system_a_dev) {
return 0;
} else if (statbuf.st_dev == system_b_dev) {
return 1;
} else {
fprintf(stderr, "WARNING: Error determining current slot "
"(/system dev_t of %d:%d does not match a=%d:%d or b=%d:%d)\n",
major(statbuf.st_dev), minor(statbuf.st_dev),
major(system_a_dev), minor(system_a_dev),
major(system_b_dev), minor(system_b_dev));
return 0;
}
}
int module_markBootSuccessful(boot_control_module_t *module)
{
return 0;
}
#define COPY_BUF_SIZE (1024*1024)
static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
{
char copy_buf[COPY_BUF_SIZE];
size_t remaining;
remaining = num_bytes;
while (remaining > 0) {
size_t num_to_read = remaining > COPY_BUF_SIZE ? COPY_BUF_SIZE : remaining;
ssize_t num_read;
do {
num_read = read(src_fd, copy_buf, num_to_read);
} while (num_read == -1 && errno == EINTR);
if (num_read <= 0) {
fprintf(stderr, "Error reading %zd bytes from source: %s\n",
num_to_read, strerror(errno));
return false;
}
size_t num_to_write = num_read;
while (num_to_write > 0) {
size_t offset = num_read - num_to_write;
ssize_t num_written;
do {
num_written = write(dst_fd, copy_buf + offset, num_to_write);
} while (num_written == -1 && errno == EINTR);
if (num_written <= 0) {
fprintf(stderr, "Error writing %zd bytes to destination: %s\n",
num_to_write, strerror(errno));
return false;
}
num_to_write -= num_written;
}
remaining -= num_read;
}
return true;
}
int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
{
BrilloBootInfo info;
int src_fd, dst_fd;
uint64_t src_size, dst_size;
char src_name[32];
if (slot >= 2)
return -EINVAL;
if (!boot_info_load(&info)) {
fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
boot_info_reset(&info);
} else {
if (!boot_info_validate(&info)) {
fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
boot_info_reset(&info);
}
}
info.active_slot = slot;
info.slot_info[slot].bootable = true;
snprintf(info.bootctrl_suffix,
sizeof(info.bootctrl_suffix),
"_%c", slot + 'a');
if (!boot_info_save(&info)) {
fprintf(stderr, "Error saving boot-info.\n");
return -errno;
}
// Finally copy the contents of boot_X into boot.
snprintf(src_name, sizeof(src_name), "boot_%c", slot + 'a');
src_fd = boot_info_open_partition(src_name, &src_size, O_RDONLY);
if (src_fd == -1) {
fprintf(stderr, "Error opening \"%s\" partition.\n", src_name);
return -errno;
}
dst_fd = boot_info_open_partition("boot", &dst_size, O_RDWR);
if (dst_fd == -1) {
fprintf(stderr, "Error opening \"boot\" partition.\n");
close(src_fd);
return -errno;
}
if (src_size != dst_size) {
fprintf(stderr,
"src (%" PRIu64 " bytes) and dst (%" PRIu64 " bytes) "
"have different sizes.\n",
src_size, dst_size);
close(src_fd);
close(dst_fd);
return -EINVAL;
}
if (!copy_data(src_fd, dst_fd, src_size)) {
close(src_fd);
close(dst_fd);
return -errno;
}
if (fsync(dst_fd) != 0) {
fprintf(stderr, "Error calling fsync on destination: %s\n",
strerror(errno));
return -errno;
}
close(src_fd);
close(dst_fd);
return 0;
}
int module_setSlotAsUnbootable(struct boot_control_module *module, unsigned slot)
{
BrilloBootInfo info;
if (slot >= 2)
return -EINVAL;
if (!boot_info_load(&info)) {
fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
boot_info_reset(&info);
} else {
if (!boot_info_validate(&info)) {
fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
boot_info_reset(&info);
}
}
info.slot_info[slot].bootable = false;
if (!boot_info_save(&info)) {
fprintf(stderr, "Error saving boot-info.\n");
return -errno;
}
return 0;
}
int module_isSlotBootable(struct boot_control_module *module, unsigned slot)
{
BrilloBootInfo info;
if (slot >= 2)
return -EINVAL;
if (!boot_info_load(&info)) {
fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
boot_info_reset(&info);
} else {
if (!boot_info_validate(&info)) {
fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
boot_info_reset(&info);
}
}
return info.slot_info[slot].bootable;
}
const char* module_getSuffix(boot_control_module_t *module, unsigned slot)
{
static const char* suffix[2] = {"_a", "_b"};
if (slot >= 2)
return NULL;
return suffix[slot];
}
static struct hw_module_methods_t module_methods = {
.open = NULL,
};
/* This boot_control HAL implementation emulates A/B by copying the
* contents of the boot partition of the requested slot to the boot
* partition. It hence works with bootloaders that are not yet aware
* of A/B. This code is only intended to be used for development.
*/
boot_control_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = BOOT_CONTROL_HARDWARE_MODULE_ID,
.name = "Copy Implementation of boot_control HAL",
.author = "The Android Open Source Project",
.methods = &module_methods,
},
.init = module_init,
.getNumberSlots = module_getNumberSlots,
.getCurrentSlot = module_getCurrentSlot,
.markBootSuccessful = module_markBootSuccessful,
.setActiveBootSlot = module_setActiveBootSlot,
.setSlotAsUnbootable = module_setSlotAsUnbootable,
.isSlotBootable = module_isSlotBootable,
.getSuffix = module_getSuffix,
};