| /* |
| * Copyright (c) 2022-2023, Arm Limited. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * |
| */ |
| |
| #include "copy_installer.h" |
| |
| #include <assert.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| |
| #include "media/volume/index/volume_index.h" |
| #include "protocols/service/fwu/packed-c/status.h" |
| #include "util.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; |
| } |