#include "repair.h"
#include <linux/min_heap.h>
#include <linux/minmax.h>
#include "logger.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "block-map.h"
#include "completion.h"
#include "constants.h"
#include "encodings.h"
#include "int-map.h"
#include "io-submitter.h"
#include "recovery-journal.h"
#include "slab-depot.h"
#include "types.h"
#include "vdo.h"
#include "wait-queue.h"
struct numbered_block_mapping {
struct block_map_slot block_map_slot;
struct block_map_entry block_map_entry;
u32 number;
} __packed;
struct recovery_point {
sequence_number_t sequence_number;
u8 sector_count;
journal_entry_count_t entry_count;
bool increment_applied;
};
DEFINE_MIN_HEAP(struct numbered_block_mapping, replay_heap);
struct repair_completion {
struct vdo_completion completion;
char *journal_data;
data_vio_count_t vio_count;
data_vio_count_t vios_complete;
struct vio *vios;
size_t block_map_entry_count;
sequence_number_t block_map_head;
sequence_number_t slab_journal_head;
sequence_number_t tail;
sequence_number_t highest_tail;
block_count_t logical_blocks_used;
block_count_t block_map_data_blocks;
struct numbered_block_mapping *entries;
size_t entry_count;
page_count_t outstanding;
page_count_t page_count;
bool launching;
struct replay_heap replay_heap;
struct numbered_block_mapping *current_entry;
struct numbered_block_mapping *current_unfetched_entry;
physical_block_number_t pbn;
struct recovery_point tail_recovery_point;
struct recovery_point next_recovery_point;
struct journal_point next_journal_point;
size_t entries_added_to_slab_journals;
page_count_t page_to_fetch;
page_count_t leaf_pages;
struct block_map_slot last_slot;
struct vdo_page_completion page_completions[] __counted_by(page_count);
};
static bool mapping_is_less_than(const void *item1, const void *item2, void __always_unused *args)
{
const struct numbered_block_mapping *mapping1 =
(const struct numbered_block_mapping *) item1;
const struct numbered_block_mapping *mapping2 =
(const struct numbered_block_mapping *) item2;
if (mapping1->block_map_slot.pbn != mapping2->block_map_slot.pbn)
return mapping1->block_map_slot.pbn < mapping2->block_map_slot.pbn;
if (mapping1->block_map_slot.slot != mapping2->block_map_slot.slot)
return mapping1->block_map_slot.slot < mapping2->block_map_slot.slot;
if (mapping1->number != mapping2->number)
return mapping1->number < mapping2->number;
return 0;
}
static void swap_mappings(void *item1, void *item2, void __always_unused *args)
{
struct numbered_block_mapping *mapping1 = item1;
struct numbered_block_mapping *mapping2 = item2;
swap(*mapping1, *mapping2);
}
static const struct min_heap_callbacks repair_min_heap = {
.less = mapping_is_less_than,
.swp = NULL,
};
static struct numbered_block_mapping *sort_next_heap_element(struct repair_completion *repair)
{
struct replay_heap *heap = &repair->replay_heap;
struct numbered_block_mapping *last;
if (heap->nr == 0)
return NULL;
last = &repair->entries[--heap->nr];
swap_mappings(heap->data, last, NULL);
min_heap_sift_down(heap, 0, &repair_min_heap, NULL);
return last;
}
static inline struct repair_completion * __must_check
as_repair_completion(struct vdo_completion *completion)
{
vdo_assert_completion_type(completion, VDO_REPAIR_COMPLETION);
return container_of(completion, struct repair_completion, completion);
}
static void prepare_repair_completion(struct repair_completion *repair,
vdo_action_fn callback, enum vdo_zone_type zone_type)
{
struct vdo_completion *completion = &repair->completion;
const struct thread_config *thread_config = &completion->vdo->thread_config;
thread_id_t thread_id;
thread_id = ((zone_type == VDO_ZONE_TYPE_LOGICAL) ?
thread_config->logical_threads[0] :
thread_config->admin_thread);
vdo_reset_completion(completion);
vdo_set_completion_callback(completion, callback, thread_id);
}
static void launch_repair_completion(struct repair_completion *repair,
vdo_action_fn callback, enum vdo_zone_type zone_type)
{
prepare_repair_completion(repair, callback, zone_type);
vdo_launch_completion(&repair->completion);
}
static void uninitialize_vios(struct repair_completion *repair)
{
while (repair->vio_count > 0)
free_vio_components(&repair->vios[--repair->vio_count]);
vdo_free(vdo_forget(repair->vios));
}
static void free_repair_completion(struct repair_completion *repair)
{
if (repair == NULL)
return;
repair->completion.vdo->block_map->zones[0].page_cache.rebuilding = false;
uninitialize_vios(repair);
vdo_free(vdo_forget(repair->journal_data));
vdo_free(vdo_forget(repair->entries));
vdo_free(repair);
}
static void finish_repair(struct vdo_completion *completion)
{
struct vdo_completion *parent = completion->parent;
struct vdo *vdo = completion->vdo;
struct repair_completion *repair = as_repair_completion(completion);
vdo_assert_on_admin_thread(vdo, __func__);
if (vdo->load_state != VDO_REBUILD_FOR_UPGRADE)
vdo->states.vdo.complete_recoveries++;
vdo_initialize_recovery_journal_post_repair(vdo->recovery_journal,
vdo->states.vdo.complete_recoveries,
repair->highest_tail,
repair->logical_blocks_used,
repair->block_map_data_blocks);
free_repair_completion(vdo_forget(repair));
if (vdo_state_requires_read_only_rebuild(vdo->load_state)) {
vdo_log_info("Read-only rebuild complete");
vdo_launch_completion(parent);
return;
}
vdo_log_info("Rebuild complete");
vdo_continue_completion(parent, vdo_allocate_reference_counters(vdo->depot));
}
static void abort_repair(struct vdo_completion *completion)
{
struct vdo_completion *parent = completion->parent;
int result = completion->result;
struct repair_completion *repair = as_repair_completion(completion);
if (vdo_state_requires_read_only_rebuild(completion->vdo->load_state))
vdo_log_info("Read-only rebuild aborted");
else
vdo_log_warning("Recovery aborted");
free_repair_completion(vdo_forget(repair));
vdo_continue_completion(parent, result);
}
static bool __must_check abort_on_error(int result, struct repair_completion *repair)
{
if (result == VDO_SUCCESS)
return false;
vdo_fail_completion(&repair->completion, result);
return true;
}
static void drain_slab_depot(struct vdo_completion *completion)
{
struct vdo *vdo = completion->vdo;
struct repair_completion *repair = as_repair_completion(completion);
const struct admin_state_code *operation;
vdo_assert_on_admin_thread(vdo, __func__);
prepare_repair_completion(repair, finish_repair, VDO_ZONE_TYPE_ADMIN);
if (vdo_state_requires_read_only_rebuild(vdo->load_state)) {
vdo_log_info("Saving rebuilt state");
operation = VDO_ADMIN_STATE_REBUILDING;
} else {
vdo_log_info("Replayed %zu journal entries into slab journals",
repair->entries_added_to_slab_journals);
operation = VDO_ADMIN_STATE_RECOVERING;
}
vdo_drain_slab_depot(vdo->depot, operation, completion);
}
static void flush_block_map_updates(struct vdo_completion *completion)
{
vdo_assert_on_admin_thread(completion->vdo, __func__);
vdo_log_info("Flushing block map changes");
prepare_repair_completion(as_repair_completion(completion), drain_slab_depot,
VDO_ZONE_TYPE_ADMIN);
vdo_drain_block_map(completion->vdo->block_map, VDO_ADMIN_STATE_RECOVERING,
completion);
}
static bool fetch_page(struct repair_completion *repair,
struct vdo_completion *completion);
static void handle_page_load_error(struct vdo_completion *completion)
{
struct repair_completion *repair = completion->parent;
repair->outstanding--;
vdo_set_completion_result(&repair->completion, completion->result);
vdo_release_page_completion(completion);
fetch_page(repair, completion);
}
static void unmap_entry(struct block_map_page *page, struct vdo_completion *completion,
slot_number_t slot)
{
page->entries[slot] = UNMAPPED_BLOCK_MAP_ENTRY;
vdo_request_page_write(completion);
}
static void remove_out_of_bounds_entries(struct block_map_page *page,
struct vdo_completion *completion,
slot_number_t start)
{
slot_number_t slot;
for (slot = start; slot < VDO_BLOCK_MAP_ENTRIES_PER_PAGE; slot++) {
struct data_location mapping = vdo_unpack_block_map_entry(&page->entries[slot]);
if (vdo_is_mapped_location(&mapping))
unmap_entry(page, completion, slot);
}
}
static bool process_slot(struct block_map_page *page, struct vdo_completion *completion,
slot_number_t slot)
{
struct slab_depot *depot = completion->vdo->depot;
int result;
struct data_location mapping = vdo_unpack_block_map_entry(&page->entries[slot]);
if (!vdo_is_valid_location(&mapping)) {
unmap_entry(page, completion, slot);
return false;
}
if (!vdo_is_mapped_location(&mapping))
return false;
if (mapping.pbn == VDO_ZERO_BLOCK)
return true;
if (!vdo_is_physical_data_block(depot, mapping.pbn)) {
unmap_entry(page, completion, slot);
return false;
}
result = vdo_adjust_reference_count_for_rebuild(depot, mapping.pbn,
VDO_JOURNAL_DATA_REMAPPING);
if (result == VDO_SUCCESS)
return true;
vdo_log_error_strerror(result,
"Could not adjust reference count for PBN %llu, slot %u mapped to PBN %llu",
(unsigned long long) vdo_get_block_map_page_pbn(page),
slot, (unsigned long long) mapping.pbn);
unmap_entry(page, completion, slot);
return false;
}
static void rebuild_reference_counts_from_page(struct repair_completion *repair,
struct vdo_completion *completion)
{
slot_number_t slot, last_slot;
struct block_map_page *page;
int result;
result = vdo_get_cached_page(completion, &page);
if (result != VDO_SUCCESS) {
vdo_set_completion_result(&repair->completion, result);
return;
}
if (!page->header.initialized)
return;
if (vdo_get_block_map_page_pbn(page) == repair->last_slot.pbn) {
last_slot = repair->last_slot.slot;
remove_out_of_bounds_entries(page, completion, last_slot);
} else {
last_slot = VDO_BLOCK_MAP_ENTRIES_PER_PAGE;
}
for (slot = 0; slot < last_slot; slot++) {
if (process_slot(page, completion, slot))
repair->logical_blocks_used++;
}
}
static void page_loaded(struct vdo_completion *completion)
{
struct repair_completion *repair = completion->parent;
repair->outstanding--;
rebuild_reference_counts_from_page(repair, completion);
vdo_release_page_completion(completion);
fetch_page(repair, completion);
}
static physical_block_number_t get_pbn_to_fetch(struct repair_completion *repair,
struct block_map *block_map)
{
physical_block_number_t pbn = VDO_ZERO_BLOCK;
if (repair->completion.result != VDO_SUCCESS)
return VDO_ZERO_BLOCK;
while ((pbn == VDO_ZERO_BLOCK) && (repair->page_to_fetch < repair->leaf_pages))
pbn = vdo_find_block_map_page_pbn(block_map, repair->page_to_fetch++);
if (vdo_is_physical_data_block(repair->completion.vdo->depot, pbn))
return pbn;
vdo_set_completion_result(&repair->completion, VDO_BAD_MAPPING);
return VDO_ZERO_BLOCK;
}
static bool fetch_page(struct repair_completion *repair,
struct vdo_completion *completion)
{
struct vdo_page_completion *page_completion = (struct vdo_page_completion *) completion;
struct block_map *block_map = repair->completion.vdo->block_map;
physical_block_number_t pbn = get_pbn_to_fetch(repair, block_map);
if (pbn != VDO_ZERO_BLOCK) {
repair->outstanding++;
vdo_get_page(page_completion, &block_map->zones[0], pbn, true, repair,
page_loaded, handle_page_load_error, true);
}
if (repair->outstanding > 0)
return false;
launch_repair_completion(repair, flush_block_map_updates, VDO_ZONE_TYPE_ADMIN);
return true;
}
static void rebuild_from_leaves(struct vdo_completion *completion)
{
page_count_t i;
struct repair_completion *repair = as_repair_completion(completion);
struct block_map *map = completion->vdo->block_map;
repair->logical_blocks_used = 0;
repair->leaf_pages = vdo_compute_block_map_page_count(map->entry_count);
repair->last_slot = (struct block_map_slot) {
.slot = map->entry_count % VDO_BLOCK_MAP_ENTRIES_PER_PAGE,
.pbn = vdo_find_block_map_page_pbn(map, repair->leaf_pages - 1),
};
if (repair->last_slot.slot == 0)
repair->last_slot.slot = VDO_BLOCK_MAP_ENTRIES_PER_PAGE;
for (i = 0; i < repair->page_count; i++) {
if (fetch_page(repair, &repair->page_completions[i].completion)) {
return;
}
}
}
static int process_entry(physical_block_number_t pbn, struct vdo_completion *completion)
{
struct repair_completion *repair = as_repair_completion(completion);
struct slab_depot *depot = completion->vdo->depot;
int result;
if ((pbn == VDO_ZERO_BLOCK) || !vdo_is_physical_data_block(depot, pbn)) {
return vdo_log_error_strerror(VDO_BAD_CONFIGURATION,
"PBN %llu out of range",
(unsigned long long) pbn);
}
result = vdo_adjust_reference_count_for_rebuild(depot, pbn,
VDO_JOURNAL_BLOCK_MAP_REMAPPING);
if (result != VDO_SUCCESS) {
return vdo_log_error_strerror(result,
"Could not adjust reference count for block map tree PBN %llu",
(unsigned long long) pbn);
}
repair->block_map_data_blocks++;
return VDO_SUCCESS;
}
static void rebuild_reference_counts(struct vdo_completion *completion)
{
struct repair_completion *repair = as_repair_completion(completion);
struct vdo *vdo = completion->vdo;
struct vdo_page_cache *cache = &vdo->block_map->zones[0].page_cache;
if (abort_on_error(vdo_allocate_reference_counters(vdo->depot), repair))
return;
if (abort_on_error(vdo_invalidate_page_cache(cache), repair))
return;
prepare_repair_completion(repair, rebuild_from_leaves, VDO_ZONE_TYPE_LOGICAL);
vdo_traverse_forest(vdo->block_map, process_entry, completion);
}
static void increment_recovery_point(struct recovery_point *point)
{
if (++point->entry_count < RECOVERY_JOURNAL_ENTRIES_PER_SECTOR)
return;
point->entry_count = 0;
if (point->sector_count < (VDO_SECTORS_PER_BLOCK - 1)) {
point->sector_count++;
return;
}
point->sequence_number++;
point->sector_count = 1;
}
static void advance_points(struct repair_completion *repair,
journal_entry_count_t entries_per_block)
{
if (!repair->next_recovery_point.increment_applied) {
repair->next_recovery_point.increment_applied = true;
return;
}
increment_recovery_point(&repair->next_recovery_point);
vdo_advance_journal_point(&repair->next_journal_point, entries_per_block);
repair->next_recovery_point.increment_applied = false;
}
static bool __must_check before_recovery_point(const struct recovery_point *first,
const struct recovery_point *second)
{
if (first->sequence_number < second->sequence_number)
return true;
if (first->sequence_number > second->sequence_number)
return false;
if (first->sector_count < second->sector_count)
return true;
return ((first->sector_count == second->sector_count) &&
(first->entry_count < second->entry_count));
}
static struct packed_journal_sector * __must_check get_sector(struct recovery_journal *journal,
char *journal_data,
sequence_number_t sequence,
u8 sector_number)
{
off_t offset;
offset = ((vdo_get_recovery_journal_block_number(journal, sequence) * VDO_BLOCK_SIZE) +
(VDO_SECTOR_SIZE * sector_number));
return (struct packed_journal_sector *) (journal_data + offset);
}
static struct recovery_journal_entry get_entry(const struct repair_completion *repair,
const struct recovery_point *point)
{
struct packed_journal_sector *sector;
sector = get_sector(repair->completion.vdo->recovery_journal,
repair->journal_data, point->sequence_number,
point->sector_count);
return vdo_unpack_recovery_journal_entry(§or->entries[point->entry_count]);
}
static int validate_recovery_journal_entry(const struct vdo *vdo,
const struct recovery_journal_entry *entry)
{
if ((entry->slot.pbn >= vdo->states.vdo.config.physical_blocks) ||
(entry->slot.slot >= VDO_BLOCK_MAP_ENTRIES_PER_PAGE) ||
!vdo_is_valid_location(&entry->mapping) ||
!vdo_is_valid_location(&entry->unmapping) ||
!vdo_is_physical_data_block(vdo->depot, entry->mapping.pbn) ||
!vdo_is_physical_data_block(vdo->depot, entry->unmapping.pbn)) {
return vdo_log_error_strerror(VDO_CORRUPT_JOURNAL,
"Invalid entry: %s (%llu, %u) from %llu to %llu is not within bounds",
vdo_get_journal_operation_name(entry->operation),
(unsigned long long) entry->slot.pbn,
entry->slot.slot,
(unsigned long long) entry->unmapping.pbn,
(unsigned long long) entry->mapping.pbn);
}
if ((entry->operation == VDO_JOURNAL_BLOCK_MAP_REMAPPING) &&
(vdo_is_state_compressed(entry->mapping.state) ||
(entry->mapping.pbn == VDO_ZERO_BLOCK) ||
(entry->unmapping.state != VDO_MAPPING_STATE_UNMAPPED) ||
(entry->unmapping.pbn != VDO_ZERO_BLOCK))) {
return vdo_log_error_strerror(VDO_CORRUPT_JOURNAL,
"Invalid entry: %s (%llu, %u) from %llu to %llu is not a valid tree mapping",
vdo_get_journal_operation_name(entry->operation),
(unsigned long long) entry->slot.pbn,
entry->slot.slot,
(unsigned long long) entry->unmapping.pbn,
(unsigned long long) entry->mapping.pbn);
}
return VDO_SUCCESS;
}
static void add_slab_journal_entries(struct vdo_completion *completion)
{
struct recovery_point *recovery_point;
struct repair_completion *repair = completion->parent;
struct vdo *vdo = completion->vdo;
struct recovery_journal *journal = vdo->recovery_journal;
struct block_allocator *allocator = vdo_as_block_allocator(completion);
vdo_prepare_completion(completion, add_slab_journal_entries,
vdo_notify_slab_journals_are_recovered,
completion->callback_thread_id, repair);
for (recovery_point = &repair->next_recovery_point;
before_recovery_point(recovery_point, &repair->tail_recovery_point);
advance_points(repair, journal->entries_per_block)) {
int result;
physical_block_number_t pbn;
struct vdo_slab *slab;
struct recovery_journal_entry entry = get_entry(repair, recovery_point);
bool increment = !repair->next_recovery_point.increment_applied;
if (increment) {
result = validate_recovery_journal_entry(vdo, &entry);
if (result != VDO_SUCCESS) {
vdo_enter_read_only_mode(vdo, result);
vdo_fail_completion(completion, result);
return;
}
pbn = entry.mapping.pbn;
} else {
pbn = entry.unmapping.pbn;
}
if (pbn == VDO_ZERO_BLOCK)
continue;
slab = vdo_get_slab(vdo->depot, pbn);
if (slab->allocator != allocator)
continue;
if (!vdo_attempt_replay_into_slab(slab, pbn, entry.operation, increment,
&repair->next_journal_point,
completion))
return;
repair->entries_added_to_slab_journals++;
}
vdo_notify_slab_journals_are_recovered(completion);
}
void vdo_replay_into_slab_journals(struct block_allocator *allocator, void *context)
{
struct vdo_completion *completion = &allocator->completion;
struct repair_completion *repair = context;
struct vdo *vdo = completion->vdo;
vdo_assert_on_physical_zone_thread(vdo, allocator->zone_number, __func__);
if (repair->entry_count == 0) {
repair->logical_blocks_used = vdo->recovery_journal->logical_blocks_used;
repair->block_map_data_blocks = vdo->recovery_journal->block_map_data_blocks;
vdo_notify_slab_journals_are_recovered(completion);
return;
}
repair->next_recovery_point = (struct recovery_point) {
.sequence_number = repair->slab_journal_head,
.sector_count = 1,
.entry_count = 0,
};
repair->next_journal_point = (struct journal_point) {
.sequence_number = repair->slab_journal_head,
.entry_count = 0,
};
vdo_log_info("Replaying entries into slab journals for zone %u",
allocator->zone_number);
completion->parent = repair;
add_slab_journal_entries(completion);
}
static void load_slab_depot(struct vdo_completion *completion)
{
struct repair_completion *repair = as_repair_completion(completion);
const struct admin_state_code *operation;
vdo_assert_on_admin_thread(completion->vdo, __func__);
if (vdo_state_requires_read_only_rebuild(completion->vdo->load_state)) {
prepare_repair_completion(repair, rebuild_reference_counts,
VDO_ZONE_TYPE_LOGICAL);
operation = VDO_ADMIN_STATE_LOADING_FOR_REBUILD;
} else {
prepare_repair_completion(repair, drain_slab_depot, VDO_ZONE_TYPE_ADMIN);
operation = VDO_ADMIN_STATE_LOADING_FOR_RECOVERY;
}
vdo_load_slab_depot(completion->vdo->depot, operation, completion, repair);
}
static void flush_block_map(struct vdo_completion *completion)
{
struct repair_completion *repair = as_repair_completion(completion);
const struct admin_state_code *operation;
vdo_assert_on_admin_thread(completion->vdo, __func__);
vdo_log_info("Flushing block map changes");
prepare_repair_completion(repair, load_slab_depot, VDO_ZONE_TYPE_ADMIN);
operation = (vdo_state_requires_read_only_rebuild(completion->vdo->load_state) ?
VDO_ADMIN_STATE_REBUILDING :
VDO_ADMIN_STATE_RECOVERING);
vdo_drain_block_map(completion->vdo->block_map, operation, completion);
}
static bool finish_if_done(struct repair_completion *repair)
{
if (repair->launching || (repair->outstanding > 0))
return false;
if (repair->completion.result != VDO_SUCCESS) {
page_count_t i;
for (i = 0; i < repair->page_count; i++) {
struct vdo_page_completion *page_completion =
&repair->page_completions[i];
if (page_completion->ready)
vdo_release_page_completion(&page_completion->completion);
}
vdo_launch_completion(&repair->completion);
return true;
}
if (repair->current_entry >= repair->entries)
return false;
launch_repair_completion(repair, flush_block_map, VDO_ZONE_TYPE_ADMIN);
return true;
}
static void abort_block_map_recovery(struct repair_completion *repair, int result)
{
vdo_set_completion_result(&repair->completion, result);
finish_if_done(repair);
}
static struct numbered_block_mapping *
find_entry_starting_next_page(struct repair_completion *repair,
struct numbered_block_mapping *current_entry, bool needs_sort)
{
size_t current_page;
if (current_entry < repair->entries)
return current_entry;
current_page = current_entry->block_map_slot.pbn;
while ((current_entry >= repair->entries) &&
(current_entry->block_map_slot.pbn == current_page)) {
if (needs_sort) {
struct numbered_block_mapping *just_sorted_entry =
sort_next_heap_element(repair);
VDO_ASSERT_LOG_ONLY(just_sorted_entry < current_entry,
"heap is returning elements in an unexpected order");
}
current_entry--;
}
return current_entry;
}
static void apply_journal_entries_to_page(struct block_map_page *page,
struct numbered_block_mapping *starting_entry,
struct numbered_block_mapping *ending_entry)
{
struct numbered_block_mapping *current_entry = starting_entry;
while (current_entry != ending_entry) {
page->entries[current_entry->block_map_slot.slot] = current_entry->block_map_entry;
current_entry--;
}
}
static void recover_ready_pages(struct repair_completion *repair,
struct vdo_completion *completion);
static void block_map_page_loaded(struct vdo_completion *completion)
{
struct repair_completion *repair = as_repair_completion(completion->parent);
repair->outstanding--;
if (!repair->launching)
recover_ready_pages(repair, completion);
}
static void handle_block_map_page_load_error(struct vdo_completion *completion)
{
struct repair_completion *repair = as_repair_completion(completion->parent);
repair->outstanding--;
abort_block_map_recovery(repair, completion->result);
}
static void fetch_block_map_page(struct repair_completion *repair,
struct vdo_completion *completion)
{
physical_block_number_t pbn;
if (repair->current_unfetched_entry < repair->entries)
return;
pbn = repair->current_unfetched_entry->block_map_slot.pbn;
repair->current_unfetched_entry =
find_entry_starting_next_page(repair, repair->current_unfetched_entry,
true);
repair->outstanding++;
vdo_get_page(((struct vdo_page_completion *) completion),
&repair->completion.vdo->block_map->zones[0], pbn, true,
&repair->completion, block_map_page_loaded,
handle_block_map_page_load_error, false);
}
static struct vdo_page_completion *get_next_page_completion(struct repair_completion *repair,
struct vdo_page_completion *completion)
{
completion++;
if (completion == (&repair->page_completions[repair->page_count]))
completion = &repair->page_completions[0];
return completion;
}
static void recover_ready_pages(struct repair_completion *repair,
struct vdo_completion *completion)
{
struct vdo_page_completion *page_completion = (struct vdo_page_completion *) completion;
if (finish_if_done(repair))
return;
if (repair->pbn != page_completion->pbn)
return;
while (page_completion->ready) {
struct numbered_block_mapping *start_of_next_page;
struct block_map_page *page;
int result;
result = vdo_get_cached_page(completion, &page);
if (result != VDO_SUCCESS) {
abort_block_map_recovery(repair, result);
return;
}
start_of_next_page =
find_entry_starting_next_page(repair, repair->current_entry,
false);
apply_journal_entries_to_page(page, repair->current_entry,
start_of_next_page);
repair->current_entry = start_of_next_page;
vdo_request_page_write(completion);
vdo_release_page_completion(completion);
if (finish_if_done(repair))
return;
repair->pbn = repair->current_entry->block_map_slot.pbn;
fetch_block_map_page(repair, completion);
page_completion = get_next_page_completion(repair, page_completion);
completion = &page_completion->completion;
}
}
static void recover_block_map(struct vdo_completion *completion)
{
struct repair_completion *repair = as_repair_completion(completion);
struct vdo *vdo = completion->vdo;
struct numbered_block_mapping *first_sorted_entry;
page_count_t i;
vdo_assert_on_logical_zone_thread(vdo, 0, __func__);
vdo->block_map->zones[0].page_cache.rebuilding =
vdo_state_requires_read_only_rebuild(vdo->load_state);
if (repair->block_map_entry_count == 0) {
vdo_log_info("Replaying 0 recovery entries into block map");
vdo_free(vdo_forget(repair->journal_data));
launch_repair_completion(repair, load_slab_depot, VDO_ZONE_TYPE_ADMIN);
return;
}
repair->replay_heap = (struct replay_heap) {
.data = repair->entries,
.nr = repair->block_map_entry_count,
.size = repair->block_map_entry_count,
};
min_heapify_all(&repair->replay_heap, &repair_min_heap, NULL);
vdo_log_info("Replaying %zu recovery entries into block map",
repair->block_map_entry_count);
repair->current_entry = &repair->entries[repair->block_map_entry_count - 1];
first_sorted_entry = sort_next_heap_element(repair);
VDO_ASSERT_LOG_ONLY(first_sorted_entry == repair->current_entry,
"heap is returning elements in an unexpected order");
repair->launching = true;
repair->pbn = repair->current_entry->block_map_slot.pbn;
repair->current_unfetched_entry = repair->current_entry;
for (i = 0; i < repair->page_count; i++) {
if (repair->current_unfetched_entry < repair->entries)
break;
fetch_block_map_page(repair, &repair->page_completions[i].completion);
}
repair->launching = false;
recover_ready_pages(repair, &repair->page_completions[0].completion);
}
static struct recovery_block_header __must_check
get_recovery_journal_block_header(struct recovery_journal *journal, char *data,
sequence_number_t sequence)
{
physical_block_number_t pbn =
vdo_get_recovery_journal_block_number(journal, sequence);
char *header = &data[pbn * VDO_BLOCK_SIZE];
return vdo_unpack_recovery_block_header((struct packed_journal_header *) header);
}
static bool __must_check is_valid_recovery_journal_block(const struct recovery_journal *journal,
const struct recovery_block_header *header,
bool old_ok)
{
if ((header->nonce != journal->nonce) ||
(header->recovery_count != journal->recovery_count))
return false;
if (header->metadata_type == VDO_METADATA_RECOVERY_JOURNAL_2)
return (header->entry_count <= journal->entries_per_block);
return (old_ok &&
(header->metadata_type == VDO_METADATA_RECOVERY_JOURNAL) &&
(header->entry_count <= RECOVERY_JOURNAL_1_ENTRIES_PER_BLOCK));
}
static bool __must_check is_exact_recovery_journal_block(const struct recovery_journal *journal,
const struct recovery_block_header *header,
sequence_number_t sequence)
{
return ((header->sequence_number == sequence) &&
(is_valid_recovery_journal_block(journal, header, true)));
}
static bool find_recovery_journal_head_and_tail(struct repair_completion *repair)
{
struct recovery_journal *journal = repair->completion.vdo->recovery_journal;
bool found_entries = false;
physical_block_number_t i;
repair->highest_tail = journal->tail;
for (i = 0; i < journal->size; i++) {
struct recovery_block_header header =
get_recovery_journal_block_header(journal, repair->journal_data, i);
if (!is_valid_recovery_journal_block(journal, &header, true)) {
continue;
}
if (vdo_get_recovery_journal_block_number(journal, header.sequence_number) != i) {
continue;
}
if (header.sequence_number >= repair->highest_tail) {
found_entries = true;
repair->highest_tail = header.sequence_number;
}
if (!found_entries)
continue;
if (header.block_map_head > repair->block_map_head)
repair->block_map_head = header.block_map_head;
if (header.slab_journal_head > repair->slab_journal_head)
repair->slab_journal_head = header.slab_journal_head;
}
return found_entries;
}
static bool unpack_entry(struct vdo *vdo, char *packed, enum vdo_metadata_type format,
struct recovery_journal_entry *entry)
{
if (format == VDO_METADATA_RECOVERY_JOURNAL_2) {
struct packed_recovery_journal_entry *packed_entry =
(struct packed_recovery_journal_entry *) packed;
*entry = vdo_unpack_recovery_journal_entry(packed_entry);
} else {
physical_block_number_t low32, high4;
struct packed_recovery_journal_entry_1 *packed_entry =
(struct packed_recovery_journal_entry_1 *) packed;
if (packed_entry->operation == VDO_JOURNAL_DATA_INCREMENT)
entry->operation = VDO_JOURNAL_DATA_REMAPPING;
else if (packed_entry->operation == VDO_JOURNAL_BLOCK_MAP_INCREMENT)
entry->operation = VDO_JOURNAL_BLOCK_MAP_REMAPPING;
else
return false;
low32 = __le32_to_cpu(packed_entry->pbn_low_word);
high4 = packed_entry->pbn_high_nibble;
entry->slot = (struct block_map_slot) {
.pbn = ((high4 << 32) | low32),
.slot = (packed_entry->slot_low | (packed_entry->slot_high << 6)),
};
entry->mapping = vdo_unpack_block_map_entry(&packed_entry->block_map_entry);
entry->unmapping = (struct data_location) {
.pbn = VDO_ZERO_BLOCK,
.state = VDO_MAPPING_STATE_UNMAPPED,
};
}
return (validate_recovery_journal_entry(vdo, entry) == VDO_SUCCESS);
}
static void append_sector_entries(struct repair_completion *repair, char *entries,
enum vdo_metadata_type format,
journal_entry_count_t entry_count)
{
journal_entry_count_t i;
struct vdo *vdo = repair->completion.vdo;
off_t increment = ((format == VDO_METADATA_RECOVERY_JOURNAL_2)
? sizeof(struct packed_recovery_journal_entry)
: sizeof(struct packed_recovery_journal_entry_1));
for (i = 0; i < entry_count; i++, entries += increment) {
struct recovery_journal_entry entry;
if (!unpack_entry(vdo, entries, format, &entry))
continue;
repair->entries[repair->block_map_entry_count] =
(struct numbered_block_mapping) {
.block_map_slot = entry.slot,
.block_map_entry = vdo_pack_block_map_entry(entry.mapping.pbn,
entry.mapping.state),
.number = repair->block_map_entry_count,
};
repair->block_map_entry_count++;
}
}
static journal_entry_count_t entries_per_sector(enum vdo_metadata_type format,
u8 sector_number)
{
if (format == VDO_METADATA_RECOVERY_JOURNAL_2)
return RECOVERY_JOURNAL_ENTRIES_PER_SECTOR;
return ((sector_number == (VDO_SECTORS_PER_BLOCK - 1))
? RECOVERY_JOURNAL_1_ENTRIES_IN_LAST_SECTOR
: RECOVERY_JOURNAL_1_ENTRIES_PER_SECTOR);
}
static void extract_entries_from_block(struct repair_completion *repair,
struct recovery_journal *journal,
sequence_number_t sequence,
enum vdo_metadata_type format,
journal_entry_count_t entries)
{
sector_count_t i;
struct recovery_block_header header =
get_recovery_journal_block_header(journal, repair->journal_data,
sequence);
if (!is_exact_recovery_journal_block(journal, &header, sequence) ||
(header.metadata_type != format)) {
return;
}
entries = min(entries, header.entry_count);
for (i = 1; i < VDO_SECTORS_PER_BLOCK; i++) {
struct packed_journal_sector *sector =
get_sector(journal, repair->journal_data, sequence, i);
journal_entry_count_t sector_entries =
min(entries, entries_per_sector(format, i));
if (vdo_is_valid_recovery_journal_sector(&header, sector, i)) {
append_sector_entries(repair, (char *) sector->entries, format,
min_t(journal_entry_count_t,
sector->entry_count,
sector_entries));
}
entries -= sector_entries;
}
}
static int parse_journal_for_rebuild(struct repair_completion *repair)
{
int result;
sequence_number_t i;
block_count_t count;
enum vdo_metadata_type format;
struct vdo *vdo = repair->completion.vdo;
struct recovery_journal *journal = vdo->recovery_journal;
journal_entry_count_t entries_per_block = journal->entries_per_block;
format = get_recovery_journal_block_header(journal, repair->journal_data,
repair->highest_tail).metadata_type;
if (format == VDO_METADATA_RECOVERY_JOURNAL)
entries_per_block = RECOVERY_JOURNAL_1_ENTRIES_PER_BLOCK;
count = ((repair->highest_tail - repair->block_map_head + 1) * entries_per_block);
result = vdo_allocate(count, __func__, &repair->entries);
if (result != VDO_SUCCESS)
return result;
for (i = repair->block_map_head; i <= repair->highest_tail; i++)
extract_entries_from_block(repair, journal, i, format, entries_per_block);
return VDO_SUCCESS;
}
static int validate_heads(struct repair_completion *repair)
{
if ((repair->block_map_head <= repair->tail) &&
(repair->slab_journal_head <= repair->tail))
return VDO_SUCCESS;
return vdo_log_error_strerror(VDO_CORRUPT_JOURNAL,
"Journal tail too early. block map head: %llu, slab journal head: %llu, tail: %llu",
(unsigned long long) repair->block_map_head,
(unsigned long long) repair->slab_journal_head,
(unsigned long long) repair->tail);
}
static int extract_new_mappings(struct repair_completion *repair)
{
int result;
struct vdo *vdo = repair->completion.vdo;
struct recovery_point recovery_point = {
.sequence_number = repair->block_map_head,
.sector_count = 1,
.entry_count = 0,
};
result = vdo_allocate(repair->entry_count, __func__, &repair->entries);
if (result != VDO_SUCCESS)
return result;
for (; before_recovery_point(&recovery_point, &repair->tail_recovery_point);
increment_recovery_point(&recovery_point)) {
struct recovery_journal_entry entry = get_entry(repair, &recovery_point);
result = validate_recovery_journal_entry(vdo, &entry);
if (result != VDO_SUCCESS) {
vdo_enter_read_only_mode(vdo, result);
return result;
}
repair->entries[repair->block_map_entry_count] =
(struct numbered_block_mapping) {
.block_map_slot = entry.slot,
.block_map_entry = vdo_pack_block_map_entry(entry.mapping.pbn,
entry.mapping.state),
.number = repair->block_map_entry_count,
};
repair->block_map_entry_count++;
}
result = VDO_ASSERT((repair->block_map_entry_count <= repair->entry_count),
"approximate entry count is an upper bound");
if (result != VDO_SUCCESS)
vdo_enter_read_only_mode(vdo, result);
return result;
}
static noinline int compute_usages(struct repair_completion *repair)
{
struct recovery_point recovery_point = {
.sequence_number = repair->tail,
.sector_count = 1,
.entry_count = 0,
};
struct vdo *vdo = repair->completion.vdo;
struct recovery_journal *journal = vdo->recovery_journal;
struct recovery_block_header header =
get_recovery_journal_block_header(journal, repair->journal_data,
repair->tail);
repair->logical_blocks_used = header.logical_blocks_used;
repair->block_map_data_blocks = header.block_map_data_blocks;
for (; before_recovery_point(&recovery_point, &repair->tail_recovery_point);
increment_recovery_point(&recovery_point)) {
struct recovery_journal_entry entry = get_entry(repair, &recovery_point);
int result;
result = validate_recovery_journal_entry(vdo, &entry);
if (result != VDO_SUCCESS) {
vdo_enter_read_only_mode(vdo, result);
return result;
}
if (entry.operation == VDO_JOURNAL_BLOCK_MAP_REMAPPING) {
repair->block_map_data_blocks++;
continue;
}
if (vdo_is_mapped_location(&entry.mapping))
repair->logical_blocks_used++;
if (vdo_is_mapped_location(&entry.unmapping))
repair->logical_blocks_used--;
}
return VDO_SUCCESS;
}
static int parse_journal_for_recovery(struct repair_completion *repair)
{
int result;
sequence_number_t i, head;
bool found_entries = false;
struct recovery_journal *journal = repair->completion.vdo->recovery_journal;
struct recovery_block_header header;
enum vdo_metadata_type expected_format;
head = min(repair->block_map_head, repair->slab_journal_head);
header = get_recovery_journal_block_header(journal, repair->journal_data, head);
expected_format = header.metadata_type;
for (i = head; i <= repair->highest_tail; i++) {
journal_entry_count_t block_entries;
u8 j;
repair->tail = i;
repair->tail_recovery_point = (struct recovery_point) {
.sequence_number = i,
.sector_count = 0,
.entry_count = 0,
};
header = get_recovery_journal_block_header(journal, repair->journal_data, i);
if (!is_exact_recovery_journal_block(journal, &header, i)) {
break;
} else if (header.metadata_type != expected_format) {
vdo_log_error_strerror(VDO_CORRUPT_JOURNAL,
"Recovery journal is in an invalid format, a read-only rebuild is required.");
vdo_enter_read_only_mode(repair->completion.vdo, VDO_CORRUPT_JOURNAL);
return VDO_CORRUPT_JOURNAL;
}
block_entries = header.entry_count;
for (j = 1; j < VDO_SECTORS_PER_BLOCK; j++) {
struct packed_journal_sector *sector =
get_sector(journal, repair->journal_data, i, j);
journal_entry_count_t sector_entries =
min_t(journal_entry_count_t, sector->entry_count,
block_entries);
if (!vdo_is_valid_recovery_journal_sector(&header, sector, j))
break;
if (sector_entries > 0) {
found_entries = true;
repair->tail_recovery_point.sector_count++;
repair->tail_recovery_point.entry_count = sector_entries;
block_entries -= sector_entries;
repair->entry_count += sector_entries;
}
if ((sector_entries < RECOVERY_JOURNAL_ENTRIES_PER_SECTOR) ||
(block_entries == 0))
break;
}
if ((header.entry_count != journal->entries_per_block) || (block_entries > 0))
break;
}
if (!found_entries) {
return validate_heads(repair);
} else if (expected_format == VDO_METADATA_RECOVERY_JOURNAL) {
vdo_log_error_strerror(VDO_UNSUPPORTED_VERSION,
"Recovery journal is in the old format. Downgrade and complete recovery, then upgrade with a clean volume");
return VDO_UNSUPPORTED_VERSION;
}
if (repair->tail_recovery_point.sector_count == 0)
repair->tail--;
result = validate_heads(repair);
if (result != VDO_SUCCESS)
return result;
vdo_log_info("Highest-numbered recovery journal block has sequence number %llu, and the highest-numbered usable block is %llu",
(unsigned long long) repair->highest_tail,
(unsigned long long) repair->tail);
result = extract_new_mappings(repair);
if (result != VDO_SUCCESS)
return result;
return compute_usages(repair);
}
static int parse_journal(struct repair_completion *repair)
{
if (!find_recovery_journal_head_and_tail(repair))
return VDO_SUCCESS;
return (vdo_state_requires_read_only_rebuild(repair->completion.vdo->load_state) ?
parse_journal_for_rebuild(repair) :
parse_journal_for_recovery(repair));
}
static void finish_journal_load(struct vdo_completion *completion)
{
struct repair_completion *repair = completion->parent;
if (++repair->vios_complete != repair->vio_count)
return;
vdo_log_info("Finished reading recovery journal");
uninitialize_vios(repair);
prepare_repair_completion(repair, recover_block_map, VDO_ZONE_TYPE_LOGICAL);
vdo_continue_completion(&repair->completion, parse_journal(repair));
}
static void handle_journal_load_error(struct vdo_completion *completion)
{
struct repair_completion *repair = completion->parent;
vdo_set_completion_result(&repair->completion, completion->result);
vio_record_metadata_io_error(as_vio(completion));
completion->callback(completion);
}
static void read_journal_endio(struct bio *bio)
{
struct vio *vio = bio->bi_private;
struct vdo *vdo = vio->completion.vdo;
continue_vio_after_io(vio, finish_journal_load, vdo->thread_config.admin_thread);
}
void vdo_repair(struct vdo_completion *parent)
{
int result;
char *ptr;
struct repair_completion *repair;
struct vdo *vdo = parent->vdo;
struct recovery_journal *journal = vdo->recovery_journal;
physical_block_number_t pbn = journal->origin;
block_count_t remaining = journal->size;
block_count_t vio_count = DIV_ROUND_UP(remaining, MAX_BLOCKS_PER_VIO);
page_count_t page_count = min_t(page_count_t,
vdo->device_config->cache_size >> 1,
MAXIMUM_SIMULTANEOUS_VDO_BLOCK_MAP_RESTORATION_READS);
vdo_assert_on_admin_thread(vdo, __func__);
if (vdo->load_state == VDO_FORCE_REBUILD) {
vdo_log_warning("Rebuilding reference counts to clear read-only mode");
vdo->states.vdo.read_only_recoveries++;
} else if (vdo->load_state == VDO_REBUILD_FOR_UPGRADE) {
vdo_log_warning("Rebuilding reference counts for upgrade");
} else {
vdo_log_warning("Device was dirty, rebuilding reference counts");
}
result = vdo_allocate_extended(page_count, page_completions, __func__, &repair);
if (result != VDO_SUCCESS) {
vdo_fail_completion(parent, result);
return;
}
vdo_initialize_completion(&repair->completion, vdo, VDO_REPAIR_COMPLETION);
repair->completion.error_handler = abort_repair;
repair->completion.parent = parent;
prepare_repair_completion(repair, finish_repair, VDO_ZONE_TYPE_ADMIN);
repair->page_count = page_count;
result = vdo_allocate(remaining * VDO_BLOCK_SIZE, __func__, &repair->journal_data);
if (abort_on_error(result, repair))
return;
result = vdo_allocate(vio_count, __func__, &repair->vios);
if (abort_on_error(result, repair))
return;
ptr = repair->journal_data;
for (repair->vio_count = 0; repair->vio_count < vio_count; repair->vio_count++) {
block_count_t blocks = min_t(block_count_t, remaining,
MAX_BLOCKS_PER_VIO);
result = allocate_vio_components(vdo, VIO_TYPE_RECOVERY_JOURNAL,
VIO_PRIORITY_METADATA,
repair, blocks, ptr,
&repair->vios[repair->vio_count]);
if (abort_on_error(result, repair))
return;
ptr += (blocks * VDO_BLOCK_SIZE);
remaining -= blocks;
}
for (vio_count = 0; vio_count < repair->vio_count;
vio_count++, pbn += MAX_BLOCKS_PER_VIO) {
vdo_submit_metadata_vio(&repair->vios[vio_count], pbn, read_journal_endio,
handle_journal_load_error, REQ_OP_READ);
}
}