Commit 4390aa13 authored by Matthew Sakai's avatar Matthew Sakai Committed by Mike Snitzer
Browse files

dm vdo: add deduplication configuration structures



Add structures which record the configuration of various deduplication
index parameters. This also includes facilities for saving and loading the
configuration and validating its integrity.

Co-developed-by: default avatarJ. corwin Coburn <corwin@hurlbutnet.net>
Signed-off-by: default avatarJ. corwin Coburn <corwin@hurlbutnet.net>
Co-developed-by: default avatarMichael Sclafani <dm-devel@lists.linux.dev>
Signed-off-by: default avatarMichael Sclafani <dm-devel@lists.linux.dev>
Co-developed-by: default avatarThomas Jaskiewicz <tom@jaskiewicz.us>
Signed-off-by: default avatarThomas Jaskiewicz <tom@jaskiewicz.us>
Co-developed-by: default avatarJohn Wiele <jwiele@redhat.com>
Signed-off-by: default avatarJohn Wiele <jwiele@redhat.com>
Signed-off-by: default avatarMatthew Sakai <msakai@redhat.com>
Signed-off-by: default avatarMike Snitzer <snitzer@kernel.org>
parent cc46b955
Loading
Loading
Loading
Loading
+378 −0
Original line number Diff line number Diff line
// 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 "uds-threads.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";

enum {
	DEFAULT_VOLUME_READ_THREADS = 2,
	MAX_VOLUME_READ_THREADS = 16,
	INDEX_CONFIG_MAGIC_LENGTH = sizeof(INDEX_CONFIG_MAGIC) - 1,
	INDEX_CONFIG_VERSION_LENGTH = 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 configuration *saved_config,
					struct geometry *saved_geometry,
					struct configuration *user)
{
	struct geometry *geometry = user->geometry;
	bool result = true;

	if (saved_geometry->record_pages_per_chapter != geometry->record_pages_per_chapter) {
		uds_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) {
		uds_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) {
		uds_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) {
		uds_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) {
		uds_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) {
		uds_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) {
		uds_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) {
		uds_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 configuration *user_config)
{
	int result;
	struct configuration config;
	struct 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 uds_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 uds_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 uds_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 = ASSERT(offset == sizeof(struct uds_configuration_6_02),
			"%zu bytes read but not decoded",
			sizeof(struct uds_configuration_6_02) - offset);
	if (result != UDS_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 uds_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)) {
		uds_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 configuration *config, u32 version)
{
	int result;
	struct 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 = ASSERT(offset == sizeof(struct uds_configuration_6_02),
			"%zu bytes encoded, of %zu expected", offset,
			sizeof(struct uds_configuration_6_02));
	if (result != UDS_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 {
		uds_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;

	uds_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 configuration **config_ptr)
{
	struct 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 = uds_allocate(1, struct configuration, __func__, &config);
	if (result != UDS_SUCCESS)
		return result;

	result = uds_make_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 configuration *config)
{
	if (config != NULL) {
		uds_free_geometry(config->geometry);
		uds_free(config);
	}
}

void uds_log_configuration(struct configuration *config)
{
	struct geometry *geometry = config->geometry;

	uds_log_debug("Configuration:");
	uds_log_debug("  Record pages per chapter:   %10u", geometry->record_pages_per_chapter);
	uds_log_debug("  Chapters per volume:        %10u", geometry->chapters_per_volume);
	uds_log_debug("  Sparse chapters per volume: %10u", geometry->sparse_chapters_per_volume);
	uds_log_debug("  Cache size (chapters):      %10u", config->cache_chapters);
	uds_log_debug("  Volume index mean delta:    %10u", config->volume_index_mean_delta);
	uds_log_debug("  Bytes per page:             %10zu", geometry->bytes_per_page);
	uds_log_debug("  Sparse sample rate:         %10u", config->sparse_sample_rate);
	uds_log_debug("  Nonce:                      %llu", (unsigned long long) config->nonce);
}
+124 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright 2023 Red Hat
 */

#ifndef UDS_CONFIG_H
#define UDS_CONFIG_H

#include "geometry.h"
#include "io-factory.h"
#include "uds.h"

/*
 * The 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 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 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 configuration **config_ptr);

void uds_free_configuration(struct configuration *config);

int __must_check uds_validate_config_contents(struct buffered_reader *reader,
					      struct configuration *config);

int __must_check uds_write_config_contents(struct buffered_writer *writer,
					   struct configuration *config, u32 version);

void uds_log_configuration(struct configuration *config);

#endif /* UDS_CONFIG_H */
+201 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2023 Red Hat
 */

#include "geometry.h"

#include <linux/compiler.h>
#include <linux/log2.h>

#include "delta-index.h"
#include "errors.h"
#include "logger.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "uds.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_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 geometry **geometry_ptr)
{
	int result;
	struct geometry *geometry;

	result = uds_allocate(1, struct geometry, "geometry", &geometry);
	if (result != UDS_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_geometry(struct geometry *source, struct geometry **geometry_ptr)
{
	return uds_make_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_geometry(struct geometry *geometry)
{
	uds_free(geometry);
}

u32 __must_check uds_map_to_physical_chapter(const struct geometry *geometry,
					     u64 virtual_chapter)
{
	u64 delta;

	if (!uds_is_reduced_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 geometry *geometry, u64 oldest_virtual_chapter,
			     u64 newest_virtual_chapter)
{
	return uds_is_sparse_geometry(geometry) &&
	       ((newest_virtual_chapter - oldest_virtual_chapter + 1) >
		geometry->dense_chapters_per_volume);
}

bool uds_is_chapter_sparse(const struct 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 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;
}
+138 −0

File added.

Preview size limit exceeded, changes collapsed.