Add file_block_store

To help with FWU debug and test, a file_block_store is added,
enabling a standard file containing a disk image to be accessed
directly by an application. This will be used to verify decoding
and configuration using of a boot image file generated by the
firmware build system.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I85a00dd0536810ce10510abf05cdcfa9ad0c7621
diff --git a/components/service/block_storage/block_store/device/file/component.cmake b/components/service/block_storage/block_store/device/file/component.cmake
new file mode 100644
index 0000000..b330197
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 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}/file_block_store.c"
+	)
diff --git a/components/service/block_storage/block_store/device/file/file_block_store.c b/components/service/block_storage/block_store/device/file/file_block_store.c
new file mode 100644
index 0000000..1d8d542
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/file_block_store.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include "file_block_store.h"
+
+#define ERASED_DATA_VAL    (0xff)
+
+ssize_t file_length(FILE *fp)
+{
+	ssize_t size = -1;
+
+	if (!fseek(fp, 0, SEEK_END))
+		size = ftell(fp);
+
+	return size;
+}
+
+static psa_status_t seek(FILE *fp, ssize_t pos)
+{
+	if (fseek(fp, pos, SEEK_SET))
+		return PSA_ERROR_BAD_STATE;
+
+	return PSA_SUCCESS;
+}
+
+static psa_status_t prepare_for_read(
+	const struct file_block_store *this_instance,
+	uint32_t lba, size_t offset,
+	size_t requested_read_len,
+	size_t *adjusted_read_len)
+{
+	assert(this_instance);
+
+	psa_status_t status = PSA_ERROR_BAD_STATE;
+
+	const struct storage_partition *storage_partition =
+		&this_instance->base_block_device.storage_partition;
+
+	ssize_t read_pos = lba * storage_partition->block_size + offset;
+	ssize_t file_len = file_length(this_instance->file_handle);
+
+	if (file_len >= 0) {
+
+		/* File exists so attempt to seek the read position to the requested LBA + offset */
+		if (read_pos <= file_len) {
+
+			size_t bytes_until_end_of_file = (size_t)(file_len - read_pos);
+			size_t bytes_until_end_of_block = storage_partition->block_size - offset;
+
+			size_t read_limit = (bytes_until_end_of_file < bytes_until_end_of_block) ?
+				bytes_until_end_of_file :
+				bytes_until_end_of_block;
+
+			*adjusted_read_len = (requested_read_len < read_limit) ?
+				requested_read_len :
+				read_limit;
+
+			status = seek(this_instance->file_handle, read_pos);
+		} else {
+
+			/* Requested block is beyond the end of the file */
+			status = PSA_ERROR_INVALID_ARGUMENT;
+		}
+	}
+
+	return status;
+}
+
+static psa_status_t write_erased(
+	const struct file_block_store *this_instance,
+	size_t pos,
+	size_t len)
+{
+	psa_status_t status = PSA_ERROR_BAD_STATE;
+	size_t remaining_len = len;
+
+	assert(this_instance);
+
+	status = seek(this_instance->file_handle, pos);
+
+	while ((status == PSA_SUCCESS) && (remaining_len > 0)) {
+
+		size_t erase_len = (remaining_len < sizeof(this_instance->erase_buf)) ?
+			remaining_len :
+			sizeof(this_instance->erase_buf);
+
+		size_t write_len = fwrite(this_instance->erase_buf, 1,
+			erase_len, this_instance->file_handle);
+
+		if (write_len != erase_len)
+			status = PSA_ERROR_BAD_STATE;
+
+		remaining_len -= erase_len;
+	}
+
+	return status;
+}
+
+static psa_status_t prepare_for_write(
+	const struct file_block_store *this_instance,
+	uint32_t lba, size_t offset,
+	size_t requested_write_len,
+	size_t *adjusted_write_len)
+{
+	assert(this_instance);
+
+	psa_status_t status = PSA_ERROR_BAD_STATE;
+
+	const struct storage_partition *storage_partition =
+		&this_instance->base_block_device.storage_partition;
+
+	size_t bytes_until_end_of_block = storage_partition->block_size - offset;
+	*adjusted_write_len = (requested_write_len < bytes_until_end_of_block) ?
+		requested_write_len :
+		bytes_until_end_of_block;
+
+	ssize_t write_pos = lba * storage_partition->block_size + offset;
+	ssize_t file_len = file_length(this_instance->file_handle);
+
+	if (file_len >= 0) {
+
+		if (write_pos > file_len) {
+
+			/* Writing beyond the current end-of-file so extend the file */
+			status = write_erased(this_instance, file_len, write_pos - file_len);
+		} else {
+
+			/* Writing over existing data */
+			status = seek(this_instance->file_handle, write_pos);
+		}
+	}
+
+	return status;
+}
+
+static psa_status_t file_block_store_get_partition_info(void *context,
+	const struct uuid_octets *partition_guid,
+	struct storage_partition_info *info)
+{
+	struct file_block_store *this_instance = (struct file_block_store *)context;
+
+	return block_device_get_partition_info(
+		&this_instance->base_block_device, partition_guid, info);
+}
+
+static psa_status_t file_block_store_open(void *context,
+	uint32_t client_id,
+	const struct uuid_octets *partition_guid,
+	storage_partition_handle_t *handle)
+{
+	struct file_block_store *this_instance = (struct file_block_store *)context;
+	psa_status_t status = PSA_ERROR_BAD_STATE;
+
+	if (this_instance->file_handle > 0) {
+
+		status = block_device_open(
+			&this_instance->base_block_device,
+			client_id, partition_guid, handle);
+	}
+
+	return status;
+}
+
+static psa_status_t file_block_store_close(void *context,
+	uint32_t client_id,
+	storage_partition_handle_t handle)
+{
+	struct file_block_store *this_instance = (struct file_block_store *)context;
+
+	return block_device_close(
+		&this_instance->base_block_device, client_id, handle);
+}
+
+static psa_status_t file_block_store_read(void *context,
+	uint32_t client_id,
+	storage_partition_handle_t handle,
+	uint32_t lba,
+	size_t offset,
+	size_t buffer_size,
+	uint8_t *buffer,
+	size_t *data_len)
+{
+	const struct file_block_store *this_instance =
+		(struct file_block_store *)context;
+
+	psa_status_t status = block_device_check_access_permitted(
+		&this_instance->base_block_device, client_id, handle);
+
+	*data_len = 0;
+
+	if (status == PSA_SUCCESS) {
+
+		const struct storage_partition *storage_partition =
+			&this_instance->base_block_device.storage_partition;
+
+		if (storage_partition_is_lba_legal(storage_partition, lba) &&
+			(offset < storage_partition->block_size)) {
+
+			size_t read_len = 0;
+
+			status = prepare_for_read(
+				this_instance,
+				lba, offset,
+				buffer_size,
+				&read_len);
+
+			if (status == PSA_SUCCESS) {
+
+				*data_len = fread(buffer, 1,
+					read_len, this_instance->file_handle);
+
+				if (*data_len != read_len)
+					status = PSA_ERROR_BAD_STATE;
+			}
+		} else
+			/* Block or offset outside of configured limits */
+			status = PSA_ERROR_INVALID_ARGUMENT;
+	}
+
+	return status;
+}
+
+static psa_status_t file_block_store_write(void *context,
+	uint32_t client_id,
+	storage_partition_handle_t handle,
+	uint32_t lba,
+	size_t offset,
+	const uint8_t *data,
+	size_t data_len,
+	size_t *num_written)
+{
+	struct file_block_store *this_instance = (struct file_block_store *)context;
+
+	psa_status_t status = block_device_check_access_permitted(
+		&this_instance->base_block_device, client_id, handle);
+
+	*num_written = 0;
+
+	if (status == PSA_SUCCESS) {
+
+		const struct storage_partition *storage_partition =
+			&this_instance->base_block_device.storage_partition;
+
+		if (storage_partition_is_lba_legal(storage_partition, lba) &&
+			(offset < storage_partition->block_size)) {
+
+			size_t adjusted_len = 0;
+
+			status = prepare_for_write(
+				this_instance,
+				lba, offset,
+				data_len,
+				&adjusted_len);
+
+			if (status == PSA_SUCCESS) {
+
+				*num_written = fwrite(data, 1,
+					adjusted_len, this_instance->file_handle);
+
+				if (*num_written != adjusted_len)
+					status = PSA_ERROR_BAD_STATE;
+			}
+		} else
+			/* Block or offset outside of configured limits */
+			status = PSA_ERROR_INVALID_ARGUMENT;
+	}
+
+	return status;
+}
+
+static psa_status_t file_block_store_erase(void *context,
+	uint32_t client_id,
+	storage_partition_handle_t handle,
+	uint32_t begin_lba,
+	size_t num_blocks)
+{
+	struct file_block_store *this_instance = (struct file_block_store *)context;
+	const struct storage_partition *storage_partition =
+		&this_instance->base_block_device.storage_partition;
+
+	psa_status_t status = block_device_check_access_permitted(
+		&this_instance->base_block_device, client_id, handle);
+
+	/* Sanitize the range of LBAs to erase */
+	if ((status == PSA_SUCCESS) &&
+		!storage_partition_is_lba_legal(storage_partition, begin_lba))
+		status = PSA_ERROR_INVALID_ARGUMENT;
+
+	if (status == PSA_SUCCESS) {
+
+		size_t blocks_remaining = storage_partition->num_blocks - begin_lba;
+		size_t blocks_to_erase = (num_blocks < blocks_remaining) ?
+			num_blocks : blocks_remaining;
+
+		ssize_t file_len = file_length(this_instance->file_handle);
+
+		if (file_len >= 0) {
+
+			/* File exists. If erased block falls within the limits of the file,
+			 * explicitly set blocks to the erased state. If erased block is
+			 * beyond EOF, there's nothing to do.
+			 */
+			ssize_t block_pos = begin_lba * storage_partition->block_size;
+
+			if (block_pos < file_len)
+				status = write_erased(this_instance,
+					block_pos,
+					blocks_to_erase * storage_partition->block_size);
+
+		} else {
+			status = PSA_ERROR_BAD_STATE;
+		}
+	}
+
+	return status;
+}
+
+struct block_store *file_block_store_init(
+	struct file_block_store *this_instance,
+	const char *filename,
+	size_t block_size)
+{
+	struct block_store *block_store = NULL;
+	size_t num_blocks = 0;
+
+	assert(this_instance);
+
+	/* Define concrete block store interface */
+	static const struct block_store_interface interface = {
+		file_block_store_get_partition_info,
+		file_block_store_open,
+		file_block_store_close,
+		file_block_store_read,
+		file_block_store_write,
+		file_block_store_erase
+	};
+
+	/* Initialize base block_store */
+	this_instance->base_block_device.base_block_store.context = this_instance;
+	this_instance->base_block_device.base_block_store.interface = &interface;
+
+	/* Initialize buffer used for erase operations */
+	memset(this_instance->erase_buf, ERASED_DATA_VAL, sizeof(this_instance->erase_buf));
+
+	/* Open the file */
+	this_instance->file_handle = fopen(filename, "r+b");
+
+	if (this_instance->file_handle) {
+
+		/* File already exists so initialise the view of the number of blocks */
+		ssize_t file_len = file_length(this_instance->file_handle);
+
+		if (file_len >= 0)
+			num_blocks = (size_t)file_len / block_size;
+
+	} else {
+
+		/* File doesn't exist so create an empty one */
+		this_instance->file_handle = fopen(filename, "w+b");
+	}
+
+	if (this_instance->file_handle)
+		block_store = block_device_init(
+			&this_instance->base_block_device, NULL, num_blocks, block_size);
+
+	return block_store;
+}
+
+void file_block_store_deinit(
+	struct file_block_store *this_instance)
+{
+	assert(this_instance);
+
+	if (this_instance->file_handle) {
+
+		fclose(this_instance->file_handle);
+		this_instance->file_handle = NULL;
+	}
+
+	block_device_deinit(&this_instance->base_block_device);
+}
+
+psa_status_t file_block_store_configure(
+	struct file_block_store *this_instance,
+	const struct uuid_octets *disk_guid,
+	size_t num_blocks,
+	size_t block_size)
+{
+	assert(this_instance);
+
+	block_device_configure(&this_instance->base_block_device,
+		disk_guid, num_blocks, block_size);
+
+	return PSA_SUCCESS;
+}
diff --git a/components/service/block_storage/block_store/device/file/file_block_store.h b/components/service/block_storage/block_store/device/file/file_block_store.h
new file mode 100644
index 0000000..cb66b9b
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/file_block_store.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FILE_BLOCK_STORE_H
+#define FILE_BLOCK_STORE_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include "service/block_storage/block_store/device/block_device.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief file_block_store structure
+ *
+ * A file_block_store is a block_device that uses a file for storage.
+ * The file represents a real storage device organized as a series of
+ * consecutive blocks. The file_block_store can be used for accessing disk
+ * image files in a Posix environment.
+ */
+struct file_block_store
+{
+	struct block_device base_block_device;
+	FILE *file_handle;
+	uint8_t erase_buf[256];
+};
+
+/**
+ * \brief Initialize a file_block_store
+ *
+ * \param[in]  file_block_store  The subject file_block_store
+ * \param[in]  filename          The host filename used for storage
+ * \param[in]  block_size        The storage block size
+ *
+ * \return Pointer to block_store or NULL on failure
+ */
+struct block_store *file_block_store_init(
+	struct file_block_store *file_block_store,
+	const char *filename,
+	size_t block_size);
+
+/**
+ * \brief De-initialize a file_block_store
+ *
+ * \param[in]  file_block_store  The subject file_block_store
+ */
+void file_block_store_deinit(
+	struct file_block_store *file_block_store);
+
+/**
+ * \brief Configure the file_block_store
+ *
+ * \param[in]  file_block_store The subject file_block_store
+ * \param[in]  disk_guid        The disk GUID (nil uuid for any)
+ * \param[in]  num_blocks       The number of contiguous blocks
+ * \param[in]  block_size       Block size in bytes
+ *
+ * \return PSA_SUCCESS if successful
+ */
+psa_status_t file_block_store_configure(
+	struct file_block_store *file_block_store,
+	const struct uuid_octets *disk_guid,
+	size_t num_blocks,
+	size_t block_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FILE_BLOCK_STORE_H */
diff --git a/components/service/block_storage/block_store/device/file/test/component.cmake b/components/service/block_storage/block_store/device/file/test/component.cmake
new file mode 100644
index 0000000..a587b5a
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/test/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 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}/file_block_store_tests.cpp"
+	)
diff --git a/components/service/block_storage/block_store/device/file/test/file_block_store_tests.cpp b/components/service/block_storage/block_store/device/file/test/file_block_store_tests.cpp
new file mode 100644
index 0000000..34ea57c
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/test/file_block_store_tests.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstring>
+#include <cstdio>
+#include <string>
+#include <stdint.h>
+#include "common/uuid/uuid.h"
+#include "service/block_storage/block_store/device/file/file_block_store.h"
+#include "CppUTest/TestHarness.h"
+
+TEST_GROUP(FileBlockStoreTests)
+{
+	void setup()
+	{
+		m_filename = std::string("file_block_store.tmp");
+		memset(m_disk_guid.octets, 0, sizeof(m_disk_guid.octets));
+
+		struct block_store *block_store = file_block_store_init(
+			&m_file_block_store,
+			m_filename.c_str(),
+			BLOCK_SIZE);
+
+		CHECK_TRUE(block_store);
+
+		psa_status_t status = file_block_store_configure(
+			&m_file_block_store,
+			&m_disk_guid,
+			NUM_BLOCKS, BLOCK_SIZE);
+
+		LONGS_EQUAL(PSA_SUCCESS, status);
+
+		status = block_store_open(
+			&m_file_block_store.base_block_device.base_block_store,
+			CLIENT_ID,
+			&m_disk_guid,
+			&m_partition_handle);
+
+		LONGS_EQUAL(PSA_SUCCESS, status);
+	}
+
+	void teardown()
+	{
+		block_store_close(
+			&m_file_block_store.base_block_device.base_block_store,
+			CLIENT_ID,
+			m_partition_handle);
+
+		file_block_store_deinit(&m_file_block_store);
+		remove(m_filename.c_str());
+	}
+
+	void set_block(
+		size_t lba, size_t offset,
+		size_t len, uint8_t val,
+		size_t *num_written)
+	{
+		struct block_store *bs = &m_file_block_store.base_block_device.base_block_store;
+		uint8_t write_buf[len];
+
+		memset(write_buf, val, len);
+		*num_written = 0;
+
+		psa_status_t status = block_store_write(
+			bs, CLIENT_ID, m_partition_handle,
+			lba, offset,
+			write_buf, len, num_written);
+
+		LONGS_EQUAL(PSA_SUCCESS, status);
+	}
+
+	void check_block(
+		size_t lba, size_t offset,
+		size_t len, uint8_t expected_val)
+	{
+		struct block_store *bs = &m_file_block_store.base_block_device.base_block_store;
+		uint8_t read_buf[len];
+		size_t num_read = 0;
+
+		psa_status_t status = block_store_read(
+			bs, CLIENT_ID, m_partition_handle,
+			lba, offset,
+			len, read_buf,
+			&num_read);
+
+		LONGS_EQUAL(PSA_SUCCESS, status);
+		UNSIGNED_LONGS_EQUAL(len, num_read);
+
+		for (size_t i = 0; i < len; i++)
+			BYTES_EQUAL(expected_val, read_buf[i]);
+	}
+
+	void erase_blocks(
+		uint32_t begin_lba,
+		size_t num_blocks)
+	{
+		struct block_store *bs = &m_file_block_store.base_block_device.base_block_store;
+
+		psa_status_t status = block_store_erase(
+			bs, CLIENT_ID, m_partition_handle,
+			begin_lba, num_blocks);
+
+		LONGS_EQUAL(PSA_SUCCESS, status);
+	}
+
+	static const size_t NUM_BLOCKS = 100;
+	static const size_t BLOCK_SIZE = 512;
+	static const uint32_t CLIENT_ID = 27;
+
+	std::string m_filename;
+	struct uuid_octets m_disk_guid;
+	struct file_block_store m_file_block_store;
+	storage_partition_handle_t m_partition_handle;
+};
+
+/*
+ * Check a sequence of whole block writes and reads.
+ */
+TEST(FileBlockStoreTests, wholeBlockRw)
+{
+	size_t num_written = 0;
+
+	set_block(7, 0, BLOCK_SIZE, 'a', &num_written);
+	UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+	set_block(6, 0, BLOCK_SIZE, 'b', &num_written);
+	UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+	set_block(1, 0, BLOCK_SIZE, 'c', &num_written);
+	UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+	set_block(9, 0, BLOCK_SIZE, 'd', &num_written);
+	UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+	/* Check written blocks are as expected */
+	check_block(7, 0, BLOCK_SIZE, 'a');
+	check_block(6, 0, BLOCK_SIZE, 'b');
+	check_block(1, 0, BLOCK_SIZE, 'c');
+	check_block(9, 0, BLOCK_SIZE, 'd');
+
+	/* Erase all the written blocks */
+	erase_blocks(9, 1);
+	erase_blocks(1, 1);
+	erase_blocks(7, 1);
+	erase_blocks(6, 1);
+}
+
+/*
+ * Check state when initialised with existing disk image file
+ */
+TEST(FileBlockStoreTests, initWithExistingDiskImage)
+{
+	/* Write to last block to expand disk image file to entire size */
+	size_t num_written = 0;
+
+	set_block(NUM_BLOCKS - 1, 0, BLOCK_SIZE, 'a', &num_written);
+	UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+	/* Close the block_store opened during setup. This will have created a disk image file */
+	block_store_close(
+		&m_file_block_store.base_block_device.base_block_store,
+		CLIENT_ID,
+		m_partition_handle);
+
+	file_block_store_deinit(&m_file_block_store);
+
+	/* Re-initialise and open */
+	struct block_store *block_store = file_block_store_init(
+		&m_file_block_store,
+		m_filename.c_str(),
+		BLOCK_SIZE);
+
+	CHECK_TRUE(block_store);
+
+	psa_status_t status = block_store_open(
+		block_store,
+		CLIENT_ID,
+		&m_disk_guid,
+		&m_partition_handle);
+
+	LONGS_EQUAL(PSA_SUCCESS, status);
+
+	/* Expect disk partition size to reflect existing disk file */
+	struct storage_partition_info disk_info;
+
+	status = block_store_get_partition_info(block_store, &m_disk_guid, &disk_info);
+	LONGS_EQUAL(PSA_SUCCESS, status);
+
+	UNSIGNED_LONGS_EQUAL(NUM_BLOCKS, disk_info.num_blocks);
+	UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, disk_info.block_size);
+}
diff --git a/components/service/block_storage/factory/file/block_store_factory.c b/components/service/block_storage/factory/file/block_store_factory.c
new file mode 100644
index 0000000..d879332
--- /dev/null
+++ b/components/service/block_storage/factory/file/block_store_factory.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <trace.h>
+#include "block_store_factory.h"
+#include "service/block_storage/block_store/device/file/file_block_store.h"
+#include "service/block_storage/block_store/partitioned/partitioned_block_store.h"
+#include "service/block_storage/config/gpt/gpt_partition_configurator.h"
+#include "media/volume/index/volume_index.h"
+#include "media/volume/block_volume/block_volume.h"
+
+#ifndef FILE_BLOCK_SIZE
+/* Default to most common block size for UEFI volumes */
+#define FILE_BLOCK_SIZE    (512)
+#endif
+
+static char disk_img_filename[256];
+
+struct block_store_assembly
+{
+	struct file_block_store file_block_store;
+	struct partitioned_block_store partitioned_block_store;
+	struct block_volume volume;
+};
+
+static void tear_down_assembly(struct block_store_assembly *assembly)
+{
+	volume_index_clear();
+
+	if (assembly) {
+		partitioned_block_store_deinit(&assembly->partitioned_block_store);
+		file_block_store_deinit(&assembly->file_block_store);
+		block_volume_deinit(&assembly->volume);
+
+		free(assembly);
+	}
+}
+
+struct block_store *file_block_store_factory_create(void)
+{
+	struct block_store *product = NULL;
+	struct block_store_assembly *assembly =
+		(struct block_store_assembly*)malloc(sizeof(struct block_store_assembly));
+
+	if (assembly) {
+
+		struct uuid_octets disk_guid;
+		memset(&disk_guid, 0, sizeof(disk_guid));
+
+		volume_index_init();
+
+		/* Ensure disk image filename is set */
+		if (disk_img_filename[0] == '\0')
+			file_block_store_factory_set_filename("secure-flash.img");
+
+		/* Initialise a file_block_store to provide underlying storage */
+		struct block_store *secure_flash = file_block_store_init(
+			&assembly->file_block_store,
+			disk_img_filename,
+			FILE_BLOCK_SIZE);
+
+		if (secure_flash) {
+
+			/* Secure flash successfully initialized so create a block_volume
+			 * associated with secure flash block store to enable it to be
+			 * accessed as a storage volume.
+			 */
+			struct volume *volume = NULL;
+
+			int result = block_volume_init(&assembly->volume,
+				secure_flash, &disk_guid,
+				&volume);
+
+			if (result == 0) {
+
+				volume_index_add(VOLUME_ID_SECURE_FLASH, volume);
+
+				/* Stack a partitioned_block_store over the back store */
+				product = partitioned_block_store_init(
+					&assembly->partitioned_block_store,
+					0,
+					&disk_guid,
+					secure_flash,
+					NULL);
+
+				if (product) {
+
+					/* Successfully created the block store stack so configure the
+					 * partitions if there are any described in the GPT. No GPT
+					 * is a valid configuration option so it's deliberately not
+					 * treated as an error. To help a platform integrator who
+					 * intended to use a GPT, a message is output if partitions
+					 * weren't configured.
+					 * */
+					bool is_configured = gpt_partition_configure(
+						&assembly->partitioned_block_store,
+						VOLUME_ID_SECURE_FLASH);
+
+					if (!is_configured)
+						IMSG("No GPT detected in %s", disk_img_filename);
+
+				} else
+					EMSG("Failed to init partitioned_block_store");
+			}
+		} else
+			EMSG("Failed to init file_block_store: %s", disk_img_filename);
+
+		if (!product) {
+
+			/* Something went wrong! */
+			tear_down_assembly(assembly);
+		}
+	} else
+		EMSG("Failed to alloc file_block_store assembly");
+
+	return product;
+}
+
+void file_block_store_factory_destroy(struct block_store *block_store)
+{
+	if (block_store) {
+
+		size_t offset_into_assembly =
+			offsetof(struct block_store_assembly, partitioned_block_store) +
+			offsetof(struct partitioned_block_store, base_block_store);
+
+		struct block_store_assembly *assembly = (struct block_store_assembly*)
+			((uint8_t*)block_store - offset_into_assembly);
+
+		tear_down_assembly(assembly);
+	}
+}
+
+void file_block_store_factory_set_filename(const char *filename)
+{
+	strncpy(disk_img_filename, filename, sizeof(disk_img_filename));
+
+	/* Ensure that filename is null terminated */
+	disk_img_filename[sizeof(disk_img_filename) - 1] = '\0';
+}
\ No newline at end of file
diff --git a/components/service/block_storage/factory/file/block_store_factory.h b/components/service/block_storage/factory/file/block_store_factory.h
new file mode 100644
index 0000000..647a4df
--- /dev/null
+++ b/components/service/block_storage/factory/file/block_store_factory.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FILE_BLOCK_STORE_FACTORY_H
+#define FILE_BLOCK_STORE_FACTORY_H
+
+#include "service/block_storage/block_store/block_store.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A block store factory that constructs a partitioned block store backed
+ * by a file block device that provides access to a disk image file.
+ * If the disk image contains a GPT, it will be used to configure a set
+ * of storage partitions.
+ *
+ * This factory is intended for deployments where Posix filesystem
+ * access is possible with access to a a disk image file.
+ */
+
+/**
+ * \brief Factory method to create a block_store
+ *
+ * \return A pointer to the constructed block_store (NULL on failure)
+ */
+struct block_store *file_block_store_factory_create(void);
+
+/**
+ * \brief Destroys a block_store created with block_store_factory_create
+ *
+ * \param[in] block_store    The block store to destroy
+ */
+void file_block_store_factory_destroy(struct block_store *block_store);
+
+/**
+ * \brief Set the filename for the disk image file
+ *
+ * \param[in] filename    Disk image filename
+ */
+void file_block_store_factory_set_filename(const char *filename);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FILE_BLOCK_STORE_FACTORY_H */
diff --git a/components/service/block_storage/factory/file/component.cmake b/components/service/block_storage/factory/file/component.cmake
new file mode 100644
index 0000000..644f039
--- /dev/null
+++ b/components/service/block_storage/factory/file/component.cmake
@@ -0,0 +1,20 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 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}/block_store_factory.c"
+	)
+
+# If none already defined, make this the default factory for the deployment
+if (NOT DEFINED TS_BLOCK_STORE_FACTORY)
+	set(TS_BLOCK_STORE_FACTORY "file_block_store_factory")
+	target_compile_definitions(${TGT} PRIVATE
+		CONCRETE_BLOCK_STORE_FACTORY=${TS_BLOCK_STORE_FACTORY})
+endif()
\ No newline at end of file
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index f2087d9..474ecde 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -96,6 +96,8 @@
 		"components/service/block_storage/block_store/device/ram"
 		"components/service/block_storage/block_store/device/ram/test"
 		"components/service/block_storage/block_store/device/null"
+		"components/service/block_storage/block_store/device/file"
+		"components/service/block_storage/block_store/device/file/test"
 		"components/service/block_storage/block_store/client"
 		"components/service/block_storage/block_store/partitioned"
 		"components/service/block_storage/block_store/partitioned/test"