boot_control_copy: A boot_control HAL implementation that works by copying.

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.

Change-Id: Ifcc1ff19530f9e6db1de882807a25c785e1959de
diff --git a/boot_control_copy/Android.mk b/boot_control_copy/Android.mk
new file mode 100644
index 0000000..0027c10
--- /dev/null
+++ b/boot_control_copy/Android.mk
@@ -0,0 +1,16 @@
+# Copyright 2015 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := boot_control_copy.c bootinfo.h bootinfo.c
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+LOCAL_C_INCLUDES := system/core/mkbootimg bootable/recovery
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_STATIC_LIBRARIES := libfs_mgr
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_MODULE:= bootctrl.default
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/boot_control_copy/boot_control_copy.c b/boot_control_copy/boot_control_copy.c
new file mode 100644
index 0000000..a5deb5a
--- /dev/null
+++ b/boot_control_copy/boot_control_copy.c
@@ -0,0 +1,260 @@
+/*
+ * 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;
+}
+
+unsigned module_getCurrentSlot(boot_control_module_t *module)
+{
+  BrilloBootInfo info;
+
+  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.active_slot;
+}
+
+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,
+};
diff --git a/boot_control_copy/bootinfo.c b/boot_control_copy/bootinfo.c
new file mode 100644
index 0000000..e3c2412
--- /dev/null
+++ b/boot_control_copy/bootinfo.c
@@ -0,0 +1,188 @@
+/*
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cutils/properties.h>
+
+#include <bootloader.h>
+#include <fs_mgr.h>
+
+#include "bootinfo.h"
+
+// Open the appropriate fstab file and fallback to /fstab.device if
+// that's what's being used.
+static struct fstab *open_fstab(void)
+{
+  char propbuf[PROPERTY_VALUE_MAX];
+  char fstab_name[PROPERTY_VALUE_MAX + 32];
+  struct fstab *fstab;
+
+  property_get("ro.hardware", propbuf, "");
+  snprintf(fstab_name, sizeof(fstab_name), "/fstab.%s", propbuf);
+  fstab = fs_mgr_read_fstab(fstab_name);
+  if (fstab != NULL)
+    return fstab;
+
+  fstab = fs_mgr_read_fstab("/fstab.device");
+  return fstab;
+}
+
+int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
+{
+  char *path;
+  int fd;
+  struct fstab *fstab;
+  struct fstab_rec *record;
+
+  // We can't use fs_mgr to look up |name| because fstab doesn't list
+  // every slot partition (it uses the slotselect option to mask the
+  // suffix) and |slot| is expected to be of that form, e.g. boot_a.
+  //
+  // We can however assume that there's an entry for the /misc mount
+  // point and use that to get the device file for the misc
+  // partition. From there we'll assume that a by-name scheme is used
+  // so we can just replace the trailing "misc" by the given |name|,
+  // e.g.
+  //
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+  //
+  // If needed, it's possible to relax this assumption in the future
+  // by trawling /sys/block looking for the appropriate sibling of
+  // misc and then finding an entry in /dev matching the sysfs entry.
+
+  fstab = open_fstab();
+  if (fstab == NULL)
+    return -1;
+  record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+  if (record == NULL) {
+    fs_mgr_free_fstab(fstab);
+    return -1;
+  }
+  if (strcmp(name, "misc") == 0) {
+    path = strdup(record->blk_device);
+  } else {
+    size_t trimmed_len, name_len;
+    const char *end_slash = strrchr(record->blk_device, '/');
+    if (end_slash == NULL) {
+      fs_mgr_free_fstab(fstab);
+      return -1;
+    }
+    trimmed_len = end_slash - record->blk_device + 1;
+    name_len = strlen(name);
+    path = calloc(trimmed_len + name_len + 1, 1);
+    strncpy(path, record->blk_device, trimmed_len);
+    strncpy(path + trimmed_len, name, name_len);
+  }
+  fs_mgr_free_fstab(fstab);
+
+  fd = open(path, flags);
+  free(path);
+
+  // If we successfully opened the device, get size if requested.
+  if (fd != -1 && out_size != NULL) {
+    if (ioctl(fd, BLKGETSIZE64, out_size) != 0) {
+      close(fd);
+      return -1;
+    }
+  }
+
+  return fd;
+}
+
+// As per struct bootloader_message which is defined in
+// bootable/recovery/bootloader.h we can use the 32 bytes in the
+// bootctrl_suffix field provided that they start with the active slot
+// suffix terminated by NUL. It just so happens that BrilloBootInfo is
+// laid out this way.
+#define BOOTINFO_OFFSET offsetof(struct bootloader_message, slot_suffix)
+
+bool boot_info_load(BrilloBootInfo *out_info)
+{
+  int fd;
+
+  memset(out_info, '\0', sizeof(BrilloBootInfo));
+
+  fd = boot_info_open_partition("misc", NULL, O_RDONLY);
+  if (fd == -1)
+    return false;
+  if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
+    close(fd);
+    return false;
+  }
+  ssize_t num_read;
+  do {
+    num_read = read(fd, (void*) out_info, sizeof(BrilloBootInfo));
+  } while (num_read == -1 && errno == EINTR);
+  close(fd);
+  if (num_read != sizeof(BrilloBootInfo))
+    return false;
+  return true;
+}
+
+bool boot_info_save(BrilloBootInfo *info)
+{
+  int fd;
+
+  fd = boot_info_open_partition("misc", NULL, O_RDWR);
+  if (fd == -1)
+    return false;
+  if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
+    close(fd);
+    return false;
+  }
+  ssize_t num_written;
+  do {
+    num_written = write(fd, (void*) info, sizeof(BrilloBootInfo));
+  } while (num_written == -1 && errno == EINTR);
+  close(fd);
+  if (num_written != sizeof(BrilloBootInfo))
+    return false;
+  return true;
+}
+
+bool boot_info_validate(BrilloBootInfo* info)
+{
+  if (info->magic[0] != 'B' ||
+      info->magic[1] != 'C' ||
+      info->magic[2] != 'c')
+    return false;
+  if (info->active_slot >= 2)
+    return false;
+  return true;
+}
+
+void boot_info_reset(BrilloBootInfo* info)
+{
+  size_t n;
+  memset(info, '\0', sizeof(BrilloBootInfo));
+  info->magic[0] = 'B';
+  info->magic[1] = 'C';
+  info->magic[2] = 'c';
+}
diff --git a/boot_control_copy/bootinfo.h b/boot_control_copy/bootinfo.h
new file mode 100644
index 0000000..4b36b2c
--- /dev/null
+++ b/boot_control_copy/bootinfo.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef BOOTINFO_H_
+#define BOOTINFO_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct BrilloSlotInfo {
+  uint8_t bootable : 1;
+  uint8_t reserved[3];
+} BrilloSlotInfo;
+
+typedef struct BrilloBootInfo {
+  // Used by fs_mgr. Must be NUL terminated.
+  char bootctrl_suffix[4];
+
+  // Magic for identification - must be 'B', 'C', 'c' (short for
+  // "boot_control copy" implementation).
+  uint8_t magic[3];
+
+  // Version of BrilloBootInfo struct, must be 0 or larger.
+  uint8_t version;
+
+  // Currently active slot.
+  uint8_t active_slot;
+
+  // Information about each slot.
+  BrilloSlotInfo slot_info[2];
+
+  uint8_t reserved[15];
+} BrilloBootInfo;
+
+// Loading and saving BrillBootInfo instances.
+bool boot_info_load(BrilloBootInfo *out_info);
+bool boot_info_save(BrilloBootInfo *info);
+
+// Returns non-zero if valid.
+bool boot_info_validate(BrilloBootInfo* info);
+void boot_info_reset(BrilloBootInfo* info);
+
+// Opens partition by |name|, e.g. "misc" or "boot_a" with |flags|
+// (e.g. O_RDONLY or O_RDWR) passed directly to open(2). Returns fd on
+// success and -1 on error.
+int boot_info_open_partition(const char *name, uint64_t *out_size, int flags);
+
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
+_Static_assert(sizeof(BrilloBootInfo) == 32, "BrilloBootInfo has wrong size");
+#endif
+
+#endif  // BOOTINFO_H