Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "chapter-index.h"
#include "errors.h"
#include "logger.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "hash-utils.h"
#include "indexer.h"
int uds_make_open_chapter_index(struct open_chapter_index **chapter_index,
const struct index_geometry *geometry, u64 volume_nonce)
{
int result;
size_t memory_size;
struct open_chapter_index *index;
result = vdo_allocate(1, "open chapter index", &index);
if (result != VDO_SUCCESS)
return result;
/*
* The delta index will rebalance delta lists when memory gets tight,
* so give the chapter index one extra page.
*/
memory_size = ((geometry->index_pages_per_chapter + 1) * geometry->bytes_per_page);
index->geometry = geometry;
index->volume_nonce = volume_nonce;
result = uds_initialize_delta_index(&index->delta_index, 1,
geometry->delta_lists_per_chapter,
geometry->chapter_mean_delta,
geometry->chapter_payload_bits,
memory_size, 'm');
if (result != UDS_SUCCESS) {
vdo_free(index);
return result;
}
index->memory_size = index->delta_index.memory_size + sizeof(struct open_chapter_index);
*chapter_index = index;
return UDS_SUCCESS;
}
void uds_free_open_chapter_index(struct open_chapter_index *chapter_index)
{
if (chapter_index == NULL)
return;
uds_uninitialize_delta_index(&chapter_index->delta_index);
vdo_free(chapter_index);
}
/* Re-initialize an open chapter index for a new chapter. */
void uds_empty_open_chapter_index(struct open_chapter_index *chapter_index,
u64 virtual_chapter_number)
{
uds_reset_delta_index(&chapter_index->delta_index);
chapter_index->virtual_chapter_number = virtual_chapter_number;
}
static inline bool was_entry_found(const struct delta_index_entry *entry, u32 address)
{
return (!entry->at_end) && (entry->key == address);
}
/* Associate a record name with the record page containing its metadata. */
int uds_put_open_chapter_index_record(struct open_chapter_index *chapter_index,
const struct uds_record_name *name,
u32 page_number)
{
int result;
struct delta_index_entry entry;
u32 address;
u32 list_number;
const u8 *found_name;
bool found;
const struct index_geometry *geometry = chapter_index->geometry;
u64 chapter_number = chapter_index->virtual_chapter_number;
u32 record_pages = geometry->record_pages_per_chapter;
result = VDO_ASSERT(page_number < record_pages,
"Page number within chapter (%u) exceeds the maximum value %u",
page_number, record_pages);
if (result != VDO_SUCCESS)
return UDS_INVALID_ARGUMENT;
address = uds_hash_to_chapter_delta_address(name, geometry);
list_number = uds_hash_to_chapter_delta_list(name, geometry);
result = uds_get_delta_index_entry(&chapter_index->delta_index, list_number,
address, name->name, &entry);
if (result != UDS_SUCCESS)
return result;
found = was_entry_found(&entry, address);
result = VDO_ASSERT(!(found && entry.is_collision),
"Chunk appears more than once in chapter %llu",
(unsigned long long) chapter_number);
if (result != VDO_SUCCESS)
return UDS_BAD_STATE;
found_name = (found ? name->name : NULL);
return uds_put_delta_index_entry(&entry, address, page_number, found_name);
}
/*
* Pack a section of an open chapter index into a chapter index page. A range of delta lists
* (starting with a specified list index) is copied from the open chapter index into a memory page.
* The number of lists copied onto the page is returned to the caller on success.
*
* @chapter_index: The open chapter index
* @memory: The memory page to use
* @first_list: The first delta list number to be copied
* @last_page: If true, this is the last page of the chapter index and all the remaining lists must
* be packed onto this page
* @lists_packed: The number of delta lists that were packed onto this page
*/
int uds_pack_open_chapter_index_page(struct open_chapter_index *chapter_index,
u8 *memory, u32 first_list, bool last_page,
u32 *lists_packed)
{
int result;
struct delta_index *delta_index = &chapter_index->delta_index;
struct delta_index_stats stats;
u64 nonce = chapter_index->volume_nonce;
u64 chapter_number = chapter_index->virtual_chapter_number;
const struct index_geometry *geometry = chapter_index->geometry;
u32 list_count = geometry->delta_lists_per_chapter;
unsigned int removals = 0;
struct delta_index_entry entry;
u32 next_list;
s32 list_number;
for (;;) {
result = uds_pack_delta_index_page(delta_index, nonce, memory,
geometry->bytes_per_page,
chapter_number, first_list,
lists_packed);
if (result != UDS_SUCCESS)
return result;
if ((first_list + *lists_packed) == list_count) {
/* All lists are packed. */
break;
} else if (*lists_packed == 0) {
/*
* The next delta list does not fit on a page. This delta list will be
* removed.
*/
} else if (last_page) {
/*
* This is the last page and there are lists left unpacked, but all of the
* remaining lists must fit on the page. Find a list that contains entries
* and remove the entire list. Try the first list that does not fit. If it
* is empty, we will select the last list that already fits and has any
* entries.
*/
} else {
/* This page is done. */
break;
}
if (removals == 0) {
uds_get_delta_index_stats(delta_index, &stats);
vdo_log_warning("The chapter index for chapter %llu contains %llu entries with %llu collisions",
(unsigned long long) chapter_number,
(unsigned long long) stats.record_count,
(unsigned long long) stats.collision_count);
}
list_number = *lists_packed;
do {
if (list_number < 0)
return UDS_OVERFLOW;
next_list = first_list + list_number--;
result = uds_start_delta_index_search(delta_index, next_list, 0,
&entry);
if (result != UDS_SUCCESS)
return result;
result = uds_next_delta_index_entry(&entry);
if (result != UDS_SUCCESS)
return result;
} while (entry.at_end);
do {
result = uds_remove_delta_index_entry(&entry);
if (result != UDS_SUCCESS)
return result;
removals++;
} while (!entry.at_end);
}
if (removals > 0) {
vdo_log_warning("To avoid chapter index page overflow in chapter %llu, %u entries were removed from the chapter index",
(unsigned long long) chapter_number, removals);
}
return UDS_SUCCESS;
}
/* Make a new chapter index page, initializing it with the data from a given index_page buffer. */
int uds_initialize_chapter_index_page(struct delta_index_page *index_page,
const struct index_geometry *geometry,
u8 *page_buffer, u64 volume_nonce)
{
return uds_initialize_delta_index_page(index_page, volume_nonce,
geometry->chapter_mean_delta,
geometry->chapter_payload_bits,
page_buffer, geometry->bytes_per_page);
}
/* Validate a chapter index page read during rebuild. */
int uds_validate_chapter_index_page(const struct delta_index_page *index_page,
const struct index_geometry *geometry)
{
int result;
const struct delta_index *delta_index = &index_page->delta_index;
u32 first = index_page->lowest_list_number;
u32 last = index_page->highest_list_number;
u32 list_number;
/* We walk every delta list from start to finish. */
for (list_number = first; list_number <= last; list_number++) {
struct delta_index_entry entry;
result = uds_start_delta_index_search(delta_index, list_number - first,
0, &entry);
if (result != UDS_SUCCESS)
return result;
for (;;) {
result = uds_next_delta_index_entry(&entry);
if (result != UDS_SUCCESS) {
/*
* A random bit stream is highly likely to arrive here when we go
* past the end of the delta list.
*/
return result;
}
if (entry.at_end)
break;
/* Also make sure that the record page field contains a plausible value. */
if (uds_get_delta_entry_value(&entry) >=
geometry->record_pages_per_chapter) {
/*
* Do not log this as an error. It happens in normal operation when
* we are doing a rebuild but haven't written the entire volume
* once.
*/
return UDS_CORRUPT_DATA;
}
}
}
return UDS_SUCCESS;
}
/*
* Search a chapter index page for a record name, returning the record page number that may contain
* the name.
*/
int uds_search_chapter_index_page(struct delta_index_page *index_page,
const struct index_geometry *geometry,
const struct uds_record_name *name,
u16 *record_page_ptr)
{
int result;
struct delta_index *delta_index = &index_page->delta_index;
u32 address = uds_hash_to_chapter_delta_address(name, geometry);
u32 delta_list_number = uds_hash_to_chapter_delta_list(name, geometry);
u32 sub_list_number = delta_list_number - index_page->lowest_list_number;
struct delta_index_entry entry;
result = uds_get_delta_index_entry(delta_index, sub_list_number, address,
name->name, &entry);
if (result != UDS_SUCCESS)
return result;
if (was_entry_found(&entry, address))
*record_page_ptr = uds_get_delta_entry_value(&entry);
else
*record_page_ptr = NO_CHAPTER_INDEX_ENTRY;
return UDS_SUCCESS;
}
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_CHAPTER_INDEX_H
#define UDS_CHAPTER_INDEX_H
#include <linux/limits.h>
#include "delta-index.h"
#include "geometry.h"
/*
* A chapter index for an open chapter is a mutable structure that tracks all the records that have
* been added to the chapter. A chapter index for a closed chapter is similar except that it is
* immutable because the contents of a closed chapter can never change, and the immutable structure
* is more efficient. Both types of chapter index are implemented with a delta index.
*/
/* The value returned when no entry is found in the chapter index. */
#define NO_CHAPTER_INDEX_ENTRY U16_MAX
struct open_chapter_index {
const struct index_geometry *geometry;
struct delta_index delta_index;
u64 virtual_chapter_number;
u64 volume_nonce;
size_t memory_size;
};
int __must_check uds_make_open_chapter_index(struct open_chapter_index **chapter_index,
const struct index_geometry *geometry,
u64 volume_nonce);
void uds_free_open_chapter_index(struct open_chapter_index *chapter_index);
void uds_empty_open_chapter_index(struct open_chapter_index *chapter_index,
u64 virtual_chapter_number);
int __must_check uds_put_open_chapter_index_record(struct open_chapter_index *chapter_index,
const struct uds_record_name *name,
u32 page_number);
int __must_check uds_pack_open_chapter_index_page(struct open_chapter_index *chapter_index,
u8 *memory, u32 first_list,
bool last_page, u32 *lists_packed);
int __must_check uds_initialize_chapter_index_page(struct delta_index_page *index_page,
const struct index_geometry *geometry,
u8 *page_buffer, u64 volume_nonce);
int __must_check uds_validate_chapter_index_page(const struct delta_index_page *index_page,
const struct index_geometry *geometry);
int __must_check uds_search_chapter_index_page(struct delta_index_page *index_page,
const struct index_geometry *geometry,
const struct uds_record_name *name,
u16 *record_page_ptr);
#endif /* UDS_CHAPTER_INDEX_H */
@@ -0,0 +1,376 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "config.h"
#include "logger.h"
#include "memory-alloc.h"
#include "numeric.h"
#include "string-utils.h"
#include "thread-utils.h"
static const u8 INDEX_CONFIG_MAGIC[] = "ALBIC";
static const u8 INDEX_CONFIG_VERSION_6_02[] = "06.02";
static const u8 INDEX_CONFIG_VERSION_8_02[] = "08.02";
#define DEFAULT_VOLUME_READ_THREADS 2
#define MAX_VOLUME_READ_THREADS 16
#define INDEX_CONFIG_MAGIC_LENGTH (sizeof(INDEX_CONFIG_MAGIC) - 1)
#define INDEX_CONFIG_VERSION_LENGTH ((int)(sizeof(INDEX_CONFIG_VERSION_6_02) - 1))
static bool is_version(const u8 *version, u8 *buffer)
{
return memcmp(version, buffer, INDEX_CONFIG_VERSION_LENGTH) == 0;
}
static bool are_matching_configurations(struct uds_configuration *saved_config,
struct index_geometry *saved_geometry,
struct uds_configuration *user)
{
struct index_geometry *geometry = user->geometry;
bool result = true;
if (saved_geometry->record_pages_per_chapter != geometry->record_pages_per_chapter) {
vdo_log_error("Record pages per chapter (%u) does not match (%u)",
saved_geometry->record_pages_per_chapter,
geometry->record_pages_per_chapter);
result = false;
}
if (saved_geometry->chapters_per_volume != geometry->chapters_per_volume) {
vdo_log_error("Chapter count (%u) does not match (%u)",
saved_geometry->chapters_per_volume,
geometry->chapters_per_volume);
result = false;
}
if (saved_geometry->sparse_chapters_per_volume != geometry->sparse_chapters_per_volume) {
vdo_log_error("Sparse chapter count (%u) does not match (%u)",
saved_geometry->sparse_chapters_per_volume,
geometry->sparse_chapters_per_volume);
result = false;
}
if (saved_config->cache_chapters != user->cache_chapters) {
vdo_log_error("Cache size (%u) does not match (%u)",
saved_config->cache_chapters, user->cache_chapters);
result = false;
}
if (saved_config->volume_index_mean_delta != user->volume_index_mean_delta) {
vdo_log_error("Volume index mean delta (%u) does not match (%u)",
saved_config->volume_index_mean_delta,
user->volume_index_mean_delta);
result = false;
}
if (saved_geometry->bytes_per_page != geometry->bytes_per_page) {
vdo_log_error("Bytes per page value (%zu) does not match (%zu)",
saved_geometry->bytes_per_page, geometry->bytes_per_page);
result = false;
}
if (saved_config->sparse_sample_rate != user->sparse_sample_rate) {
vdo_log_error("Sparse sample rate (%u) does not match (%u)",
saved_config->sparse_sample_rate,
user->sparse_sample_rate);
result = false;
}
if (saved_config->nonce != user->nonce) {
vdo_log_error("Nonce (%llu) does not match (%llu)",
(unsigned long long) saved_config->nonce,
(unsigned long long) user->nonce);
result = false;
}
return result;
}
/* Read the configuration and validate it against the provided one. */
int uds_validate_config_contents(struct buffered_reader *reader,
struct uds_configuration *user_config)
{
int result;
struct uds_configuration config;
struct index_geometry geometry;
u8 version_buffer[INDEX_CONFIG_VERSION_LENGTH];
u32 bytes_per_page;
u8 buffer[sizeof(struct uds_configuration_6_02)];
size_t offset = 0;
result = uds_verify_buffered_data(reader, INDEX_CONFIG_MAGIC,
INDEX_CONFIG_MAGIC_LENGTH);
if (result != UDS_SUCCESS)
return result;
result = uds_read_from_buffered_reader(reader, version_buffer,
INDEX_CONFIG_VERSION_LENGTH);
if (result != UDS_SUCCESS)
return vdo_log_error_strerror(result, "cannot read index config version");
if (!is_version(INDEX_CONFIG_VERSION_6_02, version_buffer) &&
!is_version(INDEX_CONFIG_VERSION_8_02, version_buffer)) {
return vdo_log_error_strerror(UDS_CORRUPT_DATA,
"unsupported configuration version: '%.*s'",
INDEX_CONFIG_VERSION_LENGTH,
version_buffer);
}
result = uds_read_from_buffered_reader(reader, buffer, sizeof(buffer));
if (result != UDS_SUCCESS)
return vdo_log_error_strerror(result, "cannot read config data");
decode_u32_le(buffer, &offset, &geometry.record_pages_per_chapter);
decode_u32_le(buffer, &offset, &geometry.chapters_per_volume);
decode_u32_le(buffer, &offset, &geometry.sparse_chapters_per_volume);
decode_u32_le(buffer, &offset, &config.cache_chapters);
offset += sizeof(u32);
decode_u32_le(buffer, &offset, &config.volume_index_mean_delta);
decode_u32_le(buffer, &offset, &bytes_per_page);
geometry.bytes_per_page = bytes_per_page;
decode_u32_le(buffer, &offset, &config.sparse_sample_rate);
decode_u64_le(buffer, &offset, &config.nonce);
result = VDO_ASSERT(offset == sizeof(struct uds_configuration_6_02),
"%zu bytes read but not decoded",
sizeof(struct uds_configuration_6_02) - offset);
if (result != VDO_SUCCESS)
return UDS_CORRUPT_DATA;
if (is_version(INDEX_CONFIG_VERSION_6_02, version_buffer)) {
user_config->geometry->remapped_virtual = 0;
user_config->geometry->remapped_physical = 0;
} else {
u8 remapping[sizeof(u64) + sizeof(u64)];
result = uds_read_from_buffered_reader(reader, remapping,
sizeof(remapping));
if (result != UDS_SUCCESS)
return vdo_log_error_strerror(result, "cannot read converted config");
offset = 0;
decode_u64_le(remapping, &offset,
&user_config->geometry->remapped_virtual);
decode_u64_le(remapping, &offset,
&user_config->geometry->remapped_physical);
}
if (!are_matching_configurations(&config, &geometry, user_config)) {
vdo_log_warning("Supplied configuration does not match save");
return UDS_NO_INDEX;
}
return UDS_SUCCESS;
}
/*
* Write the configuration to stable storage. If the superblock version is < 4, write the 6.02
* version; otherwise write the 8.02 version, indicating the configuration is for an index that has
* been reduced by one chapter.
*/
int uds_write_config_contents(struct buffered_writer *writer,
struct uds_configuration *config, u32 version)
{
int result;
struct index_geometry *geometry = config->geometry;
u8 buffer[sizeof(struct uds_configuration_8_02)];
size_t offset = 0;
result = uds_write_to_buffered_writer(writer, INDEX_CONFIG_MAGIC,
INDEX_CONFIG_MAGIC_LENGTH);
if (result != UDS_SUCCESS)
return result;
/*
* If version is < 4, the index has not been reduced by a chapter so it must be written out
* as version 6.02 so that it is still compatible with older versions of UDS.
*/
if (version >= 4) {
result = uds_write_to_buffered_writer(writer, INDEX_CONFIG_VERSION_8_02,
INDEX_CONFIG_VERSION_LENGTH);
if (result != UDS_SUCCESS)
return result;
} else {
result = uds_write_to_buffered_writer(writer, INDEX_CONFIG_VERSION_6_02,
INDEX_CONFIG_VERSION_LENGTH);
if (result != UDS_SUCCESS)
return result;
}
encode_u32_le(buffer, &offset, geometry->record_pages_per_chapter);
encode_u32_le(buffer, &offset, geometry->chapters_per_volume);
encode_u32_le(buffer, &offset, geometry->sparse_chapters_per_volume);
encode_u32_le(buffer, &offset, config->cache_chapters);
encode_u32_le(buffer, &offset, 0);
encode_u32_le(buffer, &offset, config->volume_index_mean_delta);
encode_u32_le(buffer, &offset, geometry->bytes_per_page);
encode_u32_le(buffer, &offset, config->sparse_sample_rate);
encode_u64_le(buffer, &offset, config->nonce);
result = VDO_ASSERT(offset == sizeof(struct uds_configuration_6_02),
"%zu bytes encoded, of %zu expected", offset,
sizeof(struct uds_configuration_6_02));
if (result != VDO_SUCCESS)
return result;
if (version >= 4) {
encode_u64_le(buffer, &offset, geometry->remapped_virtual);
encode_u64_le(buffer, &offset, geometry->remapped_physical);
}
return uds_write_to_buffered_writer(writer, buffer, offset);
}
/* Compute configuration parameters that depend on memory size. */
static int compute_memory_sizes(uds_memory_config_size_t mem_gb, bool sparse,
u32 *chapters_per_volume, u32 *record_pages_per_chapter,
u32 *sparse_chapters_per_volume)
{
u32 reduced_chapters = 0;
u32 base_chapters;
if (mem_gb == UDS_MEMORY_CONFIG_256MB) {
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_512MB) {
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 2 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_768MB) {
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 3 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if ((mem_gb >= 1) && (mem_gb <= UDS_MEMORY_CONFIG_MAX)) {
base_chapters = mem_gb * DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = DEFAULT_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_REDUCED_256MB) {
reduced_chapters = 1;
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_REDUCED_512MB) {
reduced_chapters = 1;
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 2 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_REDUCED_768MB) {
reduced_chapters = 1;
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 3 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if ((mem_gb >= 1 + UDS_MEMORY_CONFIG_REDUCED) &&
(mem_gb <= UDS_MEMORY_CONFIG_REDUCED_MAX)) {
reduced_chapters = 1;
base_chapters = ((mem_gb - UDS_MEMORY_CONFIG_REDUCED) *
DEFAULT_CHAPTERS_PER_VOLUME);
*record_pages_per_chapter = DEFAULT_RECORD_PAGES_PER_CHAPTER;
} else {
vdo_log_error("received invalid memory size");
return -EINVAL;
}
if (sparse) {
/* Make 95% of chapters sparse, allowing 10x more records. */
*sparse_chapters_per_volume = (19 * base_chapters) / 2;
base_chapters *= 10;
} else {
*sparse_chapters_per_volume = 0;
}
*chapters_per_volume = base_chapters - reduced_chapters;
return UDS_SUCCESS;
}
static unsigned int __must_check normalize_zone_count(unsigned int requested)
{
unsigned int zone_count = requested;
if (zone_count == 0)
zone_count = num_online_cpus() / 2;
if (zone_count < 1)
zone_count = 1;
if (zone_count > MAX_ZONES)
zone_count = MAX_ZONES;
vdo_log_info("Using %u indexing zone%s for concurrency.",
zone_count, zone_count == 1 ? "" : "s");
return zone_count;
}
static unsigned int __must_check normalize_read_threads(unsigned int requested)
{
unsigned int read_threads = requested;
if (read_threads < 1)
read_threads = DEFAULT_VOLUME_READ_THREADS;
if (read_threads > MAX_VOLUME_READ_THREADS)
read_threads = MAX_VOLUME_READ_THREADS;
return read_threads;
}
int uds_make_configuration(const struct uds_parameters *params,
struct uds_configuration **config_ptr)
{
struct uds_configuration *config;
u32 chapters_per_volume = 0;
u32 record_pages_per_chapter = 0;
u32 sparse_chapters_per_volume = 0;
int result;
result = compute_memory_sizes(params->memory_size, params->sparse,
&chapters_per_volume, &record_pages_per_chapter,
&sparse_chapters_per_volume);
if (result != UDS_SUCCESS)
return result;
result = vdo_allocate(1, __func__, &config);
if (result != VDO_SUCCESS)
return result;
result = uds_make_index_geometry(DEFAULT_BYTES_PER_PAGE, record_pages_per_chapter,
chapters_per_volume, sparse_chapters_per_volume,
0, 0, &config->geometry);
if (result != UDS_SUCCESS) {
uds_free_configuration(config);
return result;
}
config->zone_count = normalize_zone_count(params->zone_count);
config->read_threads = normalize_read_threads(params->read_threads);
config->cache_chapters = DEFAULT_CACHE_CHAPTERS;
config->volume_index_mean_delta = DEFAULT_VOLUME_INDEX_MEAN_DELTA;
config->sparse_sample_rate = (params->sparse ? DEFAULT_SPARSE_SAMPLE_RATE : 0);
config->nonce = params->nonce;
config->bdev = params->bdev;
config->offset = params->offset;
config->size = params->size;
*config_ptr = config;
return UDS_SUCCESS;
}
void uds_free_configuration(struct uds_configuration *config)
{
if (config != NULL) {
uds_free_index_geometry(config->geometry);
vdo_free(config);
}
}
void uds_log_configuration(struct uds_configuration *config)
{
struct index_geometry *geometry = config->geometry;
vdo_log_debug("Configuration:");
vdo_log_debug(" Record pages per chapter: %10u", geometry->record_pages_per_chapter);
vdo_log_debug(" Chapters per volume: %10u", geometry->chapters_per_volume);
vdo_log_debug(" Sparse chapters per volume: %10u", geometry->sparse_chapters_per_volume);
vdo_log_debug(" Cache size (chapters): %10u", config->cache_chapters);
vdo_log_debug(" Volume index mean delta: %10u", config->volume_index_mean_delta);
vdo_log_debug(" Bytes per page: %10zu", geometry->bytes_per_page);
vdo_log_debug(" Sparse sample rate: %10u", config->sparse_sample_rate);
vdo_log_debug(" Nonce: %llu", (unsigned long long) config->nonce);
}
@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_CONFIG_H
#define UDS_CONFIG_H
#include "geometry.h"
#include "indexer.h"
#include "io-factory.h"
/*
* The uds_configuration records a variety of parameters used to configure a new UDS index. Some
* parameters are provided by the client, while others are fixed or derived from user-supplied
* values. It is created when an index is created, and it is recorded in the index metadata.
*/
enum {
DEFAULT_VOLUME_INDEX_MEAN_DELTA = 4096,
DEFAULT_CACHE_CHAPTERS = 7,
DEFAULT_SPARSE_SAMPLE_RATE = 32,
MAX_ZONES = 16,
};
/* A set of configuration parameters for the indexer. */
struct uds_configuration {
/* Storage device for the index */
struct block_device *bdev;
/* The maximum allowable size of the index */
size_t size;
/* The offset where the index should start */
off_t offset;
/* Parameters for the volume */
/* The volume layout */
struct index_geometry *geometry;
/* Index owner's nonce */
u64 nonce;
/* The number of threads used to process index requests */
unsigned int zone_count;
/* The number of threads used to read volume pages */
unsigned int read_threads;
/* Size of the page cache and sparse chapter index cache in chapters */
u32 cache_chapters;
/* Parameters for the volume index */
/* The mean delta for the volume index */
u32 volume_index_mean_delta;
/* Sampling rate for sparse indexing */
u32 sparse_sample_rate;
};
/* On-disk structure of data for a version 8.02 index. */
struct uds_configuration_8_02 {
/* Smaller (16), Small (64) or large (256) indices */
u32 record_pages_per_chapter;
/* Total number of chapters per volume */
u32 chapters_per_volume;
/* Number of sparse chapters per volume */
u32 sparse_chapters_per_volume;
/* Size of the page cache, in chapters */
u32 cache_chapters;
/* Unused field */
u32 unused;
/* The volume index mean delta to use */
u32 volume_index_mean_delta;
/* Size of a page, used for both record pages and index pages */
u32 bytes_per_page;
/* Sampling rate for sparse indexing */
u32 sparse_sample_rate;
/* Index owner's nonce */
u64 nonce;
/* Virtual chapter remapped from physical chapter 0 */
u64 remapped_virtual;
/* New physical chapter which remapped chapter was moved to */
u64 remapped_physical;
} __packed;
/* On-disk structure of data for a version 6.02 index. */
struct uds_configuration_6_02 {
/* Smaller (16), Small (64) or large (256) indices */
u32 record_pages_per_chapter;
/* Total number of chapters per volume */
u32 chapters_per_volume;
/* Number of sparse chapters per volume */
u32 sparse_chapters_per_volume;
/* Size of the page cache, in chapters */
u32 cache_chapters;
/* Unused field */
u32 unused;
/* The volume index mean delta to use */
u32 volume_index_mean_delta;
/* Size of a page, used for both record pages and index pages */
u32 bytes_per_page;
/* Sampling rate for sparse indexing */
u32 sparse_sample_rate;
/* Index owner's nonce */
u64 nonce;
} __packed;
int __must_check uds_make_configuration(const struct uds_parameters *params,
struct uds_configuration **config_ptr);
void uds_free_configuration(struct uds_configuration *config);
int __must_check uds_validate_config_contents(struct buffered_reader *reader,
struct uds_configuration *config);
int __must_check uds_write_config_contents(struct buffered_writer *writer,
struct uds_configuration *config, u32 version);
void uds_log_configuration(struct uds_configuration *config);
#endif /* UDS_CONFIG_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,279 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_DELTA_INDEX_H
#define UDS_DELTA_INDEX_H
#include <linux/cache.h>
#include "numeric.h"
#include "time-utils.h"
#include "config.h"
#include "io-factory.h"
/*
* A delta index is a key-value store, where each entry maps an address (the key) to a payload (the
* value). The entries are sorted by address, and only the delta between successive addresses is
* stored in the entry. The addresses are assumed to be uniformly distributed, and the deltas are
* therefore exponentially distributed.
*
* A delta_index can either be mutable or immutable depending on its expected use. The immutable
* form of a delta index is used for the indexes of closed chapters committed to the volume. The
* mutable form of a delta index is used by the volume index, and also by the chapter index in an
* open chapter. Like the index as a whole, each mutable delta index is divided into a number of
* independent zones.
*/
struct delta_list {
/* The offset of the delta list start, in bits */
u64 start;
/* The number of bits in the delta list */
u16 size;
/* Where the last search "found" the key, in bits */
u16 save_offset;
/* The key for the record just before save_offset */
u32 save_key;
};
struct delta_zone {
/* The delta list memory */
u8 *memory;
/* The delta list headers */
struct delta_list *delta_lists;
/* Temporary starts of delta lists */
u64 *new_offsets;
/* Buffered writer for saving an index */
struct buffered_writer *buffered_writer;
/* The size of delta list memory */
size_t size;
/* Nanoseconds spent rebalancing */
ktime_t rebalance_time;
/* Number of memory rebalances */
u32 rebalance_count;
/* The number of bits in a stored value */
u8 value_bits;
/* The number of bits in the minimal key code */
u16 min_bits;
/* The number of keys used in a minimal code */
u32 min_keys;
/* The number of keys used for another code bit */
u32 incr_keys;
/* The number of records in the index */
u64 record_count;
/* The number of collision records */
u64 collision_count;
/* The number of records removed */
u64 discard_count;
/* The number of UDS_OVERFLOW errors detected */
u64 overflow_count;
/* The index of the first delta list */
u32 first_list;
/* The number of delta lists */
u32 list_count;
/* Tag belonging to this delta index */
u8 tag;
} __aligned(L1_CACHE_BYTES);
struct delta_list_save_info {
/* Tag identifying which delta index this list is in */
u8 tag;
/* Bit offset of the start of the list data */
u8 bit_offset;
/* Number of bytes of list data */
u16 byte_count;
/* The delta list number within the delta index */
u32 index;
} __packed;
struct delta_index {
/* The zones */
struct delta_zone *delta_zones;
/* The number of zones */
unsigned int zone_count;
/* The number of delta lists */
u32 list_count;
/* Maximum lists per zone */
u32 lists_per_zone;
/* Total memory allocated to this index */
size_t memory_size;
/* The number of non-empty lists at load time per zone */
u32 load_lists[MAX_ZONES];
/* True if this index is mutable */
bool mutable;
/* Tag belonging to this delta index */
u8 tag;
};
/*
* A delta_index_page describes a single page of a chapter index. The delta_index field allows the
* page to be treated as an immutable delta_index. We use the delta_zone field to treat the chapter
* index page as a single zone index, and without the need to do an additional memory allocation.
*/
struct delta_index_page {
struct delta_index delta_index;
/* These values are loaded from the delta_page_header */
u32 lowest_list_number;
u32 highest_list_number;
u64 virtual_chapter_number;
/* This structure describes the single zone of a delta index page. */
struct delta_zone delta_zone;
};
/*
* Notes on the delta_index_entries:
*
* The fields documented as "public" can be read by any code that uses a delta_index. The fields
* documented as "private" carry information between delta_index method calls and should not be
* used outside the delta_index module.
*
* (1) The delta_index_entry is used like an iterator when searching a delta list.
*
* (2) It is also the result of a successful search and can be used to refer to the element found
* by the search.
*
* (3) It is also the result of an unsuccessful search and can be used to refer to the insertion
* point for a new record.
*
* (4) If at_end is true, the delta_list entry can only be used as the insertion point for a new
* record at the end of the list.
*
* (5) If at_end is false and is_collision is true, the delta_list entry fields refer to a
* collision entry in the list, and the delta_list entry can be used as a reference to this
* entry.
*
* (6) If at_end is false and is_collision is false, the delta_list entry fields refer to a
* non-collision entry in the list. Such delta_list entries can be used as a reference to a
* found entry, or an insertion point for a non-collision entry before this entry, or an
* insertion point for a collision entry that collides with this entry.
*/
struct delta_index_entry {
/* Public fields */
/* The key for this entry */
u32 key;
/* We are after the last list entry */
bool at_end;
/* This record is a collision */
bool is_collision;
/* Private fields */
/* This delta list overflowed */
bool list_overflow;
/* The number of bits used for the value */
u8 value_bits;
/* The number of bits used for the entire entry */
u16 entry_bits;
/* The delta index zone */
struct delta_zone *delta_zone;
/* The delta list containing the entry */
struct delta_list *delta_list;
/* The delta list number */
u32 list_number;
/* Bit offset of this entry within the list */
u16 offset;
/* The delta between this and previous entry */
u32 delta;
/* Temporary delta list for immutable indices */
struct delta_list temp_delta_list;
};
struct delta_index_stats {
/* Number of bytes allocated */
size_t memory_allocated;
/* Nanoseconds spent rebalancing */
ktime_t rebalance_time;
/* Number of memory rebalances */
u32 rebalance_count;
/* The number of records in the index */
u64 record_count;
/* The number of collision records */
u64 collision_count;
/* The number of records removed */
u64 discard_count;
/* The number of UDS_OVERFLOW errors detected */
u64 overflow_count;
/* The number of delta lists */
u32 list_count;
};
int __must_check uds_initialize_delta_index(struct delta_index *delta_index,
unsigned int zone_count, u32 list_count,
u32 mean_delta, u32 payload_bits,
size_t memory_size, u8 tag);
int __must_check uds_initialize_delta_index_page(struct delta_index_page *delta_index_page,
u64 expected_nonce, u32 mean_delta,
u32 payload_bits, u8 *memory,
size_t memory_size);
void uds_uninitialize_delta_index(struct delta_index *delta_index);
void uds_reset_delta_index(const struct delta_index *delta_index);
int __must_check uds_pack_delta_index_page(const struct delta_index *delta_index,
u64 header_nonce, u8 *memory,
size_t memory_size,
u64 virtual_chapter_number, u32 first_list,
u32 *list_count);
int __must_check uds_start_restoring_delta_index(struct delta_index *delta_index,
struct buffered_reader **buffered_readers,
unsigned int reader_count);
int __must_check uds_finish_restoring_delta_index(struct delta_index *delta_index,
struct buffered_reader **buffered_readers,
unsigned int reader_count);
int __must_check uds_check_guard_delta_lists(struct buffered_reader **buffered_readers,
unsigned int reader_count);
int __must_check uds_start_saving_delta_index(const struct delta_index *delta_index,
unsigned int zone_number,
struct buffered_writer *buffered_writer);
int __must_check uds_finish_saving_delta_index(const struct delta_index *delta_index,
unsigned int zone_number);
int __must_check uds_write_guard_delta_list(struct buffered_writer *buffered_writer);
size_t __must_check uds_compute_delta_index_save_bytes(u32 list_count,
size_t memory_size);
int __must_check uds_start_delta_index_search(const struct delta_index *delta_index,
u32 list_number, u32 key,
struct delta_index_entry *iterator);
int __must_check uds_next_delta_index_entry(struct delta_index_entry *delta_entry);
int __must_check uds_remember_delta_index_offset(const struct delta_index_entry *delta_entry);
int __must_check uds_get_delta_index_entry(const struct delta_index *delta_index,
u32 list_number, u32 key, const u8 *name,
struct delta_index_entry *delta_entry);
int __must_check uds_get_delta_entry_collision(const struct delta_index_entry *delta_entry,
u8 *name);
u32 __must_check uds_get_delta_entry_value(const struct delta_index_entry *delta_entry);
int __must_check uds_set_delta_entry_value(const struct delta_index_entry *delta_entry, u32 value);
int __must_check uds_put_delta_index_entry(struct delta_index_entry *delta_entry, u32 key,
u32 value, const u8 *name);
int __must_check uds_remove_delta_index_entry(struct delta_index_entry *delta_entry);
void uds_get_delta_index_stats(const struct delta_index *delta_index,
struct delta_index_stats *stats);
size_t __must_check uds_compute_delta_index_size(u32 entry_count, u32 mean_delta,
u32 payload_bits);
u32 uds_get_delta_index_page_count(u32 entry_count, u32 list_count, u32 mean_delta,
u32 payload_bits, size_t bytes_per_page);
void uds_log_delta_index_entry(struct delta_index_entry *delta_entry);
#endif /* UDS_DELTA_INDEX_H */
@@ -0,0 +1,279 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "funnel-requestqueue.h"
#include <linux/atomic.h>
#include <linux/compiler.h>
#include <linux/wait.h>
#include "funnel-queue.h"
#include "logger.h"
#include "memory-alloc.h"
#include "thread-utils.h"
/*
* This queue will attempt to handle requests in reasonably sized batches instead of reacting
* immediately to each new request. The wait time between batches is dynamically adjusted up or
* down to try to balance responsiveness against wasted thread run time.
*
* If the wait time becomes long enough, the queue will become dormant and must be explicitly
* awoken when a new request is enqueued. The enqueue operation updates "newest" in the funnel
* queue via xchg (which is a memory barrier), and later checks "dormant" to decide whether to do a
* wakeup of the worker thread.
*
* When deciding to go to sleep, the worker thread sets "dormant" and then examines "newest" to
* decide if the funnel queue is idle. In dormant mode, the last examination of "newest" before
* going to sleep is done inside the wait_event_interruptible() macro, after a point where one or
* more memory barriers have been issued. (Preparing to sleep uses spin locks.) Even if the funnel
* queue's "next" field update isn't visible yet to make the entry accessible, its existence will
* kick the worker thread out of dormant mode and back into timer-based mode.
*
* Unbatched requests are used to communicate between different zone threads and will also cause
* the queue to awaken immediately.
*/
enum {
NANOSECOND = 1,
MICROSECOND = 1000 * NANOSECOND,
MILLISECOND = 1000 * MICROSECOND,
DEFAULT_WAIT_TIME = 20 * MICROSECOND,
MINIMUM_WAIT_TIME = DEFAULT_WAIT_TIME / 2,
MAXIMUM_WAIT_TIME = MILLISECOND,
MINIMUM_BATCH = 32,
MAXIMUM_BATCH = 64,
};
struct uds_request_queue {
/* Wait queue for synchronizing producers and consumer */
struct wait_queue_head wait_head;
/* Function to process a request */
uds_request_queue_processor_fn processor;
/* Queue of new incoming requests */
struct funnel_queue *main_queue;
/* Queue of old requests to retry */
struct funnel_queue *retry_queue;
/* The thread id of the worker thread */
struct thread *thread;
/* True if the worker was started */
bool started;
/* When true, requests can be enqueued */
bool running;
/* A flag set when the worker is waiting without a timeout */
atomic_t dormant;
};
static inline struct uds_request *poll_queues(struct uds_request_queue *queue)
{
struct funnel_queue_entry *entry;
entry = vdo_funnel_queue_poll(queue->retry_queue);
if (entry != NULL)
return container_of(entry, struct uds_request, queue_link);
entry = vdo_funnel_queue_poll(queue->main_queue);
if (entry != NULL)
return container_of(entry, struct uds_request, queue_link);
return NULL;
}
static inline bool are_queues_idle(struct uds_request_queue *queue)
{
return vdo_is_funnel_queue_idle(queue->retry_queue) &&
vdo_is_funnel_queue_idle(queue->main_queue);
}
/*
* Determine if there is a next request to process, and return it if there is. Also return flags
* indicating whether the worker thread can sleep (for the use of wait_event() macros) and whether
* the thread did sleep before returning a new request.
*/
static inline bool dequeue_request(struct uds_request_queue *queue,
struct uds_request **request_ptr, bool *waited_ptr)
{
struct uds_request *request = poll_queues(queue);
if (request != NULL) {
*request_ptr = request;
return true;
}
if (!READ_ONCE(queue->running)) {
/* Wake the worker thread so it can exit. */
*request_ptr = NULL;
return true;
}
*request_ptr = NULL;
*waited_ptr = true;
return false;
}
static void wait_for_request(struct uds_request_queue *queue, bool dormant,
unsigned long timeout, struct uds_request **request,
bool *waited)
{
if (dormant) {
wait_event_interruptible(queue->wait_head,
(dequeue_request(queue, request, waited) ||
!are_queues_idle(queue)));
return;
}
wait_event_interruptible_hrtimeout(queue->wait_head,
dequeue_request(queue, request, waited),
ns_to_ktime(timeout));
}
static void request_queue_worker(void *arg)
{
struct uds_request_queue *queue = arg;
struct uds_request *request = NULL;
unsigned long time_batch = DEFAULT_WAIT_TIME;
bool dormant = atomic_read(&queue->dormant);
bool waited = false;
long current_batch = 0;
for (;;) {
wait_for_request(queue, dormant, time_batch, &request, &waited);
if (likely(request != NULL)) {
current_batch++;
queue->processor(request);
} else if (!READ_ONCE(queue->running)) {
break;
}
if (dormant) {
/*
* The queue has been roused from dormancy. Clear the flag so enqueuers can
* stop broadcasting. No fence is needed for this transition.
*/
atomic_set(&queue->dormant, false);
dormant = false;
time_batch = DEFAULT_WAIT_TIME;
} else if (waited) {
/*
* We waited for this request to show up. Adjust the wait time to smooth
* out the batch size.
*/
if (current_batch < MINIMUM_BATCH) {
/*
* If the last batch of requests was too small, increase the wait
* time.
*/
time_batch += time_batch / 4;
if (time_batch >= MAXIMUM_WAIT_TIME) {
atomic_set(&queue->dormant, true);
dormant = true;
}
} else if (current_batch > MAXIMUM_BATCH) {
/*
* If the last batch of requests was too large, decrease the wait
* time.
*/
time_batch -= time_batch / 4;
if (time_batch < MINIMUM_WAIT_TIME)
time_batch = MINIMUM_WAIT_TIME;
}
current_batch = 0;
}
}
/*
* Ensure that we process any remaining requests that were enqueued before trying to shut
* down. The corresponding write barrier is in uds_request_queue_finish().
*/
smp_rmb();
while ((request = poll_queues(queue)) != NULL)
queue->processor(request);
}
int uds_make_request_queue(const char *queue_name,
uds_request_queue_processor_fn processor,
struct uds_request_queue **queue_ptr)
{
int result;
struct uds_request_queue *queue;
result = vdo_allocate(1, __func__, &queue);
if (result != VDO_SUCCESS)
return result;
queue->processor = processor;
queue->running = true;
atomic_set(&queue->dormant, false);
init_waitqueue_head(&queue->wait_head);
result = vdo_make_funnel_queue(&queue->main_queue);
if (result != VDO_SUCCESS) {
uds_request_queue_finish(queue);
return result;
}
result = vdo_make_funnel_queue(&queue->retry_queue);
if (result != VDO_SUCCESS) {
uds_request_queue_finish(queue);
return result;
}
result = vdo_create_thread(request_queue_worker, queue, queue_name,
&queue->thread);
if (result != VDO_SUCCESS) {
uds_request_queue_finish(queue);
return result;
}
queue->started = true;
*queue_ptr = queue;
return UDS_SUCCESS;
}
static inline void wake_up_worker(struct uds_request_queue *queue)
{
if (wq_has_sleeper(&queue->wait_head))
wake_up(&queue->wait_head);
}
void uds_request_queue_enqueue(struct uds_request_queue *queue,
struct uds_request *request)
{
struct funnel_queue *sub_queue;
bool unbatched = request->unbatched;
sub_queue = request->requeued ? queue->retry_queue : queue->main_queue;
vdo_funnel_queue_put(sub_queue, &request->queue_link);
/*
* We must wake the worker thread when it is dormant. A read fence isn't needed here since
* we know the queue operation acts as one.
*/
if (atomic_read(&queue->dormant) || unbatched)
wake_up_worker(queue);
}
void uds_request_queue_finish(struct uds_request_queue *queue)
{
if (queue == NULL)
return;
/*
* This memory barrier ensures that any requests we queued will be seen. The point is that
* when dequeue_request() sees the following update to the running flag, it will also be
* able to see any change we made to a next field in the funnel queue entry. The
* corresponding read barrier is in request_queue_worker().
*/
smp_wmb();
WRITE_ONCE(queue->running, false);
if (queue->started) {
wake_up_worker(queue);
vdo_join_threads(queue->thread);
}
vdo_free_funnel_queue(queue->main_queue);
vdo_free_funnel_queue(queue->retry_queue);
vdo_free(queue);
}
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_REQUEST_QUEUE_H
#define UDS_REQUEST_QUEUE_H
#include "indexer.h"
/*
* A simple request queue which will handle new requests in the order in which they are received,
* and will attempt to handle requeued requests before new ones. However, the nature of the
* implementation means that it cannot guarantee this ordering; the prioritization is merely a
* hint.
*/
struct uds_request_queue;
typedef void (*uds_request_queue_processor_fn)(struct uds_request *);
int __must_check uds_make_request_queue(const char *queue_name,
uds_request_queue_processor_fn processor,
struct uds_request_queue **queue_ptr);
void uds_request_queue_enqueue(struct uds_request_queue *queue,
struct uds_request *request);
void uds_request_queue_finish(struct uds_request_queue *queue);
#endif /* UDS_REQUEST_QUEUE_H */
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "geometry.h"
#include <linux/compiler.h>
#include <linux/log2.h>
#include "errors.h"
#include "logger.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "delta-index.h"
#include "indexer.h"
/*
* An index volume is divided into a fixed number of fixed-size chapters, each consisting of a
* fixed number of fixed-size pages. The volume layout is defined by two constants and four
* parameters. The constants are that index records are 32 bytes long (16-byte block name plus
* 16-byte metadata) and that open chapter index hash slots are one byte long. The four parameters
* are the number of bytes in a page, the number of record pages in a chapter, the number of
* chapters in a volume, and the number of chapters that are sparse. From these parameters, we can
* derive the rest of the layout and other index properties.
*
* The index volume is sized by its maximum memory footprint. For a dense index, the persistent
* storage is about 10 times the size of the memory footprint. For a sparse index, the persistent
* storage is about 100 times the size of the memory footprint.
*
* For a small index with a memory footprint less than 1GB, there are three possible memory
* configurations: 0.25GB, 0.5GB and 0.75GB. The default geometry for each is 1024 index records
* per 32 KB page, 1024 chapters per volume, and either 64, 128, or 192 record pages per chapter
* (resulting in 6, 13, or 20 index pages per chapter) depending on the memory configuration. For
* the VDO default of a 0.25 GB index, this yields a deduplication window of 256 GB using about 2.5
* GB for the persistent storage and 256 MB of RAM.
*
* For a larger index with a memory footprint that is a multiple of 1 GB, the geometry is 1024
* index records per 32 KB page, 256 record pages per chapter, 26 index pages per chapter, and 1024
* chapters for every GB of memory footprint. For a 1 GB volume, this yields a deduplication window
* of 1 TB using about 9GB of persistent storage and 1 GB of RAM.
*
* The above numbers hold for volumes which have no sparse chapters. A sparse volume has 10 times
* as many chapters as the corresponding non-sparse volume, which provides 10 times the
* deduplication window while using 10 times as much persistent storage as the equivalent
* non-sparse volume with the same memory footprint.
*
* If the volume has been converted from a non-lvm format to an lvm volume, the number of chapters
* per volume will have been reduced by one by eliminating physical chapter 0, and the virtual
* chapter that formerly mapped to physical chapter 0 may be remapped to another physical chapter.
* This remapping is expressed by storing which virtual chapter was remapped, and which physical
* chapter it was moved to.
*/
int uds_make_index_geometry(size_t bytes_per_page, u32 record_pages_per_chapter,
u32 chapters_per_volume, u32 sparse_chapters_per_volume,
u64 remapped_virtual, u64 remapped_physical,
struct index_geometry **geometry_ptr)
{
int result;
struct index_geometry *geometry;
result = vdo_allocate(1, "geometry", &geometry);
if (result != VDO_SUCCESS)
return result;
geometry->bytes_per_page = bytes_per_page;
geometry->record_pages_per_chapter = record_pages_per_chapter;
geometry->chapters_per_volume = chapters_per_volume;
geometry->sparse_chapters_per_volume = sparse_chapters_per_volume;
geometry->dense_chapters_per_volume = chapters_per_volume - sparse_chapters_per_volume;
geometry->remapped_virtual = remapped_virtual;
geometry->remapped_physical = remapped_physical;
geometry->records_per_page = bytes_per_page / BYTES_PER_RECORD;
geometry->records_per_chapter = geometry->records_per_page * record_pages_per_chapter;
geometry->records_per_volume = (u64) geometry->records_per_chapter * chapters_per_volume;
geometry->chapter_mean_delta = 1 << DEFAULT_CHAPTER_MEAN_DELTA_BITS;
geometry->chapter_payload_bits = bits_per(record_pages_per_chapter - 1);
/*
* We want 1 delta list for every 64 records in the chapter.
* The "| 077" ensures that the chapter_delta_list_bits computation
* does not underflow.
*/
geometry->chapter_delta_list_bits =
bits_per((geometry->records_per_chapter - 1) | 077) - 6;
geometry->delta_lists_per_chapter = 1 << geometry->chapter_delta_list_bits;
/* We need enough address bits to achieve the desired mean delta. */
geometry->chapter_address_bits =
(DEFAULT_CHAPTER_MEAN_DELTA_BITS -
geometry->chapter_delta_list_bits +
bits_per(geometry->records_per_chapter - 1));
geometry->index_pages_per_chapter =
uds_get_delta_index_page_count(geometry->records_per_chapter,
geometry->delta_lists_per_chapter,
geometry->chapter_mean_delta,
geometry->chapter_payload_bits,
bytes_per_page);
geometry->pages_per_chapter = geometry->index_pages_per_chapter + record_pages_per_chapter;
geometry->pages_per_volume = geometry->pages_per_chapter * chapters_per_volume;
geometry->bytes_per_volume =
bytes_per_page * (geometry->pages_per_volume + HEADER_PAGES_PER_VOLUME);
*geometry_ptr = geometry;
return UDS_SUCCESS;
}
int uds_copy_index_geometry(struct index_geometry *source,
struct index_geometry **geometry_ptr)
{
return uds_make_index_geometry(source->bytes_per_page,
source->record_pages_per_chapter,
source->chapters_per_volume,
source->sparse_chapters_per_volume,
source->remapped_virtual, source->remapped_physical,
geometry_ptr);
}
void uds_free_index_geometry(struct index_geometry *geometry)
{
vdo_free(geometry);
}
u32 __must_check uds_map_to_physical_chapter(const struct index_geometry *geometry,
u64 virtual_chapter)
{
u64 delta;
if (!uds_is_reduced_index_geometry(geometry))
return virtual_chapter % geometry->chapters_per_volume;
if (likely(virtual_chapter > geometry->remapped_virtual)) {
delta = virtual_chapter - geometry->remapped_virtual;
if (likely(delta > geometry->remapped_physical))
return delta % geometry->chapters_per_volume;
else
return delta - 1;
}
if (virtual_chapter == geometry->remapped_virtual)
return geometry->remapped_physical;
delta = geometry->remapped_virtual - virtual_chapter;
if (delta < geometry->chapters_per_volume)
return geometry->chapters_per_volume - delta;
/* This chapter is so old the answer doesn't matter. */
return 0;
}
/* Check whether any sparse chapters are in use. */
bool uds_has_sparse_chapters(const struct index_geometry *geometry,
u64 oldest_virtual_chapter, u64 newest_virtual_chapter)
{
return uds_is_sparse_index_geometry(geometry) &&
((newest_virtual_chapter - oldest_virtual_chapter + 1) >
geometry->dense_chapters_per_volume);
}
bool uds_is_chapter_sparse(const struct index_geometry *geometry,
u64 oldest_virtual_chapter, u64 newest_virtual_chapter,
u64 virtual_chapter_number)
{
return uds_has_sparse_chapters(geometry, oldest_virtual_chapter,
newest_virtual_chapter) &&
((virtual_chapter_number + geometry->dense_chapters_per_volume) <=
newest_virtual_chapter);
}
/* Calculate how many chapters to expire after opening the newest chapter. */
u32 uds_chapters_to_expire(const struct index_geometry *geometry, u64 newest_chapter)
{
/* If the index isn't full yet, don't expire anything. */
if (newest_chapter < geometry->chapters_per_volume)
return 0;
/* If a chapter is out of order... */
if (geometry->remapped_physical > 0) {
u64 oldest_chapter = newest_chapter - geometry->chapters_per_volume;
/*
* ... expire an extra chapter when expiring the moved chapter to free physical
* space for the new chapter ...
*/
if (oldest_chapter == geometry->remapped_virtual)
return 2;
/*
* ... but don't expire anything when the new chapter will use the physical chapter
* freed by expiring the moved chapter.
*/
if (oldest_chapter == (geometry->remapped_virtual + geometry->remapped_physical))
return 0;
}
/* Normally, just expire one. */
return 1;
}
@@ -0,0 +1,140 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_INDEX_GEOMETRY_H
#define UDS_INDEX_GEOMETRY_H
#include "indexer.h"
/*
* The index_geometry records parameters that define the layout of a UDS index volume, and the size and
* shape of various index structures. It is created when the index is created, and is referenced by
* many index sub-components.
*/
struct index_geometry {
/* Size of a chapter page, in bytes */
size_t bytes_per_page;
/* Number of record pages in a chapter */
u32 record_pages_per_chapter;
/* Total number of chapters in a volume */
u32 chapters_per_volume;
/* Number of sparsely-indexed chapters in a volume */
u32 sparse_chapters_per_volume;
/* Number of bits used to determine delta list numbers */
u8 chapter_delta_list_bits;
/* Virtual chapter remapped from physical chapter 0 */
u64 remapped_virtual;
/* New physical chapter where the remapped chapter can be found */
u64 remapped_physical;
/*
* The following properties are derived from the ones above, but they are computed and
* recorded as fields for convenience.
*/
/* Total number of pages in a volume, excluding the header */
u32 pages_per_volume;
/* Total number of bytes in a volume, including the header */
size_t bytes_per_volume;
/* Number of pages in a chapter */
u32 pages_per_chapter;
/* Number of index pages in a chapter index */
u32 index_pages_per_chapter;
/* Number of records that fit on a page */
u32 records_per_page;
/* Number of records that fit in a chapter */
u32 records_per_chapter;
/* Number of records that fit in a volume */
u64 records_per_volume;
/* Number of delta lists per chapter index */
u32 delta_lists_per_chapter;
/* Mean delta for chapter indexes */
u32 chapter_mean_delta;
/* Number of bits needed for record page numbers */
u8 chapter_payload_bits;
/* Number of bits used to compute addresses for chapter delta lists */
u8 chapter_address_bits;
/* Number of densely-indexed chapters in a volume */
u32 dense_chapters_per_volume;
};
enum {
/* The number of bytes in a record (name + metadata) */
BYTES_PER_RECORD = (UDS_RECORD_NAME_SIZE + UDS_RECORD_DATA_SIZE),
/* The default length of a page in a chapter, in bytes */
DEFAULT_BYTES_PER_PAGE = 1024 * BYTES_PER_RECORD,
/* The default maximum number of records per page */
DEFAULT_RECORDS_PER_PAGE = DEFAULT_BYTES_PER_PAGE / BYTES_PER_RECORD,
/* The default number of record pages in a chapter */
DEFAULT_RECORD_PAGES_PER_CHAPTER = 256,
/* The default number of record pages in a chapter for a small index */
SMALL_RECORD_PAGES_PER_CHAPTER = 64,
/* The default number of chapters in a volume */
DEFAULT_CHAPTERS_PER_VOLUME = 1024,
/* The default number of sparsely-indexed chapters in a volume */
DEFAULT_SPARSE_CHAPTERS_PER_VOLUME = 0,
/* The log2 of the default mean delta */
DEFAULT_CHAPTER_MEAN_DELTA_BITS = 16,
/* The log2 of the number of delta lists in a large chapter */
DEFAULT_CHAPTER_DELTA_LIST_BITS = 12,
/* The log2 of the number of delta lists in a small chapter */
SMALL_CHAPTER_DELTA_LIST_BITS = 10,
/* The number of header pages per volume */
HEADER_PAGES_PER_VOLUME = 1,
};
int __must_check uds_make_index_geometry(size_t bytes_per_page, u32 record_pages_per_chapter,
u32 chapters_per_volume,
u32 sparse_chapters_per_volume, u64 remapped_virtual,
u64 remapped_physical,
struct index_geometry **geometry_ptr);
int __must_check uds_copy_index_geometry(struct index_geometry *source,
struct index_geometry **geometry_ptr);
void uds_free_index_geometry(struct index_geometry *geometry);
u32 __must_check uds_map_to_physical_chapter(const struct index_geometry *geometry,
u64 virtual_chapter);
/*
* Check whether this geometry is reduced by a chapter. This will only be true if the volume was
* converted from a non-lvm volume to an lvm volume.
*/
static inline bool __must_check
uds_is_reduced_index_geometry(const struct index_geometry *geometry)
{
return !!(geometry->chapters_per_volume & 1);
}
static inline bool __must_check
uds_is_sparse_index_geometry(const struct index_geometry *geometry)
{
return geometry->sparse_chapters_per_volume > 0;
}
bool __must_check uds_has_sparse_chapters(const struct index_geometry *geometry,
u64 oldest_virtual_chapter,
u64 newest_virtual_chapter);
bool __must_check uds_is_chapter_sparse(const struct index_geometry *geometry,
u64 oldest_virtual_chapter,
u64 newest_virtual_chapter,
u64 virtual_chapter_number);
u32 __must_check uds_chapters_to_expire(const struct index_geometry *geometry,
u64 newest_chapter);
#endif /* UDS_INDEX_GEOMETRY_H */
@@ -0,0 +1,66 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_HASH_UTILS_H
#define UDS_HASH_UTILS_H
#include "numeric.h"
#include "geometry.h"
#include "indexer.h"
/* Utilities for extracting portions of a request name for various uses. */
/* How various portions of a record name are apportioned. */
enum {
VOLUME_INDEX_BYTES_OFFSET = 0,
VOLUME_INDEX_BYTES_COUNT = 8,
CHAPTER_INDEX_BYTES_OFFSET = 8,
CHAPTER_INDEX_BYTES_COUNT = 6,
SAMPLE_BYTES_OFFSET = 14,
SAMPLE_BYTES_COUNT = 2,
};
static inline u64 uds_extract_chapter_index_bytes(const struct uds_record_name *name)
{
const u8 *chapter_bits = &name->name[CHAPTER_INDEX_BYTES_OFFSET];
u64 bytes = (u64) get_unaligned_be16(chapter_bits) << 32;
bytes |= get_unaligned_be32(chapter_bits + 2);
return bytes;
}
static inline u64 uds_extract_volume_index_bytes(const struct uds_record_name *name)
{
return get_unaligned_be64(&name->name[VOLUME_INDEX_BYTES_OFFSET]);
}
static inline u32 uds_extract_sampling_bytes(const struct uds_record_name *name)
{
return get_unaligned_be16(&name->name[SAMPLE_BYTES_OFFSET]);
}
/* Compute the chapter delta list for a given name. */
static inline u32 uds_hash_to_chapter_delta_list(const struct uds_record_name *name,
const struct index_geometry *geometry)
{
return ((uds_extract_chapter_index_bytes(name) >> geometry->chapter_address_bits) &
((1 << geometry->chapter_delta_list_bits) - 1));
}
/* Compute the chapter delta address for a given name. */
static inline u32 uds_hash_to_chapter_delta_address(const struct uds_record_name *name,
const struct index_geometry *geometry)
{
return uds_extract_chapter_index_bytes(name) & ((1 << geometry->chapter_address_bits) - 1);
}
static inline unsigned int uds_name_to_hash_slot(const struct uds_record_name *name,
unsigned int slot_count)
{
return (unsigned int) (uds_extract_chapter_index_bytes(name) % slot_count);
}
#endif /* UDS_HASH_UTILS_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_INDEX_LAYOUT_H
#define UDS_INDEX_LAYOUT_H
#include "config.h"
#include "indexer.h"
#include "io-factory.h"
/*
* The index layout describes the format of the index on the underlying storage, and is responsible
* for creating those structures when the index is first created. It also validates the index data
* when loading a saved index, and updates it when saving the index.
*/
struct index_layout;
int __must_check uds_make_index_layout(struct uds_configuration *config, bool new_layout,
struct index_layout **layout_ptr);
void uds_free_index_layout(struct index_layout *layout);
int __must_check uds_replace_index_layout_storage(struct index_layout *layout,
struct block_device *bdev);
int __must_check uds_load_index_state(struct index_layout *layout,
struct uds_index *index);
int __must_check uds_save_index_state(struct index_layout *layout,
struct uds_index *index);
int __must_check uds_discard_open_chapter(struct index_layout *layout);
u64 __must_check uds_get_volume_nonce(struct index_layout *layout);
int __must_check uds_open_volume_bufio(struct index_layout *layout, size_t block_size,
unsigned int reserved_buffers,
struct dm_bufio_client **client_ptr);
#endif /* UDS_INDEX_LAYOUT_H */
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "index-page-map.h"
#include "errors.h"
#include "logger.h"
#include "memory-alloc.h"
#include "numeric.h"
#include "permassert.h"
#include "string-utils.h"
#include "thread-utils.h"
#include "hash-utils.h"
#include "indexer.h"
/*
* The index page map is conceptually a two-dimensional array indexed by chapter number and index
* page number within the chapter. Each entry contains the number of the last delta list on that
* index page. In order to save memory, the information for the last page in each chapter is not
* recorded, as it is known from the geometry.
*/
static const u8 PAGE_MAP_MAGIC[] = "ALBIPM02";
#define PAGE_MAP_MAGIC_LENGTH (sizeof(PAGE_MAP_MAGIC) - 1)
static inline u32 get_entry_count(const struct index_geometry *geometry)
{
return geometry->chapters_per_volume * (geometry->index_pages_per_chapter - 1);
}
int uds_make_index_page_map(const struct index_geometry *geometry,
struct index_page_map **map_ptr)
{
int result;
struct index_page_map *map;
result = vdo_allocate(1, "page map", &map);
if (result != VDO_SUCCESS)
return result;
map->geometry = geometry;
map->entries_per_chapter = geometry->index_pages_per_chapter - 1;
result = vdo_allocate(get_entry_count(geometry), "Index Page Map Entries",
&map->entries);
if (result != VDO_SUCCESS) {
uds_free_index_page_map(map);
return result;
}
*map_ptr = map;
return UDS_SUCCESS;
}
void uds_free_index_page_map(struct index_page_map *map)
{
if (map != NULL) {
vdo_free(map->entries);
vdo_free(map);
}
}
void uds_update_index_page_map(struct index_page_map *map, u64 virtual_chapter_number,
u32 chapter_number, u32 index_page_number,
u32 delta_list_number)
{
size_t slot;
map->last_update = virtual_chapter_number;
if (index_page_number == map->entries_per_chapter)
return;
slot = (chapter_number * map->entries_per_chapter) + index_page_number;
map->entries[slot] = delta_list_number;
}
u32 uds_find_index_page_number(const struct index_page_map *map,
const struct uds_record_name *name, u32 chapter_number)
{
u32 delta_list_number = uds_hash_to_chapter_delta_list(name, map->geometry);
u32 slot = chapter_number * map->entries_per_chapter;
u32 page;
for (page = 0; page < map->entries_per_chapter; page++) {
if (delta_list_number <= map->entries[slot + page])
break;
}
return page;
}
void uds_get_list_number_bounds(const struct index_page_map *map, u32 chapter_number,
u32 index_page_number, u32 *lowest_list,
u32 *highest_list)
{
u32 slot = chapter_number * map->entries_per_chapter;
*lowest_list = ((index_page_number == 0) ?
0 : map->entries[slot + index_page_number - 1] + 1);
*highest_list = ((index_page_number < map->entries_per_chapter) ?
map->entries[slot + index_page_number] :
map->geometry->delta_lists_per_chapter - 1);
}
u64 uds_compute_index_page_map_save_size(const struct index_geometry *geometry)
{
return PAGE_MAP_MAGIC_LENGTH + sizeof(u64) + sizeof(u16) * get_entry_count(geometry);
}
int uds_write_index_page_map(struct index_page_map *map, struct buffered_writer *writer)
{
int result;
u8 *buffer;
size_t offset = 0;
u64 saved_size = uds_compute_index_page_map_save_size(map->geometry);
u32 i;
result = vdo_allocate(saved_size, "page map data", &buffer);
if (result != VDO_SUCCESS)
return result;
memcpy(buffer, PAGE_MAP_MAGIC, PAGE_MAP_MAGIC_LENGTH);
offset += PAGE_MAP_MAGIC_LENGTH;
encode_u64_le(buffer, &offset, map->last_update);
for (i = 0; i < get_entry_count(map->geometry); i++)
encode_u16_le(buffer, &offset, map->entries[i]);
result = uds_write_to_buffered_writer(writer, buffer, offset);
vdo_free(buffer);
if (result != UDS_SUCCESS)
return result;
return uds_flush_buffered_writer(writer);
}
int uds_read_index_page_map(struct index_page_map *map, struct buffered_reader *reader)
{
int result;
u8 magic[PAGE_MAP_MAGIC_LENGTH];
u8 *buffer;
size_t offset = 0;
u64 saved_size = uds_compute_index_page_map_save_size(map->geometry);
u32 i;
result = vdo_allocate(saved_size, "page map data", &buffer);
if (result != VDO_SUCCESS)
return result;
result = uds_read_from_buffered_reader(reader, buffer, saved_size);
if (result != UDS_SUCCESS) {
vdo_free(buffer);
return result;
}
memcpy(&magic, buffer, PAGE_MAP_MAGIC_LENGTH);
offset += PAGE_MAP_MAGIC_LENGTH;
if (memcmp(magic, PAGE_MAP_MAGIC, PAGE_MAP_MAGIC_LENGTH) != 0) {
vdo_free(buffer);
return UDS_CORRUPT_DATA;
}
decode_u64_le(buffer, &offset, &map->last_update);
for (i = 0; i < get_entry_count(map->geometry); i++)
decode_u16_le(buffer, &offset, &map->entries[i]);
vdo_free(buffer);
vdo_log_debug("read index page map, last update %llu",
(unsigned long long) map->last_update);
return UDS_SUCCESS;
}
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_INDEX_PAGE_MAP_H
#define UDS_INDEX_PAGE_MAP_H
#include "geometry.h"
#include "io-factory.h"
/*
* The index maintains a page map which records how the chapter delta lists are distributed among
* the index pages for each chapter, allowing the volume to be efficient about reading only pages
* that it knows it will need.
*/
struct index_page_map {
const struct index_geometry *geometry;
u64 last_update;
u32 entries_per_chapter;
u16 *entries;
};
int __must_check uds_make_index_page_map(const struct index_geometry *geometry,
struct index_page_map **map_ptr);
void uds_free_index_page_map(struct index_page_map *map);
int __must_check uds_read_index_page_map(struct index_page_map *map,
struct buffered_reader *reader);
int __must_check uds_write_index_page_map(struct index_page_map *map,
struct buffered_writer *writer);
void uds_update_index_page_map(struct index_page_map *map, u64 virtual_chapter_number,
u32 chapter_number, u32 index_page_number,
u32 delta_list_number);
u32 __must_check uds_find_index_page_number(const struct index_page_map *map,
const struct uds_record_name *name,
u32 chapter_number);
void uds_get_list_number_bounds(const struct index_page_map *map, u32 chapter_number,
u32 index_page_number, u32 *lowest_list,
u32 *highest_list);
u64 uds_compute_index_page_map_save_size(const struct index_geometry *geometry);
#endif /* UDS_INDEX_PAGE_MAP_H */
@@ -0,0 +1,735 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "index-session.h"
#include <linux/atomic.h>
#include "logger.h"
#include "memory-alloc.h"
#include "time-utils.h"
#include "funnel-requestqueue.h"
#include "index.h"
#include "index-layout.h"
/*
* The index session contains a lock (the request_mutex) which ensures that only one thread can
* change the state of its index at a time. The state field indicates the current state of the
* index through a set of descriptive flags. The request_mutex must be notified whenever a
* non-transient state flag is cleared. The request_mutex is also used to count the number of
* requests currently in progress so that they can be drained when suspending or closing the index.
*
* If the index session is suspended shortly after opening an index, it may have to suspend during
* a rebuild. Depending on the size of the index, a rebuild may take a significant amount of time,
* so UDS allows the rebuild to be paused in order to suspend the session in a timely manner. When
* the index session is resumed, the rebuild can continue from where it left off. If the index
* session is shut down with a suspended rebuild, the rebuild progress is abandoned and the rebuild
* will start from the beginning the next time the index is loaded. The mutex and status fields in
* the index_load_context are used to record the state of any interrupted rebuild.
*/
enum index_session_flag_bit {
IS_FLAG_BIT_START = 8,
/* The session has started loading an index but not completed it. */
IS_FLAG_BIT_LOADING = IS_FLAG_BIT_START,
/* The session has loaded an index, which can handle requests. */
IS_FLAG_BIT_LOADED,
/* The session's index has been permanently disabled. */
IS_FLAG_BIT_DISABLED,
/* The session's index is suspended. */
IS_FLAG_BIT_SUSPENDED,
/* The session is handling some index state change. */
IS_FLAG_BIT_WAITING,
/* The session's index is closing and draining requests. */
IS_FLAG_BIT_CLOSING,
/* The session is being destroyed and is draining requests. */
IS_FLAG_BIT_DESTROYING,
};
enum index_session_flag {
IS_FLAG_LOADED = (1 << IS_FLAG_BIT_LOADED),
IS_FLAG_LOADING = (1 << IS_FLAG_BIT_LOADING),
IS_FLAG_DISABLED = (1 << IS_FLAG_BIT_DISABLED),
IS_FLAG_SUSPENDED = (1 << IS_FLAG_BIT_SUSPENDED),
IS_FLAG_WAITING = (1 << IS_FLAG_BIT_WAITING),
IS_FLAG_CLOSING = (1 << IS_FLAG_BIT_CLOSING),
IS_FLAG_DESTROYING = (1 << IS_FLAG_BIT_DESTROYING),
};
/* Release a reference to an index session. */
static void release_index_session(struct uds_index_session *index_session)
{
mutex_lock(&index_session->request_mutex);
if (--index_session->request_count == 0)
uds_broadcast_cond(&index_session->request_cond);
mutex_unlock(&index_session->request_mutex);
}
/*
* Acquire a reference to the index session for an asynchronous index request. The reference must
* eventually be released with a corresponding call to release_index_session().
*/
static int get_index_session(struct uds_index_session *index_session)
{
unsigned int state;
int result = UDS_SUCCESS;
mutex_lock(&index_session->request_mutex);
index_session->request_count++;
state = index_session->state;
mutex_unlock(&index_session->request_mutex);
if (state == IS_FLAG_LOADED) {
return UDS_SUCCESS;
} else if (state & IS_FLAG_DISABLED) {
result = UDS_DISABLED;
} else if ((state & IS_FLAG_LOADING) ||
(state & IS_FLAG_SUSPENDED) ||
(state & IS_FLAG_WAITING)) {
result = -EBUSY;
} else {
result = UDS_NO_INDEX;
}
release_index_session(index_session);
return result;
}
int uds_launch_request(struct uds_request *request)
{
int result;
if (request->callback == NULL) {
vdo_log_error("missing required callback");
return -EINVAL;
}
switch (request->type) {
case UDS_DELETE:
case UDS_POST:
case UDS_QUERY:
case UDS_QUERY_NO_UPDATE:
case UDS_UPDATE:
break;
default:
vdo_log_error("received invalid callback type");
return -EINVAL;
}
/* Reset all internal fields before processing. */
memset(&request->internal, 0, sizeof(request->internal));
result = get_index_session(request->session);
if (result != UDS_SUCCESS)
return result;
request->found = false;
request->unbatched = false;
request->index = request->session->index;
uds_enqueue_request(request, STAGE_TRIAGE);
return UDS_SUCCESS;
}
static void enter_callback_stage(struct uds_request *request)
{
if (request->status != UDS_SUCCESS) {
/* All request errors are considered unrecoverable */
mutex_lock(&request->session->request_mutex);
request->session->state |= IS_FLAG_DISABLED;
mutex_unlock(&request->session->request_mutex);
}
uds_request_queue_enqueue(request->session->callback_queue, request);
}
static inline void count_once(u64 *count_ptr)
{
WRITE_ONCE(*count_ptr, READ_ONCE(*count_ptr) + 1);
}
static void update_session_stats(struct uds_request *request)
{
struct session_stats *session_stats = &request->session->stats;
count_once(&session_stats->requests);
switch (request->type) {
case UDS_POST:
if (request->found)
count_once(&session_stats->posts_found);
else
count_once(&session_stats->posts_not_found);
if (request->location == UDS_LOCATION_IN_OPEN_CHAPTER)
count_once(&session_stats->posts_found_open_chapter);
else if (request->location == UDS_LOCATION_IN_DENSE)
count_once(&session_stats->posts_found_dense);
else if (request->location == UDS_LOCATION_IN_SPARSE)
count_once(&session_stats->posts_found_sparse);
break;
case UDS_UPDATE:
if (request->found)
count_once(&session_stats->updates_found);
else
count_once(&session_stats->updates_not_found);
break;
case UDS_DELETE:
if (request->found)
count_once(&session_stats->deletions_found);
else
count_once(&session_stats->deletions_not_found);
break;
case UDS_QUERY:
case UDS_QUERY_NO_UPDATE:
if (request->found)
count_once(&session_stats->queries_found);
else
count_once(&session_stats->queries_not_found);
break;
default:
request->status = VDO_ASSERT(false, "unknown request type: %d",
request->type);
}
}
static void handle_callbacks(struct uds_request *request)
{
struct uds_index_session *index_session = request->session;
if (request->status == UDS_SUCCESS)
update_session_stats(request);
request->status = uds_status_to_errno(request->status);
request->callback(request);
release_index_session(index_session);
}
static int __must_check make_empty_index_session(struct uds_index_session **index_session_ptr)
{
int result;
struct uds_index_session *session;
result = vdo_allocate(1, __func__, &session);
if (result != VDO_SUCCESS)
return result;
mutex_init(&session->request_mutex);
uds_init_cond(&session->request_cond);
mutex_init(&session->load_context.mutex);
uds_init_cond(&session->load_context.cond);
result = uds_make_request_queue("callbackW", &handle_callbacks,
&session->callback_queue);
if (result != UDS_SUCCESS) {
vdo_free(session);
return result;
}
*index_session_ptr = session;
return UDS_SUCCESS;
}
int uds_create_index_session(struct uds_index_session **session)
{
if (session == NULL) {
vdo_log_error("missing session pointer");
return -EINVAL;
}
return uds_status_to_errno(make_empty_index_session(session));
}
static int __must_check start_loading_index_session(struct uds_index_session *index_session)
{
int result;
mutex_lock(&index_session->request_mutex);
if (index_session->state & IS_FLAG_SUSPENDED) {
vdo_log_info("Index session is suspended");
result = -EBUSY;
} else if (index_session->state != 0) {
vdo_log_info("Index is already loaded");
result = -EBUSY;
} else {
index_session->state |= IS_FLAG_LOADING;
result = UDS_SUCCESS;
}
mutex_unlock(&index_session->request_mutex);
return result;
}
static void finish_loading_index_session(struct uds_index_session *index_session,
int result)
{
mutex_lock(&index_session->request_mutex);
index_session->state &= ~IS_FLAG_LOADING;
if (result == UDS_SUCCESS)
index_session->state |= IS_FLAG_LOADED;
uds_broadcast_cond(&index_session->request_cond);
mutex_unlock(&index_session->request_mutex);
}
static int initialize_index_session(struct uds_index_session *index_session,
enum uds_open_index_type open_type)
{
int result;
struct uds_configuration *config;
result = uds_make_configuration(&index_session->parameters, &config);
if (result != UDS_SUCCESS) {
vdo_log_error_strerror(result, "Failed to allocate config");
return result;
}
memset(&index_session->stats, 0, sizeof(index_session->stats));
result = uds_make_index(config, open_type, &index_session->load_context,
enter_callback_stage, &index_session->index);
if (result != UDS_SUCCESS)
vdo_log_error_strerror(result, "Failed to make index");
else
uds_log_configuration(config);
uds_free_configuration(config);
return result;
}
static const char *get_open_type_string(enum uds_open_index_type open_type)
{
switch (open_type) {
case UDS_CREATE:
return "creating index";
case UDS_LOAD:
return "loading or rebuilding index";
case UDS_NO_REBUILD:
return "loading index";
default:
return "unknown open method";
}
}
/*
* Open an index under the given session. This operation will fail if the
* index session is suspended, or if there is already an open index.
*/
int uds_open_index(enum uds_open_index_type open_type,
const struct uds_parameters *parameters,
struct uds_index_session *session)
{
int result;
char name[BDEVNAME_SIZE];
if (parameters == NULL) {
vdo_log_error("missing required parameters");
return -EINVAL;
}
if (parameters->bdev == NULL) {
vdo_log_error("missing required block device");
return -EINVAL;
}
if (session == NULL) {
vdo_log_error("missing required session pointer");
return -EINVAL;
}
result = start_loading_index_session(session);
if (result != UDS_SUCCESS)
return uds_status_to_errno(result);
session->parameters = *parameters;
format_dev_t(name, parameters->bdev->bd_dev);
vdo_log_info("%s: %s", get_open_type_string(open_type), name);
result = initialize_index_session(session, open_type);
if (result != UDS_SUCCESS)
vdo_log_error_strerror(result, "Failed %s",
get_open_type_string(open_type));
finish_loading_index_session(session, result);
return uds_status_to_errno(result);
}
static void wait_for_no_requests_in_progress(struct uds_index_session *index_session)
{
mutex_lock(&index_session->request_mutex);
while (index_session->request_count > 0) {
uds_wait_cond(&index_session->request_cond,
&index_session->request_mutex);
}
mutex_unlock(&index_session->request_mutex);
}
static int __must_check save_index(struct uds_index_session *index_session)
{
wait_for_no_requests_in_progress(index_session);
return uds_save_index(index_session->index);
}
static void suspend_rebuild(struct uds_index_session *session)
{
mutex_lock(&session->load_context.mutex);
switch (session->load_context.status) {
case INDEX_OPENING:
session->load_context.status = INDEX_SUSPENDING;
/* Wait until the index indicates that it is not replaying. */
while ((session->load_context.status != INDEX_SUSPENDED) &&
(session->load_context.status != INDEX_READY)) {
uds_wait_cond(&session->load_context.cond,
&session->load_context.mutex);
}
break;
case INDEX_READY:
/* Index load does not need to be suspended. */
break;
case INDEX_SUSPENDED:
case INDEX_SUSPENDING:
case INDEX_FREEING:
default:
/* These cases should not happen. */
VDO_ASSERT_LOG_ONLY(false, "Bad load context state %u",
session->load_context.status);
break;
}
mutex_unlock(&session->load_context.mutex);
}
/*
* Suspend index operation, draining all current index requests and preventing new index requests
* from starting. Optionally saves all index data before returning.
*/
int uds_suspend_index_session(struct uds_index_session *session, bool save)
{
int result = UDS_SUCCESS;
bool no_work = false;
bool rebuilding = false;
/* Wait for any current index state change to complete. */
mutex_lock(&session->request_mutex);
while (session->state & IS_FLAG_CLOSING)
uds_wait_cond(&session->request_cond, &session->request_mutex);
if ((session->state & IS_FLAG_WAITING) || (session->state & IS_FLAG_DESTROYING)) {
no_work = true;
vdo_log_info("Index session is already changing state");
result = -EBUSY;
} else if (session->state & IS_FLAG_SUSPENDED) {
no_work = true;
} else if (session->state & IS_FLAG_LOADING) {
session->state |= IS_FLAG_WAITING;
rebuilding = true;
} else if (session->state & IS_FLAG_LOADED) {
session->state |= IS_FLAG_WAITING;
} else {
no_work = true;
session->state |= IS_FLAG_SUSPENDED;
uds_broadcast_cond(&session->request_cond);
}
mutex_unlock(&session->request_mutex);
if (no_work)
return uds_status_to_errno(result);
if (rebuilding)
suspend_rebuild(session);
else if (save)
result = save_index(session);
else
result = uds_flush_index_session(session);
mutex_lock(&session->request_mutex);
session->state &= ~IS_FLAG_WAITING;
session->state |= IS_FLAG_SUSPENDED;
uds_broadcast_cond(&session->request_cond);
mutex_unlock(&session->request_mutex);
return uds_status_to_errno(result);
}
static int replace_device(struct uds_index_session *session, struct block_device *bdev)
{
int result;
result = uds_replace_index_storage(session->index, bdev);
if (result != UDS_SUCCESS)
return result;
session->parameters.bdev = bdev;
return UDS_SUCCESS;
}
/*
* Resume index operation after being suspended. If the index is suspended and the supplied block
* device differs from the current backing store, the index will start using the new backing store.
*/
int uds_resume_index_session(struct uds_index_session *session,
struct block_device *bdev)
{
int result = UDS_SUCCESS;
bool no_work = false;
bool resume_replay = false;
mutex_lock(&session->request_mutex);
if (session->state & IS_FLAG_WAITING) {
vdo_log_info("Index session is already changing state");
no_work = true;
result = -EBUSY;
} else if (!(session->state & IS_FLAG_SUSPENDED)) {
/* If not suspended, just succeed. */
no_work = true;
result = UDS_SUCCESS;
} else {
session->state |= IS_FLAG_WAITING;
if (session->state & IS_FLAG_LOADING)
resume_replay = true;
}
mutex_unlock(&session->request_mutex);
if (no_work)
return result;
if ((session->index != NULL) && (bdev != session->parameters.bdev)) {
result = replace_device(session, bdev);
if (result != UDS_SUCCESS) {
mutex_lock(&session->request_mutex);
session->state &= ~IS_FLAG_WAITING;
uds_broadcast_cond(&session->request_cond);
mutex_unlock(&session->request_mutex);
return uds_status_to_errno(result);
}
}
if (resume_replay) {
mutex_lock(&session->load_context.mutex);
switch (session->load_context.status) {
case INDEX_SUSPENDED:
session->load_context.status = INDEX_OPENING;
/* Notify the index to start replaying again. */
uds_broadcast_cond(&session->load_context.cond);
break;
case INDEX_READY:
/* There is no index rebuild to resume. */
break;
case INDEX_OPENING:
case INDEX_SUSPENDING:
case INDEX_FREEING:
default:
/* These cases should not happen; do nothing. */
VDO_ASSERT_LOG_ONLY(false, "Bad load context state %u",
session->load_context.status);
break;
}
mutex_unlock(&session->load_context.mutex);
}
mutex_lock(&session->request_mutex);
session->state &= ~IS_FLAG_WAITING;
session->state &= ~IS_FLAG_SUSPENDED;
uds_broadcast_cond(&session->request_cond);
mutex_unlock(&session->request_mutex);
return UDS_SUCCESS;
}
static int save_and_free_index(struct uds_index_session *index_session)
{
int result = UDS_SUCCESS;
bool suspended;
struct uds_index *index = index_session->index;
if (index == NULL)
return UDS_SUCCESS;
mutex_lock(&index_session->request_mutex);
suspended = (index_session->state & IS_FLAG_SUSPENDED);
mutex_unlock(&index_session->request_mutex);
if (!suspended) {
result = uds_save_index(index);
if (result != UDS_SUCCESS)
vdo_log_warning_strerror(result,
"ignoring error from save_index");
}
uds_free_index(index);
index_session->index = NULL;
/*
* Reset all index state that happens to be in the index
* session, so it doesn't affect any future index.
*/
mutex_lock(&index_session->load_context.mutex);
index_session->load_context.status = INDEX_OPENING;
mutex_unlock(&index_session->load_context.mutex);
mutex_lock(&index_session->request_mutex);
/* Only the suspend bit will remain relevant. */
index_session->state &= IS_FLAG_SUSPENDED;
mutex_unlock(&index_session->request_mutex);
return result;
}
/* Save and close the current index. */
int uds_close_index(struct uds_index_session *index_session)
{
int result = UDS_SUCCESS;
/* Wait for any current index state change to complete. */
mutex_lock(&index_session->request_mutex);
while ((index_session->state & IS_FLAG_WAITING) ||
(index_session->state & IS_FLAG_CLOSING)) {
uds_wait_cond(&index_session->request_cond,
&index_session->request_mutex);
}
if (index_session->state & IS_FLAG_SUSPENDED) {
vdo_log_info("Index session is suspended");
result = -EBUSY;
} else if ((index_session->state & IS_FLAG_DESTROYING) ||
!(index_session->state & IS_FLAG_LOADED)) {
/* The index doesn't exist, hasn't finished loading, or is being destroyed. */
result = UDS_NO_INDEX;
} else {
index_session->state |= IS_FLAG_CLOSING;
}
mutex_unlock(&index_session->request_mutex);
if (result != UDS_SUCCESS)
return uds_status_to_errno(result);
vdo_log_debug("Closing index");
wait_for_no_requests_in_progress(index_session);
result = save_and_free_index(index_session);
vdo_log_debug("Closed index");
mutex_lock(&index_session->request_mutex);
index_session->state &= ~IS_FLAG_CLOSING;
uds_broadcast_cond(&index_session->request_cond);
mutex_unlock(&index_session->request_mutex);
return uds_status_to_errno(result);
}
/* This will save and close an open index before destroying the session. */
int uds_destroy_index_session(struct uds_index_session *index_session)
{
int result;
bool load_pending = false;
vdo_log_debug("Destroying index session");
/* Wait for any current index state change to complete. */
mutex_lock(&index_session->request_mutex);
while ((index_session->state & IS_FLAG_WAITING) ||
(index_session->state & IS_FLAG_CLOSING)) {
uds_wait_cond(&index_session->request_cond,
&index_session->request_mutex);
}
if (index_session->state & IS_FLAG_DESTROYING) {
mutex_unlock(&index_session->request_mutex);
vdo_log_info("Index session is already closing");
return -EBUSY;
}
index_session->state |= IS_FLAG_DESTROYING;
load_pending = ((index_session->state & IS_FLAG_LOADING) &&
(index_session->state & IS_FLAG_SUSPENDED));
mutex_unlock(&index_session->request_mutex);
if (load_pending) {
/* Tell the index to terminate the rebuild. */
mutex_lock(&index_session->load_context.mutex);
if (index_session->load_context.status == INDEX_SUSPENDED) {
index_session->load_context.status = INDEX_FREEING;
uds_broadcast_cond(&index_session->load_context.cond);
}
mutex_unlock(&index_session->load_context.mutex);
/* Wait until the load exits before proceeding. */
mutex_lock(&index_session->request_mutex);
while (index_session->state & IS_FLAG_LOADING) {
uds_wait_cond(&index_session->request_cond,
&index_session->request_mutex);
}
mutex_unlock(&index_session->request_mutex);
}
wait_for_no_requests_in_progress(index_session);
result = save_and_free_index(index_session);
uds_request_queue_finish(index_session->callback_queue);
index_session->callback_queue = NULL;
vdo_log_debug("Destroyed index session");
vdo_free(index_session);
return uds_status_to_errno(result);
}
/* Wait until all callbacks for index operations are complete. */
int uds_flush_index_session(struct uds_index_session *index_session)
{
wait_for_no_requests_in_progress(index_session);
uds_wait_for_idle_index(index_session->index);
return UDS_SUCCESS;
}
/* Statistics collection is intended to be thread-safe. */
static void collect_stats(const struct uds_index_session *index_session,
struct uds_index_stats *stats)
{
const struct session_stats *session_stats = &index_session->stats;
stats->current_time = ktime_to_seconds(current_time_ns(CLOCK_REALTIME));
stats->posts_found = READ_ONCE(session_stats->posts_found);
stats->in_memory_posts_found = READ_ONCE(session_stats->posts_found_open_chapter);
stats->dense_posts_found = READ_ONCE(session_stats->posts_found_dense);
stats->sparse_posts_found = READ_ONCE(session_stats->posts_found_sparse);
stats->posts_not_found = READ_ONCE(session_stats->posts_not_found);
stats->updates_found = READ_ONCE(session_stats->updates_found);
stats->updates_not_found = READ_ONCE(session_stats->updates_not_found);
stats->deletions_found = READ_ONCE(session_stats->deletions_found);
stats->deletions_not_found = READ_ONCE(session_stats->deletions_not_found);
stats->queries_found = READ_ONCE(session_stats->queries_found);
stats->queries_not_found = READ_ONCE(session_stats->queries_not_found);
stats->requests = READ_ONCE(session_stats->requests);
}
int uds_get_index_session_stats(struct uds_index_session *index_session,
struct uds_index_stats *stats)
{
if (stats == NULL) {
vdo_log_error("received a NULL index stats pointer");
return -EINVAL;
}
collect_stats(index_session, stats);
if (index_session->index != NULL) {
uds_get_index_stats(index_session->index, stats);
} else {
stats->entries_indexed = 0;
stats->memory_used = 0;
stats->collisions = 0;
stats->entries_discarded = 0;
}
return UDS_SUCCESS;
}
void uds_wait_cond(struct cond_var *cv, struct mutex *mutex)
{
DEFINE_WAIT(__wait);
prepare_to_wait(&cv->wait_queue, &__wait, TASK_IDLE);
mutex_unlock(mutex);
schedule();
finish_wait(&cv->wait_queue, &__wait);
mutex_lock(mutex);
}
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_INDEX_SESSION_H
#define UDS_INDEX_SESSION_H
#include <linux/atomic.h>
#include <linux/cache.h>
#include "thread-utils.h"
#include "config.h"
#include "indexer.h"
/*
* The index session mediates all interactions with a UDS index. Once the index session is created,
* it can be used to open, close, suspend, or recreate an index. It implements the majority of the
* functions in the top-level UDS API.
*
* If any deduplication request fails due to an internal error, the index is marked disabled. It
* will not accept any further requests and can only be closed. Closing the index will clear the
* disabled flag, and the index can then be reopened and recovered using the same index session.
*/
struct __aligned(L1_CACHE_BYTES) session_stats {
/* Post requests that found an entry */
u64 posts_found;
/* Post requests found in the open chapter */
u64 posts_found_open_chapter;
/* Post requests found in the dense index */
u64 posts_found_dense;
/* Post requests found in the sparse index */
u64 posts_found_sparse;
/* Post requests that did not find an entry */
u64 posts_not_found;
/* Update requests that found an entry */
u64 updates_found;
/* Update requests that did not find an entry */
u64 updates_not_found;
/* Delete requests that found an entry */
u64 deletions_found;
/* Delete requests that did not find an entry */
u64 deletions_not_found;
/* Query requests that found an entry */
u64 queries_found;
/* Query requests that did not find an entry */
u64 queries_not_found;
/* Total number of requests */
u64 requests;
};
enum index_suspend_status {
/* An index load has started but the index is not ready for use. */
INDEX_OPENING = 0,
/* The index is able to handle requests. */
INDEX_READY,
/* The index is attempting to suspend a rebuild. */
INDEX_SUSPENDING,
/* An index rebuild has been suspended. */
INDEX_SUSPENDED,
/* An index rebuild is being stopped in order to shut down. */
INDEX_FREEING,
};
struct index_load_context {
struct mutex mutex;
struct cond_var cond;
enum index_suspend_status status;
};
struct uds_index_session {
unsigned int state;
struct uds_index *index;
struct uds_request_queue *callback_queue;
struct uds_parameters parameters;
struct index_load_context load_context;
struct mutex request_mutex;
struct cond_var request_cond;
int request_count;
struct session_stats stats;
};
#endif /* UDS_INDEX_SESSION_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_INDEX_H
#define UDS_INDEX_H
#include "index-layout.h"
#include "index-session.h"
#include "open-chapter.h"
#include "volume.h"
#include "volume-index.h"
/*
* The index is a high-level structure which represents the totality of the UDS index. It manages
* the queues for incoming requests and dispatches them to the appropriate sub-components like the
* volume or the volume index. It also manages administrative tasks such as saving and loading the
* index.
*
* The index is divided into a number of independent zones and assigns each request to a zone based
* on its name. Most sub-components are similarly divided into zones as well so that requests in
* each zone usually operate without interference or coordination between zones.
*/
typedef void (*index_callback_fn)(struct uds_request *request);
struct index_zone {
struct uds_index *index;
struct open_chapter_zone *open_chapter;
struct open_chapter_zone *writing_chapter;
u64 oldest_virtual_chapter;
u64 newest_virtual_chapter;
unsigned int id;
};
struct uds_index {
bool has_saved_open_chapter;
bool need_to_save;
struct index_load_context *load_context;
struct index_layout *layout;
struct volume_index *volume_index;
struct volume *volume;
unsigned int zone_count;
struct index_zone **zones;
u64 oldest_virtual_chapter;
u64 newest_virtual_chapter;
u64 last_save;
u64 prev_save;
struct chapter_writer *chapter_writer;
index_callback_fn callback;
struct uds_request_queue *triage_queue;
struct uds_request_queue *zone_queues[] __counted_by(zone_count);
};
enum request_stage {
STAGE_TRIAGE,
STAGE_INDEX,
STAGE_MESSAGE,
};
int __must_check uds_make_index(struct uds_configuration *config,
enum uds_open_index_type open_type,
struct index_load_context *load_context,
index_callback_fn callback, struct uds_index **new_index);
int __must_check uds_save_index(struct uds_index *index);
void uds_free_index(struct uds_index *index);
int __must_check uds_replace_index_storage(struct uds_index *index,
struct block_device *bdev);
void uds_get_index_stats(struct uds_index *index, struct uds_index_stats *counters);
void uds_enqueue_request(struct uds_request *request, enum request_stage stage);
void uds_wait_for_idle_index(struct uds_index *index);
#endif /* UDS_INDEX_H */
@@ -0,0 +1,352 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef INDEXER_H
#define INDEXER_H
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/wait.h>
#include "funnel-queue.h"
/*
* UDS public API
*
* The Universal Deduplication System (UDS) is an efficient name-value store. When used for
* deduplicating storage, the names are generally hashes of data blocks and the associated data is
* where that block is located on the underlying storage medium. The stored names are expected to
* be randomly distributed among the space of possible names. If this assumption is violated, the
* UDS index will store fewer names than normal but will otherwise continue to work. The data
* associated with each name can be any 16-byte value.
*
* A client must first create an index session to interact with an index. Once created, the session
* can be shared among multiple threads or users. When a session is destroyed, it will also close
* and save any associated index.
*
* To make a request, a client must allocate a uds_request structure and set the required fields
* before launching it. UDS will invoke the provided callback to complete the request. After the
* callback has been called, the uds_request structure can be freed or reused for a new request.
* There are five types of requests:
*
* A UDS_UPDATE request will associate the provided name with the provided data. Any previous data
* associated with that name will be discarded.
*
* A UDS_QUERY request will return the data associated with the provided name, if any. The entry
* for the name will also be marked as most recent, as if the data had been updated.
*
* A UDS_POST request is a combination of UDS_QUERY and UDS_UPDATE. If there is already data
* associated with the provided name, that data is returned. If there is no existing association,
* the name is associated with the newly provided data. This request is equivalent to a UDS_QUERY
* request followed by a UDS_UPDATE request if no data is found, but it is much more efficient.
*
* A UDS_QUERY_NO_UPDATE request will return the data associated with the provided name, but will
* not change the recency of the entry for the name. This request is primarily useful for testing,
* to determine whether an entry exists without changing the internal state of the index.
*
* A UDS_DELETE request removes any data associated with the provided name. This operation is
* generally not necessary, because the index will automatically discard its oldest entries once it
* becomes full.
*/
/* General UDS constants and structures */
enum uds_request_type {
/* Create or update the mapping for a name, and make the name most recent. */
UDS_UPDATE,
/* Return any mapped data for a name, and make the name most recent. */
UDS_QUERY,
/*
* Return any mapped data for a name, or map the provided data to the name if there is no
* current data, and make the name most recent.
*/
UDS_POST,
/* Return any mapped data for a name without updating its recency. */
UDS_QUERY_NO_UPDATE,
/* Remove any mapping for a name. */
UDS_DELETE,
} __packed;
enum uds_open_index_type {
/* Create a new index. */
UDS_CREATE,
/* Load an existing index and try to recover if necessary. */
UDS_LOAD,
/* Load an existing index, but only if it was saved cleanly. */
UDS_NO_REBUILD,
};
enum {
/* The record name size in bytes */
UDS_RECORD_NAME_SIZE = 16,
/* The maximum record data size in bytes */
UDS_RECORD_DATA_SIZE = 16,
};
/*
* A type representing a UDS memory configuration which is either a positive integer number of
* gigabytes or one of the six special constants for configurations smaller than one gigabyte.
*/
typedef int uds_memory_config_size_t;
enum {
/* The maximum configurable amount of memory */
UDS_MEMORY_CONFIG_MAX = 1024,
/* Flag indicating that the index has one less chapter than usual */
UDS_MEMORY_CONFIG_REDUCED = 0x1000,
UDS_MEMORY_CONFIG_REDUCED_MAX = 1024 + UDS_MEMORY_CONFIG_REDUCED,
/* Special values indicating sizes less than 1 GB */
UDS_MEMORY_CONFIG_256MB = -256,
UDS_MEMORY_CONFIG_512MB = -512,
UDS_MEMORY_CONFIG_768MB = -768,
UDS_MEMORY_CONFIG_REDUCED_256MB = -1280,
UDS_MEMORY_CONFIG_REDUCED_512MB = -1536,
UDS_MEMORY_CONFIG_REDUCED_768MB = -1792,
};
struct uds_record_name {
unsigned char name[UDS_RECORD_NAME_SIZE];
};
struct uds_record_data {
unsigned char data[UDS_RECORD_DATA_SIZE];
};
struct uds_volume_record {
struct uds_record_name name;
struct uds_record_data data;
};
struct uds_parameters {
/* The block_device used for storage */
struct block_device *bdev;
/* The maximum allowable size of the index on storage */
size_t size;
/* The offset where the index should start */
off_t offset;
/* The maximum memory allocation, in GB */
uds_memory_config_size_t memory_size;
/* Whether the index should include sparse chapters */
bool sparse;
/* A 64-bit nonce to validate the index */
u64 nonce;
/* The number of threads used to process index requests */
unsigned int zone_count;
/* The number of threads used to read volume pages */
unsigned int read_threads;
};
/*
* These statistics capture characteristics of the current index, including resource usage and
* requests processed since the index was opened.
*/
struct uds_index_stats {
/* The total number of records stored in the index */
u64 entries_indexed;
/* An estimate of the index's memory usage, in bytes */
u64 memory_used;
/* The number of collisions recorded in the volume index */
u64 collisions;
/* The number of entries discarded from the index since startup */
u64 entries_discarded;
/* The time at which these statistics were fetched */
s64 current_time;
/* The number of post calls that found an existing entry */
u64 posts_found;
/* The number of post calls that added an entry */
u64 posts_not_found;
/*
* The number of post calls that found an existing entry that is current enough to only
* exist in memory and not have been committed to disk yet
*/
u64 in_memory_posts_found;
/*
* The number of post calls that found an existing entry in the dense portion of the index
*/
u64 dense_posts_found;
/*
* The number of post calls that found an existing entry in the sparse portion of the index
*/
u64 sparse_posts_found;
/* The number of update calls that updated an existing entry */
u64 updates_found;
/* The number of update calls that added a new entry */
u64 updates_not_found;
/* The number of delete requests that deleted an existing entry */
u64 deletions_found;
/* The number of delete requests that did nothing */
u64 deletions_not_found;
/* The number of query calls that found existing entry */
u64 queries_found;
/* The number of query calls that did not find an entry */
u64 queries_not_found;
/* The total number of requests processed */
u64 requests;
};
enum uds_index_region {
/* No location information has been determined */
UDS_LOCATION_UNKNOWN = 0,
/* The index page entry has been found */
UDS_LOCATION_INDEX_PAGE_LOOKUP,
/* The record page entry has been found */
UDS_LOCATION_RECORD_PAGE_LOOKUP,
/* The record is not in the index */
UDS_LOCATION_UNAVAILABLE,
/* The record was found in the open chapter */
UDS_LOCATION_IN_OPEN_CHAPTER,
/* The record was found in the dense part of the index */
UDS_LOCATION_IN_DENSE,
/* The record was found in the sparse part of the index */
UDS_LOCATION_IN_SPARSE,
} __packed;
/* Zone message requests are used to communicate between index zones. */
enum uds_zone_message_type {
/* A standard request with no message */
UDS_MESSAGE_NONE = 0,
/* Add a chapter to the sparse chapter index cache */
UDS_MESSAGE_SPARSE_CACHE_BARRIER,
/* Close a chapter to keep the zone from falling behind */
UDS_MESSAGE_ANNOUNCE_CHAPTER_CLOSED,
} __packed;
struct uds_zone_message {
/* The type of message, determining how it will be processed */
enum uds_zone_message_type type;
/* The virtual chapter number to which the message applies */
u64 virtual_chapter;
} __packed;
struct uds_index_session;
struct uds_index;
struct uds_request;
/* Once this callback has been invoked, the uds_request structure can be reused or freed. */
typedef void (*uds_request_callback_fn)(struct uds_request *request);
struct uds_request {
/* These input fields must be set before launching a request. */
/* The name of the record to look up or create */
struct uds_record_name record_name;
/* New data to associate with the record name, if applicable */
struct uds_record_data new_metadata;
/* A callback to invoke when the request is complete */
uds_request_callback_fn callback;
/* The index session that will manage this request */
struct uds_index_session *session;
/* The type of operation to perform, as describe above */
enum uds_request_type type;
/* These output fields are set when a request is complete. */
/* The existing data associated with the request name, if any */
struct uds_record_data old_metadata;
/* True if the record name had an existing entry in the index */
bool found;
/* Either UDS_SUCCESS or an error code for the request */
int status;
/* The remaining fields are used internally and should not be altered by clients. */
struct_group(internal,
/* The virtual chapter containing the record name, if known */
u64 virtual_chapter;
/* The region of the index containing the record name */
enum uds_index_region location;
/* If true, process request immediately by waking the worker thread */
bool unbatched;
/* If true, continue this request before processing newer requests */
bool requeued;
/* Control message for coordinating between zones */
struct uds_zone_message zone_message;
/* The number of the zone which will process this request*/
unsigned int zone_number;
/* A link for adding a request to a lock-free queue */
struct funnel_queue_entry queue_link;
/* A link for adding a request to a standard linked list */
struct uds_request *next_request;
/* A pointer to the index processing this request */
struct uds_index *index;
);
};
/* Compute the number of bytes needed to store an index. */
int __must_check uds_compute_index_size(const struct uds_parameters *parameters,
u64 *index_size);
/* A session is required for most index operations. */
int __must_check uds_create_index_session(struct uds_index_session **session);
/* Destroying an index session also closes and saves the associated index. */
int uds_destroy_index_session(struct uds_index_session *session);
/*
* Create or open an index with an existing session. This operation fails if the index session is
* suspended, or if there is already an open index.
*/
int __must_check uds_open_index(enum uds_open_index_type open_type,
const struct uds_parameters *parameters,
struct uds_index_session *session);
/*
* Wait until all callbacks for index operations are complete, and prevent new index operations
* from starting. New index operations will fail with EBUSY until the session is resumed. Also
* optionally saves the index.
*/
int __must_check uds_suspend_index_session(struct uds_index_session *session, bool save);
/*
* Allow new index operations for an index, whether it was suspended or not. If the index is
* suspended and the supplied block device differs from the current backing store, the index will
* start using the new backing store instead.
*/
int __must_check uds_resume_index_session(struct uds_index_session *session,
struct block_device *bdev);
/* Wait until all outstanding index operations are complete. */
int __must_check uds_flush_index_session(struct uds_index_session *session);
/* Close an index. This operation fails if the index session is suspended. */
int __must_check uds_close_index(struct uds_index_session *session);
/* Get index statistics since the last time the index was opened. */
int __must_check uds_get_index_session_stats(struct uds_index_session *session,
struct uds_index_stats *stats);
/* This function will fail if any required field of the request is not set. */
int __must_check uds_launch_request(struct uds_request *request);
struct cond_var {
wait_queue_head_t wait_queue;
};
static inline void uds_init_cond(struct cond_var *cv)
{
init_waitqueue_head(&cv->wait_queue);
}
static inline void uds_signal_cond(struct cond_var *cv)
{
wake_up(&cv->wait_queue);
}
static inline void uds_broadcast_cond(struct cond_var *cv)
{
wake_up_all(&cv->wait_queue);
}
void uds_wait_cond(struct cond_var *cv, struct mutex *mutex);
#endif /* INDEXER_H */
@@ -0,0 +1,415 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "io-factory.h"
#include <linux/atomic.h>
#include <linux/blkdev.h>
#include <linux/err.h>
#include <linux/mount.h>
#include "logger.h"
#include "memory-alloc.h"
#include "numeric.h"
/*
* The I/O factory object manages access to index storage, which is a contiguous range of blocks on
* a block device.
*
* The factory holds the open device and is responsible for closing it. The factory has methods to
* make helper structures that can be used to access sections of the index.
*/
struct io_factory {
struct block_device *bdev;
atomic_t ref_count;
};
/* The buffered reader allows efficient I/O by reading page-sized segments into a buffer. */
struct buffered_reader {
struct io_factory *factory;
struct dm_bufio_client *client;
struct dm_buffer *buffer;
sector_t limit;
sector_t block_number;
u8 *start;
u8 *end;
};
#define MAX_READ_AHEAD_BLOCKS 4
/*
* The buffered writer allows efficient I/O by buffering writes and committing page-sized segments
* to storage.
*/
struct buffered_writer {
struct io_factory *factory;
struct dm_bufio_client *client;
struct dm_buffer *buffer;
sector_t limit;
sector_t block_number;
u8 *start;
u8 *end;
int error;
};
static void uds_get_io_factory(struct io_factory *factory)
{
atomic_inc(&factory->ref_count);
}
int uds_make_io_factory(struct block_device *bdev, struct io_factory **factory_ptr)
{
int result;
struct io_factory *factory;
result = vdo_allocate(1, __func__, &factory);
if (result != VDO_SUCCESS)
return result;
factory->bdev = bdev;
atomic_set_release(&factory->ref_count, 1);
*factory_ptr = factory;
return UDS_SUCCESS;
}
int uds_replace_storage(struct io_factory *factory, struct block_device *bdev)
{
factory->bdev = bdev;
return UDS_SUCCESS;
}
/* Free an I/O factory once all references have been released. */
void uds_put_io_factory(struct io_factory *factory)
{
if (atomic_add_return(-1, &factory->ref_count) <= 0)
vdo_free(factory);
}
size_t uds_get_writable_size(struct io_factory *factory)
{
return bdev_nr_bytes(factory->bdev);
}
/* Create a struct dm_bufio_client for an index region starting at offset. */
int uds_make_bufio(struct io_factory *factory, off_t block_offset, size_t block_size,
unsigned int reserved_buffers, struct dm_bufio_client **client_ptr)
{
struct dm_bufio_client *client;
client = dm_bufio_client_create(factory->bdev, block_size, reserved_buffers, 0,
NULL, NULL, 0);
if (IS_ERR(client))
return -PTR_ERR(client);
dm_bufio_set_sector_offset(client, block_offset * SECTORS_PER_BLOCK);
*client_ptr = client;
return UDS_SUCCESS;
}
static void read_ahead(struct buffered_reader *reader, sector_t block_number)
{
if (block_number < reader->limit) {
sector_t read_ahead = min((sector_t) MAX_READ_AHEAD_BLOCKS,
reader->limit - block_number);
dm_bufio_prefetch(reader->client, block_number, read_ahead);
}
}
void uds_free_buffered_reader(struct buffered_reader *reader)
{
if (reader == NULL)
return;
if (reader->buffer != NULL)
dm_bufio_release(reader->buffer);
dm_bufio_client_destroy(reader->client);
uds_put_io_factory(reader->factory);
vdo_free(reader);
}
/* Create a buffered reader for an index region starting at offset. */
int uds_make_buffered_reader(struct io_factory *factory, off_t offset, u64 block_count,
struct buffered_reader **reader_ptr)
{
int result;
struct dm_bufio_client *client = NULL;
struct buffered_reader *reader = NULL;
result = uds_make_bufio(factory, offset, UDS_BLOCK_SIZE, 1, &client);
if (result != UDS_SUCCESS)
return result;
result = vdo_allocate(1, "buffered reader", &reader);
if (result != VDO_SUCCESS) {
dm_bufio_client_destroy(client);
return result;
}
*reader = (struct buffered_reader) {
.factory = factory,
.client = client,
.buffer = NULL,
.limit = block_count,
.block_number = 0,
.start = NULL,
.end = NULL,
};
read_ahead(reader, 0);
uds_get_io_factory(factory);
*reader_ptr = reader;
return UDS_SUCCESS;
}
static int position_reader(struct buffered_reader *reader, sector_t block_number,
off_t offset)
{
struct dm_buffer *buffer = NULL;
void *data;
if ((reader->end == NULL) || (block_number != reader->block_number)) {
if (block_number >= reader->limit)
return UDS_OUT_OF_RANGE;
if (reader->buffer != NULL)
dm_bufio_release(vdo_forget(reader->buffer));
data = dm_bufio_read(reader->client, block_number, &buffer);
if (IS_ERR(data))
return -PTR_ERR(data);
reader->buffer = buffer;
reader->start = data;
if (block_number == reader->block_number + 1)
read_ahead(reader, block_number + 1);
}
reader->block_number = block_number;
reader->end = reader->start + offset;
return UDS_SUCCESS;
}
static size_t bytes_remaining_in_read_buffer(struct buffered_reader *reader)
{
return (reader->end == NULL) ? 0 : reader->start + UDS_BLOCK_SIZE - reader->end;
}
static int reset_reader(struct buffered_reader *reader)
{
sector_t block_number;
if (bytes_remaining_in_read_buffer(reader) > 0)
return UDS_SUCCESS;
block_number = reader->block_number;
if (reader->end != NULL)
block_number++;
return position_reader(reader, block_number, 0);
}
int uds_read_from_buffered_reader(struct buffered_reader *reader, u8 *data,
size_t length)
{
int result = UDS_SUCCESS;
size_t chunk_size;
while (length > 0) {
result = reset_reader(reader);
if (result != UDS_SUCCESS)
return result;
chunk_size = min(length, bytes_remaining_in_read_buffer(reader));
memcpy(data, reader->end, chunk_size);
length -= chunk_size;
data += chunk_size;
reader->end += chunk_size;
}
return UDS_SUCCESS;
}
/*
* Verify that the next data on the reader matches the required value. If the value matches, the
* matching contents are consumed. If the value does not match, the reader state is unchanged.
*/
int uds_verify_buffered_data(struct buffered_reader *reader, const u8 *value,
size_t length)
{
int result = UDS_SUCCESS;
size_t chunk_size;
sector_t start_block_number = reader->block_number;
int start_offset = reader->end - reader->start;
while (length > 0) {
result = reset_reader(reader);
if (result != UDS_SUCCESS) {
result = UDS_CORRUPT_DATA;
break;
}
chunk_size = min(length, bytes_remaining_in_read_buffer(reader));
if (memcmp(value, reader->end, chunk_size) != 0) {
result = UDS_CORRUPT_DATA;
break;
}
length -= chunk_size;
value += chunk_size;
reader->end += chunk_size;
}
if (result != UDS_SUCCESS)
position_reader(reader, start_block_number, start_offset);
return result;
}
/* Create a buffered writer for an index region starting at offset. */
int uds_make_buffered_writer(struct io_factory *factory, off_t offset, u64 block_count,
struct buffered_writer **writer_ptr)
{
int result;
struct dm_bufio_client *client = NULL;
struct buffered_writer *writer;
result = uds_make_bufio(factory, offset, UDS_BLOCK_SIZE, 1, &client);
if (result != UDS_SUCCESS)
return result;
result = vdo_allocate(1, "buffered writer", &writer);
if (result != VDO_SUCCESS) {
dm_bufio_client_destroy(client);
return result;
}
*writer = (struct buffered_writer) {
.factory = factory,
.client = client,
.buffer = NULL,
.limit = block_count,
.start = NULL,
.end = NULL,
.block_number = 0,
.error = UDS_SUCCESS,
};
uds_get_io_factory(factory);
*writer_ptr = writer;
return UDS_SUCCESS;
}
static size_t get_remaining_write_space(struct buffered_writer *writer)
{
return writer->start + UDS_BLOCK_SIZE - writer->end;
}
static int __must_check prepare_next_buffer(struct buffered_writer *writer)
{
struct dm_buffer *buffer = NULL;
void *data;
if (writer->block_number >= writer->limit) {
writer->error = UDS_OUT_OF_RANGE;
return UDS_OUT_OF_RANGE;
}
data = dm_bufio_new(writer->client, writer->block_number, &buffer);
if (IS_ERR(data)) {
writer->error = -PTR_ERR(data);
return writer->error;
}
writer->buffer = buffer;
writer->start = data;
writer->end = data;
return UDS_SUCCESS;
}
static int flush_previous_buffer(struct buffered_writer *writer)
{
size_t available;
if (writer->buffer == NULL)
return writer->error;
if (writer->error == UDS_SUCCESS) {
available = get_remaining_write_space(writer);
if (available > 0)
memset(writer->end, 0, available);
dm_bufio_mark_buffer_dirty(writer->buffer);
}
dm_bufio_release(writer->buffer);
writer->buffer = NULL;
writer->start = NULL;
writer->end = NULL;
writer->block_number++;
return writer->error;
}
void uds_free_buffered_writer(struct buffered_writer *writer)
{
int result;
if (writer == NULL)
return;
flush_previous_buffer(writer);
result = -dm_bufio_write_dirty_buffers(writer->client);
if (result != UDS_SUCCESS)
vdo_log_warning_strerror(result, "%s: failed to sync storage", __func__);
dm_bufio_client_destroy(writer->client);
uds_put_io_factory(writer->factory);
vdo_free(writer);
}
/*
* Append data to the buffer, writing as needed. If no data is provided, zeros are written instead.
* If a write error occurs, it is recorded and returned on every subsequent write attempt.
*/
int uds_write_to_buffered_writer(struct buffered_writer *writer, const u8 *data,
size_t length)
{
int result = writer->error;
size_t chunk_size;
while ((length > 0) && (result == UDS_SUCCESS)) {
if (writer->buffer == NULL) {
result = prepare_next_buffer(writer);
continue;
}
chunk_size = min(length, get_remaining_write_space(writer));
if (data == NULL) {
memset(writer->end, 0, chunk_size);
} else {
memcpy(writer->end, data, chunk_size);
data += chunk_size;
}
length -= chunk_size;
writer->end += chunk_size;
if (get_remaining_write_space(writer) == 0)
result = uds_flush_buffered_writer(writer);
}
return result;
}
int uds_flush_buffered_writer(struct buffered_writer *writer)
{
if (writer->error != UDS_SUCCESS)
return writer->error;
return flush_previous_buffer(writer);
}
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_IO_FACTORY_H
#define UDS_IO_FACTORY_H
#include <linux/dm-bufio.h>
/*
* The I/O factory manages all low-level I/O operations to the underlying storage device. Its main
* clients are the index layout and the volume. The buffered reader and buffered writer interfaces
* are helpers for accessing data in a contiguous range of storage blocks.
*/
struct buffered_reader;
struct buffered_writer;
struct io_factory;
enum {
UDS_BLOCK_SIZE = 4096,
SECTORS_PER_BLOCK = UDS_BLOCK_SIZE >> SECTOR_SHIFT,
};
int __must_check uds_make_io_factory(struct block_device *bdev,
struct io_factory **factory_ptr);
int __must_check uds_replace_storage(struct io_factory *factory,
struct block_device *bdev);
void uds_put_io_factory(struct io_factory *factory);
size_t __must_check uds_get_writable_size(struct io_factory *factory);
int __must_check uds_make_bufio(struct io_factory *factory, off_t block_offset,
size_t block_size, unsigned int reserved_buffers,
struct dm_bufio_client **client_ptr);
int __must_check uds_make_buffered_reader(struct io_factory *factory, off_t offset,
u64 block_count,
struct buffered_reader **reader_ptr);
void uds_free_buffered_reader(struct buffered_reader *reader);
int __must_check uds_read_from_buffered_reader(struct buffered_reader *reader, u8 *data,
size_t length);
int __must_check uds_verify_buffered_data(struct buffered_reader *reader, const u8 *value,
size_t length);
int __must_check uds_make_buffered_writer(struct io_factory *factory, off_t offset,
u64 block_count,
struct buffered_writer **writer_ptr);
void uds_free_buffered_writer(struct buffered_writer *buffer);
int __must_check uds_write_to_buffered_writer(struct buffered_writer *writer,
const u8 *data, size_t length);
int __must_check uds_flush_buffered_writer(struct buffered_writer *writer);
#endif /* UDS_IO_FACTORY_H */
@@ -0,0 +1,424 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "open-chapter.h"
#include <linux/log2.h>
#include "logger.h"
#include "memory-alloc.h"
#include "numeric.h"
#include "permassert.h"
#include "config.h"
#include "hash-utils.h"
/*
* Each index zone has a dedicated open chapter zone structure which gets an equal share of the
* open chapter space. Records are assigned to zones based on their record name. Within each zone,
* records are stored in an array in the order they arrive. Additionally, a reference to each
* record is stored in a hash table to help determine if a new record duplicates an existing one.
* If new metadata for an existing name arrives, the record is altered in place. The array of
* records is 1-based so that record number 0 can be used to indicate an unused hash slot.
*
* Deleted records are marked with a flag rather than actually removed to simplify hash table
* management. The array of deleted flags overlays the array of hash slots, but the flags are
* indexed by record number instead of by record name. The number of hash slots will always be a
* power of two that is greater than the number of records to be indexed, guaranteeing that hash
* insertion cannot fail, and that there are sufficient flags for all records.
*
* Once any open chapter zone fills its available space, the chapter is closed. The records from
* each zone are interleaved to attempt to preserve temporal locality and assigned to record pages.
* Empty or deleted records are replaced by copies of a valid record so that the record pages only
* contain valid records. The chapter then constructs a delta index which maps each record name to
* the record page on which that record can be found, which is split into index pages. These
* structures are then passed to the volume to be recorded on storage.
*
* When the index is saved, the open chapter records are saved in a single array, once again
* interleaved to attempt to preserve temporal locality. When the index is reloaded, there may be a
* different number of zones than previously, so the records must be parcelled out to their new
* zones. In addition, depending on the distribution of record names, a new zone may have more
* records than it has space. In this case, the latest records for that zone will be discarded.
*/
static const u8 OPEN_CHAPTER_MAGIC[] = "ALBOC";
static const u8 OPEN_CHAPTER_VERSION[] = "02.00";
#define OPEN_CHAPTER_MAGIC_LENGTH (sizeof(OPEN_CHAPTER_MAGIC) - 1)
#define OPEN_CHAPTER_VERSION_LENGTH (sizeof(OPEN_CHAPTER_VERSION) - 1)
#define LOAD_RATIO 2
static inline size_t records_size(const struct open_chapter_zone *open_chapter)
{
return sizeof(struct uds_volume_record) * (1 + open_chapter->capacity);
}
static inline size_t slots_size(size_t slot_count)
{
return sizeof(struct open_chapter_zone_slot) * slot_count;
}
int uds_make_open_chapter(const struct index_geometry *geometry, unsigned int zone_count,
struct open_chapter_zone **open_chapter_ptr)
{
int result;
struct open_chapter_zone *open_chapter;
size_t capacity = geometry->records_per_chapter / zone_count;
size_t slot_count = (1 << bits_per(capacity * LOAD_RATIO));
result = vdo_allocate_extended(slot_count, slots, "open chapter", &open_chapter);
if (result != VDO_SUCCESS)
return result;
open_chapter->slot_count = slot_count;
open_chapter->capacity = capacity;
result = vdo_allocate_cache_aligned(records_size(open_chapter), "record pages",
&open_chapter->records);
if (result != VDO_SUCCESS) {
uds_free_open_chapter(open_chapter);
return result;
}
*open_chapter_ptr = open_chapter;
return UDS_SUCCESS;
}
void uds_reset_open_chapter(struct open_chapter_zone *open_chapter)
{
open_chapter->size = 0;
open_chapter->deletions = 0;
memset(open_chapter->records, 0, records_size(open_chapter));
memset(open_chapter->slots, 0, slots_size(open_chapter->slot_count));
}
static unsigned int probe_chapter_slots(struct open_chapter_zone *open_chapter,
const struct uds_record_name *name)
{
struct uds_volume_record *record;
unsigned int slot_count = open_chapter->slot_count;
unsigned int slot = uds_name_to_hash_slot(name, slot_count);
unsigned int record_number;
unsigned int attempts = 1;
while (true) {
record_number = open_chapter->slots[slot].record_number;
/*
* If the hash slot is empty, we've reached the end of a chain without finding the
* record and should terminate the search.
*/
if (record_number == 0)
return slot;
/*
* If the name of the record referenced by the slot matches and has not been
* deleted, then we've found the requested name.
*/
record = &open_chapter->records[record_number];
if ((memcmp(&record->name, name, UDS_RECORD_NAME_SIZE) == 0) &&
!open_chapter->slots[record_number].deleted)
return slot;
/*
* Quadratic probing: advance the probe by 1, 2, 3, etc. and try again. This
* performs better than linear probing and works best for 2^N slots.
*/
slot = (slot + attempts++) % slot_count;
}
}
void uds_search_open_chapter(struct open_chapter_zone *open_chapter,
const struct uds_record_name *name,
struct uds_record_data *metadata, bool *found)
{
unsigned int slot;
unsigned int record_number;
slot = probe_chapter_slots(open_chapter, name);
record_number = open_chapter->slots[slot].record_number;
if (record_number == 0) {
*found = false;
} else {
*found = true;
*metadata = open_chapter->records[record_number].data;
}
}
/* Add a record to the open chapter zone and return the remaining space. */
int uds_put_open_chapter(struct open_chapter_zone *open_chapter,
const struct uds_record_name *name,
const struct uds_record_data *metadata)
{
unsigned int slot;
unsigned int record_number;
struct uds_volume_record *record;
if (open_chapter->size >= open_chapter->capacity)
return 0;
slot = probe_chapter_slots(open_chapter, name);
record_number = open_chapter->slots[slot].record_number;
if (record_number == 0) {
record_number = ++open_chapter->size;
open_chapter->slots[slot].record_number = record_number;
}
record = &open_chapter->records[record_number];
record->name = *name;
record->data = *metadata;
return open_chapter->capacity - open_chapter->size;
}
void uds_remove_from_open_chapter(struct open_chapter_zone *open_chapter,
const struct uds_record_name *name)
{
unsigned int slot;
unsigned int record_number;
slot = probe_chapter_slots(open_chapter, name);
record_number = open_chapter->slots[slot].record_number;
if (record_number > 0) {
open_chapter->slots[record_number].deleted = true;
open_chapter->deletions += 1;
}
}
void uds_free_open_chapter(struct open_chapter_zone *open_chapter)
{
if (open_chapter != NULL) {
vdo_free(open_chapter->records);
vdo_free(open_chapter);
}
}
/* Map each record name to its record page number in the delta chapter index. */
static int fill_delta_chapter_index(struct open_chapter_zone **chapter_zones,
unsigned int zone_count,
struct open_chapter_index *index,
struct uds_volume_record *collated_records)
{
int result;
unsigned int records_per_chapter;
unsigned int records_per_page;
unsigned int record_index;
unsigned int records = 0;
u32 page_number;
unsigned int z;
int overflow_count = 0;
struct uds_volume_record *fill_record = NULL;
/*
* The record pages should not have any empty space, so find a record with which to fill
* the chapter zone if it was closed early, and also to replace any deleted records. The
* last record in any filled zone is guaranteed to not have been deleted, so use one of
* those.
*/
for (z = 0; z < zone_count; z++) {
struct open_chapter_zone *zone = chapter_zones[z];
if (zone->size == zone->capacity) {
fill_record = &zone->records[zone->size];
break;
}
}
records_per_chapter = index->geometry->records_per_chapter;
records_per_page = index->geometry->records_per_page;
for (records = 0; records < records_per_chapter; records++) {
struct uds_volume_record *record = &collated_records[records];
struct open_chapter_zone *open_chapter;
/* The record arrays in the zones are 1-based. */
record_index = 1 + (records / zone_count);
page_number = records / records_per_page;
open_chapter = chapter_zones[records % zone_count];
/* Use the fill record in place of an unused record. */
if (record_index > open_chapter->size ||
open_chapter->slots[record_index].deleted) {
*record = *fill_record;
continue;
}
*record = open_chapter->records[record_index];
result = uds_put_open_chapter_index_record(index, &record->name,
page_number);
switch (result) {
case UDS_SUCCESS:
break;
case UDS_OVERFLOW:
overflow_count++;
break;
default:
vdo_log_error_strerror(result,
"failed to build open chapter index");
return result;
}
}
if (overflow_count > 0)
vdo_log_warning("Failed to add %d entries to chapter index",
overflow_count);
return UDS_SUCCESS;
}
int uds_close_open_chapter(struct open_chapter_zone **chapter_zones,
unsigned int zone_count, struct volume *volume,
struct open_chapter_index *chapter_index,
struct uds_volume_record *collated_records,
u64 virtual_chapter_number)
{
int result;
uds_empty_open_chapter_index(chapter_index, virtual_chapter_number);
result = fill_delta_chapter_index(chapter_zones, zone_count, chapter_index,
collated_records);
if (result != UDS_SUCCESS)
return result;
return uds_write_chapter(volume, chapter_index, collated_records);
}
int uds_save_open_chapter(struct uds_index *index, struct buffered_writer *writer)
{
int result;
struct open_chapter_zone *open_chapter;
struct uds_volume_record *record;
u8 record_count_data[sizeof(u32)];
u32 record_count = 0;
unsigned int record_index;
unsigned int z;
result = uds_write_to_buffered_writer(writer, OPEN_CHAPTER_MAGIC,
OPEN_CHAPTER_MAGIC_LENGTH);
if (result != UDS_SUCCESS)
return result;
result = uds_write_to_buffered_writer(writer, OPEN_CHAPTER_VERSION,
OPEN_CHAPTER_VERSION_LENGTH);
if (result != UDS_SUCCESS)
return result;
for (z = 0; z < index->zone_count; z++) {
open_chapter = index->zones[z]->open_chapter;
record_count += open_chapter->size - open_chapter->deletions;
}
put_unaligned_le32(record_count, record_count_data);
result = uds_write_to_buffered_writer(writer, record_count_data,
sizeof(record_count_data));
if (result != UDS_SUCCESS)
return result;
record_index = 1;
while (record_count > 0) {
for (z = 0; z < index->zone_count; z++) {
open_chapter = index->zones[z]->open_chapter;
if (record_index > open_chapter->size)
continue;
if (open_chapter->slots[record_index].deleted)
continue;
record = &open_chapter->records[record_index];
result = uds_write_to_buffered_writer(writer, (u8 *) record,
sizeof(*record));
if (result != UDS_SUCCESS)
return result;
record_count--;
}
record_index++;
}
return uds_flush_buffered_writer(writer);
}
u64 uds_compute_saved_open_chapter_size(struct index_geometry *geometry)
{
unsigned int records_per_chapter = geometry->records_per_chapter;
return OPEN_CHAPTER_MAGIC_LENGTH + OPEN_CHAPTER_VERSION_LENGTH + sizeof(u32) +
records_per_chapter * sizeof(struct uds_volume_record);
}
static int load_version20(struct uds_index *index, struct buffered_reader *reader)
{
int result;
u32 record_count;
u8 record_count_data[sizeof(u32)];
struct uds_volume_record record;
/*
* Track which zones cannot accept any more records. If the open chapter had a different
* number of zones previously, some new zones may have more records than they have space
* for. These overflow records will be discarded.
*/
bool full_flags[MAX_ZONES] = {
false,
};
result = uds_read_from_buffered_reader(reader, (u8 *) &record_count_data,
sizeof(record_count_data));
if (result != UDS_SUCCESS)
return result;
record_count = get_unaligned_le32(record_count_data);
while (record_count-- > 0) {
unsigned int zone = 0;
result = uds_read_from_buffered_reader(reader, (u8 *) &record,
sizeof(record));
if (result != UDS_SUCCESS)
return result;
if (index->zone_count > 1)
zone = uds_get_volume_index_zone(index->volume_index,
&record.name);
if (!full_flags[zone]) {
struct open_chapter_zone *open_chapter;
unsigned int remaining;
open_chapter = index->zones[zone]->open_chapter;
remaining = uds_put_open_chapter(open_chapter, &record.name,
&record.data);
/* Do not allow any zone to fill completely. */
full_flags[zone] = (remaining <= 1);
}
}
return UDS_SUCCESS;
}
int uds_load_open_chapter(struct uds_index *index, struct buffered_reader *reader)
{
u8 version[OPEN_CHAPTER_VERSION_LENGTH];
int result;
result = uds_verify_buffered_data(reader, OPEN_CHAPTER_MAGIC,
OPEN_CHAPTER_MAGIC_LENGTH);
if (result != UDS_SUCCESS)
return result;
result = uds_read_from_buffered_reader(reader, version, sizeof(version));
if (result != UDS_SUCCESS)
return result;
if (memcmp(OPEN_CHAPTER_VERSION, version, sizeof(version)) != 0) {
return vdo_log_error_strerror(UDS_CORRUPT_DATA,
"Invalid open chapter version: %.*s",
(int) sizeof(version), version);
}
return load_version20(index, reader);
}
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_OPEN_CHAPTER_H
#define UDS_OPEN_CHAPTER_H
#include "chapter-index.h"
#include "geometry.h"
#include "index.h"
#include "volume.h"
/*
* The open chapter tracks the newest records in memory. Like the index as a whole, each open
* chapter is divided into a number of independent zones which are interleaved when the chapter is
* committed to the volume.
*/
enum {
OPEN_CHAPTER_RECORD_NUMBER_BITS = 23,
};
struct open_chapter_zone_slot {
/* If non-zero, the record number addressed by this hash slot */
unsigned int record_number : OPEN_CHAPTER_RECORD_NUMBER_BITS;
/* If true, the record at the index of this hash slot was deleted */
bool deleted : 1;
} __packed;
struct open_chapter_zone {
/* The maximum number of records that can be stored */
unsigned int capacity;
/* The number of records stored */
unsigned int size;
/* The number of deleted records */
unsigned int deletions;
/* Array of chunk records, 1-based */
struct uds_volume_record *records;
/* The number of slots in the hash table */
unsigned int slot_count;
/* The hash table slots, referencing virtual record numbers */
struct open_chapter_zone_slot slots[] __counted_by(slot_count);
};
int __must_check uds_make_open_chapter(const struct index_geometry *geometry,
unsigned int zone_count,
struct open_chapter_zone **open_chapter_ptr);
void uds_reset_open_chapter(struct open_chapter_zone *open_chapter);
void uds_search_open_chapter(struct open_chapter_zone *open_chapter,
const struct uds_record_name *name,
struct uds_record_data *metadata, bool *found);
int __must_check uds_put_open_chapter(struct open_chapter_zone *open_chapter,
const struct uds_record_name *name,
const struct uds_record_data *metadata);
void uds_remove_from_open_chapter(struct open_chapter_zone *open_chapter,
const struct uds_record_name *name);
void uds_free_open_chapter(struct open_chapter_zone *open_chapter);
int __must_check uds_close_open_chapter(struct open_chapter_zone **chapter_zones,
unsigned int zone_count, struct volume *volume,
struct open_chapter_index *chapter_index,
struct uds_volume_record *collated_records,
u64 virtual_chapter_number);
int __must_check uds_save_open_chapter(struct uds_index *index,
struct buffered_writer *writer);
int __must_check uds_load_open_chapter(struct uds_index *index,
struct buffered_reader *reader);
u64 uds_compute_saved_open_chapter_size(struct index_geometry *geometry);
#endif /* UDS_OPEN_CHAPTER_H */
@@ -0,0 +1,329 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "radix-sort.h"
#include <linux/limits.h>
#include <linux/types.h>
#include "memory-alloc.h"
#include "string-utils.h"
/*
* This implementation allocates one large object to do the sorting, which can be reused as many
* times as desired. The amount of memory required is logarithmically proportional to the number of
* keys to be sorted.
*/
/* Piles smaller than this are handled with a simple insertion sort. */
#define INSERTION_SORT_THRESHOLD 12
/* Sort keys are pointers to immutable fixed-length arrays of bytes. */
typedef const u8 *sort_key_t;
/*
* The keys are separated into piles based on the byte in each keys at the current offset, so the
* number of keys with each byte must be counted.
*/
struct histogram {
/* The number of non-empty bins */
u16 used;
/* The index (key byte) of the first non-empty bin */
u16 first;
/* The index (key byte) of the last non-empty bin */
u16 last;
/* The number of occurrences of each specific byte */
u32 size[256];
};
/*
* Sub-tasks are manually managed on a stack, both for performance and to put a logarithmic bound
* on the stack space needed.
*/
struct task {
/* Pointer to the first key to sort. */
sort_key_t *first_key;
/* Pointer to the last key to sort. */
sort_key_t *last_key;
/* The offset into the key at which to continue sorting. */
u16 offset;
/* The number of bytes remaining in the sort keys. */
u16 length;
};
struct radix_sorter {
unsigned int count;
struct histogram bins;
sort_key_t *pile[256];
struct task *end_of_stack;
struct task insertion_list[256];
struct task stack[];
};
/* Compare a segment of two fixed-length keys starting at an offset. */
static inline int compare(sort_key_t key1, sort_key_t key2, u16 offset, u16 length)
{
return memcmp(&key1[offset], &key2[offset], length);
}
/* Insert the next unsorted key into an array of sorted keys. */
static inline void insert_key(const struct task task, sort_key_t *next)
{
/* Pull the unsorted key out, freeing up the array slot. */
sort_key_t unsorted = *next;
/* Compare the key to the preceding sorted entries, shifting down ones that are larger. */
while ((--next >= task.first_key) &&
(compare(unsorted, next[0], task.offset, task.length) < 0))
next[1] = next[0];
/* Insert the key into the last slot that was cleared, sorting it. */
next[1] = unsorted;
}
/*
* Sort a range of key segments using an insertion sort. This simple sort is faster than the
* 256-way radix sort when the number of keys to sort is small.
*/
static inline void insertion_sort(const struct task task)
{
sort_key_t *next;
for (next = task.first_key + 1; next <= task.last_key; next++)
insert_key(task, next);
}
/* Push a sorting task onto a task stack. */
static inline void push_task(struct task **stack_pointer, sort_key_t *first_key,
u32 count, u16 offset, u16 length)
{
struct task *task = (*stack_pointer)++;
task->first_key = first_key;
task->last_key = &first_key[count - 1];
task->offset = offset;
task->length = length;
}
static inline void swap_keys(sort_key_t *a, sort_key_t *b)
{
sort_key_t c = *a;
*a = *b;
*b = c;
}
/*
* Count the number of times each byte value appears in the arrays of keys to sort at the current
* offset, keeping track of the number of non-empty bins, and the index of the first and last
* non-empty bin.
*/
static inline void measure_bins(const struct task task, struct histogram *bins)
{
sort_key_t *key_ptr;
/*
* Subtle invariant: bins->used and bins->size[] are zero because the sorting code clears
* it all out as it goes. Even though this structure is re-used, we don't need to pay to
* zero it before starting a new tally.
*/
bins->first = U8_MAX;
bins->last = 0;
for (key_ptr = task.first_key; key_ptr <= task.last_key; key_ptr++) {
/* Increment the count for the byte in the key at the current offset. */
u8 bin = (*key_ptr)[task.offset];
u32 size = ++bins->size[bin];
/* Track non-empty bins. */
if (size == 1) {
bins->used += 1;
if (bin < bins->first)
bins->first = bin;
if (bin > bins->last)
bins->last = bin;
}
}
}
/*
* Convert the bin sizes to pointers to where each pile goes.
*
* pile[0] = first_key + bin->size[0],
* pile[1] = pile[0] + bin->size[1], etc.
*
* After the keys are moved to the appropriate pile, we'll need to sort each of the piles by the
* next radix position. A new task is put on the stack for each pile containing lots of keys, or a
* new task is put on the list for each pile containing few keys.
*
* @stack: pointer the top of the stack
* @end_of_stack: the end of the stack
* @list: pointer the head of the list
* @pile: array for pointers to the end of each pile
* @bins: the histogram of the sizes of each pile
* @first_key: the first key of the stack
* @offset: the next radix position to sort by
* @length: the number of bytes remaining in the sort keys
*
* Return: UDS_SUCCESS or an error code
*/
static inline int push_bins(struct task **stack, struct task *end_of_stack,
struct task **list, sort_key_t *pile[],
struct histogram *bins, sort_key_t *first_key,
u16 offset, u16 length)
{
sort_key_t *pile_start = first_key;
int bin;
for (bin = bins->first; ; bin++) {
u32 size = bins->size[bin];
/* Skip empty piles. */
if (size == 0)
continue;
/* There's no need to sort empty keys. */
if (length > 0) {
if (size > INSERTION_SORT_THRESHOLD) {
if (*stack >= end_of_stack)
return UDS_BAD_STATE;
push_task(stack, pile_start, size, offset, length);
} else if (size > 1) {
push_task(list, pile_start, size, offset, length);
}
}
pile_start += size;
pile[bin] = pile_start;
if (--bins->used == 0)
break;
}
return UDS_SUCCESS;
}
int uds_make_radix_sorter(unsigned int count, struct radix_sorter **sorter)
{
int result;
unsigned int stack_size = count / INSERTION_SORT_THRESHOLD;
struct radix_sorter *radix_sorter;
result = vdo_allocate_extended(stack_size, stack, __func__, &radix_sorter);
if (result != VDO_SUCCESS)
return result;
radix_sorter->count = count;
radix_sorter->end_of_stack = radix_sorter->stack + stack_size;
*sorter = radix_sorter;
return UDS_SUCCESS;
}
void uds_free_radix_sorter(struct radix_sorter *sorter)
{
vdo_free(sorter);
}
/*
* Sort pointers to fixed-length keys (arrays of bytes) using a radix sort. The sort implementation
* is unstable, so the relative ordering of equal keys is not preserved.
*/
int uds_radix_sort(struct radix_sorter *sorter, const unsigned char *keys[],
unsigned int count, unsigned short length)
{
struct task start;
struct histogram *bins = &sorter->bins;
sort_key_t **pile = sorter->pile;
struct task *task_stack = sorter->stack;
/* All zero-length keys are identical and therefore already sorted. */
if ((count == 0) || (length == 0))
return UDS_SUCCESS;
/* The initial task is to sort the entire length of all the keys. */
start = (struct task) {
.first_key = keys,
.last_key = &keys[count - 1],
.offset = 0,
.length = length,
};
if (count <= INSERTION_SORT_THRESHOLD) {
insertion_sort(start);
return UDS_SUCCESS;
}
if (count > sorter->count)
return UDS_INVALID_ARGUMENT;
/*
* Repeatedly consume a sorting task from the stack and process it, pushing new sub-tasks
* onto the stack for each radix-sorted pile. When all tasks and sub-tasks have been
* processed, the stack will be empty and all the keys in the starting task will be fully
* sorted.
*/
for (*task_stack = start; task_stack >= sorter->stack; task_stack--) {
const struct task task = *task_stack;
struct task *insertion_task_list;
int result;
sort_key_t *fence;
sort_key_t *end;
measure_bins(task, bins);
/*
* Now that we know how large each bin is, generate pointers for each of the piles
* and push a new task to sort each pile by the next radix byte.
*/
insertion_task_list = sorter->insertion_list;
result = push_bins(&task_stack, sorter->end_of_stack,
&insertion_task_list, pile, bins, task.first_key,
task.offset + 1, task.length - 1);
if (result != UDS_SUCCESS) {
memset(bins, 0, sizeof(*bins));
return result;
}
/* Now bins->used is zero again. */
/*
* Don't bother processing the last pile: when piles 0..N-1 are all in place, then
* pile N must also be in place.
*/
end = task.last_key - bins->size[bins->last];
bins->size[bins->last] = 0;
for (fence = task.first_key; fence <= end; ) {
u8 bin;
sort_key_t key = *fence;
/*
* The radix byte of the key tells us which pile it belongs in. Swap it for
* an unprocessed item just below that pile, and repeat.
*/
while (--pile[bin = key[task.offset]] > fence)
swap_keys(pile[bin], &key);
/*
* The pile reached the fence. Put the key at the bottom of that pile,
* completing it, and advance the fence to the next pile.
*/
*fence = key;
fence += bins->size[bin];
bins->size[bin] = 0;
}
/* Now bins->size[] is all zero again. */
/*
* When the number of keys in a task gets small enough, it is faster to use an
* insertion sort than to keep subdividing into tiny piles.
*/
while (--insertion_task_list >= sorter->insertion_list)
insertion_sort(*insertion_task_list);
}
return UDS_SUCCESS;
}
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_RADIX_SORT_H
#define UDS_RADIX_SORT_H
/*
* Radix sort is implemented using an American Flag sort, an unstable, in-place 8-bit radix
* exchange sort. This is adapted from the algorithm in the paper by Peter M. McIlroy, Keith
* Bostic, and M. Douglas McIlroy, "Engineering Radix Sort".
*
* http://www.usenix.org/publications/compsystems/1993/win_mcilroy.pdf
*/
struct radix_sorter;
int __must_check uds_make_radix_sorter(unsigned int count, struct radix_sorter **sorter);
void uds_free_radix_sorter(struct radix_sorter *sorter);
int __must_check uds_radix_sort(struct radix_sorter *sorter, const unsigned char *keys[],
unsigned int count, unsigned short length);
#endif /* UDS_RADIX_SORT_H */
@@ -0,0 +1,622 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "sparse-cache.h"
#include <linux/cache.h>
#include <linux/delay.h>
#include <linux/dm-bufio.h>
#include "logger.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "chapter-index.h"
#include "config.h"
#include "index.h"
/*
* Since the cache is small, it is implemented as a simple array of cache entries. Searching for a
* specific virtual chapter is implemented as a linear search. The cache replacement policy is
* least-recently-used (LRU). Again, the small size of the cache allows the LRU order to be
* maintained by shifting entries in an array list.
*
* Changing the contents of the cache requires the coordinated participation of all zone threads
* via the careful use of barrier messages sent to all the index zones by the triage queue worker
* thread. The critical invariant for coordination is that the cache membership must not change
* between updates, so that all calls to uds_sparse_cache_contains() from the zone threads must all
* receive the same results for every virtual chapter number. To ensure that critical invariant,
* state changes such as "that virtual chapter is no longer in the volume" and "skip searching that
* chapter because it has had too many cache misses" are represented separately from the cache
* membership information (the virtual chapter number).
*
* As a result of this invariant, we have the guarantee that every zone thread will call
* uds_update_sparse_cache() once and exactly once to request a chapter that is not in the cache,
* and the serialization of the barrier requests from the triage queue ensures they will all
* request the same chapter number. This means the only synchronization we need can be provided by
* a pair of thread barriers used only in the uds_update_sparse_cache() call, providing a critical
* section where a single zone thread can drive the cache update while all the other zone threads
* are known to be blocked, waiting in the second barrier. Outside that critical section, all the
* zone threads implicitly hold a shared lock. Inside it, the thread for zone zero holds an
* exclusive lock. No other threads may access or modify the cache entries.
*
* Chapter statistics must only be modified by a single thread, which is also the zone zero thread.
* All fields that might be frequently updated by that thread are kept in separate cache-aligned
* structures so they will not cause cache contention via "false sharing" with the fields that are
* frequently accessed by all of the zone threads.
*
* The LRU order is managed independently by each zone thread, and each zone uses its own list for
* searching and cache membership queries. The zone zero list is used to decide which chapter to
* evict when the cache is updated, and its search list is copied to the other threads at that
* time.
*
* The virtual chapter number field of the cache entry is the single field indicating whether a
* chapter is a member of the cache or not. The value NO_CHAPTER is used to represent a null or
* undefined chapter number. When present in the virtual chapter number field of a
* cached_chapter_index, it indicates that the cache entry is dead, and all the other fields of
* that entry (other than immutable pointers to cache memory) are undefined and irrelevant. Any
* cache entry that is not marked as dead is fully defined and a member of the cache, and
* uds_sparse_cache_contains() will always return true for any virtual chapter number that appears
* in any of the cache entries.
*
* A chapter index that is a member of the cache may be excluded from searches between calls to
* uds_update_sparse_cache() in two different ways. First, when a chapter falls off the end of the
* volume, its virtual chapter number will be less that the oldest virtual chapter number. Since
* that chapter is no longer part of the volume, there's no point in continuing to search that
* chapter index. Once invalidated, that virtual chapter will still be considered a member of the
* cache, but it will no longer be searched for matching names.
*
* The second mechanism is a heuristic based on keeping track of the number of consecutive search
* misses in a given chapter index. Once that count exceeds a threshold, the skip_search flag will
* be set to true, causing the chapter to be skipped when searching the entire cache, but still
* allowing it to be found when searching for a hook in that specific chapter. Finding a hook will
* clear the skip_search flag, once again allowing the non-hook searches to use that cache entry.
* Again, regardless of the state of the skip_search flag, the virtual chapter must still
* considered to be a member of the cache for uds_sparse_cache_contains().
*/
#define SKIP_SEARCH_THRESHOLD 20000
#define ZONE_ZERO 0
/*
* These counters are essentially fields of the struct cached_chapter_index, but are segregated
* into this structure because they are frequently modified. They are grouped and aligned to keep
* them on different cache lines from the chapter fields that are accessed far more often than they
* are updated.
*/
struct __aligned(L1_CACHE_BYTES) cached_index_counters {
u64 consecutive_misses;
};
struct __aligned(L1_CACHE_BYTES) cached_chapter_index {
/*
* The virtual chapter number of the cached chapter index. NO_CHAPTER means this cache
* entry is unused. This field must only be modified in the critical section in
* uds_update_sparse_cache().
*/
u64 virtual_chapter;
u32 index_pages_count;
/*
* These pointers are immutable during the life of the cache. The contents of the arrays
* change when the cache entry is replaced.
*/
struct delta_index_page *index_pages;
struct dm_buffer **page_buffers;
/*
* If set, skip the chapter when searching the entire cache. This flag is just a
* performance optimization. This flag is mutable between cache updates, but it rarely
* changes and is frequently accessed, so it groups with the immutable fields.
*/
bool skip_search;
/*
* The cache-aligned counters change often and are placed at the end of the structure to
* prevent false sharing with the more stable fields above.
*/
struct cached_index_counters counters;
};
/*
* A search_list represents an ordering of the sparse chapter index cache entry array, from most
* recently accessed to least recently accessed, which is the order in which the indexes should be
* searched and the reverse order in which they should be evicted from the cache.
*
* Cache entries that are dead or empty are kept at the end of the list, avoiding the need to even
* iterate over them to search, and ensuring that dead entries are replaced before any live entries
* are evicted.
*
* The search list is instantiated for each zone thread, avoiding any need for synchronization. The
* structure is allocated on a cache boundary to avoid false sharing of memory cache lines between
* zone threads.
*/
struct search_list {
u8 capacity;
u8 first_dead_entry;
struct cached_chapter_index *entries[];
};
struct threads_barrier {
/* Lock for this barrier object */
struct semaphore lock;
/* Semaphore for threads waiting at this barrier */
struct semaphore wait;
/* Number of threads which have arrived */
int arrived;
/* Total number of threads using this barrier */
int thread_count;
};
struct sparse_cache {
const struct index_geometry *geometry;
unsigned int capacity;
unsigned int zone_count;
unsigned int skip_threshold;
struct search_list *search_lists[MAX_ZONES];
struct cached_chapter_index **scratch_entries;
struct threads_barrier begin_update_barrier;
struct threads_barrier end_update_barrier;
struct cached_chapter_index chapters[];
};
static void initialize_threads_barrier(struct threads_barrier *barrier,
unsigned int thread_count)
{
sema_init(&barrier->lock, 1);
barrier->arrived = 0;
barrier->thread_count = thread_count;
sema_init(&barrier->wait, 0);
}
static inline void __down(struct semaphore *semaphore)
{
/*
* Do not use down(semaphore). Instead use down_interruptible so that
* we do not get 120 second stall messages in kern.log.
*/
while (down_interruptible(semaphore) != 0) {
/*
* If we're called from a user-mode process (e.g., "dmsetup
* remove") while waiting for an operation that may take a
* while (e.g., UDS index save), and a signal is sent (SIGINT,
* SIGUSR2), then down_interruptible will not block. If that
* happens, sleep briefly to avoid keeping the CPU locked up in
* this loop. We could just call cond_resched, but then we'd
* still keep consuming CPU time slices and swamp other threads
* trying to do computational work.
*/
fsleep(1000);
}
}
static void enter_threads_barrier(struct threads_barrier *barrier)
{
__down(&barrier->lock);
if (++barrier->arrived == barrier->thread_count) {
/* last thread */
int i;
for (i = 1; i < barrier->thread_count; i++)
up(&barrier->wait);
barrier->arrived = 0;
up(&barrier->lock);
} else {
up(&barrier->lock);
__down(&barrier->wait);
}
}
static int __must_check initialize_cached_chapter_index(struct cached_chapter_index *chapter,
const struct index_geometry *geometry)
{
int result;
chapter->virtual_chapter = NO_CHAPTER;
chapter->index_pages_count = geometry->index_pages_per_chapter;
result = vdo_allocate(chapter->index_pages_count, __func__, &chapter->index_pages);
if (result != VDO_SUCCESS)
return result;
return vdo_allocate(chapter->index_pages_count, "sparse index volume pages",
&chapter->page_buffers);
}
static int __must_check make_search_list(struct sparse_cache *cache,
struct search_list **list_ptr)
{
struct search_list *list;
unsigned int bytes;
u8 i;
int result;
bytes = (sizeof(struct search_list) +
(cache->capacity * sizeof(struct cached_chapter_index *)));
result = vdo_allocate_cache_aligned(bytes, "search list", &list);
if (result != VDO_SUCCESS)
return result;
list->capacity = cache->capacity;
list->first_dead_entry = 0;
for (i = 0; i < list->capacity; i++)
list->entries[i] = &cache->chapters[i];
*list_ptr = list;
return UDS_SUCCESS;
}
int uds_make_sparse_cache(const struct index_geometry *geometry, unsigned int capacity,
unsigned int zone_count, struct sparse_cache **cache_ptr)
{
int result;
unsigned int i;
struct sparse_cache *cache;
unsigned int bytes;
bytes = (sizeof(struct sparse_cache) + (capacity * sizeof(struct cached_chapter_index)));
result = vdo_allocate_cache_aligned(bytes, "sparse cache", &cache);
if (result != VDO_SUCCESS)
return result;
cache->geometry = geometry;
cache->capacity = capacity;
cache->zone_count = zone_count;
/*
* Scale down the skip threshold since the cache only counts cache misses in zone zero, but
* requests are being handled in all zones.
*/
cache->skip_threshold = (SKIP_SEARCH_THRESHOLD / zone_count);
initialize_threads_barrier(&cache->begin_update_barrier, zone_count);
initialize_threads_barrier(&cache->end_update_barrier, zone_count);
for (i = 0; i < capacity; i++) {
result = initialize_cached_chapter_index(&cache->chapters[i], geometry);
if (result != UDS_SUCCESS)
goto out;
}
for (i = 0; i < zone_count; i++) {
result = make_search_list(cache, &cache->search_lists[i]);
if (result != UDS_SUCCESS)
goto out;
}
/* purge_search_list() needs some temporary lists for sorting. */
result = vdo_allocate(capacity * 2, "scratch entries", &cache->scratch_entries);
if (result != VDO_SUCCESS)
goto out;
*cache_ptr = cache;
return UDS_SUCCESS;
out:
uds_free_sparse_cache(cache);
return result;
}
static inline void set_skip_search(struct cached_chapter_index *chapter,
bool skip_search)
{
/* Check before setting to reduce cache line contention. */
if (READ_ONCE(chapter->skip_search) != skip_search)
WRITE_ONCE(chapter->skip_search, skip_search);
}
static void score_search_hit(struct cached_chapter_index *chapter)
{
chapter->counters.consecutive_misses = 0;
set_skip_search(chapter, false);
}
static void score_search_miss(struct sparse_cache *cache,
struct cached_chapter_index *chapter)
{
chapter->counters.consecutive_misses++;
if (chapter->counters.consecutive_misses > cache->skip_threshold)
set_skip_search(chapter, true);
}
static void release_cached_chapter_index(struct cached_chapter_index *chapter)
{
unsigned int i;
chapter->virtual_chapter = NO_CHAPTER;
if (chapter->page_buffers == NULL)
return;
for (i = 0; i < chapter->index_pages_count; i++) {
if (chapter->page_buffers[i] != NULL)
dm_bufio_release(vdo_forget(chapter->page_buffers[i]));
}
}
void uds_free_sparse_cache(struct sparse_cache *cache)
{
unsigned int i;
if (cache == NULL)
return;
vdo_free(cache->scratch_entries);
for (i = 0; i < cache->zone_count; i++)
vdo_free(cache->search_lists[i]);
for (i = 0; i < cache->capacity; i++) {
release_cached_chapter_index(&cache->chapters[i]);
vdo_free(cache->chapters[i].index_pages);
vdo_free(cache->chapters[i].page_buffers);
}
vdo_free(cache);
}
/*
* Take the indicated element of the search list and move it to the start, pushing the pointers
* previously before it back down the list.
*/
static inline void set_newest_entry(struct search_list *search_list, u8 index)
{
struct cached_chapter_index *newest;
if (index > 0) {
newest = search_list->entries[index];
memmove(&search_list->entries[1], &search_list->entries[0],
index * sizeof(struct cached_chapter_index *));
search_list->entries[0] = newest;
}
/*
* This function may have moved a dead chapter to the front of the list for reuse, in which
* case the set of dead chapters becomes smaller.
*/
if (search_list->first_dead_entry <= index)
search_list->first_dead_entry++;
}
bool uds_sparse_cache_contains(struct sparse_cache *cache, u64 virtual_chapter,
unsigned int zone_number)
{
struct search_list *search_list;
struct cached_chapter_index *chapter;
u8 i;
/*
* The correctness of the barriers depends on the invariant that between calls to
* uds_update_sparse_cache(), the answers this function returns must never vary: the result
* for a given chapter must be identical across zones. That invariant must be maintained
* even if the chapter falls off the end of the volume, or if searching it is disabled
* because of too many search misses.
*/
search_list = cache->search_lists[zone_number];
for (i = 0; i < search_list->first_dead_entry; i++) {
chapter = search_list->entries[i];
if (virtual_chapter == chapter->virtual_chapter) {
if (zone_number == ZONE_ZERO)
score_search_hit(chapter);
set_newest_entry(search_list, i);
return true;
}
}
return false;
}
/*
* Re-sort cache entries into three sets (active, skippable, and dead) while maintaining the LRU
* ordering that already existed. This operation must only be called during the critical section in
* uds_update_sparse_cache().
*/
static void purge_search_list(struct search_list *search_list,
struct sparse_cache *cache, u64 oldest_virtual_chapter)
{
struct cached_chapter_index **entries;
struct cached_chapter_index **skipped;
struct cached_chapter_index **dead;
struct cached_chapter_index *chapter;
unsigned int next_alive = 0;
unsigned int next_skipped = 0;
unsigned int next_dead = 0;
unsigned int i;
entries = &search_list->entries[0];
skipped = &cache->scratch_entries[0];
dead = &cache->scratch_entries[search_list->capacity];
for (i = 0; i < search_list->first_dead_entry; i++) {
chapter = search_list->entries[i];
if ((chapter->virtual_chapter < oldest_virtual_chapter) ||
(chapter->virtual_chapter == NO_CHAPTER))
dead[next_dead++] = chapter;
else if (chapter->skip_search)
skipped[next_skipped++] = chapter;
else
entries[next_alive++] = chapter;
}
memcpy(&entries[next_alive], skipped,
next_skipped * sizeof(struct cached_chapter_index *));
memcpy(&entries[next_alive + next_skipped], dead,
next_dead * sizeof(struct cached_chapter_index *));
search_list->first_dead_entry = next_alive + next_skipped;
}
static int __must_check cache_chapter_index(struct cached_chapter_index *chapter,
u64 virtual_chapter,
const struct volume *volume)
{
int result;
release_cached_chapter_index(chapter);
result = uds_read_chapter_index_from_volume(volume, virtual_chapter,
chapter->page_buffers,
chapter->index_pages);
if (result != UDS_SUCCESS)
return result;
chapter->counters.consecutive_misses = 0;
chapter->virtual_chapter = virtual_chapter;
chapter->skip_search = false;
return UDS_SUCCESS;
}
static inline void copy_search_list(const struct search_list *source,
struct search_list *target)
{
*target = *source;
memcpy(target->entries, source->entries,
source->capacity * sizeof(struct cached_chapter_index *));
}
/*
* Update the sparse cache to contain a chapter index. This function must be called by all the zone
* threads with the same chapter number to correctly enter the thread barriers used to synchronize
* the cache updates.
*/
int uds_update_sparse_cache(struct index_zone *zone, u64 virtual_chapter)
{
int result = UDS_SUCCESS;
const struct uds_index *index = zone->index;
struct sparse_cache *cache = index->volume->sparse_cache;
if (uds_sparse_cache_contains(cache, virtual_chapter, zone->id))
return UDS_SUCCESS;
/*
* Wait for every zone thread to reach its corresponding barrier request and invoke this
* function before starting to modify the cache.
*/
enter_threads_barrier(&cache->begin_update_barrier);
/*
* This is the start of the critical section: the zone zero thread is captain, effectively
* holding an exclusive lock on the sparse cache. All the other zone threads must do
* nothing between the two barriers. They will wait at the end_update_barrier again for the
* captain to finish the update.
*/
if (zone->id == ZONE_ZERO) {
unsigned int z;
struct search_list *list = cache->search_lists[ZONE_ZERO];
purge_search_list(list, cache, zone->oldest_virtual_chapter);
if (virtual_chapter >= index->oldest_virtual_chapter) {
set_newest_entry(list, list->capacity - 1);
result = cache_chapter_index(list->entries[0], virtual_chapter,
index->volume);
}
for (z = 1; z < cache->zone_count; z++)
copy_search_list(list, cache->search_lists[z]);
}
/*
* This is the end of the critical section. All cache invariants must have been restored.
*/
enter_threads_barrier(&cache->end_update_barrier);
return result;
}
void uds_invalidate_sparse_cache(struct sparse_cache *cache)
{
unsigned int i;
for (i = 0; i < cache->capacity; i++)
release_cached_chapter_index(&cache->chapters[i]);
}
static inline bool should_skip_chapter(struct cached_chapter_index *chapter,
u64 oldest_chapter, u64 requested_chapter)
{
if ((chapter->virtual_chapter == NO_CHAPTER) ||
(chapter->virtual_chapter < oldest_chapter))
return true;
if (requested_chapter != NO_CHAPTER)
return requested_chapter != chapter->virtual_chapter;
else
return READ_ONCE(chapter->skip_search);
}
static int __must_check search_cached_chapter_index(struct cached_chapter_index *chapter,
const struct index_geometry *geometry,
const struct index_page_map *index_page_map,
const struct uds_record_name *name,
u16 *record_page_ptr)
{
u32 physical_chapter =
uds_map_to_physical_chapter(geometry, chapter->virtual_chapter);
u32 index_page_number =
uds_find_index_page_number(index_page_map, name, physical_chapter);
struct delta_index_page *index_page =
&chapter->index_pages[index_page_number];
return uds_search_chapter_index_page(index_page, geometry, name,
record_page_ptr);
}
int uds_search_sparse_cache(struct index_zone *zone, const struct uds_record_name *name,
u64 *virtual_chapter_ptr, u16 *record_page_ptr)
{
int result;
struct volume *volume = zone->index->volume;
struct sparse_cache *cache = volume->sparse_cache;
struct cached_chapter_index *chapter;
struct search_list *search_list;
u8 i;
/* Search the entire cache unless a specific chapter was requested. */
bool search_one = (*virtual_chapter_ptr != NO_CHAPTER);
*record_page_ptr = NO_CHAPTER_INDEX_ENTRY;
search_list = cache->search_lists[zone->id];
for (i = 0; i < search_list->first_dead_entry; i++) {
chapter = search_list->entries[i];
if (should_skip_chapter(chapter, zone->oldest_virtual_chapter,
*virtual_chapter_ptr))
continue;
result = search_cached_chapter_index(chapter, cache->geometry,
volume->index_page_map, name,
record_page_ptr);
if (result != UDS_SUCCESS)
return result;
if (*record_page_ptr != NO_CHAPTER_INDEX_ENTRY) {
/*
* In theory, this might be a false match while a true match exists in
* another chapter, but that's a very rare case and not worth the extra
* search complexity.
*/
set_newest_entry(search_list, i);
if (zone->id == ZONE_ZERO)
score_search_hit(chapter);
*virtual_chapter_ptr = chapter->virtual_chapter;
return UDS_SUCCESS;
}
if (zone->id == ZONE_ZERO)
score_search_miss(cache, chapter);
if (search_one)
break;
}
return UDS_SUCCESS;
}
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_SPARSE_CACHE_H
#define UDS_SPARSE_CACHE_H
#include "geometry.h"
#include "indexer.h"
/*
* The sparse cache is a cache of entire chapter indexes from sparse chapters used for searching
* for names after all other search paths have failed. It contains only complete chapter indexes;
* record pages from sparse chapters and single index pages used for resolving hooks are kept in
* the regular page cache in the volume.
*
* The most important property of this cache is the absence of synchronization for read operations.
* Safe concurrent access to the cache by the zone threads is controlled by the triage queue and
* the barrier requests it issues to the zone queues. The set of cached chapters does not and must
* not change between the carefully coordinated calls to uds_update_sparse_cache() from the zone
* threads. Outside of updates, every zone will get the same result when calling
* uds_sparse_cache_contains() as every other zone.
*/
struct index_zone;
struct sparse_cache;
int __must_check uds_make_sparse_cache(const struct index_geometry *geometry,
unsigned int capacity, unsigned int zone_count,
struct sparse_cache **cache_ptr);
void uds_free_sparse_cache(struct sparse_cache *cache);
bool uds_sparse_cache_contains(struct sparse_cache *cache, u64 virtual_chapter,
unsigned int zone_number);
int __must_check uds_update_sparse_cache(struct index_zone *zone, u64 virtual_chapter);
void uds_invalidate_sparse_cache(struct sparse_cache *cache);
int __must_check uds_search_sparse_cache(struct index_zone *zone,
const struct uds_record_name *name,
u64 *virtual_chapter_ptr, u16 *record_page_ptr);
#endif /* UDS_SPARSE_CACHE_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_VOLUME_INDEX_H
#define UDS_VOLUME_INDEX_H
#include <linux/limits.h>
#include "thread-utils.h"
#include "config.h"
#include "delta-index.h"
#include "indexer.h"
/*
* The volume index is the primary top-level index for UDS. It contains records which map a record
* name to the chapter where a record with that name is stored. This mapping can definitively say
* when no record exists. However, because we only use a subset of the name for this index, it
* cannot definitively say that a record for the entry does exist. It can only say that if a record
* exists, it will be in a particular chapter. The request can then be dispatched to that chapter
* for further processing.
*
* If the volume_index_record does not actually match the record name, the index can store a more
* specific collision record to disambiguate the new entry from the existing one. Index entries are
* managed with volume_index_record structures.
*/
#define NO_CHAPTER U64_MAX
struct volume_index_stats {
/* Nanoseconds spent rebalancing */
ktime_t rebalance_time;
/* Number of memory rebalances */
u32 rebalance_count;
/* The number of records in the index */
u64 record_count;
/* The number of collision records */
u64 collision_count;
/* The number of records removed */
u64 discard_count;
/* The number of UDS_OVERFLOWs detected */
u64 overflow_count;
/* The number of delta lists */
u32 delta_lists;
/* Number of early flushes */
u64 early_flushes;
};
struct volume_sub_index_zone {
u64 virtual_chapter_low;
u64 virtual_chapter_high;
u64 early_flushes;
} __aligned(L1_CACHE_BYTES);
struct volume_sub_index {
/* The delta index */
struct delta_index delta_index;
/* The first chapter to be flushed in each zone */
u64 *flush_chapters;
/* The zones */
struct volume_sub_index_zone *zones;
/* The volume nonce */
u64 volume_nonce;
/* Expected size of a chapter (per zone) */
u64 chapter_zone_bits;
/* Maximum size of the index (per zone) */
u64 max_zone_bits;
/* The number of bits in address mask */
u8 address_bits;
/* Mask to get address within delta list */
u32 address_mask;
/* The number of bits in chapter number */
u8 chapter_bits;
/* The largest storable chapter number */
u32 chapter_mask;
/* The number of chapters used */
u32 chapter_count;
/* The number of delta lists */
u32 list_count;
/* The number of zones */
unsigned int zone_count;
/* The amount of memory allocated */
u64 memory_size;
};
struct volume_index_zone {
/* Protects the sampled index in this zone */
struct mutex hook_mutex;
} __aligned(L1_CACHE_BYTES);
struct volume_index {
u32 sparse_sample_rate;
unsigned int zone_count;
u64 memory_size;
struct volume_sub_index vi_non_hook;
struct volume_sub_index vi_hook;
struct volume_index_zone *zones;
};
/*
* The volume_index_record structure is used to facilitate processing of a record name. A client
* first calls uds_get_volume_index_record() to find the volume index record for a record name. The
* fields of the record can then be examined to determine the state of the record.
*
* If is_found is false, then the index did not find an entry for the record name. Calling
* uds_put_volume_index_record() will insert a new entry for that name at the proper place.
*
* If is_found is true, then we did find an entry for the record name, and the virtual_chapter and
* is_collision fields reflect the entry found. Subsequently, a call to
* uds_remove_volume_index_record() will remove the entry, a call to
* uds_set_volume_index_record_chapter() will update the existing entry, and a call to
* uds_put_volume_index_record() will insert a new collision record after the existing entry.
*/
struct volume_index_record {
/* Public fields */
/* Chapter where the record info is found */
u64 virtual_chapter;
/* This record is a collision */
bool is_collision;
/* This record is the requested record */
bool is_found;
/* Private fields */
/* Zone that contains this name */
unsigned int zone_number;
/* The volume index */
struct volume_sub_index *sub_index;
/* Mutex for accessing this delta index entry in the hook index */
struct mutex *mutex;
/* The record name to which this record refers */
const struct uds_record_name *name;
/* The delta index entry for this record */
struct delta_index_entry delta_entry;
};
int __must_check uds_make_volume_index(const struct uds_configuration *config,
u64 volume_nonce,
struct volume_index **volume_index);
void uds_free_volume_index(struct volume_index *volume_index);
int __must_check uds_compute_volume_index_save_blocks(const struct uds_configuration *config,
size_t block_size,
u64 *block_count);
unsigned int __must_check uds_get_volume_index_zone(const struct volume_index *volume_index,
const struct uds_record_name *name);
bool __must_check uds_is_volume_index_sample(const struct volume_index *volume_index,
const struct uds_record_name *name);
/*
* This function is only used to manage sparse cache membership. Most requests should use
* uds_get_volume_index_record() to look up index records instead.
*/
u64 __must_check uds_lookup_volume_index_name(const struct volume_index *volume_index,
const struct uds_record_name *name);
int __must_check uds_get_volume_index_record(struct volume_index *volume_index,
const struct uds_record_name *name,
struct volume_index_record *record);
int __must_check uds_put_volume_index_record(struct volume_index_record *record,
u64 virtual_chapter);
int __must_check uds_remove_volume_index_record(struct volume_index_record *record);
int __must_check uds_set_volume_index_record_chapter(struct volume_index_record *record,
u64 virtual_chapter);
void uds_set_volume_index_open_chapter(struct volume_index *volume_index,
u64 virtual_chapter);
void uds_set_volume_index_zone_open_chapter(struct volume_index *volume_index,
unsigned int zone_number,
u64 virtual_chapter);
int __must_check uds_load_volume_index(struct volume_index *volume_index,
struct buffered_reader **readers,
unsigned int reader_count);
int __must_check uds_save_volume_index(struct volume_index *volume_index,
struct buffered_writer **writers,
unsigned int writer_count);
void uds_get_volume_index_stats(const struct volume_index *volume_index,
struct volume_index_stats *stats);
#endif /* UDS_VOLUME_INDEX_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,172 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_VOLUME_H
#define UDS_VOLUME_H
#include <linux/atomic.h>
#include <linux/cache.h>
#include <linux/dm-bufio.h>
#include <linux/limits.h>
#include "permassert.h"
#include "thread-utils.h"
#include "chapter-index.h"
#include "config.h"
#include "geometry.h"
#include "indexer.h"
#include "index-layout.h"
#include "index-page-map.h"
#include "radix-sort.h"
#include "sparse-cache.h"
/*
* The volume manages deduplication records on permanent storage. The term "volume" can also refer
* to the region of permanent storage where the records (and the chapters containing them) are
* stored. The volume handles all I/O to this region by reading, caching, and writing chapter pages
* as necessary.
*/
enum index_lookup_mode {
/* Always do lookups in all chapters normally */
LOOKUP_NORMAL,
/* Only do a subset of lookups needed when rebuilding an index */
LOOKUP_FOR_REBUILD,
};
struct queued_read {
bool invalid;
bool reserved;
u32 physical_page;
struct uds_request *first_request;
struct uds_request *last_request;
};
struct __aligned(L1_CACHE_BYTES) search_pending_counter {
u64 atomic_value;
};
struct cached_page {
/* Whether this page is currently being read asynchronously */
bool read_pending;
/* The physical page stored in this cache entry */
u32 physical_page;
/* The value of the volume clock when this page was last used */
s64 last_used;
/* The cached page buffer */
struct dm_buffer *buffer;
/* The chapter index page, meaningless for record pages */
struct delta_index_page index_page;
};
struct page_cache {
/* The number of zones */
unsigned int zone_count;
/* The number of volume pages that can be cached */
u32 indexable_pages;
/* The maximum number of simultaneously cached pages */
u16 cache_slots;
/* An index for each physical page noting where it is in the cache */
u16 *index;
/* The array of cached pages */
struct cached_page *cache;
/* A counter for each zone tracking if a search is occurring there */
struct search_pending_counter *search_pending_counters;
/* The read queue entries as a circular array */
struct queued_read *read_queue;
/* All entries above this point are constant after initialization. */
/*
* These values are all indexes into the array of read queue entries. New entries in the
* read queue are enqueued at read_queue_last. To dequeue entries, a reader thread gets the
* lock and then claims the entry pointed to by read_queue_next_read and increments that
* value. After the read is completed, the reader thread calls release_read_queue_entry(),
* which increments read_queue_first until it points to a pending read, or is equal to
* read_queue_next_read. This means that if multiple reads are outstanding,
* read_queue_first might not advance until the last of the reads finishes.
*/
u16 read_queue_first;
u16 read_queue_next_read;
u16 read_queue_last;
atomic64_t clock;
};
struct volume {
struct index_geometry *geometry;
struct dm_bufio_client *client;
u64 nonce;
size_t cache_size;
/* A single page worth of records, for sorting */
const struct uds_volume_record **record_pointers;
/* Sorter for sorting records within each page */
struct radix_sorter *radix_sorter;
struct sparse_cache *sparse_cache;
struct page_cache page_cache;
struct index_page_map *index_page_map;
struct mutex read_threads_mutex;
struct cond_var read_threads_cond;
struct cond_var read_threads_read_done_cond;
struct thread **reader_threads;
unsigned int read_thread_count;
bool read_threads_exiting;
enum index_lookup_mode lookup_mode;
unsigned int reserved_buffers;
};
int __must_check uds_make_volume(const struct uds_configuration *config,
struct index_layout *layout,
struct volume **new_volume);
void uds_free_volume(struct volume *volume);
int __must_check uds_replace_volume_storage(struct volume *volume,
struct index_layout *layout,
struct block_device *bdev);
int __must_check uds_find_volume_chapter_boundaries(struct volume *volume,
u64 *lowest_vcn, u64 *highest_vcn,
bool *is_empty);
int __must_check uds_search_volume_page_cache(struct volume *volume,
struct uds_request *request,
bool *found);
int __must_check uds_search_volume_page_cache_for_rebuild(struct volume *volume,
const struct uds_record_name *name,
u64 virtual_chapter,
bool *found);
int __must_check uds_search_cached_record_page(struct volume *volume,
struct uds_request *request, u32 chapter,
u16 record_page_number, bool *found);
void uds_forget_chapter(struct volume *volume, u64 chapter);
int __must_check uds_write_chapter(struct volume *volume,
struct open_chapter_index *chapter_index,
const struct uds_volume_record records[]);
void uds_prefetch_volume_chapter(const struct volume *volume, u32 chapter);
int __must_check uds_read_chapter_index_from_volume(const struct volume *volume,
u64 virtual_chapter,
struct dm_buffer *volume_buffers[],
struct delta_index_page index_pages[]);
int __must_check uds_get_volume_record_page(struct volume *volume, u32 chapter,
u32 page_number, u8 **data_ptr);
int __must_check uds_get_volume_index_page(struct volume *volume, u32 chapter,
u32 page_number,
struct delta_index_page **page_ptr);
#endif /* UDS_VOLUME_H */