| /* |
| * Copyright (c) 2021, Arm Limited. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include "variable_index.h" |
| |
| /* Private functions */ |
| static uint64_t name_hash( |
| const EFI_GUID *guid, |
| size_t name_size, |
| const int16_t *name) |
| { |
| /* Using djb2 hash by Dan Bernstein */ |
| uint64_t hash = 5381; |
| |
| /* Calculate hash over GUID */ |
| hash = ((hash << 5) + hash) + guid->Data1; |
| hash = ((hash << 5) + hash) + guid->Data2; |
| hash = ((hash << 5) + hash) + guid->Data3; |
| |
| for (int i = 0; i < 8; ++i) { |
| |
| hash = ((hash << 5) + hash) + guid->Data4[i]; |
| } |
| |
| /* Extend to cover name up to but not including null terminator */ |
| for (int i = 0; i < name_size / sizeof(int16_t); ++i) { |
| |
| if (!name[i]) break; |
| hash = ((hash << 5) + hash) + name[i]; |
| } |
| |
| return hash; |
| } |
| |
| static uint64_t generate_uid( |
| const struct variable_index *context, |
| const EFI_GUID *guid, |
| size_t name_size, |
| const int16_t *name) |
| { |
| uint64_t uid = name_hash(guid, name_size, name); |
| |
| /* todo - handle collision */ |
| (void)context; |
| |
| return uid; |
| } |
| |
| static int find_variable( |
| const struct variable_index *context, |
| const EFI_GUID *guid, |
| size_t name_size, |
| const int16_t *name) |
| { |
| int found_pos = -1; |
| uint64_t uid = name_hash(guid, name_size, name); |
| |
| for (int pos = 0; pos < context->max_variables; pos++) { |
| |
| if ((context->entries[pos].in_use) && |
| (uid == context->entries[pos].info.metadata.uid)) { |
| |
| found_pos = pos; |
| break; |
| } |
| } |
| |
| return found_pos; |
| } |
| |
| static int find_free( |
| const struct variable_index *context) |
| { |
| int free_pos = -1; |
| |
| for (int pos = 0; pos < context->max_variables; pos++) { |
| |
| if (!context->entries[pos].in_use) { |
| |
| free_pos = pos; |
| break; |
| } |
| } |
| |
| return free_pos; |
| } |
| |
| static void mark_dirty(struct variable_entry *entry) |
| { |
| if (entry->info.metadata.attributes & EFI_VARIABLE_NON_VOLATILE) |
| entry->dirty = true; |
| } |
| |
| static struct variable_entry *containing_entry(const struct variable_info *info) |
| { |
| size_t info_offset = offsetof(struct variable_entry, info); |
| struct variable_entry *entry = (struct variable_entry*)((uint8_t*)info - info_offset); |
| return entry; |
| } |
| |
| /* Public functions */ |
| efi_status_t variable_index_init( |
| struct variable_index *context, |
| size_t max_variables) |
| { |
| context->max_variables = max_variables; |
| context->entries = (struct variable_entry*) |
| malloc(sizeof(struct variable_entry) * max_variables); |
| |
| if (context->entries) { |
| memset(context->entries, 0, sizeof(struct variable_entry) * max_variables); |
| } |
| |
| return (context->entries) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES; |
| } |
| |
| void variable_index_deinit( |
| struct variable_index *context) |
| { |
| free(context->entries); |
| } |
| |
| size_t variable_index_max_dump_size( |
| struct variable_index *context) |
| { |
| return sizeof(struct variable_metadata) * context->max_variables; |
| } |
| |
| struct variable_info *variable_index_find( |
| struct variable_index *context, |
| const EFI_GUID *guid, |
| size_t name_size, |
| const int16_t *name) |
| { |
| struct variable_info *result = NULL; |
| int pos = find_variable(context, guid, name_size, name); |
| |
| if (pos >= 0) { |
| |
| result = &context->entries[pos].info; |
| } |
| |
| return result; |
| } |
| |
| struct variable_info *variable_index_find_next( |
| const struct variable_index *context, |
| const EFI_GUID *guid, |
| size_t name_size, |
| const int16_t *name, |
| efi_status_t *status) |
| { |
| struct variable_info *result = NULL; |
| *status = EFI_NOT_FOUND; |
| |
| if (name_size >= sizeof(int16_t)) { |
| |
| /* |
| * Name must be at least one character long to accommodate |
| * the mandatory null terminator. |
| */ |
| if (name[0] != 0) { |
| |
| /* Find next from current name */ |
| int pos = find_variable(context, guid, name_size, name); |
| |
| if (pos >= 0) { |
| |
| /* Iterate to next used entry */ |
| ++pos; |
| while (pos < context->max_variables) { |
| |
| if (context->entries[pos].in_use && |
| context->entries[pos].info.is_variable_set) { |
| |
| result = &context->entries[pos].info; |
| *status = EFI_SUCCESS; |
| break; |
| } |
| |
| ++pos; |
| } |
| } |
| else { |
| |
| /* A non-empty name was provided but it wasn't found */ |
| *status = EFI_INVALID_PARAMETER; |
| } |
| } |
| else { |
| |
| /* Find first */ |
| int pos = 0; |
| |
| while (pos < context->max_variables) { |
| |
| if (context->entries[pos].in_use && |
| context->entries[pos].info.is_variable_set) { |
| |
| result = &context->entries[pos].info; |
| *status = EFI_SUCCESS; |
| break; |
| } |
| |
| ++pos; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| static void set_variable_name( |
| struct variable_info *info, |
| size_t name_size, |
| const int16_t *name) |
| { |
| size_t trimmed_size = 0; |
| |
| /* Trim the saved name to only include a single null terminator. |
| * Any additional terminators included in the client-set name size |
| * are discarded. |
| */ |
| for (size_t i = 0; i < name_size; i++) { |
| |
| ++trimmed_size; |
| info->metadata.name[i] = name[i]; |
| |
| if (!name[i]) break; |
| } |
| |
| info->metadata.name_size = trimmed_size * sizeof(int16_t); |
| } |
| |
| static struct variable_entry *add_entry( |
| struct variable_index *context, |
| const EFI_GUID *guid, |
| size_t name_size, |
| const int16_t *name) |
| { |
| struct variable_entry *entry = NULL; |
| |
| if (name_size <= (VARIABLE_INDEX_MAX_NAME_SIZE * sizeof(int16_t))) { |
| |
| int pos = find_free(context); |
| |
| if (pos >= 0) { |
| |
| entry = &context->entries[pos]; |
| |
| struct variable_info *info = &entry->info; |
| |
| /* Initialize metadata */ |
| info->metadata.uid = generate_uid(context, guid, name_size, name); |
| info->metadata.guid = *guid; |
| info->metadata.attributes = 0; |
| set_variable_name(info, name_size, name); |
| |
| info->is_constraints_set = false; |
| info->is_variable_set = false; |
| |
| entry->in_use = true; |
| } |
| } |
| |
| return entry; |
| } |
| |
| struct variable_info *variable_index_add_entry( |
| struct variable_index *context, |
| const EFI_GUID *guid, |
| size_t name_size, |
| const int16_t *name) |
| { |
| struct variable_info *info = NULL; |
| struct variable_entry *entry = add_entry(context, guid, name_size, name); |
| |
| if (entry) { |
| |
| info = &entry->info; |
| } |
| |
| return info; |
| } |
| |
| void variable_index_remove_unused_entry( |
| struct variable_index *context, |
| struct variable_info *info) |
| { |
| if (info && |
| !info->is_constraints_set && |
| !info->is_variable_set) { |
| |
| struct variable_entry *entry = containing_entry(info); |
| entry->in_use = false; |
| |
| memset(info, 0, sizeof(struct variable_info)); |
| } |
| } |
| |
| void variable_index_set_variable( |
| struct variable_info *info, |
| uint32_t attributes) |
| { |
| struct variable_entry *entry = containing_entry(info); |
| |
| info->metadata.attributes = attributes; |
| info->is_variable_set = true; |
| |
| mark_dirty(entry); |
| } |
| |
| void variable_index_clear_variable( |
| struct variable_index *context, |
| struct variable_info *info) |
| { |
| if (info) { |
| |
| struct variable_entry *entry = containing_entry(info); |
| mark_dirty(entry); |
| |
| /* Mark variable as no longer set */ |
| entry->info.is_variable_set = false; |
| } |
| } |
| |
| void variable_index_set_constraints( |
| struct variable_info *info, |
| const struct variable_constraints *constraints) |
| { |
| if (info) { |
| |
| info->check_constraints = *constraints; |
| info->is_constraints_set = true; |
| } |
| } |
| |
| bool variable_index_dump( |
| struct variable_index *context, |
| size_t buffer_size, |
| uint8_t *buffer, |
| size_t *data_len) |
| { |
| bool any_dirty = false; |
| uint8_t *dump_pos = buffer; |
| size_t bytes_dumped = 0; |
| |
| for (int pos = 0; pos < context->max_variables; pos++) { |
| |
| struct variable_entry *entry = &context->entries[pos]; |
| struct variable_metadata *metadata = &entry->info.metadata; |
| |
| if (entry->in_use && |
| entry->info.is_variable_set && |
| (metadata->attributes & EFI_VARIABLE_NON_VOLATILE) && |
| ((bytes_dumped + sizeof(struct variable_metadata)) <= buffer_size)) { |
| |
| memcpy(dump_pos, metadata, sizeof(struct variable_metadata)); |
| bytes_dumped += sizeof(struct variable_metadata); |
| dump_pos += sizeof(struct variable_metadata); |
| } |
| |
| any_dirty |= entry->dirty; |
| entry->dirty = false; |
| } |
| |
| *data_len = bytes_dumped; |
| |
| return any_dirty; |
| } |
| |
| size_t variable_index_restore( |
| const struct variable_index *context, |
| size_t data_len, |
| const uint8_t *buffer) |
| { |
| size_t bytes_loaded = 0; |
| const uint8_t *load_pos = buffer; |
| int pos = 0; |
| |
| while (bytes_loaded < data_len) { |
| |
| if ((data_len - bytes_loaded) >= sizeof(struct variable_metadata)) { |
| |
| struct variable_entry *entry = &context->entries[pos]; |
| struct variable_metadata *metadata = &entry->info.metadata; |
| |
| memcpy(metadata, load_pos, sizeof(struct variable_metadata)); |
| |
| entry->info.is_variable_set = true; |
| entry->in_use = true; |
| |
| bytes_loaded += sizeof(struct variable_metadata); |
| load_pos += sizeof(struct variable_metadata); |
| |
| ++pos; |
| } |
| else { |
| |
| /* Not a whole number of variable_metadata structs! */ |
| break; |
| } |
| } |
| |
| return bytes_loaded; |
| } |