// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "volume.h"
#include <linux/atomic.h>
#include <linux/dm-bufio.h>
#include <linux/err.h>
#include "errors.h"
#include "logger.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "string-utils.h"
#include "thread-utils.h"
#include "chapter-index.h"
#include "config.h"
#include "geometry.h"
#include "hash-utils.h"
#include "index.h"
#include "sparse-cache.h"
/*
* The first block of the volume layout is reserved for the volume header , which is no longer used .
* The remainder of the volume is divided into chapters consisting of several pages of records , and
* several pages of static index to use to find those records . The index pages are recorded first ,
* followed by the record pages . The chapters are written in order as they are filled , so the
* volume storage acts as a circular log of the most recent chapters , with each new chapter
* overwriting the oldest saved one .
*
* When a new chapter is filled and closed , the records from that chapter are sorted and
* interleaved in approximate temporal order , and assigned to record pages . Then a static delta
* index is generated to store which record page contains each record . The in - memory index page map
* is also updated to indicate which delta lists fall on each chapter index page . This means that
* when a record is read , the volume only has to load a single index page and a single record page ,
* rather than search the entire chapter . These index and record pages are written to storage , and
* the index pages are transferred to the page cache under the theory that the most recently
* written chapter is likely to be accessed again soon .
*
* When reading a record , the volume index will indicate which chapter should contain it . The
* volume uses the index page map to determine which chapter index page needs to be loaded , and
* then reads the relevant record page number from the chapter index . Both index and record pages
* are stored in a page cache when read for the common case that subsequent records need the same
* pages . The page cache evicts the least recently accessed entries when caching new pages . In
* addition , the volume uses dm - bufio to manage access to the storage , which may allow for
* additional caching depending on available system resources .
*
* Record requests are handled from cached pages when possible . If a page needs to be read , it is
* placed on a queue along with the request that wants to read it . Any requests for the same page
* that arrive while the read is pending are added to the queue entry . A separate reader thread
* handles the queued reads , adding the page to the cache and updating any requests queued with it
* so they can continue processing . This allows the index zone threads to continue processing new
* requests rather than wait for the storage reads .
*
* When an index rebuild is necessary , the volume reads each stored chapter to determine which
* range of chapters contain valid records , so that those records can be used to reconstruct the
* in - memory volume index .
*/
/* The maximum allowable number of contiguous bad chapters */
#define MAX_BAD_CHAPTERS 100
#define VOLUME_CACHE_MAX_ENTRIES (U16_MAX >> 1 )
#define VOLUME_CACHE_QUEUED_FLAG (1 << 15 )
#define VOLUME_CACHE_MAX_QUEUED_READS 4096
static const u64 BAD_CHAPTER = U64_MAX;
/*
* The invalidate counter is two 32 bits fields stored together atomically . The low order 32 bits
* are the physical page number of the cached page being read . The high order 32 bits are a
* sequence number . This value is written when the zone that owns it begins or completes a cache
* search . Any other thread will only read the counter in wait_for_pending_searches ( ) while waiting
* to update the cache contents .
*/
union invalidate_counter {
u64 value;
struct {
u32 page;
u32 counter;
};
};
static inline u32 map_to_page_number(struct index_geometry *geometry, u32 physical_page)
{
return (physical_page - HEADER_PAGES_PER_VOLUME) % geometry->pages_per_chapter;
}
static inline u32 map_to_chapter_number(struct index_geometry *geometry, u32 physical_page)
{
return (physical_page - HEADER_PAGES_PER_VOLUME) / geometry->pages_per_chapter;
}
static inline bool is_record_page(struct index_geometry *geometry, u32 physical_page)
{
return map_to_page_number(geometry, physical_page) >= geometry->index_pages_per_chapter;
}
static u32 map_to_physical_page(const struct index_geometry *geometry, u32 chapter, u32 page)
{
/* Page zero is the header page, so the first chapter index page is page one. */
return HEADER_PAGES_PER_VOLUME + (geometry->pages_per_chapter * chapter) + page;
}
static inline union invalidate_counter get_invalidate_counter(struct page_cache *cache,
unsigned int zone_number)
{
return (union invalidate_counter) {
.value = READ_ONCE(cache->search_pending_counters[zone_number].atomic_value),
};
}
static inline void set_invalidate_counter(struct page_cache *cache,
unsigned int zone_number,
union invalidate_counter invalidate_counter)
{
WRITE_ONCE(cache->search_pending_counters[zone_number].atomic_value,
invalidate_counter.value);
}
static inline bool search_pending(union invalidate_counter invalidate_counter)
{
return (invalidate_counter.counter & 1 ) != 0 ;
}
/* Lock the cache for a zone in order to search for a page. */
static void begin_pending_search(struct page_cache *cache, u32 physical_page,
unsigned int zone_number)
{
union invalidate_counter invalidate_counter =
get_invalidate_counter(cache, zone_number);
invalidate_counter.page = physical_page;
invalidate_counter.counter++;
set_invalidate_counter(cache, zone_number, invalidate_counter);
VDO_ASSERT_LOG_ONLY(search_pending(invalidate_counter),
"Search is pending for zone %u" , zone_number);
/*
* This memory barrier ensures that the write to the invalidate counter is seen by other
* threads before this thread accesses the cached page . The corresponding read memory
* barrier is in wait_for_pending_searches ( ) .
*/
smp_mb();
}
/* Unlock the cache for a zone by clearing its invalidate counter. */
static void end_pending_search(struct page_cache *cache, unsigned int zone_number)
{
union invalidate_counter invalidate_counter;
/*
* This memory barrier ensures that this thread completes reads of the
* cached page before other threads see the write to the invalidate
* counter .
*/
smp_mb();
invalidate_counter = get_invalidate_counter(cache, zone_number);
VDO_ASSERT_LOG_ONLY(search_pending(invalidate_counter),
"Search is pending for zone %u" , zone_number);
invalidate_counter.counter++;
set_invalidate_counter(cache, zone_number, invalidate_counter);
}
static void wait_for_pending_searches(struct page_cache *cache, u32 physical_page)
{
union invalidate_counter initial_counters[MAX_ZONES];
unsigned int i;
/*
* We hold the read_threads_mutex . We are waiting for threads that do not hold the
* read_threads_mutex . Those threads have " locked " their targeted page by setting the
* search_pending_counter . The corresponding write memory barrier is in
* begin_pending_search ( ) .
*/
smp_mb();
for (i = 0 ; i < cache->zone_count; i++)
initial_counters[i] = get_invalidate_counter(cache, i);
for (i = 0 ; i < cache->zone_count; i++) {
if (search_pending(initial_counters[i]) &&
(initial_counters[i].page == physical_page)) {
/*
* There is an active search using the physical page . We need to wait for
* the search to finish .
*
* FIXME : Investigate using wait_event ( ) to wait for the search to finish .
*/
while (initial_counters[i].value ==
get_invalidate_counter(cache, i).value)
cond_resched();
}
}
}
static void release_page_buffer(struct cached_page *page)
{
if (page->buffer != NULL)
dm_bufio_release(vdo_forget(page->buffer));
}
static void clear_cache_page(struct page_cache *cache, struct cached_page *page)
{
/* Do not clear read_pending because the read queue relies on it. */
release_page_buffer(page);
page->physical_page = cache->indexable_pages;
WRITE_ONCE(page->last_used, 0 );
}
static void make_page_most_recent(struct page_cache *cache, struct cached_page *page)
{
/*
* ASSERTION : We are either a zone thread holding a search_pending_counter , or we are any
* thread holding the read_threads_mutex .
*/
if (atomic64_read(&cache->clock) != READ_ONCE(page->last_used))
WRITE_ONCE(page->last_used, atomic64_inc_return(&cache->clock));
}
/* Select a page to remove from the cache to make space for a new entry. */
static struct cached_page *select_victim_in_cache(struct page_cache *cache)
{
struct cached_page *page;
int oldest_index = 0 ;
s64 oldest_time = S64_MAX;
s64 last_used;
u16 i;
/* Find the oldest unclaimed page. We hold the read_threads_mutex. */
for (i = 0 ; i < cache->cache_slots; i++) {
/* A page with a pending read must not be replaced. */
if (cache->cache[i].read_pending)
continue ;
last_used = READ_ONCE(cache->cache[i].last_used);
if (last_used <= oldest_time) {
oldest_time = last_used;
oldest_index = i;
}
}
page = &cache->cache[oldest_index];
if (page->physical_page != cache->indexable_pages) {
WRITE_ONCE(cache->index[page->physical_page], cache->cache_slots);
wait_for_pending_searches(cache, page->physical_page);
}
page->read_pending = true ;
clear_cache_page(cache, page);
return page;
}
/* Make a newly filled cache entry available to other threads. */
static int put_page_in_cache(struct page_cache *cache, u32 physical_page,
struct cached_page *page)
{
int result;
/* We hold the read_threads_mutex. */
result = VDO_ASSERT((page->read_pending), "page to install has a pending read" );
if (result != VDO_SUCCESS)
return result;
page->physical_page = physical_page;
make_page_most_recent(cache, page);
page->read_pending = false ;
/*
* We hold the read_threads_mutex , but we must have a write memory barrier before making
* the cached_page available to the readers that do not hold the mutex . The corresponding
* read memory barrier is in get_page_and_index ( ) .
*/
smp_wmb();
/* This assignment also clears the queued flag. */
WRITE_ONCE(cache->index[physical_page], page - cache->cache);
return UDS_SUCCESS;
}
static void cancel_page_in_cache(struct page_cache *cache, u32 physical_page,
struct cached_page *page)
{
int result;
/* We hold the read_threads_mutex. */
result = VDO_ASSERT((page->read_pending), "page to install has a pending read" );
if (result != VDO_SUCCESS)
return ;
clear_cache_page(cache, page);
page->read_pending = false ;
/* Clear the mapping and the queued flag for the new page. */
WRITE_ONCE(cache->index[physical_page], cache->cache_slots);
}
static inline u16 next_queue_position(u16 position)
{
return (position + 1 ) % VOLUME_CACHE_MAX_QUEUED_READS;
}
static inline void advance_queue_position(u16 *position)
{
*position = next_queue_position(*position);
}
static inline bool read_queue_is_full(struct page_cache *cache)
{
return cache->read_queue_first == next_queue_position(cache->read_queue_last);
}
static bool enqueue_read(struct page_cache *cache, struct uds_request *request,
u32 physical_page)
{
struct queued_read *queue_entry;
u16 last = cache->read_queue_last;
u16 read_queue_index;
/* We hold the read_threads_mutex. */
if ((cache->index[physical_page] & VOLUME_CACHE_QUEUED_FLAG) == 0 ) {
/* This page has no existing entry in the queue. */
if (read_queue_is_full(cache))
return false ;
/* Fill in the read queue entry. */
cache->read_queue[last].physical_page = physical_page;
cache->read_queue[last].invalid = false ;
cache->read_queue[last].first_request = NULL;
cache->read_queue[last].last_request = NULL;
/* Point the cache index to the read queue entry. */
read_queue_index = last;
WRITE_ONCE(cache->index[physical_page],
read_queue_index | VOLUME_CACHE_QUEUED_FLAG);
advance_queue_position(&cache->read_queue_last);
} else {
/* It's already queued, so add this request to the existing entry. */
read_queue_index = cache->index[physical_page] & ~VOLUME_CACHE_QUEUED_FLAG;
}
request->next_request = NULL;
queue_entry = &cache->read_queue[read_queue_index];
if (queue_entry->first_request == NULL)
queue_entry->first_request = request;
else
queue_entry->last_request->next_request = request;
queue_entry->last_request = request;
return true ;
}
static void enqueue_page_read(struct volume *volume, struct uds_request *request,
u32 physical_page)
{
/* Mark the page as queued, so that chapter invalidation knows to cancel a read. */
while (!enqueue_read(&volume->page_cache, request, physical_page)) {
vdo_log_debug("Read queue full, waiting for reads to finish" );
uds_wait_cond(&volume->read_threads_read_done_cond,
&volume->read_threads_mutex);
}
uds_signal_cond(&volume->read_threads_cond);
}
/*
* Reserve the next read queue entry for processing , but do not actually remove it from the queue .
* Must be followed by release_queued_requests ( ) .
*/
static struct queued_read *reserve_read_queue_entry(struct page_cache *cache)
{
/* We hold the read_threads_mutex. */
struct queued_read *entry;
u16 index_value;
bool queued;
/* No items to dequeue */
if (cache->read_queue_next_read == cache->read_queue_last)
return NULL;
entry = &cache->read_queue[cache->read_queue_next_read];
index_value = cache->index[entry->physical_page];
queued = (index_value & VOLUME_CACHE_QUEUED_FLAG) != 0 ;
/* Check to see if it's still queued before resetting. */
if (entry->invalid && queued)
WRITE_ONCE(cache->index[entry->physical_page], cache->cache_slots);
/*
* If a synchronous read has taken this page , set invalid to true so it doesn ' t get
* overwritten . Requests will just be requeued .
*/
if (!queued)
entry->invalid = true ;
entry->reserved = true ;
advance_queue_position(&cache->read_queue_next_read);
return entry;
}
static inline struct queued_read *wait_to_reserve_read_queue_entry(struct volume *volume)
{
struct queued_read *queue_entry = NULL;
while (!volume->read_threads_exiting) {
queue_entry = reserve_read_queue_entry(&volume->page_cache);
if (queue_entry != NULL)
break ;
uds_wait_cond(&volume->read_threads_cond, &volume->read_threads_mutex);
}
return queue_entry;
}
static int init_chapter_index_page(const struct volume *volume, u8 *index_page,
u32 chapter, u32 index_page_number,
struct delta_index_page *chapter_index_page)
{
u64 ci_virtual;
u32 ci_chapter;
u32 lowest_list;
u32 highest_list;
struct index_geometry *geometry = volume->geometry;
int result;
result = uds_initialize_chapter_index_page(chapter_index_page, geometry,
index_page, volume->nonce);
if (volume->lookup_mode == LOOKUP_FOR_REBUILD)
return result;
if (result != UDS_SUCCESS) {
return vdo_log_error_strerror(result,
"Reading chapter index page for chapter %u page %u" ,
chapter, index_page_number);
}
uds_get_list_number_bounds(volume->index_page_map, chapter, index_page_number,
&lowest_list, &highest_list);
ci_virtual = chapter_index_page->virtual_chapter_number;
ci_chapter = uds_map_to_physical_chapter(geometry, ci_virtual);
if ((chapter == ci_chapter) &&
(lowest_list == chapter_index_page->lowest_list_number) &&
(highest_list == chapter_index_page->highest_list_number))
return UDS_SUCCESS;
vdo_log_warning("Index page map updated to %llu" ,
(unsigned long long ) volume->index_page_map->last_update);
vdo_log_warning("Page map expects that chapter %u page %u has range %u to %u, but chapter index page has chapter %llu with range %u to %u" ,
chapter, index_page_number, lowest_list, highest_list,
(unsigned long long ) ci_virtual,
chapter_index_page->lowest_list_number,
chapter_index_page->highest_list_number);
return vdo_log_error_strerror(UDS_CORRUPT_DATA,
"index page map mismatch with chapter index" );
}
static int initialize_index_page(const struct volume *volume, u32 physical_page,
struct cached_page *page)
{
u32 chapter = map_to_chapter_number(volume->geometry, physical_page);
u32 index_page_number = map_to_page_number(volume->geometry, physical_page);
return init_chapter_index_page(volume, dm_bufio_get_block_data(page->buffer),
chapter, index_page_number, &page->index_page);
}
static bool search_record_page(const u8 record_page[],
const struct uds_record_name *name,
const struct index_geometry *geometry,
struct uds_record_data *metadata)
{
/*
* The array of records is sorted by name and stored as a binary tree in heap order , so the
* root of the tree is the first array element .
*/
u32 node = 0 ;
const struct uds_volume_record *records = (const struct uds_volume_record *) record_page;
while (node < geometry->records_per_page) {
int result;
const struct uds_volume_record *record = &records[node];
result = memcmp(name, &record->name, UDS_RECORD_NAME_SIZE);
if (result == 0 ) {
if (metadata != NULL)
*metadata = record->data;
return true ;
}
/* The children of node N are at indexes 2N+1 and 2N+2. */
node = ((2 * node) + ((result < 0 ) ? 1 : 2 ));
}
return false ;
}
/*
* If we ' ve read in a record page , we ' re going to do an immediate search , to speed up processing by
* avoiding get_record_from_zone ( ) , and to ensure that requests make progress even when queued . If
* we ' ve read in an index page , we save the record page number so we don ' t have to resolve the
* index page again . We use the location , virtual_chapter , and old_metadata fields in the request
* to allow the index code to know where to begin processing the request again .
*/
static int search_page(struct cached_page *page, const struct volume *volume,
struct uds_request *request, u32 physical_page)
{
int result;
enum uds_index_region location;
u16 record_page_number;
if (is_record_page(volume->geometry, physical_page)) {
if (search_record_page(dm_bufio_get_block_data(page->buffer),
&request->record_name, volume->geometry,
&request->old_metadata))
location = UDS_LOCATION_RECORD_PAGE_LOOKUP;
else
location = UDS_LOCATION_UNAVAILABLE;
} else {
result = uds_search_chapter_index_page(&page->index_page,
volume->geometry,
&request->record_name,
&record_page_number);
if (result != UDS_SUCCESS)
return result;
if (record_page_number == NO_CHAPTER_INDEX_ENTRY) {
location = UDS_LOCATION_UNAVAILABLE;
} else {
location = UDS_LOCATION_INDEX_PAGE_LOOKUP;
*((u16 *) &request->old_metadata) = record_page_number;
}
}
request->location = location;
request->found = false ;
return UDS_SUCCESS;
}
static int process_entry(struct volume *volume, struct queued_read *entry)
{
u32 page_number = entry->physical_page;
struct uds_request *request;
struct cached_page *page = NULL;
u8 *page_data;
int result;
if (entry->invalid) {
vdo_log_debug("Requeuing requests for invalid page" );
return UDS_SUCCESS;
}
page = select_victim_in_cache(&volume->page_cache);
mutex_unlock(&volume->read_threads_mutex);
page_data = dm_bufio_read(volume->client, page_number, &page->buffer);
mutex_lock(&volume->read_threads_mutex);
if (IS_ERR(page_data)) {
result = -PTR_ERR(page_data);
vdo_log_warning_strerror(result,
"error reading physical page %u from volume" ,
page_number);
cancel_page_in_cache(&volume->page_cache, page_number, page);
return result;
}
if (entry->invalid) {
vdo_log_warning("Page %u invalidated after read" , page_number);
cancel_page_in_cache(&volume->page_cache, page_number, page);
return UDS_SUCCESS;
}
if (!is_record_page(volume->geometry, page_number)) {
result = initialize_index_page(volume, page_number, page);
if (result != UDS_SUCCESS) {
vdo_log_warning("Error initializing chapter index page" );
cancel_page_in_cache(&volume->page_cache, page_number, page);
return result;
}
}
result = put_page_in_cache(&volume->page_cache, page_number, page);
if (result != UDS_SUCCESS) {
vdo_log_warning("Error putting page %u in cache" , page_number);
cancel_page_in_cache(&volume->page_cache, page_number, page);
return result;
}
request = entry->first_request;
while ((request != NULL) && (result == UDS_SUCCESS)) {
result = search_page(page, volume, request, page_number);
request = request->next_request;
}
return result;
}
static void release_queued_requests(struct volume *volume, struct queued_read *entry,
int result)
{
struct page_cache *cache = &volume->page_cache;
u16 next_read = cache->read_queue_next_read;
struct uds_request *request;
struct uds_request *next;
for (request = entry->first_request; request != NULL; request = next) {
next = request->next_request;
request->status = result;
request->requeued = true ;
uds_enqueue_request(request, STAGE_INDEX);
}
entry->reserved = false ;
/* Move the read_queue_first pointer as far as we can. */
while ((cache->read_queue_first != next_read) &&
(!cache->read_queue[cache->read_queue_first].reserved))
advance_queue_position(&cache->read_queue_first);
uds_broadcast_cond(&volume->read_threads_read_done_cond);
}
static void read_thread_function(void *arg)
{
struct volume *volume = arg;
vdo_log_debug("reader starting" );
mutex_lock(&volume->read_threads_mutex);
while (true ) {
struct queued_read *queue_entry;
int result;
queue_entry = wait_to_reserve_read_queue_entry(volume);
if (volume->read_threads_exiting)
break ;
result = process_entry(volume, queue_entry);
release_queued_requests(volume, queue_entry, result);
}
mutex_unlock(&volume->read_threads_mutex);
vdo_log_debug("reader done" );
}
static void get_page_and_index(struct page_cache *cache, u32 physical_page,
int *queue_index, struct cached_page **page_ptr)
{
u16 index_value;
u16 index;
bool queued;
/*
* ASSERTION : We are either a zone thread holding a search_pending_counter , or we are any
* thread holding the read_threads_mutex .
*
* Holding only a search_pending_counter is the most frequent case .
*/
/*
* It would be unlikely for the compiler to turn the usage of index_value into two reads of
* cache - > index , but it would be possible and very bad if those reads did not return the
* same bits .
*/
index_value = READ_ONCE(cache->index[physical_page]);
queued = (index_value & VOLUME_CACHE_QUEUED_FLAG) != 0 ;
index = index_value & ~VOLUME_CACHE_QUEUED_FLAG;
if (!queued && (index < cache->cache_slots)) {
*page_ptr = &cache->cache[index];
/*
* We have acquired access to the cached page , but unless we hold the
* read_threads_mutex , we need a read memory barrier now . The corresponding write
* memory barrier is in put_page_in_cache ( ) .
*/
smp_rmb();
} else {
*page_ptr = NULL;
}
*queue_index = queued ? index : -1 ;
}
static void get_page_from_cache(struct page_cache *cache, u32 physical_page,
struct cached_page **page)
{
/*
* ASSERTION : We are in a zone thread .
* ASSERTION : We holding a search_pending_counter or the read_threads_mutex .
*/
int queue_index = -1 ;
get_page_and_index(cache, physical_page, &queue_index, page);
}
static int read_page_locked(struct volume *volume, u32 physical_page,
struct cached_page **page_ptr)
{
int result = UDS_SUCCESS;
struct cached_page *page = NULL;
u8 *page_data;
page = select_victim_in_cache(&volume->page_cache);
page_data = dm_bufio_read(volume->client, physical_page, &page->buffer);
if (IS_ERR(page_data)) {
result = -PTR_ERR(page_data);
vdo_log_warning_strerror(result,
"error reading physical page %u from volume" ,
physical_page);
cancel_page_in_cache(&volume->page_cache, physical_page, page);
return result;
}
if (!is_record_page(volume->geometry, physical_page)) {
result = initialize_index_page(volume, physical_page, page);
if (result != UDS_SUCCESS) {
if (volume->lookup_mode != LOOKUP_FOR_REBUILD)
vdo_log_warning("Corrupt index page %u" , physical_page);
cancel_page_in_cache(&volume->page_cache, physical_page, page);
return result;
}
}
result = put_page_in_cache(&volume->page_cache, physical_page, page);
if (result != UDS_SUCCESS) {
vdo_log_warning("Error putting page %u in cache" , physical_page);
cancel_page_in_cache(&volume->page_cache, physical_page, page);
return result;
}
*page_ptr = page;
return UDS_SUCCESS;
}
/* Retrieve a page from the cache while holding the read threads mutex. */
static int get_volume_page_locked(struct volume *volume, u32 physical_page,
struct cached_page **page_ptr)
{
int result;
struct cached_page *page = NULL;
get_page_from_cache(&volume->page_cache, physical_page, &page);
if (page == NULL) {
result = read_page_locked(volume, physical_page, &page);
if (result != UDS_SUCCESS)
return result;
} else {
make_page_most_recent(&volume->page_cache, page);
}
*page_ptr = page;
return UDS_SUCCESS;
}
/* Retrieve a page from the cache while holding a search_pending lock. */
static int get_volume_page_protected(struct volume *volume, struct uds_request *request,
u32 physical_page, struct cached_page **page_ptr)
{
struct cached_page *page;
unsigned int zone_number = request->zone_number;
get_page_from_cache(&volume->page_cache, physical_page, &page);
if (page != NULL) {
if (zone_number == 0 ) {
/* Only one zone is allowed to update the LRU. */
make_page_most_recent(&volume->page_cache, page);
}
*page_ptr = page;
return UDS_SUCCESS;
}
/* Prepare to enqueue a read for the page. */
end_pending_search(&volume->page_cache, zone_number);
mutex_lock(&volume->read_threads_mutex);
/*
* Do the lookup again while holding the read mutex ( no longer the fast case so this should
* be fine to repeat ) . We need to do this because a page may have been added to the cache
* by a reader thread between the time we searched above and the time we went to actually
* try to enqueue it below . This could result in us enqueuing another read for a page which
* is already in the cache , which would mean we end up with two entries in the cache for
* the same page .
*/
get_page_from_cache(&volume->page_cache, physical_page, &page);
if (page == NULL) {
enqueue_page_read(volume, request, physical_page);
/*
* The performance gain from unlocking first , while " search pending " mode is off ,
* turns out to be significant in some cases . The page is not available yet so
* the order does not matter for correctness as it does below .
*/
mutex_unlock(&volume->read_threads_mutex);
begin_pending_search(&volume->page_cache, physical_page, zone_number);
return UDS_QUEUED;
}
/*
* Now that the page is loaded , the volume needs to switch to " reader thread unlocked " and
* " search pending " state in careful order so no other thread can mess with the data before
* the caller gets to look at it .
*/
begin_pending_search(&volume->page_cache, physical_page, zone_number);
mutex_unlock(&volume->read_threads_mutex);
*page_ptr = page;
return UDS_SUCCESS;
}
static int get_volume_page(struct volume *volume, u32 chapter, u32 page_number,
struct cached_page **page_ptr)
{
int result;
u32 physical_page = map_to_physical_page(volume->geometry, chapter, page_number);
mutex_lock(&volume->read_threads_mutex);
result = get_volume_page_locked(volume, physical_page, page_ptr);
mutex_unlock(&volume->read_threads_mutex);
return result;
}
int uds_get_volume_record_page(struct volume *volume, u32 chapter, u32 page_number,
u8 **data_ptr)
{
int result;
struct cached_page *page = NULL;
result = get_volume_page(volume, chapter, page_number, &page);
if (result == UDS_SUCCESS)
*data_ptr = dm_bufio_get_block_data(page->buffer);
return result;
}
int uds_get_volume_index_page(struct volume *volume, u32 chapter, u32 page_number,
struct delta_index_page **index_page_ptr)
{
int result;
struct cached_page *page = NULL;
result = get_volume_page(volume, chapter, page_number, &page);
if (result == UDS_SUCCESS)
*index_page_ptr = &page->index_page;
return result;
}
/*
* Find the record page associated with a name in a given index page . This will return UDS_QUEUED
* if the page in question must be read from storage .
*/
static int search_cached_index_page(struct volume *volume, struct uds_request *request,
u32 chapter, u32 index_page_number,
u16 *record_page_number)
{
int result;
struct cached_page *page = NULL;
unsigned int zone_number = request->zone_number;
u32 physical_page = map_to_physical_page(volume->geometry, chapter,
index_page_number);
/*
* Make sure the invalidate counter is updated before we try and read the mapping . This
* prevents this thread from reading a page in the cache which has already been marked for
* invalidation by the reader thread , before the reader thread has noticed that the
* invalidate_counter has been incremented .
*/
begin_pending_search(&volume->page_cache, physical_page, zone_number);
result = get_volume_page_protected(volume, request, physical_page, &page);
if (result != UDS_SUCCESS) {
end_pending_search(&volume->page_cache, zone_number);
return result;
}
result = uds_search_chapter_index_page(&page->index_page, volume->geometry,
&request->record_name,
record_page_number);
end_pending_search(&volume->page_cache, zone_number);
return result;
}
/*
* Find the metadata associated with a name in a given record page . This will return UDS_QUEUED if
* the page in question must be read from storage .
*/
int uds_search_cached_record_page(struct volume *volume, struct uds_request *request,
u32 chapter, u16 record_page_number, bool *found)
{
struct cached_page *record_page;
struct index_geometry *geometry = volume->geometry;
unsigned int zone_number = request->zone_number;
int result;
u32 physical_page, page_number;
*found = false ;
if (record_page_number == NO_CHAPTER_INDEX_ENTRY)
return UDS_SUCCESS;
result = VDO_ASSERT(record_page_number < geometry->record_pages_per_chapter,
"0 <= %d < %u" , record_page_number,
geometry->record_pages_per_chapter);
if (result != VDO_SUCCESS)
return result;
page_number = geometry->index_pages_per_chapter + record_page_number;
physical_page = map_to_physical_page(volume->geometry, chapter, page_number);
/*
* Make sure the invalidate counter is updated before we try and read the mapping . This
* prevents this thread from reading a page in the cache which has already been marked for
* invalidation by the reader thread , before the reader thread has noticed that the
* invalidate_counter has been incremented .
*/
begin_pending_search(&volume->page_cache, physical_page, zone_number);
result = get_volume_page_protected(volume, request, physical_page, &record_page);
if (result != UDS_SUCCESS) {
end_pending_search(&volume->page_cache, zone_number);
return result;
}
if (search_record_page(dm_bufio_get_block_data(record_page->buffer),
&request->record_name, geometry, &request->old_metadata))
*found = true ;
end_pending_search(&volume->page_cache, zone_number);
return UDS_SUCCESS;
}
void uds_prefetch_volume_chapter(const struct volume *volume, u32 chapter)
{
const struct index_geometry *geometry = volume->geometry;
u32 physical_page = map_to_physical_page(geometry, chapter, 0 );
dm_bufio_prefetch(volume->client, physical_page, geometry->pages_per_chapter);
}
int 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 result;
u32 i;
const struct index_geometry *geometry = volume->geometry;
u32 physical_chapter = uds_map_to_physical_chapter(geometry, virtual_chapter);
u32 physical_page = map_to_physical_page(geometry, physical_chapter, 0 );
dm_bufio_prefetch(volume->client, physical_page, geometry->index_pages_per_chapter);
for (i = 0 ; i < geometry->index_pages_per_chapter; i++) {
u8 *index_page;
index_page = dm_bufio_read(volume->client, physical_page + i,
&volume_buffers[i]);
if (IS_ERR(index_page)) {
result = -PTR_ERR(index_page);
vdo_log_warning_strerror(result,
"error reading physical page %u" ,
physical_page);
return result;
}
result = init_chapter_index_page(volume, index_page, physical_chapter, i,
&index_pages[i]);
if (result != UDS_SUCCESS)
return result;
}
return UDS_SUCCESS;
}
int uds_search_volume_page_cache(struct volume *volume, struct uds_request *request,
bool *found)
{
int result;
u32 physical_chapter =
uds_map_to_physical_chapter(volume->geometry, request->virtual_chapter);
u32 index_page_number;
u16 record_page_number;
index_page_number = uds_find_index_page_number(volume->index_page_map,
&request->record_name,
physical_chapter);
if (request->location == UDS_LOCATION_INDEX_PAGE_LOOKUP) {
record_page_number = *((u16 *) &request->old_metadata);
} else {
result = search_cached_index_page(volume, request, physical_chapter,
index_page_number,
&record_page_number);
if (result != UDS_SUCCESS)
return result;
}
return uds_search_cached_record_page(volume, request, physical_chapter,
record_page_number, found);
}
int uds_search_volume_page_cache_for_rebuild(struct volume *volume,
const struct uds_record_name *name,
u64 virtual_chapter, bool *found)
{
int result;
struct index_geometry *geometry = volume->geometry;
struct cached_page *page;
u32 physical_chapter = uds_map_to_physical_chapter(geometry, virtual_chapter);
u32 index_page_number;
u16 record_page_number;
u32 page_number;
*found = false ;
index_page_number =
uds_find_index_page_number(volume->index_page_map, name,
physical_chapter);
result = get_volume_page(volume, physical_chapter, index_page_number, &page);
if (result != UDS_SUCCESS)
return result;
result = uds_search_chapter_index_page(&page->index_page, geometry, name,
&record_page_number);
if (result != UDS_SUCCESS)
return result;
if (record_page_number == NO_CHAPTER_INDEX_ENTRY)
return UDS_SUCCESS;
page_number = geometry->index_pages_per_chapter + record_page_number;
result = get_volume_page(volume, physical_chapter, page_number, &page);
if (result != UDS_SUCCESS)
return result;
*found = search_record_page(dm_bufio_get_block_data(page->buffer), name,
geometry, NULL);
return UDS_SUCCESS;
}
static void invalidate_page(struct page_cache *cache, u32 physical_page)
{
struct cached_page *page;
int queue_index = -1 ;
/* We hold the read_threads_mutex. */
get_page_and_index(cache, physical_page, &queue_index, &page);
if (page != NULL) {
WRITE_ONCE(cache->index[page->physical_page], cache->cache_slots);
wait_for_pending_searches(cache, page->physical_page);
clear_cache_page(cache, page);
} else if (queue_index > -1 ) {
vdo_log_debug("setting pending read to invalid" );
cache->read_queue[queue_index].invalid = true ;
}
}
void uds_forget_chapter(struct volume *volume, u64 virtual_chapter)
{
u32 physical_chapter =
uds_map_to_physical_chapter(volume->geometry, virtual_chapter);
u32 first_page = map_to_physical_page(volume->geometry, physical_chapter, 0 );
u32 i;
vdo_log_debug("forgetting chapter %llu" , (unsigned long long ) virtual_chapter);
mutex_lock(&volume->read_threads_mutex);
for (i = 0 ; i < volume->geometry->pages_per_chapter; i++)
invalidate_page(&volume->page_cache, first_page + i);
mutex_unlock(&volume->read_threads_mutex);
}
/*
* Donate an index pages from a newly written chapter to the page cache since it is likely to be
* used again soon . The caller must already hold the reader thread mutex .
*/
static int donate_index_page_locked(struct volume *volume, u32 physical_chapter,
u32 index_page_number, struct dm_buffer *page_buffer)
{
int result;
struct cached_page *page = NULL;
u32 physical_page =
map_to_physical_page(volume->geometry, physical_chapter,
index_page_number);
page = select_victim_in_cache(&volume->page_cache);
page->buffer = page_buffer;
result = init_chapter_index_page(volume, dm_bufio_get_block_data(page_buffer),
physical_chapter, index_page_number,
&page->index_page);
if (result != UDS_SUCCESS) {
vdo_log_warning("Error initialize chapter index page" );
cancel_page_in_cache(&volume->page_cache, physical_page, page);
return result;
}
result = put_page_in_cache(&volume->page_cache, physical_page, page);
if (result != UDS_SUCCESS) {
vdo_log_warning("Error putting page %u in cache" , physical_page);
cancel_page_in_cache(&volume->page_cache, physical_page, page);
return result;
}
return UDS_SUCCESS;
}
static int write_index_pages(struct volume *volume, u32 physical_chapter_number,
struct open_chapter_index *chapter_index)
{
struct index_geometry *geometry = volume->geometry;
struct dm_buffer *page_buffer;
u32 first_index_page = map_to_physical_page(geometry, physical_chapter_number, 0 );
u32 delta_list_number = 0 ;
u32 index_page_number;
for (index_page_number = 0 ;
index_page_number < geometry->index_pages_per_chapter;
index_page_number++) {
u8 *page_data;
u32 physical_page = first_index_page + index_page_number;
u32 lists_packed;
bool last_page;
int result;
page_data = dm_bufio_new(volume->client, physical_page, &page_buffer);
if (IS_ERR(page_data)) {
return vdo_log_warning_strerror(-PTR_ERR(page_data),
"failed to prepare index page" );
}
last_page = ((index_page_number + 1 ) == geometry->index_pages_per_chapter);
result = uds_pack_open_chapter_index_page(chapter_index, page_data,
delta_list_number, last_page,
&lists_packed);
if (result != UDS_SUCCESS) {
dm_bufio_release(page_buffer);
return vdo_log_warning_strerror(result,
"failed to pack index page" );
}
dm_bufio_mark_buffer_dirty(page_buffer);
if (lists_packed == 0 ) {
vdo_log_debug("no delta lists packed on chapter %u page %u" ,
physical_chapter_number, index_page_number);
} else {
delta_list_number += lists_packed;
}
uds_update_index_page_map(volume->index_page_map,
chapter_index->virtual_chapter_number,
physical_chapter_number, index_page_number,
delta_list_number - 1 );
mutex_lock(&volume->read_threads_mutex);
result = donate_index_page_locked(volume, physical_chapter_number,
index_page_number, page_buffer);
mutex_unlock(&volume->read_threads_mutex);
if (result != UDS_SUCCESS) {
dm_bufio_release(page_buffer);
return result;
}
}
return UDS_SUCCESS;
}
static u32 encode_tree(u8 record_page[],
const struct uds_volume_record *sorted_pointers[],
u32 next_record, u32 node, u32 node_count)
{
if (node < node_count) {
u32 child = (2 * node) + 1 ;
next_record = encode_tree(record_page, sorted_pointers, next_record,
child, node_count);
/*
* In - order traversal : copy the contents of the next record into the page at the
* node offset .
*/
memcpy(&record_page[node * BYTES_PER_RECORD],
sorted_pointers[next_record++], BYTES_PER_RECORD);
next_record = encode_tree(record_page, sorted_pointers, next_record,
child + 1 , node_count);
}
return next_record;
}
static int encode_record_page(const struct volume *volume,
const struct uds_volume_record records[], u8 record_page[])
{
int result;
u32 i;
u32 records_per_page = volume->geometry->records_per_page;
const struct uds_volume_record **record_pointers = volume->record_pointers;
for (i = 0 ; i < records_per_page; i++)
record_pointers[i] = &records[i];
/*
* Sort the record pointers by using just the names in the records , which is less work than
* sorting the entire record values .
*/
BUILD_BUG_ON(offsetof(struct uds_volume_record, name) != 0 );
result = uds_radix_sort(volume->radix_sorter, (const u8 **) record_pointers,
records_per_page, UDS_RECORD_NAME_SIZE);
if (result != UDS_SUCCESS)
return result;
encode_tree(record_page, record_pointers, 0 , 0 , records_per_page);
return UDS_SUCCESS;
}
static int write_record_pages(struct volume *volume, u32 physical_chapter_number,
const struct uds_volume_record *records)
{
u32 record_page_number;
struct index_geometry *geometry = volume->geometry;
struct dm_buffer *page_buffer;
const struct uds_volume_record *next_record = records;
u32 first_record_page = map_to_physical_page(geometry, physical_chapter_number,
geometry->index_pages_per_chapter);
for (record_page_number = 0 ;
record_page_number < geometry->record_pages_per_chapter;
record_page_number++) {
u8 *page_data;
u32 physical_page = first_record_page + record_page_number;
int result;
page_data = dm_bufio_new(volume->client, physical_page, &page_buffer);
if (IS_ERR(page_data)) {
return vdo_log_warning_strerror(-PTR_ERR(page_data),
"failed to prepare record page" );
}
result = encode_record_page(volume, next_record, page_data);
if (result != UDS_SUCCESS) {
dm_bufio_release(page_buffer);
return vdo_log_warning_strerror(result,
"failed to encode record page %u" ,
record_page_number);
}
next_record += geometry->records_per_page;
dm_bufio_mark_buffer_dirty(page_buffer);
dm_bufio_release(page_buffer);
}
return UDS_SUCCESS;
}
int uds_write_chapter(struct volume *volume, struct open_chapter_index *chapter_index,
const struct uds_volume_record *records)
{
int result;
u32 physical_chapter_number =
uds_map_to_physical_chapter(volume->geometry,
chapter_index->virtual_chapter_number);
result = write_index_pages(volume, physical_chapter_number, chapter_index);
if (result != UDS_SUCCESS)
return result;
result = write_record_pages(volume, physical_chapter_number, records);
if (result != UDS_SUCCESS)
return result;
result = -dm_bufio_write_dirty_buffers(volume->client);
if (result != UDS_SUCCESS)
vdo_log_error_strerror(result, "cannot sync chapter to volume" );
return result;
}
static void probe_chapter(struct volume *volume, u32 chapter_number,
u64 *virtual_chapter_number)
{
const struct index_geometry *geometry = volume->geometry;
u32 expected_list_number = 0 ;
u32 i;
u64 vcn = BAD_CHAPTER;
*virtual_chapter_number = BAD_CHAPTER;
dm_bufio_prefetch(volume->client,
map_to_physical_page(geometry, chapter_number, 0 ),
geometry->index_pages_per_chapter);
for (i = 0 ; i < geometry->index_pages_per_chapter; i++) {
struct delta_index_page *page;
int result;
result = uds_get_volume_index_page(volume, chapter_number, i, &page);
if (result != UDS_SUCCESS)
return ;
if (page->virtual_chapter_number == BAD_CHAPTER) {
vdo_log_error("corrupt index page in chapter %u" ,
chapter_number);
return ;
}
if (vcn == BAD_CHAPTER) {
vcn = page->virtual_chapter_number;
} else if (page->virtual_chapter_number != vcn) {
vdo_log_error("inconsistent chapter %u index page %u: expected vcn %llu, got vcn %llu" ,
chapter_number, i, (unsigned long long ) vcn,
(unsigned long long ) page->virtual_chapter_number);
return ;
}
if (expected_list_number != page->lowest_list_number) {
vdo_log_error("inconsistent chapter %u index page %u: expected list number %u, got list number %u" ,
chapter_number, i, expected_list_number,
page->lowest_list_number);
return ;
}
expected_list_number = page->highest_list_number + 1 ;
result = uds_validate_chapter_index_page(page, geometry);
if (result != UDS_SUCCESS)
return ;
}
if (chapter_number != uds_map_to_physical_chapter(geometry, vcn)) {
vdo_log_error("chapter %u vcn %llu is out of phase (%u)" , chapter_number,
(unsigned long long ) vcn, geometry->chapters_per_volume);
return ;
}
*virtual_chapter_number = vcn;
}
/* Find the last valid physical chapter in the volume. */
static void find_real_end_of_volume(struct volume *volume, u32 limit, u32 *limit_ptr)
{
u32 span = 1 ;
u32 tries = 0 ;
while (limit > 0 ) {
u32 chapter = (span > limit) ? 0 : limit - span;
u64 vcn = 0 ;
probe_chapter(volume, chapter, &vcn);
if (vcn == BAD_CHAPTER) {
limit = chapter;
if (++tries > 1 )
span *= 2 ;
} else {
if (span == 1 )
break ;
span /= 2 ;
tries = 0 ;
}
}
*limit_ptr = limit;
}
static int find_chapter_limits(struct volume *volume, u32 chapter_limit, u64 *lowest_vcn,
u64 *highest_vcn)
{
struct index_geometry *geometry = volume->geometry;
u64 zero_vcn;
u64 lowest = BAD_CHAPTER;
u64 highest = BAD_CHAPTER;
u64 moved_chapter = BAD_CHAPTER;
u32 left_chapter = 0 ;
u32 right_chapter = 0 ;
u32 bad_chapters = 0 ;
/*
* This method assumes there is at most one run of contiguous bad chapters caused by
* unflushed writes . Either the bad spot is at the beginning and end , or somewhere in the
* middle . Wherever it is , the highest and lowest VCNs are adjacent to it . Otherwise the
* volume is cleanly saved and somewhere in the middle of it the highest VCN immediately
* precedes the lowest one .
*/
/* It doesn't matter if this results in a bad spot (BAD_CHAPTER). */
probe_chapter(volume, 0 , &zero_vcn);
/*
* Binary search for end of the discontinuity in the monotonically increasing virtual
* chapter numbers ; bad spots are treated as a span of BAD_CHAPTER values . In effect we ' re
* searching for the index of the smallest value less than zero_vcn . In the case we go off
* the end it means that chapter 0 has the lowest vcn .
*
* If a virtual chapter is out - of - order , it will be the one moved by conversion . Always
* skip over the moved chapter when searching , adding it to the range at the end if
* necessary .
*/
if (geometry->remapped_physical > 0 ) {
u64 remapped_vcn;
probe_chapter(volume, geometry->remapped_physical, &remapped_vcn);
if (remapped_vcn == geometry->remapped_virtual)
moved_chapter = geometry->remapped_physical;
}
left_chapter = 0 ;
right_chapter = chapter_limit;
while (left_chapter < right_chapter) {
u64 probe_vcn;
u32 chapter = (left_chapter + right_chapter) / 2 ;
if (chapter == moved_chapter)
chapter--;
probe_chapter(volume, chapter, &probe_vcn);
if (zero_vcn <= probe_vcn) {
left_chapter = chapter + 1 ;
if (left_chapter == moved_chapter)
left_chapter++;
} else {
right_chapter = chapter;
}
}
/* If left_chapter goes off the end, chapter 0 has the lowest virtual chapter number.*/
if (left_chapter >= chapter_limit)
left_chapter = 0 ;
/* At this point, left_chapter is the chapter with the lowest virtual chapter number. */
probe_chapter(volume, left_chapter, &lowest);
/* The moved chapter might be the lowest in the range. */
if ((moved_chapter != BAD_CHAPTER) && (lowest == geometry->remapped_virtual + 1 ))
lowest = geometry->remapped_virtual;
/*
* Circularly scan backwards , moving over any bad chapters until encountering a good one ,
* which is the chapter with the highest vcn .
*/
while (highest == BAD_CHAPTER) {
right_chapter = (right_chapter + chapter_limit - 1 ) % chapter_limit;
if (right_chapter == moved_chapter)
continue ;
probe_chapter(volume, right_chapter, &highest);
if (bad_chapters++ >= MAX_BAD_CHAPTERS) {
vdo_log_error("too many bad chapters in volume: %u" ,
bad_chapters);
return UDS_CORRUPT_DATA;
}
}
*lowest_vcn = lowest;
*highest_vcn = highest;
return UDS_SUCCESS;
}
/*
* Find the highest and lowest contiguous chapters present in the volume and determine their
* virtual chapter numbers . This is used by rebuild .
*/
int uds_find_volume_chapter_boundaries(struct volume *volume, u64 *lowest_vcn,
u64 *highest_vcn, bool *is_empty)
{
u32 chapter_limit = volume->geometry->chapters_per_volume;
find_real_end_of_volume(volume, chapter_limit, &chapter_limit);
if (chapter_limit == 0 ) {
*lowest_vcn = 0 ;
*highest_vcn = 0 ;
*is_empty = true ;
return UDS_SUCCESS;
}
*is_empty = false ;
return find_chapter_limits(volume, chapter_limit, lowest_vcn, highest_vcn);
}
int __must_check uds_replace_volume_storage(struct volume *volume,
struct index_layout *layout,
struct block_device *bdev)
{
int result;
u32 i;
result = uds_replace_index_layout_storage(layout, bdev);
if (result != UDS_SUCCESS)
return result;
/* Release all outstanding dm_bufio objects */
for (i = 0 ; i < volume->page_cache.indexable_pages; i++)
volume->page_cache.index[i] = volume->page_cache.cache_slots;
for (i = 0 ; i < volume->page_cache.cache_slots; i++)
clear_cache_page(&volume->page_cache, &volume->page_cache.cache[i]);
if (volume->sparse_cache != NULL)
uds_invalidate_sparse_cache(volume->sparse_cache);
if (volume->client != NULL)
dm_bufio_client_destroy(vdo_forget(volume->client));
return uds_open_volume_bufio(layout, volume->geometry->bytes_per_page,
volume->reserved_buffers, &volume->client);
}
static int __must_check initialize_page_cache(struct page_cache *cache,
const struct index_geometry *geometry,
u32 chapters_in_cache,
unsigned int zone_count)
{
int result;
u32 i;
cache->indexable_pages = geometry->pages_per_volume + 1 ;
cache->cache_slots = chapters_in_cache * geometry->record_pages_per_chapter;
cache->zone_count = zone_count;
atomic64_set(&cache->clock, 1 );
result = VDO_ASSERT((cache->cache_slots <= VOLUME_CACHE_MAX_ENTRIES),
"requested cache size, %u, within limit %u" ,
cache->cache_slots, VOLUME_CACHE_MAX_ENTRIES);
if (result != VDO_SUCCESS)
return result;
result = vdo_allocate(VOLUME_CACHE_MAX_QUEUED_READS, struct queued_read,
"volume read queue" , &cache->read_queue);
if (result != VDO_SUCCESS)
return result;
result = vdo_allocate(cache->zone_count, struct search_pending_counter,
"Volume Cache Zones" , &cache->search_pending_counters);
if (result != VDO_SUCCESS)
return result;
result = vdo_allocate(cache->indexable_pages, u16, "page cache index" ,
&cache->index);
if (result != VDO_SUCCESS)
return result;
result = vdo_allocate(cache->cache_slots, struct cached_page, "page cache cache" ,
&cache->cache);
if (result != VDO_SUCCESS)
return result;
/* Initialize index values to invalid values. */
for (i = 0 ; i < cache->indexable_pages; i++)
cache->index[i] = cache->cache_slots;
for (i = 0 ; i < cache->cache_slots; i++)
clear_cache_page(cache, &cache->cache[i]);
return UDS_SUCCESS;
}
int uds_make_volume(const struct uds_configuration *config, struct index_layout *layout,
struct volume **new_volume)
{
unsigned int i;
struct volume *volume = NULL;
struct index_geometry *geometry;
unsigned int reserved_buffers;
int result;
result = vdo_allocate(1 , struct volume, "volume" , &volume);
if (result != VDO_SUCCESS)
return result;
volume->nonce = uds_get_volume_nonce(layout);
result = uds_copy_index_geometry(config->geometry, &volume->geometry);
if (result != UDS_SUCCESS) {
uds_free_volume(volume);
return vdo_log_warning_strerror(result,
"failed to allocate geometry: error" );
}
geometry = volume->geometry;
/*
* Reserve a buffer for each entry in the page cache , one for the chapter writer , and one
* for each entry in the sparse cache .
*/
reserved_buffers = config->cache_chapters * geometry->record_pages_per_chapter;
reserved_buffers += 1 ;
if (uds_is_sparse_index_geometry(geometry))
reserved_buffers += (config->cache_chapters * geometry->index_pages_per_chapter);
volume->reserved_buffers = reserved_buffers;
result = uds_open_volume_bufio(layout, geometry->bytes_per_page,
volume->reserved_buffers, &volume->client);
if (result != UDS_SUCCESS) {
uds_free_volume(volume);
return result;
}
result = uds_make_radix_sorter(geometry->records_per_page,
&volume->radix_sorter);
if (result != UDS_SUCCESS) {
uds_free_volume(volume);
return result;
}
result = vdo_allocate(geometry->records_per_page,
const struct uds_volume_record *, "record pointers" ,
&volume->record_pointers);
if (result != VDO_SUCCESS) {
uds_free_volume(volume);
return result;
}
if (uds_is_sparse_index_geometry(geometry)) {
size_t page_size = sizeof (struct delta_index_page) + geometry->bytes_per_page;
result = uds_make_sparse_cache(geometry, config->cache_chapters,
config->zone_count,
&volume->sparse_cache);
if (result != UDS_SUCCESS) {
uds_free_volume(volume);
return result;
}
volume->cache_size =
page_size * geometry->index_pages_per_chapter * config->cache_chapters;
}
result = initialize_page_cache(&volume->page_cache, geometry,
config->cache_chapters, config->zone_count);
if (result != UDS_SUCCESS) {
uds_free_volume(volume);
return result;
}
volume->cache_size += volume->page_cache.cache_slots * sizeof (struct delta_index_page);
result = uds_make_index_page_map(geometry, &volume->index_page_map);
if (result != UDS_SUCCESS) {
uds_free_volume(volume);
return result;
}
mutex_init(&volume->read_threads_mutex);
uds_init_cond(&volume->read_threads_read_done_cond);
uds_init_cond(&volume->read_threads_cond);
result = vdo_allocate(config->read_threads, struct thread *, "reader threads" ,
&volume->reader_threads);
if (result != VDO_SUCCESS) {
uds_free_volume(volume);
return result;
}
for (i = 0 ; i < config->read_threads; i++) {
result = vdo_create_thread(read_thread_function, (void *) volume,
"reader" , &volume->reader_threads[i]);
if (result != VDO_SUCCESS) {
uds_free_volume(volume);
return result;
}
volume->read_thread_count = i + 1 ;
}
*new_volume = volume;
return UDS_SUCCESS;
}
static void uninitialize_page_cache(struct page_cache *cache)
{
u16 i;
if (cache->cache != NULL) {
for (i = 0 ; i < cache->cache_slots; i++)
release_page_buffer(&cache->cache[i]);
}
vdo_free(cache->index);
vdo_free(cache->cache);
vdo_free(cache->search_pending_counters);
vdo_free(cache->read_queue);
}
void uds_free_volume(struct volume *volume)
{
if (volume == NULL)
return ;
if (volume->reader_threads != NULL) {
unsigned int i;
/* This works even if some threads weren't started. */
mutex_lock(&volume->read_threads_mutex);
volume->read_threads_exiting = true ;
uds_broadcast_cond(&volume->read_threads_cond);
mutex_unlock(&volume->read_threads_mutex);
for (i = 0 ; i < volume->read_thread_count; i++)
vdo_join_threads(volume->reader_threads[i]);
vdo_free(volume->reader_threads);
volume->reader_threads = NULL;
}
/* Must destroy the client AFTER freeing the cached pages. */
uninitialize_page_cache(&volume->page_cache);
uds_free_sparse_cache(volume->sparse_cache);
if (volume->client != NULL)
dm_bufio_client_destroy(vdo_forget(volume->client));
uds_free_index_page_map(volume->index_page_map);
uds_free_radix_sorter(volume->radix_sorter);
vdo_free(volume->geometry);
vdo_free(volume->record_pointers);
vdo_free(volume);
}
Messung V0.5 in Prozent C=97 H=93 G=94
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland