Add fwu copy_installer

The copy_installer can be used to copy the contents from a source
volume into a destination volume. It is intended to be used to
handle the case where an incoming update package only describes
a partial update. Images not included in the update need to be
installed into the update bank using a copy of the previously
active bank.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: Ibd2778b6efa5533adfb5fb309e2661e743d2dbff
diff --git a/components/service/fwu/installer/copy/component.cmake b/components/service/fwu/installer/copy/component.cmake
new file mode 100644
index 0000000..6703209
--- /dev/null
+++ b/components/service/fwu/installer/copy/component.cmake
@@ -0,0 +1,16 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/copy_installer.c"
+	)
+
+target_compile_definitions(${TGT} PRIVATE
+	COPY_INSTALLER_AVAILABLE)
\ No newline at end of file
diff --git a/components/service/fwu/installer/copy/copy_installer.c b/components/service/fwu/installer/copy/copy_installer.c
new file mode 100644
index 0000000..3ef19f3
--- /dev/null
+++ b/components/service/fwu/installer/copy/copy_installer.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <util.h>
+#include <media/volume/index/volume_index.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include "copy_installer.h"
+
+#define COPY_CHUNK_SIZE		(4096)
+
+
+static int close_volumes_on_error(
+	struct copy_installer *subject)
+{
+	volume_close(subject->source_volume);
+	volume_close(subject->destination_volume);
+
+	return FWU_STATUS_UNKNOWN;
+}
+
+static int copy_volume_contents(
+	struct copy_installer *subject,
+	size_t target_copy_len)
+{
+	int status = FWU_STATUS_SUCCESS;
+	size_t copy_len = 0;
+	uint8_t *copy_buf = malloc(COPY_CHUNK_SIZE);
+
+	if (!copy_buf)
+		return FWU_STATUS_UNKNOWN;
+
+	while (copy_len < target_copy_len) {
+
+		size_t actual_read_len = 0;
+		size_t actual_write_len = 0;
+		size_t remaining_len = target_copy_len - copy_len;
+		size_t requested_read_len = (remaining_len < COPY_CHUNK_SIZE) ?
+			remaining_len : COPY_CHUNK_SIZE;
+
+		status = volume_read(
+			subject->source_volume,
+			(uintptr_t)copy_buf,
+			requested_read_len,
+			&actual_read_len);
+
+		if (status)
+			break;
+
+		status = volume_write(
+			subject->destination_volume,
+			(const uintptr_t)copy_buf,
+			actual_read_len,
+			&actual_write_len);
+
+		if (status)
+			break;
+
+		if (actual_read_len != actual_write_len) {
+			status = FWU_STATUS_UNKNOWN;
+			break;
+		}
+
+		copy_len += actual_read_len;
+	}
+
+	free(copy_buf);
+
+	return status;
+}
+
+static int copy_installer_begin(void *context,
+	unsigned int current_volume_id,
+	unsigned int update_volume_id)
+{
+	struct copy_installer *subject = (struct copy_installer *)context;
+
+	int status = volume_index_find(
+		update_volume_id,
+		&subject->destination_volume);
+
+	if (status == 0) {
+
+		status = volume_index_find(
+			current_volume_id,
+			&subject->source_volume);
+	}
+
+	if (status != 0) {
+
+		subject->destination_volume = NULL;
+		subject->source_volume = NULL;
+	}
+
+	return status;
+}
+
+static int copy_installer_finalize(void *context)
+{
+	struct copy_installer *subject = (struct copy_installer *)context;
+
+	assert(subject->source_volume);
+	assert(subject->destination_volume);
+
+	/* Open the source and destination volumes */
+	int source_status = volume_open(subject->source_volume);
+
+	if (source_status)
+		return source_status;
+
+	int destination_status = volume_open(subject->destination_volume);
+
+	if (destination_status) {
+
+		volume_close(subject->source_volume);
+		return destination_status;
+	}
+
+	/* Prepare the destination volume for writes */
+	destination_status = volume_erase(subject->destination_volume);
+
+	if (destination_status)
+		return close_volumes_on_error(subject);
+
+	/* Determine how much to copy */
+	size_t source_size = 0;
+	size_t destination_size = 0;
+
+	source_status = volume_size(subject->source_volume, &source_size);
+
+	if (source_status)
+		return close_volumes_on_error(subject);
+
+	destination_status = volume_size(subject->source_volume, &destination_size);
+
+	if (destination_status)
+		return close_volumes_on_error(subject);
+
+	/* Perform the copy */
+	int copy_status = copy_volume_contents(subject, MIN(source_size, destination_size));
+
+	/* All done */
+	source_status = volume_close(subject->source_volume);
+	destination_status = volume_close(subject->destination_volume);
+
+	return
+		(copy_status) ? copy_status :
+		(destination_status) ? destination_status :
+		(source_status) ? source_status :
+		FWU_STATUS_SUCCESS;
+}
+
+static void copy_installer_abort(void *context)
+{
+	struct copy_installer *subject = (struct copy_installer *)context;
+
+	subject->source_volume = NULL;
+	subject->destination_volume = NULL;
+}
+
+static int copy_installer_open(void *context,
+	const struct image_info *image_info)
+{
+	(void)context;
+	(void)image_info;
+
+	return FWU_STATUS_DENIED;
+}
+
+static int copy_installer_commit(void *context)
+{
+	(void)context;
+
+	return FWU_STATUS_DENIED;
+}
+
+static int copy_installer_write(void *context,
+	const uint8_t *data,
+	size_t data_len)
+{
+	(void)context;
+	(void)data;
+	(void)data_len;
+
+	return FWU_STATUS_DENIED;
+}
+
+static int copy_installer_enumerate(void *context,
+	uint32_t volume_id,
+	struct fw_directory *fw_directory)
+{
+	(void)volume_id;
+	(void)fw_directory;
+
+	/* A copy_installer can never be used to install externally provided images
+	 * don't advertise any images via the fw_directory. Just quietly return success.
+	 */
+	return FWU_STATUS_SUCCESS;
+}
+
+void copy_installer_init(struct copy_installer *subject,
+	const struct uuid_octets *location_uuid,
+	uint32_t location_id)
+{
+	/* Define concrete installer interface */
+	static const struct installer_interface interface = {
+		copy_installer_begin,
+		copy_installer_finalize,
+		copy_installer_abort,
+		copy_installer_open,
+		copy_installer_commit,
+		copy_installer_write,
+		copy_installer_enumerate
+	};
+
+	/* Initialize base installer - a copy_installer is a type of
+	 * installer that always updates a whole volume by copying
+	 * from another.
+	 */
+	installer_init(&subject->base_installer,
+		INSTALL_TYPE_WHOLE_VOLUME_COPY,
+		location_id,
+		location_uuid,
+		subject, &interface);
+
+	/* Initialize copy_installer specifics */
+	subject->source_volume = NULL;
+	subject->destination_volume = NULL;
+}
+
+void copy_installer_deinit(struct copy_installer *subject)
+{
+	(void)subject;
+}
diff --git a/components/service/fwu/installer/copy/copy_installer.h b/components/service/fwu/installer/copy/copy_installer.h
new file mode 100644
index 0000000..c59745b
--- /dev/null
+++ b/components/service/fwu/installer/copy/copy_installer.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FWU_COPY_INSTALLER_H
+#define FWU_COPY_INSTALLER_H
+
+#include <stdint.h>
+#include <service/fwu/installer/installer.h>
+#include <media/volume/volume.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief copy_installer structure definition
+ *
+ * A copy_installer is an installer that copies previously installed images
+ * from a source volume into a destination volume. A copy_installer does not
+ * consume externally provided installation data. A copy_installer may be
+ * used in situations where an update has resulted in no change to the
+ * firmware for a subsystem (location) but it is necessary to copy image
+ * data to the update bank. The copy_installer has no knowledge of the
+ * actual size of image data that needs to copied so the entire source
+ * volume is copied to the destination. This will potentially be wasteful
+ * in that unnecessary data may be copied.
+ */
+struct copy_installer {
+	struct installer base_installer;
+	struct volume *destination_volume;
+	struct volume *source_volume;
+};
+
+/**
+ * \brief Initialize a copy_installer
+ *
+ * \param[in]  subject    The subject copy_installer
+ * \param[in]  location_uuid The associated location UUID
+ * \param[in]  location_id Identifies where to install qualifying images
+ */
+void copy_installer_init(struct copy_installer *subject,
+	const struct uuid_octets *location_uuid,
+	uint32_t location_id);
+
+/**
+ * \brief De-initialize a copy_installer
+ *
+ * \param[in]  subject    The subject copy_installer
+ */
+void copy_installer_deinit(struct copy_installer *subject);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FWU_COPY_INSTALLER_H */
diff --git a/components/service/fwu/installer/copy/test/component.cmake b/components/service/fwu/installer/copy/test/component.cmake
new file mode 100644
index 0000000..61a12ad
--- /dev/null
+++ b/components/service/fwu/installer/copy/test/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/copy_installer_tests.cpp"
+	)
diff --git a/components/service/fwu/installer/copy/test/copy_installer_tests.cpp b/components/service/fwu/installer/copy/test/copy_installer_tests.cpp
new file mode 100644
index 0000000..3a51033
--- /dev/null
+++ b/components/service/fwu/installer/copy/test/copy_installer_tests.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstdlib>
+#include <cstring>
+#include <common/uuid/uuid.h>
+#include <media/disk/guid.h>
+#include <media/volume/block_volume/block_volume.h>
+#include <media/volume/index/volume_index.h>
+#include <media/volume/volume.h>
+#include <service/block_storage/factory/ref_ram_gpt/block_store_factory.h>
+#include <service/block_storage/config/ref/ref_partition_configurator.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <service/fwu/installer/copy/copy_installer.h>
+#include <service/fwu/installer/raw/raw_installer.h>
+#include <service/fwu/installer/installer_index.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include <CppUTest/TestHarness.h>
+
+TEST_GROUP(FwuCopyInstallerTests)
+{
+	void setup()
+	{
+		int result;
+		struct uuid_octets partition_guid;
+
+		m_image = NULL;
+
+		installer_index_init();
+		volume_index_init();
+
+		/* Use the reference disk configuration and use partition 1 & 2 as
+		 * storage for A and B firmware banks.
+		 */
+		m_block_store = ref_ram_gpt_block_store_factory_create();
+
+		/* Construct fw volume A */
+		uuid_guid_octets_from_canonical(&partition_guid, REF_PARTITION_1_GUID);
+
+		result = block_volume_init(&m_block_volume_a,
+			m_block_store, &partition_guid,
+			&m_fw_volume_a);
+
+		LONGS_EQUAL(0, result);
+		CHECK_TRUE(m_fw_volume_a);
+
+		/* Construct fw volume B */
+		uuid_guid_octets_from_canonical(&partition_guid, REF_PARTITION_2_GUID);
+
+		result = block_volume_init(&m_block_volume_b,
+			m_block_store, &partition_guid,
+			&m_fw_volume_b);
+
+		LONGS_EQUAL(0, result);
+		CHECK_TRUE(m_fw_volume_b);
+
+		/* Prepare an image_info structure to describe the image to
+		 * install into one of volumes.
+		 */
+		uuid_guid_octets_from_canonical(&m_image_info.img_type_uuid,
+			"1c22ca2c-9732-49e6-ba3b-eed40e27fda3");
+
+		m_image_info.max_size =
+			(REF_PARTITION_1_ENDING_LBA - REF_PARTITION_1_STARTING_LBA + 1) *
+			REF_PARTITION_BLOCK_SIZE;
+		m_image_info.lowest_accepted_version = 1;
+		m_image_info.active_version = 1;
+		m_image_info.permissions = 0;
+		m_image_info.image_index = 0;
+		m_image_info.location_id = FW_STORE_LOCATION_ID;
+		m_image_info.install_type = INSTALL_TYPE_WHOLE_VOLUME;
+
+		/* Mimic a platform configuration where storage volumes are assigned
+		 * location and usage IDs. These usage IDs correspond to an A/B banked
+		 * firmware store.
+		 */
+		volume_index_add(
+			banked_volume_id(FW_STORE_LOCATION_ID, BANKED_USAGE_ID_FW_BANK_A),
+			m_fw_volume_a);
+		volume_index_add(
+			banked_volume_id(FW_STORE_LOCATION_ID, BANKED_USAGE_ID_FW_BANK_B),
+			m_fw_volume_b);
+
+		/* A platform configuration will also determine which installers are
+		 * assigned to which locations. For these tests, there is a raw_installer
+		 * to install the initial image and a copy_installer to install a copy
+		 * into the other bank.
+		 */
+		raw_installer_init(&m_raw_installer,
+			&m_image_info.img_type_uuid, FW_STORE_LOCATION_ID);
+		installer_index_register(&m_raw_installer.base_installer);
+
+		copy_installer_init(&m_copy_installer,
+			&m_image_info.img_type_uuid, FW_STORE_LOCATION_ID);
+		installer_index_register(&m_copy_installer.base_installer);
+	}
+
+	void teardown()
+	{
+		delete[] m_image;
+
+		raw_installer_deinit(&m_raw_installer);
+		copy_installer_deinit(&m_copy_installer);
+
+		installer_index_clear();
+		volume_index_clear();
+
+		block_volume_deinit(&m_block_volume_a);
+		block_volume_deinit(&m_block_volume_b);
+		ref_ram_gpt_block_store_factory_destroy(m_block_store);
+	}
+
+	void create_image(size_t len)
+	{
+		m_image = new uint8_t[len];
+		m_image_len = len;
+
+		for (size_t i = 0; i < len; i++)
+			m_image[i] = (uint8_t)rand();
+	}
+
+	void install_initial_image(size_t len)
+	{
+		create_image(len);
+
+		/* Expect to find a suitable installer for the given image_info */
+		struct installer *installer = installer_index_find(
+			m_image_info.install_type, m_image_info.location_id);
+		CHECK_TRUE(installer);
+		UNSIGNED_LONGS_EQUAL(FW_STORE_LOCATION_ID, installer->location_id);
+
+		/* Begin installation transaction - installing into volume A */
+		int status = installer_begin(installer,
+			banked_volume_id(FW_STORE_LOCATION_ID,
+				BANKED_USAGE_ID_FW_BANK_B),  /* Current volume */
+			banked_volume_id(FW_STORE_LOCATION_ID,
+				BANKED_USAGE_ID_FW_BANK_A)); /* Update volume */
+		LONGS_EQUAL(0, status);
+
+		status = installer_open(installer, &m_image_info);
+		LONGS_EQUAL(0, status);
+
+		status = installer_write(installer, m_image, m_image_len);
+		LONGS_EQUAL(0, status);
+
+		status = installer_commit(installer);
+		LONGS_EQUAL(0, status);
+
+		status = installer_finalize(installer);
+		LONGS_EQUAL(0, status);
+
+		check_update_installed(m_fw_volume_a);
+	}
+
+	void check_update_installed(struct volume *volume)
+	{
+		int status = 0;
+		size_t total_read = 0;
+
+		status = volume_open(volume);
+		LONGS_EQUAL(0, status);
+
+		while (total_read < m_image_len) {
+
+			uint8_t read_buf[1000];
+			size_t len_read = 0;
+			size_t bytes_remaining = m_image_len - total_read;
+			size_t req_len = (bytes_remaining > sizeof(read_buf)) ?
+				sizeof(read_buf) : bytes_remaining;
+
+			memset(read_buf, 0, sizeof(read_buf));
+
+			status = volume_read(volume,
+				(uintptr_t)read_buf, req_len,
+				&len_read);
+			LONGS_EQUAL(0, status);
+			UNSIGNED_LONGS_EQUAL(req_len, len_read);
+
+			MEMCMP_EQUAL(&m_image[total_read], read_buf, len_read);
+
+			total_read += len_read;
+		}
+
+		status = volume_close(volume);
+		LONGS_EQUAL(0, status);
+	}
+
+
+	static const unsigned int FW_STORE_LOCATION_ID = 0x100;
+
+	struct block_store *m_block_store;
+	struct block_volume m_block_volume_a;
+	struct block_volume m_block_volume_b;
+	struct volume *m_fw_volume_a;
+	struct volume *m_fw_volume_b;
+	struct raw_installer m_raw_installer;
+	struct copy_installer m_copy_installer;
+	struct image_info m_image_info;
+	uint8_t *m_image;
+	size_t m_image_len;
+};
+
+TEST(FwuCopyInstallerTests, installAndCopy)
+{
+	/* Install an arbitrary size image into bank A */
+	install_initial_image(13011);
+
+	/* Expect to find a suitable copy installer */
+	struct installer *installer = installer_index_find(
+		INSTALL_TYPE_WHOLE_VOLUME_COPY, FW_STORE_LOCATION_ID);
+	CHECK_TRUE(installer);
+	UNSIGNED_LONGS_EQUAL(FW_STORE_LOCATION_ID, installer->location_id);
+
+	/* Begin installation transaction - installing into volume B */
+	int status = installer_begin(installer,
+		banked_volume_id(FW_STORE_LOCATION_ID,
+			BANKED_USAGE_ID_FW_BANK_A),  /* Current volume */
+		banked_volume_id(FW_STORE_LOCATION_ID,
+			BANKED_USAGE_ID_FW_BANK_B)); /* Update volume */
+	LONGS_EQUAL(0, status);
+
+	/* Finalize the installation -  the copy should happen here */
+	status = installer_finalize(installer);
+	LONGS_EQUAL(0, status);
+
+	/* Expect volume B to contain a copy of what's in volume A */
+	check_update_installed(m_fw_volume_b);
+}
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index f0aaa8c..13077f1 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -115,6 +115,8 @@
 		"components/service/fwu/installer"
 		"components/service/fwu/installer/raw"
 		"components/service/fwu/installer/raw/test"
+		"components/service/fwu/installer/copy"
+		"components/service/fwu/installer/copy/test"
 		"components/service/fwu/inspector/mock"
 		"components/service/crypto/client/cpp"
 		"components/service/crypto/client/cpp/protocol/protobuf"