This patch implements logic of search/recovery of last actual superblock. Signed-off-by: Viacheslav Dubeyko <slava@xxxxxxxxxxx> CC: Viacheslav Dubeyko <viacheslav.dubeyko@xxxxxxxxxxxxx> CC: Luka Perkov <luka.perkov@xxxxxxxxxx> CC: Bruno Banelli <bruno.banelli@xxxxxxxxxx> --- fs/ssdfs/recovery.c | 3144 +++++++++++++++++++++++++++++++++++++++++++ fs/ssdfs/recovery.h | 446 ++++++ 2 files changed, 3590 insertions(+) create mode 100644 fs/ssdfs/recovery.c create mode 100644 fs/ssdfs/recovery.h diff --git a/fs/ssdfs/recovery.c b/fs/ssdfs/recovery.c new file mode 100644 index 000000000000..dcb56ac0d682 --- /dev/null +++ b/fs/ssdfs/recovery.c @@ -0,0 +1,3144 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * SSDFS -- SSD-oriented File System. + * + * fs/ssdfs/recovery.c - searching actual state and recovery on mount code. + * + * Copyright (c) 2014-2019 HGST, a Western Digital Company. + * http://www.hgst.com/ + * Copyright (c) 2014-2023 Viacheslav Dubeyko <slava@xxxxxxxxxxx> + * http://www.ssdfs.org/ + * + * (C) Copyright 2014-2019, HGST, Inc., All rights reserved. + * + * Created by HGST, San Jose Research Center, Storage Architecture Group + * + * Authors: Viacheslav Dubeyko <slava@xxxxxxxxxxx> + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#include <linux/slab.h> +#include <linux/pagevec.h> +#include <linux/blkdev.h> + +#include "peb_mapping_queue.h" +#include "peb_mapping_table_cache.h" +#include "ssdfs.h" +#include "page_array.h" +#include "page_vector.h" +#include "peb.h" +#include "offset_translation_table.h" +#include "segment_bitmap.h" +#include "peb_mapping_table.h" +#include "recovery.h" + +#include <trace/events/ssdfs.h> + +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING +atomic64_t ssdfs_recovery_page_leaks; +atomic64_t ssdfs_recovery_memory_leaks; +atomic64_t ssdfs_recovery_cache_leaks; +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ + +/* + * void ssdfs_recovery_cache_leaks_increment(void *kaddr) + * void ssdfs_recovery_cache_leaks_decrement(void *kaddr) + * void *ssdfs_recovery_kmalloc(size_t size, gfp_t flags) + * void *ssdfs_recovery_kzalloc(size_t size, gfp_t flags) + * void *ssdfs_recovery_kcalloc(size_t n, size_t size, gfp_t flags) + * void ssdfs_recovery_kfree(void *kaddr) + * struct page *ssdfs_recovery_alloc_page(gfp_t gfp_mask) + * struct page *ssdfs_recovery_add_pagevec_page(struct pagevec *pvec) + * void ssdfs_recovery_free_page(struct page *page) + * void ssdfs_recovery_pagevec_release(struct pagevec *pvec) + */ +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING + SSDFS_MEMORY_LEAKS_CHECKER_FNS(recovery) +#else + SSDFS_MEMORY_ALLOCATOR_FNS(recovery) +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ + +void ssdfs_recovery_memory_leaks_init(void) +{ +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING + atomic64_set(&ssdfs_recovery_page_leaks, 0); + atomic64_set(&ssdfs_recovery_memory_leaks, 0); + atomic64_set(&ssdfs_recovery_cache_leaks, 0); +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ +} + +void ssdfs_recovery_check_memory_leaks(void) +{ +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING + if (atomic64_read(&ssdfs_recovery_page_leaks) != 0) { + SSDFS_ERR("RECOVERY: " + "memory leaks include %lld pages\n", + atomic64_read(&ssdfs_recovery_page_leaks)); + } + + if (atomic64_read(&ssdfs_recovery_memory_leaks) != 0) { + SSDFS_ERR("RECOVERY: " + "memory allocator suffers from %lld leaks\n", + atomic64_read(&ssdfs_recovery_memory_leaks)); + } + + if (atomic64_read(&ssdfs_recovery_cache_leaks) != 0) { + SSDFS_ERR("RECOVERY: " + "caches suffers from %lld leaks\n", + atomic64_read(&ssdfs_recovery_cache_leaks)); + } +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ +} + +int ssdfs_init_sb_info(struct ssdfs_fs_info *fsi, + struct ssdfs_sb_info *sbi) +{ + void *vh_buf = NULL; + void *vs_buf = NULL; + size_t hdr_size = sizeof(struct ssdfs_segment_header); + size_t footer_size = sizeof(struct ssdfs_log_footer); + int err; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("sbi %p, hdr_size %zu, footer_size %zu\n", + sbi, hdr_size, footer_size); + + BUG_ON(!sbi); +#endif /* CONFIG_SSDFS_DEBUG */ + + sbi->vh_buf = NULL; + sbi->vs_buf = NULL; + + hdr_size = max_t(size_t, hdr_size, (size_t)SSDFS_4KB); + sbi->vh_buf_size = hdr_size; + footer_size = max_t(size_t, footer_size, (size_t)SSDFS_4KB); + sbi->vs_buf_size = footer_size; + + vh_buf = ssdfs_recovery_kzalloc(sbi->vh_buf_size, GFP_KERNEL); + vs_buf = ssdfs_recovery_kzalloc(sbi->vs_buf_size, GFP_KERNEL); + if (unlikely(!vh_buf || !vs_buf)) { + SSDFS_ERR("unable to allocate superblock buffers\n"); + err = -ENOMEM; + goto free_buf; + } + + sbi->vh_buf = vh_buf; + sbi->vs_buf = vs_buf; + + return 0; + +free_buf: + ssdfs_recovery_kfree(vh_buf); + ssdfs_recovery_kfree(vs_buf); + return err; +} + +void ssdfs_destruct_sb_info(struct ssdfs_sb_info *sbi) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!sbi); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!sbi->vh_buf || !sbi->vs_buf) + return; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("sbi %p, sbi->vh_buf %p, sbi->vs_buf %p, " + "sbi->last_log.leb_id %llu, sbi->last_log.peb_id %llu, " + "sbi->last_log.page_offset %u, " + "sbi->last_log.pages_count %u\n", + sbi, sbi->vh_buf, sbi->vs_buf, sbi->last_log.leb_id, + sbi->last_log.peb_id, sbi->last_log.page_offset, + sbi->last_log.pages_count); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_recovery_kfree(sbi->vh_buf); + ssdfs_recovery_kfree(sbi->vs_buf); + sbi->vh_buf = NULL; + sbi->vh_buf_size = 0; + sbi->vs_buf = NULL; + sbi->vs_buf_size = 0; + memset(&sbi->last_log, 0, sizeof(struct ssdfs_peb_extent)); +} + +void ssdfs_backup_sb_info(struct ssdfs_fs_info *fsi) +{ + size_t hdr_size = sizeof(struct ssdfs_segment_header); + size_t footer_size = sizeof(struct ssdfs_log_footer); + size_t extent_size = sizeof(struct ssdfs_peb_extent); + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf || !fsi->sbi.vs_buf); + BUG_ON(!fsi->sbi_backup.vh_buf || !fsi->sbi_backup.vs_buf); + + SSDFS_DBG("last_log: leb_id %llu, peb_id %llu, " + "page_offset %u, pages_count %u, " + "volume state: free_pages %llu, timestamp %#llx, " + "cno %#llx, fs_state %#x\n", + fsi->sbi.last_log.leb_id, + fsi->sbi.last_log.peb_id, + fsi->sbi.last_log.page_offset, + fsi->sbi.last_log.pages_count, + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->free_pages), + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->timestamp), + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->cno), + le16_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->state)); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_memcpy(fsi->sbi_backup.vh_buf, 0, hdr_size, + fsi->sbi.vh_buf, 0, hdr_size, + hdr_size); + ssdfs_memcpy(fsi->sbi_backup.vs_buf, 0, footer_size, + fsi->sbi.vs_buf, 0, footer_size, + footer_size); + ssdfs_memcpy(&fsi->sbi_backup.last_log, 0, extent_size, + &fsi->sbi.last_log, 0, extent_size, + extent_size); +} + +void ssdfs_copy_sb_info(struct ssdfs_fs_info *fsi, + struct ssdfs_recovery_env *env) +{ + size_t hdr_size = sizeof(struct ssdfs_segment_header); + size_t vhdr_size = sizeof(struct ssdfs_volume_header); + size_t footer_size = sizeof(struct ssdfs_log_footer); + size_t extent_size = sizeof(struct ssdfs_peb_extent); + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf || !fsi->sbi.vs_buf); + BUG_ON(!fsi->sbi_backup.vh_buf || !fsi->sbi_backup.vs_buf); + BUG_ON(!env); + BUG_ON(!env->sbi.vh_buf || !env->sbi.vs_buf); + BUG_ON(!env->sbi_backup.vh_buf || !env->sbi_backup.vs_buf); + + SSDFS_DBG("last_log: leb_id %llu, peb_id %llu, " + "page_offset %u, pages_count %u, " + "volume state: free_pages %llu, timestamp %#llx, " + "cno %#llx, fs_state %#x\n", + env->sbi.last_log.leb_id, + env->sbi.last_log.peb_id, + env->sbi.last_log.page_offset, + env->sbi.last_log.pages_count, + le64_to_cpu(SSDFS_VS(env->sbi.vs_buf)->free_pages), + le64_to_cpu(SSDFS_VS(env->sbi.vs_buf)->timestamp), + le64_to_cpu(SSDFS_VS(env->sbi.vs_buf)->cno), + le16_to_cpu(SSDFS_VS(env->sbi.vs_buf)->state)); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_memcpy(fsi->sbi.vh_buf, 0, hdr_size, + env->sbi.vh_buf, 0, hdr_size, + hdr_size); + ssdfs_memcpy(fsi->sbi.vs_buf, 0, footer_size, + env->sbi.vs_buf, 0, footer_size, + footer_size); + ssdfs_memcpy(&fsi->sbi.last_log, 0, extent_size, + &env->sbi.last_log, 0, extent_size, + extent_size); + ssdfs_memcpy(fsi->sbi_backup.vh_buf, 0, hdr_size, + env->sbi_backup.vh_buf, 0, hdr_size, + hdr_size); + ssdfs_memcpy(fsi->sbi_backup.vs_buf, 0, footer_size, + env->sbi_backup.vs_buf, 0, footer_size, + footer_size); + ssdfs_memcpy(&fsi->sbi_backup.last_log, 0, extent_size, + &env->sbi_backup.last_log, 0, extent_size, + extent_size); + ssdfs_memcpy(&fsi->last_vh, 0, vhdr_size, + &env->last_vh, 0, vhdr_size, + vhdr_size); +} + +void ssdfs_restore_sb_info(struct ssdfs_fs_info *fsi) +{ + size_t hdr_size = sizeof(struct ssdfs_segment_header); + size_t footer_size = sizeof(struct ssdfs_log_footer); + size_t extent_size = sizeof(struct ssdfs_peb_extent); + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf || !fsi->sbi.vs_buf); + BUG_ON(!fsi->sbi_backup.vh_buf || !fsi->sbi_backup.vs_buf); + + SSDFS_DBG("last_log: leb_id %llu, peb_id %llu, " + "page_offset %u, pages_count %u, " + "volume state: free_pages %llu, timestamp %#llx, " + "cno %#llx, fs_state %#x\n", + fsi->sbi.last_log.leb_id, + fsi->sbi.last_log.peb_id, + fsi->sbi.last_log.page_offset, + fsi->sbi.last_log.pages_count, + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->free_pages), + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->timestamp), + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->cno), + le16_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->state)); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_memcpy(fsi->sbi.vh_buf, 0, hdr_size, + fsi->sbi_backup.vh_buf, 0, hdr_size, + hdr_size); + ssdfs_memcpy(fsi->sbi.vs_buf, 0, footer_size, + fsi->sbi_backup.vs_buf, 0, footer_size, + footer_size); + ssdfs_memcpy(&fsi->sbi.last_log, 0, extent_size, + &fsi->sbi_backup.last_log, 0, extent_size, + extent_size); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("last_log: leb_id %llu, peb_id %llu, " + "page_offset %u, pages_count %u, " + "volume state: free_pages %llu, timestamp %#llx, " + "cno %#llx, fs_state %#x\n", + fsi->sbi.last_log.leb_id, + fsi->sbi.last_log.peb_id, + fsi->sbi.last_log.page_offset, + fsi->sbi.last_log.pages_count, + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->free_pages), + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->timestamp), + le64_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->cno), + le16_to_cpu(SSDFS_VS(fsi->sbi.vs_buf)->state)); +#endif /* CONFIG_SSDFS_DEBUG */ +} + +static int find_seg_with_valid_start_peb(struct ssdfs_fs_info *fsi, + size_t seg_size, + loff_t *offset, + u64 threshold, + int silent, + int op_type) +{ + struct super_block *sb = fsi->sb; + loff_t off; + size_t hdr_size = sizeof(struct ssdfs_segment_header); + struct ssdfs_volume_header *vh; + bool magic_valid = false; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fsi %p, seg_size %zu, start_offset %llu, " + "threshold %llu, silent %#x, op_type %#x\n", + fsi, seg_size, (unsigned long long)*offset, + threshold, silent, op_type); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (op_type) { + case SSDFS_USE_PEB_ISBAD_OP: + if (!fsi->devops->peb_isbad) { + SSDFS_ERR("unable to detect bad PEB\n"); + return -EOPNOTSUPP; + } + break; + + case SSDFS_USE_READ_OP: + if (!fsi->devops->read) { + SSDFS_ERR("unable to read from device\n"); + return -EOPNOTSUPP; + } + break; + + default: + BUG(); + }; + + if (*offset != SSDFS_RESERVED_VBR_SIZE) + off = (*offset / seg_size) * seg_size; + else + off = *offset; + + while (off < threshold) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("off %llu\n", (u64)off); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (op_type) { + case SSDFS_USE_PEB_ISBAD_OP: + err = fsi->devops->peb_isbad(sb, off); + magic_valid = true; + break; + + case SSDFS_USE_READ_OP: + err = fsi->devops->read(sb, off, hdr_size, + fsi->sbi.vh_buf); + vh = SSDFS_VH(fsi->sbi.vh_buf); + magic_valid = is_ssdfs_magic_valid(&vh->magic); + break; + + default: + BUG(); + }; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("HEADER DUMP: magic_valid %#x, err %d\n", + magic_valid, err); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + fsi->sbi.vh_buf, hdr_size); + SSDFS_DBG("\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!err) { + if (magic_valid) { + *offset = off; + return 0; + } + } else if (!silent) { + SSDFS_NOTICE("offset %llu is in bad PEB\n", + (unsigned long long)off); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("offset %llu is in bad PEB\n", + (unsigned long long)off); +#endif /* CONFIG_SSDFS_DEBUG */ + } + + off += 2 * seg_size; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find valid PEB\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return -ENODATA; +} + +static int ssdfs_find_any_valid_volume_header(struct ssdfs_fs_info *fsi, + loff_t offset, + int silent) +{ + struct super_block *sb; + size_t seg_size = SSDFS_128KB; + loff_t start_offset = offset; + size_t hdr_size = sizeof(struct ssdfs_segment_header); + u64 dev_size; + u64 threshold; + struct ssdfs_volume_header *vh; + bool magic_valid, crc_valid; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); + BUG_ON(!fsi->devops->read); + + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p, silent %#x\n", + fsi, fsi->sbi.vh_buf, silent); +#endif /* CONFIG_SSDFS_DEBUG */ + + sb = fsi->sb; + dev_size = fsi->devops->device_size(sb); + +try_seg_size: + threshold = SSDFS_MAPTBL_PROTECTION_STEP; + threshold *= SSDFS_MAPTBL_PROTECTION_RANGE; + threshold *= seg_size; + threshold = min_t(u64, dev_size, threshold + offset); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("offset %llu, dev_size %llu, threshold %llu\n", + offset, dev_size, threshold); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (fsi->devops->peb_isbad) { + err = fsi->devops->peb_isbad(sb, offset); + if (err) { + if (!silent) { + SSDFS_NOTICE("offset %llu is in bad PEB\n", + (unsigned long long)offset); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("offset %llu is in bad PEB\n", + (unsigned long long)offset); +#endif /* CONFIG_SSDFS_DEBUG */ + } + + offset += seg_size; + err = find_seg_with_valid_start_peb(fsi, seg_size, + &offset, threshold, + silent, + SSDFS_USE_PEB_ISBAD_OP); + if (err) { + switch (seg_size) { + case SSDFS_128KB: + offset = start_offset; + seg_size = SSDFS_256KB; + goto try_seg_size; + + case SSDFS_256KB: + offset = start_offset; + seg_size = SSDFS_512KB; + goto try_seg_size; + + case SSDFS_512KB: + offset = start_offset; + seg_size = SSDFS_2MB; + goto try_seg_size; + + case SSDFS_2MB: + offset = start_offset; + seg_size = SSDFS_8MB; + goto try_seg_size; + + default: + /* finish search */ + break; + } + + SSDFS_NOTICE("unable to find valid start PEB: " + "err %d\n", err); + return err; + } + } + } + + err = find_seg_with_valid_start_peb(fsi, seg_size, &offset, + threshold, silent, + SSDFS_USE_READ_OP); + if (unlikely(err)) { + switch (seg_size) { + case SSDFS_128KB: + offset = start_offset; + seg_size = SSDFS_256KB; + goto try_seg_size; + + case SSDFS_256KB: + offset = start_offset; + seg_size = SSDFS_512KB; + goto try_seg_size; + + case SSDFS_512KB: + offset = start_offset; + seg_size = SSDFS_2MB; + goto try_seg_size; + + case SSDFS_2MB: + offset = start_offset; + seg_size = SSDFS_8MB; + goto try_seg_size; + + default: + /* finish search */ + break; + } + + SSDFS_NOTICE("unable to find valid start PEB\n"); + return err; + } + + vh = SSDFS_VH(fsi->sbi.vh_buf); + + seg_size = 1 << vh->log_segsize; + + magic_valid = is_ssdfs_magic_valid(&vh->magic); + crc_valid = is_ssdfs_volume_header_csum_valid(fsi->sbi.vh_buf, + hdr_size); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("magic_valid %#x, crc_valid %#x\n", + magic_valid, crc_valid); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!magic_valid && !crc_valid) { + if (!silent) + SSDFS_NOTICE("valid magic is not detected\n"); + else + SSDFS_DBG("valid magic is not detected\n"); + return -ENOENT; + } else if ((magic_valid && !crc_valid) || (!magic_valid && crc_valid)) { + loff_t start_off; + +try_again: + start_off = offset; + if (offset >= (threshold - seg_size)) { + if (!silent) + SSDFS_NOTICE("valid magic is not detected\n"); + else + SSDFS_DBG("valid magic is not detected\n"); + return -ENOENT; + } + + if (fsi->devops->peb_isbad) { + err = find_seg_with_valid_start_peb(fsi, seg_size, + &offset, threshold, + silent, + SSDFS_USE_PEB_ISBAD_OP); + if (err) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find valid start PEB: " + "err %d\n", err); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; + } + } + + if (start_off == offset) + offset += seg_size; + + err = find_seg_with_valid_start_peb(fsi, seg_size, &offset, + threshold, silent, + SSDFS_USE_READ_OP); + if (unlikely(err)) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find valid start PEB: " + "err %d\n", err); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; + } + + magic_valid = is_ssdfs_magic_valid(&vh->magic); + crc_valid = is_ssdfs_volume_header_csum_valid(fsi->sbi.vh_buf, + hdr_size); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("magic_valid %#x, crc_valid %#x\n", + magic_valid, crc_valid); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!(magic_valid && crc_valid)) { + if (!silent) + SSDFS_NOTICE("valid magic is not detected\n"); + else + SSDFS_DBG("valid magic is not detected\n"); + return -ENOENT; + } + } + + if (!is_ssdfs_volume_header_consistent(fsi, vh, dev_size)) + goto try_again; + + fsi->pagesize = 1 << vh->log_pagesize; + + if (fsi->is_zns_device) { + fsi->erasesize = fsi->zone_size; + fsi->segsize = fsi->erasesize * le16_to_cpu(vh->pebs_per_seg); + } else { + fsi->erasesize = 1 << vh->log_erasesize; + fsi->segsize = 1 << vh->log_segsize; + } + + fsi->pages_per_seg = fsi->segsize / fsi->pagesize; + fsi->pages_per_peb = fsi->erasesize / fsi->pagesize; + fsi->pebs_per_seg = 1 << vh->log_pebs_per_seg; + + return 0; +} + +static int ssdfs_read_checked_sb_info(struct ssdfs_fs_info *fsi, u64 peb_id, + u32 pages_off, bool silent) +{ + u32 lf_off; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + + SSDFS_DBG("fsi %p, peb_id %llu, pages_off %u, silent %#x\n", + fsi, peb_id, pages_off, silent); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_read_checked_segment_header(fsi, peb_id, pages_off, + fsi->sbi.vh_buf, silent); + if (err) { + if (!silent) { + SSDFS_ERR("volume header is corrupted: " + "peb_id %llu, offset %d, err %d\n", + peb_id, pages_off, err); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("volume header is corrupted: " + "peb_id %llu, offset %d, err %d\n", + peb_id, pages_off, err); +#endif /* CONFIG_SSDFS_DEBUG */ + } + return err; + } + + lf_off = SSDFS_LOG_FOOTER_OFF(fsi->sbi.vh_buf); + + err = ssdfs_read_checked_log_footer(fsi, SSDFS_SEG_HDR(fsi->sbi.vh_buf), + peb_id, lf_off, fsi->sbi.vs_buf, + silent); + if (err) { + if (!silent) { + SSDFS_ERR("log footer is corrupted: " + "peb_id %llu, offset %d, err %d\n", + peb_id, lf_off, err); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("log footer is corrupted: " + "peb_id %llu, offset %d, err %d\n", + peb_id, lf_off, err); +#endif /* CONFIG_SSDFS_DEBUG */ + } + return err; + } + + return 0; +} + +static int ssdfs_read_checked_sb_info2(struct ssdfs_fs_info *fsi, u64 peb_id, + u32 pages_off, bool silent, + u32 *cur_off) +{ + u32 bytes_off; + u32 log_pages; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + + SSDFS_DBG("fsi %p, peb_id %llu, pages_off %u, silent %#x\n", + fsi, peb_id, pages_off, silent); +#endif /* CONFIG_SSDFS_DEBUG */ + + bytes_off = pages_off * fsi->pagesize; + + err = ssdfs_read_unchecked_log_footer(fsi, peb_id, bytes_off, + fsi->sbi.vs_buf, silent, + &log_pages); + if (err) { + if (!silent) { + SSDFS_ERR("fail to read the log footer: " + "peb_id %llu, offset %u, err %d\n", + peb_id, bytes_off, err); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fail to read the log footer: " + "peb_id %llu, offset %u, err %d\n", + peb_id, bytes_off, err); +#endif /* CONFIG_SSDFS_DEBUG */ + } + return err; + } + + if (log_pages == 0 || + log_pages > fsi->pages_per_peb || + pages_off < log_pages) { + if (!silent) { + SSDFS_ERR("invalid log_pages %u\n", log_pages); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("invalid log_pages %u\n", log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + } + return -ERANGE; + } + + pages_off -= log_pages - 1; + *cur_off -= log_pages - 1; + + err = ssdfs_read_checked_segment_header(fsi, peb_id, pages_off, + fsi->sbi.vh_buf, silent); + if (err) { + if (!silent) { + SSDFS_ERR("volume header is corrupted: " + "peb_id %llu, offset %d, err %d\n", + peb_id, pages_off, err); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("volume header is corrupted: " + "peb_id %llu, offset %d, err %d\n", + peb_id, pages_off, err); +#endif /* CONFIG_SSDFS_DEBUG */ + } + return err; + } + + err = ssdfs_check_log_footer(fsi, + SSDFS_SEG_HDR(fsi->sbi.vh_buf), + SSDFS_LF(fsi->sbi.vs_buf), + silent); + if (err) { + if (!silent) { + SSDFS_ERR("log footer is corrupted: " + "peb_id %llu, bytes_off %u, err %d\n", + peb_id, bytes_off, err); + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("log footer is corrupted: " + "peb_id %llu, bytes_off %u, err %d\n", + peb_id, bytes_off, err); +#endif /* CONFIG_SSDFS_DEBUG */ + } + return err; + } + + return 0; +} + +static int ssdfs_find_any_valid_sb_segment(struct ssdfs_fs_info *fsi, + u64 start_peb_id) +{ +#ifdef CONFIG_SSDFS_DEBUG + size_t hdr_size = sizeof(struct ssdfs_segment_header); +#endif /* CONFIG_SSDFS_DEBUG */ + size_t vh_size = sizeof(struct ssdfs_volume_header); + struct ssdfs_volume_header *vh; + struct ssdfs_segment_header *seg_hdr; + u64 dev_size; + loff_t offset = start_peb_id * fsi->erasesize; + loff_t step = SSDFS_RESERVED_SB_SEGS * SSDFS_128KB; + u64 last_cno, cno; + __le64 peb1, peb2; + __le64 leb1, leb2; + u64 checked_pebs[SSDFS_SB_CHAIN_MAX][SSDFS_SB_SEG_COPY_MAX]; + int i, j; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); + BUG_ON(!fsi->devops->read); + BUG_ON(!is_ssdfs_magic_valid(&SSDFS_VH(fsi->sbi.vh_buf)->magic)); + BUG_ON(!is_ssdfs_volume_header_csum_valid(fsi->sbi.vh_buf, hdr_size)); + + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p, start_peb_id %llu\n", + fsi, fsi->sbi.vh_buf, start_peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + i = SSDFS_SB_CHAIN_MAX; + dev_size = fsi->devops->device_size(fsi->sb); + memset(checked_pebs, 0xFF, + (SSDFS_SB_CHAIN_MAX * sizeof(u64)) + + (SSDFS_SB_SEG_COPY_MAX * sizeof(u64))); + +try_next_volume_portion: + ssdfs_memcpy(&fsi->last_vh, 0, vh_size, + fsi->sbi.vh_buf, 0, vh_size, + vh_size); + last_cno = le64_to_cpu(SSDFS_SEG_HDR(fsi->sbi.vh_buf)->cno); + +try_again: + switch (i) { + case SSDFS_SB_CHAIN_MAX: + i = SSDFS_CUR_SB_SEG; + break; + + case SSDFS_CUR_SB_SEG: + i = SSDFS_NEXT_SB_SEG; + break; + + case SSDFS_NEXT_SB_SEG: + i = SSDFS_RESERVED_SB_SEG; + break; + + default: + offset += step; + + if (offset >= dev_size) + goto fail_find_sb_seg; + + err = ssdfs_find_any_valid_volume_header(fsi, offset, true); + if (err) + goto fail_find_sb_seg; + else { + i = SSDFS_SB_CHAIN_MAX; + goto try_next_volume_portion; + } + break; + } + + err = -ENODATA; + + for (j = SSDFS_MAIN_SB_SEG; j < SSDFS_SB_SEG_COPY_MAX; j++) { + u64 leb_id = le64_to_cpu(fsi->last_vh.sb_pebs[i][j].leb_id); + u64 peb_id = le64_to_cpu(fsi->last_vh.sb_pebs[i][j].peb_id); + u16 seg_type; + + if (peb_id == U64_MAX || leb_id == U64_MAX) { + err = -ERANGE; + SSDFS_ERR("invalid peb_id %llu, leb_id %llu\n", + leb_id, peb_id); + goto fail_find_sb_seg; + } + + if (start_peb_id > peb_id) + continue; + + if (checked_pebs[i][j] == peb_id) + continue; + else + checked_pebs[i][j] = peb_id; + + if ((peb_id * fsi->erasesize) < dev_size) + offset = peb_id * fsi->erasesize; + + err = ssdfs_read_checked_sb_info(fsi, peb_id, + 0, true); + if (err) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("peb_id %llu is corrupted: err %d\n", + peb_id, err); +#endif /* CONFIG_SSDFS_DEBUG */ + continue; + } + + fsi->sbi.last_log.leb_id = leb_id; + fsi->sbi.last_log.peb_id = peb_id; + fsi->sbi.last_log.page_offset = 0; + fsi->sbi.last_log.pages_count = + SSDFS_LOG_PAGES(fsi->sbi.vh_buf); + + seg_hdr = SSDFS_SEG_HDR(fsi->sbi.vh_buf); + seg_type = SSDFS_SEG_TYPE(seg_hdr); + + if (seg_type == SSDFS_SB_SEG_TYPE) + return 0; + else { + err = -EIO; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("PEB %llu is not sb segment\n", + peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + } + + if (!err) + goto compare_vh_info; + } + + if (err) { + ssdfs_memcpy(fsi->sbi.vh_buf, 0, vh_size, + &fsi->last_vh, 0, vh_size, + vh_size); + goto try_again; + } + +compare_vh_info: + vh = SSDFS_VH(fsi->sbi.vh_buf); + seg_hdr = SSDFS_SEG_HDR(fsi->sbi.vh_buf); + leb1 = fsi->last_vh.sb_pebs[SSDFS_CUR_SB_SEG][SSDFS_MAIN_SB_SEG].leb_id; + leb2 = vh->sb_pebs[SSDFS_CUR_SB_SEG][SSDFS_MAIN_SB_SEG].leb_id; + peb1 = fsi->last_vh.sb_pebs[SSDFS_CUR_SB_SEG][SSDFS_MAIN_SB_SEG].peb_id; + peb2 = vh->sb_pebs[SSDFS_CUR_SB_SEG][SSDFS_MAIN_SB_SEG].peb_id; + cno = le64_to_cpu(seg_hdr->cno); + + if (cno > last_cno && (leb1 != leb2 || peb1 != peb2)) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("cno %llu, last_cno %llu, " + "leb1 %llu, leb2 %llu, " + "peb1 %llu, peb2 %llu\n", + cno, last_cno, + le64_to_cpu(leb1), le64_to_cpu(leb2), + le64_to_cpu(peb1), le64_to_cpu(peb2)); +#endif /* CONFIG_SSDFS_DEBUG */ + goto try_again; + } + +fail_find_sb_seg: + SSDFS_CRIT("unable to find any valid segment with superblocks chain\n"); + return -EIO; +} + +static inline bool is_sb_peb_exhausted2(struct ssdfs_fs_info *fsi, + u64 leb_id, u64 peb_id) +{ +#ifdef CONFIG_SSDFS_DEBUG + size_t hdr_size = sizeof(struct ssdfs_segment_header); +#endif /* CONFIG_SSDFS_DEBUG */ + struct ssdfs_peb_extent checking_page; + u64 pages_per_peb; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); + BUG_ON(!fsi->devops->read); + BUG_ON(!is_ssdfs_magic_valid(&SSDFS_VH(fsi->sbi.vh_buf)->magic)); + BUG_ON(!is_ssdfs_volume_header_csum_valid(fsi->sbi.vh_buf, hdr_size)); + + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p, " + "leb_id %llu, peb_id %llu\n", + fsi, fsi->sbi.vh_buf, + leb_id, peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!fsi->devops->can_write_page) { + SSDFS_CRIT("fail to find latest valid sb info: " + "can_write_page is not supported\n"); + return true; + } + + if (leb_id >= U64_MAX || peb_id >= U64_MAX) { + SSDFS_ERR("invalid leb_id %llu or peb_id %llu\n", + leb_id, peb_id); + return true; + } + + checking_page.leb_id = leb_id; + checking_page.peb_id = peb_id; + + if (fsi->is_zns_device) { + pages_per_peb = div64_u64(fsi->zone_capacity, fsi->pagesize); + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(pages_per_peb >= U32_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + checking_page.page_offset = (u32)pages_per_peb - 2; + } else { + checking_page.page_offset = fsi->pages_per_peb - 2; + } + + checking_page.pages_count = 1; + + err = ssdfs_can_write_sb_log(fsi->sb, &checking_page); + if (!err) + return false; + + return true; +} + +static inline bool is_cur_main_sb_peb_exhausted2(struct ssdfs_fs_info *fsi) +{ + u64 leb_id; + u64 peb_id; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); +#endif /* CONFIG_SSDFS_DEBUG */ + + leb_id = SSDFS_MAIN_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + peb_id = SSDFS_MAIN_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p, " + "leb_id %llu, peb_id %llu\n", + fsi, fsi->sbi.vh_buf, + leb_id, peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + return is_sb_peb_exhausted2(fsi, leb_id, peb_id); +} + +static inline bool is_cur_copy_sb_peb_exhausted2(struct ssdfs_fs_info *fsi) +{ + u64 leb_id; + u64 peb_id; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); +#endif /* CONFIG_SSDFS_DEBUG */ + + leb_id = SSDFS_COPY_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + peb_id = SSDFS_COPY_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p, " + "leb_id %llu, peb_id %llu\n", + fsi, fsi->sbi.vh_buf, + leb_id, peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + return is_sb_peb_exhausted2(fsi, leb_id, peb_id); +} + +static int ssdfs_find_latest_valid_sb_segment(struct ssdfs_fs_info *fsi) +{ +#ifdef CONFIG_SSDFS_DEBUG + size_t hdr_size = sizeof(struct ssdfs_segment_header); +#endif /* CONFIG_SSDFS_DEBUG */ + struct ssdfs_volume_header *last_vh; + u64 cur_main_sb_peb, cur_copy_sb_peb; + u64 cno1, cno2; + u64 cur_peb, next_peb, prev_peb; + u64 cur_leb, next_leb, prev_leb; + u16 seg_type; + loff_t offset; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); + BUG_ON(!fsi->devops->read); + BUG_ON(!is_ssdfs_magic_valid(&SSDFS_VH(fsi->sbi.vh_buf)->magic)); + BUG_ON(!is_ssdfs_volume_header_csum_valid(fsi->sbi.vh_buf, hdr_size)); + + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p\n", fsi, fsi->sbi.vh_buf); +#endif /* CONFIG_SSDFS_DEBUG */ + +try_next_peb: + last_vh = SSDFS_VH(fsi->sbi.vh_buf); + cur_main_sb_peb = SSDFS_MAIN_SB_PEB(last_vh, SSDFS_CUR_SB_SEG); + cur_copy_sb_peb = SSDFS_COPY_SB_PEB(last_vh, SSDFS_CUR_SB_SEG); + + if (cur_main_sb_peb != fsi->sbi.last_log.peb_id && + cur_copy_sb_peb != fsi->sbi.last_log.peb_id) { + SSDFS_ERR("volume header is corrupted\n"); +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("cur_main_sb_peb %llu, cur_copy_sb_peb %llu, " + "read PEB %llu\n", + cur_main_sb_peb, cur_copy_sb_peb, + fsi->sbi.last_log.peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + err = -EIO; + goto end_search; + } + + if (cur_main_sb_peb == fsi->sbi.last_log.peb_id) { + if (!is_cur_main_sb_peb_exhausted2(fsi)) + goto end_search; + } else { + if (!is_cur_copy_sb_peb_exhausted2(fsi)) + goto end_search; + } + + ssdfs_backup_sb_info(fsi); + + next_leb = SSDFS_MAIN_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_NEXT_SB_SEG); + next_peb = SSDFS_MAIN_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_NEXT_SB_SEG); + if (next_leb == U64_MAX || next_peb == U64_MAX) { + err = -ERANGE; + SSDFS_ERR("invalid next_leb %llu, next_peb %llu\n", + next_leb, next_peb); + goto end_search; + } + + err = ssdfs_read_checked_sb_info(fsi, next_peb, 0, true); + if (!err) { + fsi->sbi.last_log.leb_id = next_leb; + fsi->sbi.last_log.peb_id = next_peb; + fsi->sbi.last_log.page_offset = 0; + fsi->sbi.last_log.pages_count = + SSDFS_LOG_PAGES(fsi->sbi.vh_buf); + goto check_volume_header; + } else { + ssdfs_restore_sb_info(fsi); + err = 0; /* try to read the backup copy */ + } + + next_leb = SSDFS_COPY_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_NEXT_SB_SEG); + next_peb = SSDFS_COPY_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_NEXT_SB_SEG); + if (next_leb == U64_MAX || next_peb == U64_MAX) { + err = -ERANGE; + SSDFS_ERR("invalid next_leb %llu, next_peb %llu\n", + next_leb, next_peb); + goto end_search; + } + + err = ssdfs_read_checked_sb_info(fsi, next_peb, 0, true); + if (err) { + if (err == -EIO) { + /* next sb segments are corrupted */ +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("next sb PEB %llu is corrupted\n", + next_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + } else { + /* next sb segments are invalid */ +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("next sb PEB %llu is invalid\n", + next_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + } + + ssdfs_restore_sb_info(fsi); + + offset = next_peb * fsi->erasesize; + + err = ssdfs_find_any_valid_volume_header(fsi, offset, true); + if (err) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find any valid header: " + "peb_id %llu\n", + next_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto rollback_valid_vh; + } + + err = ssdfs_find_any_valid_sb_segment(fsi, next_peb); + if (err) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find any valid sb seg: " + "peb_id %llu\n", + next_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto rollback_valid_vh; + } else + goto try_next_peb; + } + + fsi->sbi.last_log.leb_id = next_leb; + fsi->sbi.last_log.peb_id = next_peb; + fsi->sbi.last_log.page_offset = 0; + fsi->sbi.last_log.pages_count = SSDFS_LOG_PAGES(fsi->sbi.vh_buf); + +check_volume_header: + seg_type = SSDFS_SEG_TYPE(SSDFS_SEG_HDR(fsi->sbi.vh_buf)); + if (seg_type != SSDFS_SB_SEG_TYPE) { + SSDFS_DBG("invalid segment type\n"); + err = 0; + goto mount_fs_read_only; + } + + cno1 = SSDFS_SEG_CNO(fsi->sbi_backup.vh_buf); + cno2 = SSDFS_SEG_CNO(fsi->sbi.vh_buf); + if (cno1 >= cno2) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("last cno %llu is not lesser than read cno %llu\n", + cno1, cno2); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + next_peb = SSDFS_MAIN_SB_PEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_NEXT_SB_SEG); + cur_peb = SSDFS_MAIN_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + if (next_peb != cur_peb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("next_peb %llu doesn't equal to cur_peb %llu\n", + next_peb, cur_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + prev_peb = SSDFS_MAIN_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_PREV_SB_SEG); + cur_peb = SSDFS_MAIN_SB_PEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_CUR_SB_SEG); + if (prev_peb != cur_peb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("prev_peb %llu doesn't equal to cur_peb %llu\n", + prev_peb, cur_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + next_leb = SSDFS_MAIN_SB_LEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_NEXT_SB_SEG); + cur_leb = SSDFS_MAIN_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + if (next_leb != cur_leb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("next_leb %llu doesn't equal to cur_leb %llu\n", + next_leb, cur_leb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + prev_leb = SSDFS_MAIN_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_PREV_SB_SEG); + cur_leb = SSDFS_MAIN_SB_LEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_CUR_SB_SEG); + if (prev_leb != cur_leb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("prev_leb %llu doesn't equal to cur_leb %llu\n", + prev_leb, cur_leb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + next_peb = SSDFS_COPY_SB_PEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_NEXT_SB_SEG); + cur_peb = SSDFS_COPY_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + if (next_peb != cur_peb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("next_peb %llu doesn't equal to cur_peb %llu\n", + next_peb, cur_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + prev_peb = SSDFS_COPY_SB_PEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_PREV_SB_SEG); + cur_peb = SSDFS_COPY_SB_PEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_CUR_SB_SEG); + if (prev_peb != cur_peb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("prev_peb %llu doesn't equal to cur_peb %llu\n", + prev_peb, cur_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + next_leb = SSDFS_COPY_SB_LEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_NEXT_SB_SEG); + cur_leb = SSDFS_COPY_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_CUR_SB_SEG); + if (next_leb != cur_leb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("next_leb %llu doesn't equal to cur_leb %llu\n", + next_leb, cur_leb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + prev_leb = SSDFS_COPY_SB_LEB(SSDFS_VH(fsi->sbi.vh_buf), + SSDFS_PREV_SB_SEG); + cur_leb = SSDFS_COPY_SB_LEB(SSDFS_VH(fsi->sbi_backup.vh_buf), + SSDFS_CUR_SB_SEG); + if (prev_leb != cur_leb) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("prev_leb %llu doesn't equal to cur_leb %llu\n", + prev_leb, cur_leb); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + goto mount_fs_read_only; + } + + goto try_next_peb; + +mount_fs_read_only: + SSDFS_NOTICE("unable to mount in RW mode: " + "chain of superblock's segments is broken\n"); + fsi->sb->s_flags |= SB_RDONLY; + +rollback_valid_vh: + ssdfs_restore_sb_info(fsi); + +end_search: + return err; +} + +static inline +u64 ssdfs_swap_current_sb_peb(struct ssdfs_volume_header *vh, u64 peb) +{ + if (peb == SSDFS_MAIN_SB_PEB(vh, SSDFS_CUR_SB_SEG)) + return SSDFS_COPY_SB_PEB(vh, SSDFS_CUR_SB_SEG); + else if (peb == SSDFS_COPY_SB_PEB(vh, SSDFS_CUR_SB_SEG)) + return SSDFS_MAIN_SB_PEB(vh, SSDFS_CUR_SB_SEG); + + BUG(); + return ULLONG_MAX; +} + +static inline +u64 ssdfs_swap_current_sb_leb(struct ssdfs_volume_header *vh, u64 leb) +{ + if (leb == SSDFS_MAIN_SB_LEB(vh, SSDFS_CUR_SB_SEG)) + return SSDFS_COPY_SB_LEB(vh, SSDFS_CUR_SB_SEG); + else if (leb == SSDFS_COPY_SB_LEB(vh, SSDFS_CUR_SB_SEG)) + return SSDFS_MAIN_SB_LEB(vh, SSDFS_CUR_SB_SEG); + + BUG(); + return ULLONG_MAX; +} + +/* + * This method expects that first volume header and log footer + * are checked yet and they are valid. + */ +static int ssdfs_find_latest_valid_sb_info(struct ssdfs_fs_info *fsi) +{ + struct ssdfs_segment_header *last_seg_hdr; + u64 leb, peb; + u32 cur_off, low_off, high_off; + u32 log_pages; + u64 pages_per_peb; + int err = 0; +#ifdef CONFIG_SSDFS_DEBUG + size_t hdr_size = sizeof(struct ssdfs_segment_header); +#endif /* CONFIG_SSDFS_DEBUG */ + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); + BUG_ON(!fsi->devops->read); + BUG_ON(!is_ssdfs_magic_valid(&SSDFS_VH(fsi->sbi.vh_buf)->magic)); + BUG_ON(!is_ssdfs_volume_header_csum_valid(fsi->sbi.vh_buf, hdr_size)); + + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p\n", fsi, fsi->sbi.vh_buf); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_backup_sb_info(fsi); + last_seg_hdr = SSDFS_SEG_HDR(fsi->sbi.vh_buf); + leb = fsi->sbi.last_log.leb_id; + peb = fsi->sbi.last_log.peb_id; + log_pages = SSDFS_LOG_PAGES(last_seg_hdr); + + if (fsi->is_zns_device) + pages_per_peb = div64_u64(fsi->zone_capacity, fsi->pagesize); + else + pages_per_peb = fsi->pages_per_peb; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(pages_per_peb >= U32_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + low_off = fsi->sbi.last_log.page_offset; + high_off = (u32)pages_per_peb; + cur_off = low_off + log_pages; + + do { + u32 diff_pages, diff_logs; + u64 cno1, cno2; + u64 copy_leb, copy_peb; + u32 peb_pages_off; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(cur_off >= pages_per_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + + peb_pages_off = cur_off % (u32)pages_per_peb; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(peb_pages_off > U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (leb == U64_MAX || peb == U64_MAX) { + err = -ENODATA; + break; + } + + err = ssdfs_read_checked_sb_info(fsi, peb, + peb_pages_off, true); + cno1 = SSDFS_SEG_CNO(fsi->sbi_backup.vh_buf); + cno2 = SSDFS_SEG_CNO(fsi->sbi.vh_buf); + if (err == -EIO || cno1 >= cno2) { + void *buf = fsi->sbi_backup.vh_buf; + + copy_peb = ssdfs_swap_current_sb_peb(buf, peb); + copy_leb = ssdfs_swap_current_sb_leb(buf, leb); + if (copy_leb == U64_MAX || copy_peb == U64_MAX) { + err = -ERANGE; + break; + } + + err = ssdfs_read_checked_sb_info(fsi, copy_peb, + peb_pages_off, true); + cno1 = SSDFS_SEG_CNO(fsi->sbi_backup.vh_buf); + cno2 = SSDFS_SEG_CNO(fsi->sbi.vh_buf); + if (!err) { + peb = copy_peb; + leb = copy_leb; + fsi->sbi.last_log.leb_id = leb; + fsi->sbi.last_log.peb_id = peb; + fsi->sbi.last_log.page_offset = cur_off; + fsi->sbi.last_log.pages_count = + SSDFS_LOG_PAGES(fsi->sbi.vh_buf); + } + } else { + fsi->sbi.last_log.leb_id = leb; + fsi->sbi.last_log.peb_id = peb; + fsi->sbi.last_log.page_offset = cur_off; + fsi->sbi.last_log.pages_count = + SSDFS_LOG_PAGES(fsi->sbi.vh_buf); + } + + if (err == -ENODATA || err == -EIO || cno1 >= cno2) { + err = !err ? -EIO : err; + high_off = cur_off; + } else if (err) { + /* we have internal error */ + break; + } else { + ssdfs_backup_sb_info(fsi); + low_off = cur_off; + } + + diff_pages = high_off - low_off; + diff_logs = (diff_pages / log_pages) / 2; + cur_off = low_off + (diff_logs * log_pages); + } while (cur_off > low_off && cur_off < high_off); + + if (err) { + if (err == -ENODATA || err == -EIO) { + /* previous read log was valid */ + err = 0; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("cur_off %u, low_off %u, high_off %u\n", + cur_off, low_off, high_off); +#endif /* CONFIG_SSDFS_DEBUG */ + } else { + SSDFS_ERR("fail to find valid volume header: err %d\n", + err); + } + + ssdfs_restore_sb_info(fsi); + } + + return err; +} + +/* + * This method expects that first volume header and log footer + * are checked yet and they are valid. + */ +static int ssdfs_find_latest_valid_sb_info2(struct ssdfs_fs_info *fsi) +{ + struct ssdfs_segment_header *last_seg_hdr; + struct ssdfs_peb_extent checking_page; + u64 leb, peb; + u32 cur_off, low_off, high_off; + u32 log_pages; + u32 start_offset; + u32 found_log_off; + u64 cno1, cno2; + u64 copy_leb, copy_peb; + u32 peb_pages_off; + u64 pages_per_peb; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->sbi.vh_buf); + + SSDFS_DBG("fsi %p, fsi->sbi.vh_buf %p\n", fsi, fsi->sbi.vh_buf); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!fsi->devops->can_write_page) { + SSDFS_CRIT("fail to find latest valid sb info: " + "can_write_page is not supported\n"); + return -EOPNOTSUPP; + } + + ssdfs_backup_sb_info(fsi); + last_seg_hdr = SSDFS_SEG_HDR(fsi->sbi.vh_buf); + leb = fsi->sbi.last_log.leb_id; + peb = fsi->sbi.last_log.peb_id; + + if (leb == U64_MAX || peb == U64_MAX) { + ssdfs_restore_sb_info(fsi); + SSDFS_ERR("invalid leb_id %llu or peb_id %llu\n", + leb, peb); + return -ERANGE; + } + + if (fsi->is_zns_device) + pages_per_peb = div64_u64(fsi->zone_capacity, fsi->pagesize); + else + pages_per_peb = fsi->pages_per_peb; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(pages_per_peb >= U32_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + log_pages = SSDFS_LOG_PAGES(last_seg_hdr); + start_offset = fsi->sbi.last_log.page_offset + log_pages; + low_off = start_offset; + high_off = (u32)pages_per_peb; + cur_off = low_off; + + checking_page.leb_id = leb; + checking_page.peb_id = peb; + checking_page.page_offset = cur_off; + checking_page.pages_count = 1; + + err = ssdfs_can_write_sb_log(fsi->sb, &checking_page); + if (err == -EIO) { + /* correct low bound */ + err = 0; + low_off++; + } else if (err) { + SSDFS_ERR("fail to check for write PEB %llu\n", + peb); + return err; + } else { + ssdfs_restore_sb_info(fsi); + + /* previous read log was valid */ +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("cur_off %u, low_off %u, high_off %u\n", + cur_off, low_off, high_off); +#endif /* CONFIG_SSDFS_DEBUG */ + return 0; + } + + cur_off = high_off - 1; + + do { + u32 diff_pages; + + checking_page.leb_id = leb; + checking_page.peb_id = peb; + checking_page.page_offset = cur_off; + checking_page.pages_count = 1; + + err = ssdfs_can_write_sb_log(fsi->sb, &checking_page); + if (err == -EIO) { + /* correct low bound */ + err = 0; + low_off = cur_off; + } else if (err) { + SSDFS_ERR("fail to check for write PEB %llu\n", + peb); + return err; + } else { + /* correct upper bound */ + high_off = cur_off; + } + + diff_pages = (high_off - low_off) / 2; + cur_off = low_off + diff_pages; + } while (cur_off > low_off && cur_off < high_off); + + peb_pages_off = cur_off % (u32)pages_per_peb; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(peb_pages_off > U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + found_log_off = cur_off; + err = ssdfs_read_checked_sb_info2(fsi, peb, peb_pages_off, true, + &found_log_off); + cno1 = SSDFS_SEG_CNO(fsi->sbi_backup.vh_buf); + cno2 = SSDFS_SEG_CNO(fsi->sbi.vh_buf); + + if (err == -EIO || cno1 >= cno2) { + void *buf = fsi->sbi_backup.vh_buf; + + copy_peb = ssdfs_swap_current_sb_peb(buf, peb); + copy_leb = ssdfs_swap_current_sb_leb(buf, leb); + if (copy_leb == U64_MAX || copy_peb == U64_MAX) { + err = -ERANGE; + goto finish_find_latest_sb_info; + } + + found_log_off = cur_off; + err = ssdfs_read_checked_sb_info2(fsi, copy_peb, + peb_pages_off, true, + &found_log_off); + cno1 = SSDFS_SEG_CNO(fsi->sbi_backup.vh_buf); + cno2 = SSDFS_SEG_CNO(fsi->sbi.vh_buf); + if (!err) { + peb = copy_peb; + leb = copy_leb; + fsi->sbi.last_log.leb_id = leb; + fsi->sbi.last_log.peb_id = peb; + fsi->sbi.last_log.page_offset = found_log_off; + fsi->sbi.last_log.pages_count = + SSDFS_LOG_PAGES(fsi->sbi.vh_buf); + } + } else { + fsi->sbi.last_log.leb_id = leb; + fsi->sbi.last_log.peb_id = peb; + fsi->sbi.last_log.page_offset = found_log_off; + fsi->sbi.last_log.pages_count = + SSDFS_LOG_PAGES(fsi->sbi.vh_buf); + } + +finish_find_latest_sb_info: + if (err) { + if (err == -ENODATA || err == -EIO) { + /* previous read log was valid */ + err = 0; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("cur_off %u, low_off %u, high_off %u\n", + cur_off, low_off, high_off); +#endif /* CONFIG_SSDFS_DEBUG */ + } else { + SSDFS_ERR("fail to find valid volume header: err %d\n", + err); + } + + ssdfs_restore_sb_info(fsi); + } + + return err; +} + +static int ssdfs_check_fs_state(struct ssdfs_fs_info *fsi) +{ + if (fsi->sb->s_flags & SB_RDONLY) + return 0; + + switch (fsi->fs_state) { + case SSDFS_MOUNTED_FS: + SSDFS_NOTICE("unable to mount in RW mode: " + "file system didn't unmounted cleanly: " + "Please, run fsck utility\n"); + fsi->sb->s_flags |= SB_RDONLY; + return -EROFS; + + case SSDFS_ERROR_FS: + if (!ssdfs_test_opt(fsi->mount_opts, IGNORE_FS_STATE)) { + SSDFS_NOTICE("unable to mount in RW mode: " + "file system contains errors: " + "Please, run fsck utility\n"); + fsi->sb->s_flags |= SB_RDONLY; + return -EROFS; + } + break; + }; + + return 0; +} + +static int ssdfs_check_feature_compatibility(struct ssdfs_fs_info *fsi) +{ + u64 features; + + features = fsi->fs_feature_incompat & ~SSDFS_FEATURE_INCOMPAT_SUPP; + if (features) { + SSDFS_NOTICE("unable to mount: " + "unsupported incompatible features %llu\n", + features); + return -EOPNOTSUPP; + } + + features = fsi->fs_feature_compat_ro & ~SSDFS_FEATURE_COMPAT_RO_SUPP; + if (!(fsi->sb->s_flags & SB_RDONLY) && features) { + SSDFS_NOTICE("unable to mount in RW mode: " + "unsupported RO compatible features %llu\n", + features); + fsi->sb->s_flags |= SB_RDONLY; + return -EROFS; + } + + features = fsi->fs_feature_compat & ~SSDFS_FEATURE_COMPAT_SUPP; + if (features) + SSDFS_WARN("unknown compatible features %llu\n", features); + + return 0; +} + +static inline void ssdfs_init_sb_segs_array(struct ssdfs_fs_info *fsi) +{ + int i, j; + + for (i = SSDFS_CUR_SB_SEG; i < SSDFS_SB_CHAIN_MAX; i++) { + for (j = SSDFS_MAIN_SB_SEG; j < SSDFS_SB_SEG_COPY_MAX; j++) { + fsi->sb_lebs[i][j] = + le64_to_cpu(fsi->vh->sb_pebs[i][j].leb_id); + fsi->sb_pebs[i][j] = + le64_to_cpu(fsi->vh->sb_pebs[i][j].peb_id); + } + } +} + +static int ssdfs_initialize_fs_info(struct ssdfs_fs_info *fsi) +{ + int err; + + init_rwsem(&fsi->volume_sem); + + fsi->vh = SSDFS_VH(fsi->sbi.vh_buf); + fsi->vs = SSDFS_VS(fsi->sbi.vs_buf); + + fsi->sb_seg_log_pages = le16_to_cpu(fsi->vh->sb_seg_log_pages); + fsi->segbmap_log_pages = le16_to_cpu(fsi->vh->segbmap_log_pages); + fsi->maptbl_log_pages = le16_to_cpu(fsi->vh->maptbl_log_pages); + fsi->lnodes_seg_log_pages = le16_to_cpu(fsi->vh->lnodes_seg_log_pages); + fsi->hnodes_seg_log_pages = le16_to_cpu(fsi->vh->hnodes_seg_log_pages); + fsi->inodes_seg_log_pages = le16_to_cpu(fsi->vh->inodes_seg_log_pages); + fsi->user_data_log_pages = le16_to_cpu(fsi->vh->user_data_log_pages); + + /* Static volume information */ + fsi->log_pagesize = fsi->vh->log_pagesize; + fsi->pagesize = 1 << fsi->vh->log_pagesize; + fsi->log_erasesize = fsi->vh->log_erasesize; + fsi->log_segsize = fsi->vh->log_segsize; + fsi->segsize = 1 << fsi->vh->log_segsize; + fsi->log_pebs_per_seg = fsi->vh->log_pebs_per_seg; + fsi->pebs_per_seg = 1 << fsi->vh->log_pebs_per_seg; + fsi->pages_per_peb = fsi->erasesize / fsi->pagesize; + fsi->pages_per_seg = fsi->segsize / fsi->pagesize; + fsi->lebs_per_peb_index = le32_to_cpu(fsi->vh->lebs_per_peb_index); + + if (fsi->is_zns_device) { + u64 peb_pages_capacity = + fsi->zone_capacity >> fsi->vh->log_pagesize; + + fsi->erasesize = fsi->zone_size; + fsi->segsize = fsi->erasesize * + le16_to_cpu(fsi->vh->pebs_per_seg); + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(peb_pages_capacity >= U32_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi->peb_pages_capacity = (u32)peb_pages_capacity; + atomic_set(&fsi->open_zones, le32_to_cpu(fsi->vs->open_zones)); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("open_zones %d\n", + atomic_read(&fsi->open_zones)); +#endif /* CONFIG_SSDFS_DEBUG */ + } else { + fsi->erasesize = 1 << fsi->vh->log_erasesize; + fsi->segsize = 1 << fsi->vh->log_segsize; + fsi->peb_pages_capacity = fsi->pages_per_peb; + } + + if (fsi->pages_per_peb > U16_MAX) + fsi->leb_pages_capacity = U16_MAX; + else + fsi->leb_pages_capacity = fsi->pages_per_peb; + + fsi->fs_ctime = le64_to_cpu(fsi->vh->create_time); + fsi->fs_cno = le64_to_cpu(fsi->vh->create_cno); + fsi->raw_inode_size = le16_to_cpu(fsi->vs->inodes_btree.desc.item_size); + fsi->create_threads_per_seg = + le16_to_cpu(fsi->vh->create_threads_per_seg); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("STATIC VOLUME INFO:\n"); + SSDFS_DBG("pagesize %u, erasesize %u, segsize %u\n", + fsi->pagesize, fsi->erasesize, fsi->segsize); + SSDFS_DBG("pebs_per_seg %u, pages_per_peb %u, " + "pages_per_seg %u, lebs_per_peb_index %u\n", + fsi->pebs_per_seg, fsi->pages_per_peb, + fsi->pages_per_seg, fsi->lebs_per_peb_index); + SSDFS_DBG("zone_size %llu, zone_capacity %llu, " + "leb_pages_capacity %u, peb_pages_capacity %u, " + "open_zones %d\n", + fsi->zone_size, fsi->zone_capacity, + fsi->leb_pages_capacity, fsi->peb_pages_capacity, + atomic_read(&fsi->open_zones)); + SSDFS_DBG("fs_ctime %llu, fs_cno %llu, " + "raw_inode_size %u, create_threads_per_seg %u\n", + (u64)fsi->fs_ctime, (u64)fsi->fs_cno, + fsi->raw_inode_size, + fsi->create_threads_per_seg); +#endif /* CONFIG_SSDFS_DEBUG */ + + /* Mutable volume info */ + init_rwsem(&fsi->sb_segs_sem); + ssdfs_init_sb_segs_array(fsi); + + mutex_init(&fsi->resize_mutex); + fsi->nsegs = le64_to_cpu(fsi->vs->nsegs); + + spin_lock_init(&fsi->volume_state_lock); + + fsi->free_pages = 0; + fsi->reserved_new_user_data_pages = 0; + fsi->updated_user_data_pages = 0; + fsi->flushing_user_data_requests = 0; + fsi->fs_mount_time = ssdfs_current_timestamp(); + fsi->fs_mod_time = le64_to_cpu(fsi->vs->timestamp); + ssdfs_init_boot_vs_mount_timediff(fsi); + fsi->fs_mount_cno = le64_to_cpu(fsi->vs->cno); + fsi->fs_flags = le32_to_cpu(fsi->vs->flags); + fsi->fs_state = le16_to_cpu(fsi->vs->state); + + fsi->fs_errors = le16_to_cpu(fsi->vs->errors); + ssdfs_initialize_fs_errors_option(fsi); + + fsi->fs_feature_compat = le64_to_cpu(fsi->vs->feature_compat); + fsi->fs_feature_compat_ro = le64_to_cpu(fsi->vs->feature_compat_ro); + fsi->fs_feature_incompat = le64_to_cpu(fsi->vs->feature_incompat); + + ssdfs_memcpy(fsi->fs_uuid, 0, SSDFS_UUID_SIZE, + fsi->vs->uuid, 0, SSDFS_UUID_SIZE, + SSDFS_UUID_SIZE); + ssdfs_memcpy(fsi->fs_label, 0, SSDFS_VOLUME_LABEL_MAX, + fsi->vs->label, 0, SSDFS_VOLUME_LABEL_MAX, + SSDFS_VOLUME_LABEL_MAX); + + fsi->metadata_options.blk_bmap.flags = + le16_to_cpu(fsi->vs->blkbmap.flags); + fsi->metadata_options.blk_bmap.compression = + fsi->vs->blkbmap.compression; + fsi->metadata_options.blk2off_tbl.flags = + le16_to_cpu(fsi->vs->blk2off_tbl.flags); + fsi->metadata_options.blk2off_tbl.compression = + fsi->vs->blk2off_tbl.compression; + fsi->metadata_options.user_data.flags = + le16_to_cpu(fsi->vs->user_data.flags); + fsi->metadata_options.user_data.compression = + fsi->vs->user_data.compression; + fsi->metadata_options.user_data.migration_threshold = + le16_to_cpu(fsi->vs->user_data.migration_threshold); + + fsi->migration_threshold = le16_to_cpu(fsi->vs->migration_threshold); + if (fsi->migration_threshold == 0 || + fsi->migration_threshold >= U16_MAX) { + /* use default value */ + fsi->migration_threshold = fsi->pebs_per_seg; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("MUTABLE VOLUME INFO:\n"); + SSDFS_DBG("sb_lebs[CUR][MAIN] %llu, sb_pebs[CUR][MAIN] %llu\n", + fsi->sb_lebs[SSDFS_CUR_SB_SEG][SSDFS_MAIN_SB_SEG], + fsi->sb_pebs[SSDFS_CUR_SB_SEG][SSDFS_MAIN_SB_SEG]); + SSDFS_DBG("sb_lebs[CUR][COPY] %llu, sb_pebs[CUR][COPY] %llu\n", + fsi->sb_lebs[SSDFS_CUR_SB_SEG][SSDFS_COPY_SB_SEG], + fsi->sb_pebs[SSDFS_CUR_SB_SEG][SSDFS_COPY_SB_SEG]); + SSDFS_DBG("sb_lebs[NEXT][MAIN] %llu, sb_pebs[NEXT][MAIN] %llu\n", + fsi->sb_lebs[SSDFS_NEXT_SB_SEG][SSDFS_MAIN_SB_SEG], + fsi->sb_pebs[SSDFS_NEXT_SB_SEG][SSDFS_MAIN_SB_SEG]); + SSDFS_DBG("sb_lebs[NEXT][COPY] %llu, sb_pebs[NEXT][COPY] %llu\n", + fsi->sb_lebs[SSDFS_NEXT_SB_SEG][SSDFS_COPY_SB_SEG], + fsi->sb_pebs[SSDFS_NEXT_SB_SEG][SSDFS_COPY_SB_SEG]); + SSDFS_DBG("sb_lebs[RESERVED][MAIN] %llu, sb_pebs[RESERVED][MAIN] %llu\n", + fsi->sb_lebs[SSDFS_RESERVED_SB_SEG][SSDFS_MAIN_SB_SEG], + fsi->sb_pebs[SSDFS_RESERVED_SB_SEG][SSDFS_MAIN_SB_SEG]); + SSDFS_DBG("sb_lebs[RESERVED][COPY] %llu, sb_pebs[RESERVED][COPY] %llu\n", + fsi->sb_lebs[SSDFS_RESERVED_SB_SEG][SSDFS_COPY_SB_SEG], + fsi->sb_pebs[SSDFS_RESERVED_SB_SEG][SSDFS_COPY_SB_SEG]); + SSDFS_DBG("sb_lebs[PREV][MAIN] %llu, sb_pebs[PREV][MAIN] %llu\n", + fsi->sb_lebs[SSDFS_PREV_SB_SEG][SSDFS_MAIN_SB_SEG], + fsi->sb_pebs[SSDFS_PREV_SB_SEG][SSDFS_MAIN_SB_SEG]); + SSDFS_DBG("sb_lebs[PREV][COPY] %llu, sb_pebs[PREV][COPY] %llu\n", + fsi->sb_lebs[SSDFS_PREV_SB_SEG][SSDFS_COPY_SB_SEG], + fsi->sb_pebs[SSDFS_PREV_SB_SEG][SSDFS_COPY_SB_SEG]); + SSDFS_DBG("nsegs %llu, free_pages %llu\n", + fsi->nsegs, fsi->free_pages); + SSDFS_DBG("fs_mount_time %llu, fs_mod_time %llu, fs_mount_cno %llu\n", + fsi->fs_mount_time, fsi->fs_mod_time, fsi->fs_mount_cno); + SSDFS_DBG("fs_flags %#x, fs_state %#x, fs_errors %#x\n", + fsi->fs_flags, fsi->fs_state, fsi->fs_errors); + SSDFS_DBG("fs_feature_compat %llu, fs_feature_compat_ro %llu, " + "fs_feature_incompat %llu\n", + fsi->fs_feature_compat, fsi->fs_feature_compat_ro, + fsi->fs_feature_incompat); + SSDFS_DBG("migration_threshold %u\n", + fsi->migration_threshold); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi->sb->s_blocksize = fsi->pagesize; + fsi->sb->s_blocksize_bits = blksize_bits(fsi->pagesize); + + ssdfs_maptbl_cache_init(&fsi->maptbl_cache); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("VOLUME HEADER DUMP\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + fsi->vh, fsi->pagesize); + SSDFS_DBG("END\n"); + + SSDFS_DBG("VOLUME STATE DUMP\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + fsi->vs, fsi->pagesize); + SSDFS_DBG("END\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_check_fs_state(fsi); + if (err && err != -EROFS) + return err; + + err = ssdfs_check_feature_compatibility(fsi); + if (err) + return err; + + if (fsi->leb_pages_capacity >= U16_MAX) { +#ifdef CONFIG_SSDFS_TESTING + SSDFS_DBG("Continue in testing mode: " + "leb_pages_capacity %u, peb_pages_capacity %u\n", + fsi->leb_pages_capacity, + fsi->peb_pages_capacity); + return 0; +#else + SSDFS_NOTICE("unable to mount in RW mode: " + "Please, format volume with bigger logical block size.\n"); + SSDFS_NOTICE("STATIC VOLUME INFO:\n"); + SSDFS_NOTICE("pagesize %u, erasesize %u, segsize %u\n", + fsi->pagesize, fsi->erasesize, fsi->segsize); + SSDFS_NOTICE("pebs_per_seg %u, pages_per_peb %u, " + "pages_per_seg %u\n", + fsi->pebs_per_seg, fsi->pages_per_peb, + fsi->pages_per_seg); + SSDFS_NOTICE("zone_size %llu, zone_capacity %llu, " + "leb_pages_capacity %u, peb_pages_capacity %u\n", + fsi->zone_size, fsi->zone_capacity, + fsi->leb_pages_capacity, fsi->peb_pages_capacity); + + fsi->sb->s_flags |= SB_RDONLY; + return -EROFS; +#endif /* CONFIG_SSDFS_TESTING */ + } + + return 0; +} + +static +int ssdfs_check_maptbl_cache_header(struct ssdfs_maptbl_cache_header *hdr, + u16 sequence_id, + u64 prev_end_leb) +{ + size_t bytes_count, calculated; + u64 start_leb, end_leb; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!hdr); + + SSDFS_DBG("maptbl_cache_hdr %p\n", hdr); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (hdr->magic.common != cpu_to_le32(SSDFS_SUPER_MAGIC) || + hdr->magic.key != cpu_to_le16(SSDFS_MAPTBL_CACHE_MAGIC)) { + SSDFS_ERR("invalid maptbl cache magic signature\n"); + return -EIO; + } + + if (le16_to_cpu(hdr->sequence_id) != sequence_id) { + SSDFS_ERR("invalid sequence_id\n"); + return -EIO; + } + + bytes_count = le16_to_cpu(hdr->bytes_count); + + if (bytes_count > PAGE_SIZE) { + SSDFS_ERR("invalid bytes_count %zu\n", + bytes_count); + return -EIO; + } + + calculated = le16_to_cpu(hdr->items_count) * + sizeof(struct ssdfs_leb2peb_pair); + + if (bytes_count < calculated) { + SSDFS_ERR("bytes_count %zu < calculated %zu\n", + bytes_count, calculated); + return -EIO; + } + + start_leb = le64_to_cpu(hdr->start_leb); + end_leb = le64_to_cpu(hdr->end_leb); + + if (start_leb > end_leb || + (prev_end_leb != U64_MAX && prev_end_leb >= start_leb)) { + SSDFS_ERR("invalid LEB range: start_leb %llu, " + "end_leb %llu, prev_end_leb %llu\n", + start_leb, end_leb, prev_end_leb); + return -EIO; + } + + return 0; +} + +static int ssdfs_read_maptbl_cache(struct ssdfs_fs_info *fsi) +{ + struct ssdfs_segment_header *seg_hdr; + struct ssdfs_metadata_descriptor *meta_desc; + struct ssdfs_maptbl_cache_header *maptbl_cache_hdr; + u32 read_off; + u32 read_bytes = 0; + u32 bytes_count; + u32 pages_count; + u64 peb_id; + struct page *page; + void *kaddr; + u64 prev_end_leb; + u32 csum = ~0; + int i; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->devops->read); + + SSDFS_DBG("fsi %p\n", fsi); +#endif /* CONFIG_SSDFS_DEBUG */ + + seg_hdr = SSDFS_SEG_HDR(fsi->sbi.vh_buf); + + if (!ssdfs_log_has_maptbl_cache(seg_hdr)) { + SSDFS_ERR("sb segment hasn't maptbl cache\n"); + return -EIO; + } + + down_write(&fsi->maptbl_cache.lock); + + meta_desc = &seg_hdr->desc_array[SSDFS_MAPTBL_CACHE_INDEX]; + read_off = le32_to_cpu(meta_desc->offset); + bytes_count = le32_to_cpu(meta_desc->size); + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(bytes_count >= INT_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + peb_id = fsi->sbi.last_log.peb_id; + + pages_count = (bytes_count + PAGE_SIZE - 1) >> PAGE_SHIFT; + + for (i = 0; i < pages_count; i++) { + struct ssdfs_maptbl_cache *cache = &fsi->maptbl_cache; + size_t size; + + size = min_t(size_t, (size_t)PAGE_SIZE, + (size_t)(bytes_count - read_bytes)); + + page = ssdfs_maptbl_cache_add_pagevec_page(cache); + if (unlikely(IS_ERR_OR_NULL(page))) { + err = !page ? -ENOMEM : PTR_ERR(page); + SSDFS_ERR("fail to add pagevec page: err %d\n", + err); + goto finish_read_maptbl_cache; + } + + ssdfs_lock_page(page); + + kaddr = kmap_local_page(page); + err = ssdfs_unaligned_read_buffer(fsi, peb_id, + read_off, kaddr, size); + flush_dcache_page(page); + kunmap_local(kaddr); + + if (unlikely(err)) { + ssdfs_unlock_page(page); + SSDFS_ERR("fail to read page: " + "peb %llu, offset %u, size %zu, err %d\n", + peb_id, read_off, size, err); + goto finish_read_maptbl_cache; + } + + ssdfs_unlock_page(page); + + read_off += size; + read_bytes += size; + } + + prev_end_leb = U64_MAX; + + for (i = 0; i < pages_count; i++) { + page = fsi->maptbl_cache.pvec.pages[i]; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(i >= U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_lock_page(page); + kaddr = kmap_local_page(page); + + maptbl_cache_hdr = SSDFS_MAPTBL_CACHE_HDR(kaddr); + + err = ssdfs_check_maptbl_cache_header(maptbl_cache_hdr, + (u16)i, + prev_end_leb); + if (unlikely(err)) { + SSDFS_ERR("invalid maptbl cache header: " + "page_index %d, err %d\n", + i, err); + goto unlock_cur_page; + } + + prev_end_leb = le64_to_cpu(maptbl_cache_hdr->end_leb); + + csum = crc32(csum, kaddr, + le16_to_cpu(maptbl_cache_hdr->bytes_count)); + +unlock_cur_page: + kunmap_local(kaddr); + ssdfs_unlock_page(page); + + if (unlikely(err)) + goto finish_read_maptbl_cache; + } + + if (csum != le32_to_cpu(meta_desc->check.csum)) { + err = -EIO; + SSDFS_ERR("invalid checksum\n"); + goto finish_read_maptbl_cache; + } + + if (bytes_count < PAGE_SIZE) + bytes_count = PAGE_SIZE; + + atomic_set(&fsi->maptbl_cache.bytes_count, (int)bytes_count); + +finish_read_maptbl_cache: + up_write(&fsi->maptbl_cache.lock); + + return err; +} + +static inline bool is_ssdfs_snapshot_rules_exist(struct ssdfs_fs_info *fsi) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); +#endif /* CONFIG_SSDFS_DEBUG */ + + return ssdfs_log_footer_has_snapshot_rules(SSDFS_LF(fsi->vs)); +} + +static inline +int ssdfs_check_snapshot_rules_header(struct ssdfs_snapshot_rules_header *hdr) +{ + size_t item_size = sizeof(struct ssdfs_snapshot_rule_info); + u16 items_count; + u16 items_capacity; + u32 area_size; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!hdr); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (le32_to_cpu(hdr->magic) != SSDFS_SNAPSHOT_RULES_MAGIC) { + SSDFS_ERR("invalid snapshot rules magic %#x\n", + le32_to_cpu(hdr->magic)); + return -EIO; + } + + if (le16_to_cpu(hdr->item_size) != item_size) { + SSDFS_ERR("invalid item size %u\n", + le16_to_cpu(hdr->item_size)); + return -EIO; + } + + items_count = le16_to_cpu(hdr->items_count); + items_capacity = le16_to_cpu(hdr->items_capacity); + + if (items_count > items_capacity) { + SSDFS_ERR("corrupted header: " + "items_count %u > items_capacity %u\n", + items_count, items_capacity); + return -EIO; + } + + area_size = le32_to_cpu(hdr->area_size); + + if (area_size != ((u32)items_capacity * item_size)) { + SSDFS_ERR("corrupted header: " + "area_size %u, items_capacity %u, " + "item_size %zu\n", + area_size, items_capacity, item_size); + return -EIO; + } + + return 0; +} + +static inline int ssdfs_read_snapshot_rules(struct ssdfs_fs_info *fsi) +{ + struct ssdfs_log_footer *footer; + struct ssdfs_snapshot_rules_list *rules_list; + struct ssdfs_metadata_descriptor *meta_desc; + struct ssdfs_snapshot_rules_header snap_rules_hdr; + size_t sr_hdr_size = sizeof(struct ssdfs_snapshot_rules_header); + struct ssdfs_snapshot_rule_info info; + size_t rule_size = sizeof(struct ssdfs_snapshot_rule_info); + struct pagevec pvec; + u32 read_off; + u32 read_bytes = 0; + u32 bytes_count; + u32 pages_count; + u64 peb_id; + struct page *page; + void *kaddr; + u32 csum = ~0; + u16 items_count; + int i; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi); + BUG_ON(!fsi->devops->read); + + SSDFS_DBG("fsi %p\n", fsi); +#endif /* CONFIG_SSDFS_DEBUG */ + + footer = SSDFS_LF(fsi->sbi.vs_buf); + rules_list = &fsi->snapshots.rules_list; + + if (!ssdfs_log_footer_has_snapshot_rules(footer)) { + SSDFS_ERR("footer hasn't snapshot rules table\n"); + return -EIO; + } + + meta_desc = &footer->desc_array[SSDFS_SNAPSHOT_RULES_AREA_INDEX]; + read_off = le32_to_cpu(meta_desc->offset); + bytes_count = le32_to_cpu(meta_desc->size); + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(bytes_count >= INT_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + peb_id = fsi->sbi.last_log.peb_id; + + pages_count = (bytes_count + PAGE_SIZE - 1) >> PAGE_SHIFT; + pagevec_init(&pvec); + + for (i = 0; i < pages_count; i++) { + size_t size; + + size = min_t(size_t, (size_t)PAGE_SIZE, + (size_t)(bytes_count - read_bytes)); + + page = ssdfs_snapshot_rules_add_pagevec_page(&pvec); + if (unlikely(IS_ERR_OR_NULL(page))) { + err = !page ? -ENOMEM : PTR_ERR(page); + SSDFS_ERR("fail to add pagevec page: err %d\n", + err); + goto finish_read_snapshot_rules; + } + + ssdfs_lock_page(page); + + kaddr = kmap_local_page(page); + err = ssdfs_unaligned_read_buffer(fsi, peb_id, + read_off, kaddr, size); + flush_dcache_page(page); + kunmap_local(kaddr); + + if (unlikely(err)) { + ssdfs_unlock_page(page); + SSDFS_ERR("fail to read page: " + "peb %llu, offset %u, size %zu, err %d\n", + peb_id, read_off, size, err); + goto finish_read_snapshot_rules; + } + + ssdfs_unlock_page(page); + + read_off += size; + read_bytes += size; + } + + page = pvec.pages[0]; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!page); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_lock_page(page); + ssdfs_memcpy_from_page(&snap_rules_hdr, 0, sr_hdr_size, + page, 0, PAGE_SIZE, + sr_hdr_size); + ssdfs_unlock_page(page); + + err = ssdfs_check_snapshot_rules_header(&snap_rules_hdr); + if (unlikely(err)) { + SSDFS_ERR("invalid snapshot rules header: " + "err %d\n", err); + goto finish_read_snapshot_rules; + } + + for (i = 0; i < pages_count; i++) { + page = pvec.pages[i]; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(i >= U16_MAX); + BUG_ON(!page); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_lock_page(page); + kaddr = kmap_local_page(page); + csum = crc32(csum, kaddr, le16_to_cpu(meta_desc->check.bytes)); + kunmap_local(kaddr); + ssdfs_unlock_page(page); + } + + if (csum != le32_to_cpu(meta_desc->check.csum)) { + err = -EIO; + SSDFS_ERR("invalid checksum\n"); + goto finish_read_snapshot_rules; + } + + items_count = le16_to_cpu(snap_rules_hdr.items_count); + read_off = sr_hdr_size; + + for (i = 0; i < items_count; i++) { + struct ssdfs_snapshot_rule_item *ptr; + + err = ssdfs_unaligned_read_pagevec(&pvec, read_off, + rule_size, &info); + if (unlikely(err)) { + SSDFS_ERR("fail to read a snapshot rule: " + "read_off %u, index %d, err %d\n", + read_off, i, err); + goto finish_read_snapshot_rules; + } + + ptr = ssdfs_snapshot_rule_alloc(); + if (!ptr) { + err = -ENOMEM; + SSDFS_ERR("fail to allocate rule item\n"); + goto finish_read_snapshot_rules; + } + + ssdfs_memcpy(&ptr->rule, 0, rule_size, + &info, 0, rule_size, + rule_size); + + ssdfs_snapshot_rules_list_add_tail(rules_list, ptr); + + read_off += rule_size; + } + +finish_read_snapshot_rules: + ssdfs_snapshot_rules_pagevec_release(&pvec); + return err; +} + +static int ssdfs_init_recovery_environment(struct ssdfs_fs_info *fsi, + struct ssdfs_volume_header *vh, + u64 pebs_per_volume, + struct ssdfs_recovery_env *env) +{ + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi || !vh || !env); + + SSDFS_DBG("fsi %p, vh %p, env %p\n", fsi, vh, env); +#endif /* CONFIG_SSDFS_DEBUG */ + + env->found = NULL; + env->err = 0; + env->fsi = fsi; + env->pebs_per_volume = pebs_per_volume; + + atomic_set(&env->state, SSDFS_RECOVERY_UNKNOWN_STATE); + + err = ssdfs_init_sb_info(fsi, &env->sbi); + if (likely(!err)) + err = ssdfs_init_sb_info(fsi, &env->sbi_backup); + + if (unlikely(err)) { + SSDFS_ERR("fail to prepare sb info: err %d\n", err); + return err; + } + + return 0; +} + +static inline bool has_thread_finished(struct ssdfs_recovery_env *env) +{ + switch (atomic_read(&env->state)) { + case SSDFS_RECOVERY_FAILED: + case SSDFS_RECOVERY_FINISHED: + return true; + + case SSDFS_START_RECOVERY: + return false; + } + + return true; +} + +static inline u16 ssdfs_get_pebs_per_stripe(u64 pebs_per_volume, + u64 processed_pebs, + u32 fragments_count, + u16 pebs_per_fragment, + u16 stripes_per_fragment, + u16 pebs_per_stripe) +{ + u64 fragment_index; + u64 pebs_per_aligned_fragments; + u64 pebs_per_last_fragment; + u64 calculated = U16_MAX; + u32 remainder; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("pebs_per_volume %llu, processed_pebs %llu, " + "fragments_count %u, pebs_per_fragment %u, " + "stripes_per_fragment %u, pebs_per_stripe %u\n", + pebs_per_volume, processed_pebs, + fragments_count, pebs_per_fragment, + stripes_per_fragment, pebs_per_stripe); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (fragments_count == 0) { + SSDFS_WARN("invalid fragments_count %u\n", + fragments_count); + return pebs_per_stripe; + } + + fragment_index = processed_pebs / pebs_per_fragment; + + if (fragment_index >= fragments_count) { + SSDFS_WARN("fragment_index %llu >= fragments_count %u\n", + fragment_index, fragments_count); + return pebs_per_stripe; + } + + if ((fragment_index + 1) < fragments_count) + calculated = pebs_per_stripe; + else { + pebs_per_aligned_fragments = fragments_count - 1; + pebs_per_aligned_fragments *= pebs_per_fragment; + + if (pebs_per_aligned_fragments >= pebs_per_volume) { + SSDFS_WARN("calculated %llu >= pebs_per_volume %llu\n", + pebs_per_aligned_fragments, + pebs_per_volume); + return 0; + } + + pebs_per_last_fragment = pebs_per_volume - + pebs_per_aligned_fragments; + calculated = pebs_per_last_fragment / stripes_per_fragment; + + div_u64_rem(pebs_per_last_fragment, + (u64)stripes_per_fragment, &remainder); + + if (remainder != 0) + calculated++; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("calculated: fragment_index %llu, pebs_per_stripe %llu\n", + fragment_index, calculated); + + BUG_ON(calculated > pebs_per_stripe); +#endif /* CONFIG_SSDFS_DEBUG */ + + return (u16)calculated; +} + +static inline +void ssdfs_init_found_pebs_details(struct ssdfs_found_protected_pebs *ptr) +{ + int i, j; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr); + + SSDFS_DBG("ptr %p\n", ptr); +#endif /* CONFIG_SSDFS_DEBUG */ + + ptr->start_peb = U64_MAX; + ptr->pebs_count = U32_MAX; + ptr->lower_offset = U64_MAX; + ptr->middle_offset = U64_MAX; + ptr->upper_offset = U64_MAX; + ptr->current_offset = U64_MAX; + ptr->search_phase = SSDFS_RECOVERY_NO_SEARCH; + + for (i = 0; i < SSDFS_PROTECTED_PEB_CHAIN_MAX; i++) { + struct ssdfs_found_protected_peb *cur_peb; + + cur_peb = &ptr->array[i]; + + cur_peb->peb.peb_id = U64_MAX; + cur_peb->peb.is_superblock_peb = false; + cur_peb->peb.state = SSDFS_PEB_NOT_CHECKED; + + for (j = 0; j < SSDFS_SB_CHAIN_MAX; j++) { + struct ssdfs_superblock_pebs_pair *cur_pair; + struct ssdfs_found_peb *cur_sb_peb; + + cur_pair = &cur_peb->found.sb_pebs[j]; + + cur_sb_peb = &cur_pair->pair[SSDFS_MAIN_SB_SEG]; + cur_sb_peb->peb_id = U64_MAX; + cur_sb_peb->is_superblock_peb = false; + cur_sb_peb->state = SSDFS_PEB_NOT_CHECKED; + + cur_sb_peb = &cur_pair->pair[SSDFS_COPY_SB_SEG]; + cur_sb_peb->peb_id = U64_MAX; + cur_sb_peb->is_superblock_peb = false; + cur_sb_peb->state = SSDFS_PEB_NOT_CHECKED; + } + } +} + +static inline +int ssdfs_start_recovery_thread_activity(struct ssdfs_recovery_env *env, + struct ssdfs_found_protected_pebs *found, + u64 start_peb, u32 pebs_count, int search_phase) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || env->found || !found); + + SSDFS_DBG("env %p, found %p, start_peb %llu, " + "pebs_count %u, search_phase %#x\n", + env, found, start_peb, + pebs_count, search_phase); +#endif /* CONFIG_SSDFS_DEBUG */ + + env->found = found; + env->err = 0; + + if (search_phase == SSDFS_RECOVERY_FAST_SEARCH) { + env->found->start_peb = start_peb; + env->found->pebs_count = pebs_count; + } else if (search_phase == SSDFS_RECOVERY_SLOW_SEARCH) { + struct ssdfs_found_protected_peb *protected; + u64 lower_peb_id; + u64 upper_peb_id; + u64 last_cno_peb_id; + + if (env->found->start_peb != start_peb || + env->found->pebs_count != pebs_count) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ignore search in fragment: " + "found (start_peb %llu, pebs_count %u), " + "start_peb %llu, pebs_count %u\n", + env->found->start_peb, + env->found->pebs_count, + start_peb, pebs_count); +#endif /* CONFIG_SSDFS_DEBUG */ + env->err = -ENODATA; + atomic_set(&env->state, SSDFS_RECOVERY_FAILED); + return -ENODATA; + } + + protected = &env->found->array[SSDFS_LOWER_PEB_INDEX]; + lower_peb_id = protected->peb.peb_id; + + protected = &env->found->array[SSDFS_UPPER_PEB_INDEX]; + upper_peb_id = protected->peb.peb_id; + + protected = &env->found->array[SSDFS_LAST_CNO_PEB_INDEX]; + last_cno_peb_id = protected->peb.peb_id; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("protected PEBs: " + "lower %llu, upper %llu, last_cno_peb %llu\n", + lower_peb_id, upper_peb_id, last_cno_peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (lower_peb_id >= U64_MAX) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ignore search in fragment: " + "found (start_peb %llu, pebs_count %u), " + "start_peb %llu, pebs_count %u, " + "lower %llu, upper %llu, " + "last_cno_peb %llu\n", + env->found->start_peb, + env->found->pebs_count, + start_peb, pebs_count, + lower_peb_id, upper_peb_id, + last_cno_peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + env->err = -ENODATA; + atomic_set(&env->state, SSDFS_RECOVERY_FAILED); + return -ENODATA; + } else if (lower_peb_id == env->found->start_peb && + upper_peb_id >= U64_MAX) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ignore search in fragment: " + "found (start_peb %llu, pebs_count %u), " + "start_peb %llu, pebs_count %u, " + "lower %llu, upper %llu, " + "last_cno_peb %llu\n", + env->found->start_peb, + env->found->pebs_count, + start_peb, pebs_count, + lower_peb_id, upper_peb_id, + last_cno_peb_id); +#endif /* CONFIG_SSDFS_DEBUG */ + env->err = -ENODATA; + atomic_set(&env->state, SSDFS_RECOVERY_FAILED); + return -ENODATA; + } + } else { + SSDFS_ERR("unexpected search phase %#x\n", + search_phase); + return -ERANGE; + } + + env->found->search_phase = search_phase; + atomic_set(&env->state, SSDFS_START_RECOVERY); + wake_up(&env->request_wait_queue); + + return 0; +} + +static inline +int ssdfs_wait_recovery_thread_finish(struct ssdfs_fs_info *fsi, + struct ssdfs_recovery_env *env, + u32 stripe_id, + bool *has_sb_peb_found) +{ + struct ssdfs_segment_header *seg_hdr; + wait_queue_head_t *wq; + u64 cno1, cno2; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !has_sb_peb_found); + + SSDFS_DBG("env %p, has_sb_peb_found %p, stripe_id %u\n", + env, has_sb_peb_found, stripe_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + /* + * Do not change has_sb_peb_found + * if nothing has been found!!!! + */ + + wq = &env->result_wait_queue; + + wait_event_interruptible_timeout(*wq, + has_thread_finished(env), + SSDFS_DEFAULT_TIMEOUT); + + switch (atomic_read(&env->state)) { + case SSDFS_RECOVERY_FINISHED: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("stripe %u has SB segment\n", + stripe_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + seg_hdr = SSDFS_SEG_HDR(fsi->sbi.vh_buf); + cno1 = le64_to_cpu(seg_hdr->cno); + seg_hdr = SSDFS_SEG_HDR(env->sbi.vh_buf); + cno2 = le64_to_cpu(seg_hdr->cno); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("cno1 %llu, cno2 %llu\n", + cno1, cno2); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (cno1 <= cno2) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("copy sb info: " + "stripe_id %u\n", + stripe_id); +#endif /* CONFIG_SSDFS_DEBUG */ + ssdfs_copy_sb_info(fsi, env); + *has_sb_peb_found = true; + } + break; + + case SSDFS_RECOVERY_FAILED: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("stripe %u has nothing\n", + stripe_id); +#endif /* CONFIG_SSDFS_DEBUG */ + break; + + case SSDFS_START_RECOVERY: + err = -ERANGE; + SSDFS_WARN("thread is working too long: " + "stripe %u\n", + stripe_id); + atomic_set(&env->state, SSDFS_RECOVERY_FAILED); + break; + + default: + BUG(); + } + + env->found = NULL; + return err; +} + +int ssdfs_gather_superblock_info(struct ssdfs_fs_info *fsi, int silent) +{ + struct ssdfs_volume_header *vh; + struct ssdfs_recovery_env *array = NULL; + struct ssdfs_found_protected_pebs *found_pebs = NULL; + u64 dev_size; + u32 erasesize; + u64 pebs_per_volume; + u32 fragments_count = 0; + u16 pebs_per_fragment = 0; + u16 stripes_per_fragment = 0; + u16 pebs_per_stripe = 0; + u32 stripes_count = 0; + u32 threads_count; + u32 jobs_count; + u32 processed_stripes = 0; + u64 processed_pebs = 0; + bool has_sb_peb_found1, has_sb_peb_found2; + bool has_iteration_succeeded; + u16 calculated; + int i; + int err = 0; + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("fsi %p, silent %#x\n", fsi, silent); +#else + SSDFS_DBG("fsi %p, silent %#x\n", fsi, silent); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + err = ssdfs_init_sb_info(fsi, &fsi->sbi); + if (likely(!err)) { + err = ssdfs_init_sb_info(fsi, &fsi->sbi_backup); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to prepare sb info: err %d\n", err); + goto free_buf; + } + + err = ssdfs_find_any_valid_volume_header(fsi, + SSDFS_RESERVED_VBR_SIZE, + silent); + if (err) + goto forget_buf; + + vh = SSDFS_VH(fsi->sbi.vh_buf); + fragments_count = le32_to_cpu(vh->maptbl.fragments_count); + pebs_per_fragment = le16_to_cpu(vh->maptbl.pebs_per_fragment); + pebs_per_stripe = le16_to_cpu(vh->maptbl.pebs_per_stripe); + stripes_per_fragment = le16_to_cpu(vh->maptbl.stripes_per_fragment); + + dev_size = fsi->devops->device_size(fsi->sb); + erasesize = 1 << vh->log_erasesize; + pebs_per_volume = div_u64(dev_size, erasesize); + + stripes_count = fragments_count * stripes_per_fragment; + threads_count = min_t(u32, SSDFS_RECOVERY_THREADS, stripes_count); + + has_sb_peb_found1 = false; + has_sb_peb_found2 = false; + + found_pebs = ssdfs_recovery_kcalloc(stripes_count, + sizeof(struct ssdfs_found_protected_pebs), + GFP_KERNEL); + if (!found_pebs) { + err = -ENOMEM; + SSDFS_ERR("fail to allocate the PEBs details array\n"); + goto free_environment; + } + + for (i = 0; i < stripes_count; i++) { + ssdfs_init_found_pebs_details(&found_pebs[i]); + } + + array = ssdfs_recovery_kcalloc(threads_count, + sizeof(struct ssdfs_recovery_env), + GFP_KERNEL); + if (!array) { + err = -ENOMEM; + SSDFS_ERR("fail to allocate the environment\n"); + goto free_environment; + } + + for (i = 0; i < threads_count; i++) { + err = ssdfs_init_recovery_environment(fsi, vh, + pebs_per_volume, &array[i]); + if (unlikely(err)) { + SSDFS_ERR("fail to prepare sb info: err %d\n", err); + + for (; i >= 0; i--) { + ssdfs_destruct_sb_info(&array[i].sbi); + ssdfs_destruct_sb_info(&array[i].sbi_backup); + } + + goto free_environment; + } + } + + for (i = 0; i < threads_count; i++) { + err = ssdfs_recovery_start_thread(&array[i], i); + if (unlikely(err)) { + if (err == -EINTR) { + /* + * Ignore this error. + */ + } else { + SSDFS_ERR("fail to start thread: " + "id %u, err %d\n", + i, err); + } + + for (; i >= 0; i--) + ssdfs_recovery_stop_thread(&array[i]); + + goto destruct_sb_info; + } + } + + jobs_count = 1; + + processed_stripes = 0; + processed_pebs = 0; + + while (processed_pebs < pebs_per_volume) { + /* Fast search phase */ + has_iteration_succeeded = false; + + if (processed_stripes >= stripes_count) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("processed_stripes %u >= stripes_count %u\n", + processed_stripes, stripes_count); +#endif /* CONFIG_SSDFS_DEBUG */ + goto try_slow_search; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("FAST_SEARCH: jobs_count %u\n", jobs_count); +#endif /* CONFIG_SSDFS_DEBUG */ + + for (i = 0; i < jobs_count; i++) { + calculated = + ssdfs_get_pebs_per_stripe(pebs_per_volume, + processed_pebs, + fragments_count, + pebs_per_fragment, + stripes_per_fragment, + pebs_per_stripe); + + if ((processed_pebs + calculated) > pebs_per_volume) + calculated = pebs_per_volume - processed_pebs; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("i %d, start_peb %llu, pebs_count %u\n", + i, processed_pebs, calculated); + SSDFS_DBG("pebs_per_volume %llu, processed_pebs %llu\n", + pebs_per_volume, processed_pebs); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_start_recovery_thread_activity(&array[i], + &found_pebs[processed_stripes + i], + processed_pebs, calculated, + SSDFS_RECOVERY_FAST_SEARCH); + if (err) { + SSDFS_ERR("fail to start thread's activity: " + "err %d\n", err); + goto finish_sb_peb_search; + } + + processed_pebs += calculated; + } + + for (i = 0; i < jobs_count; i++) { + err = ssdfs_wait_recovery_thread_finish(fsi, + &array[i], + processed_stripes + i, + &has_iteration_succeeded); + if (unlikely(err)) { + has_sb_peb_found1 = false; + goto finish_sb_peb_search; + } + + switch (array[i].err) { + case 0: + /* SB PEB has been found */ + /* continue logic */ + break; + + case -ENODATA: + case -ENOENT: + case -EAGAIN: + case -E2BIG: + /* SB PEB has not been found */ + /* continue logic */ + break; + + default: + /* Something is going wrong */ + /* stop execution */ + err = array[i].err; + has_sb_peb_found1 = false; + SSDFS_ERR("fail to find valid SB PEB: " + "err %d\n", err); + goto finish_sb_peb_search; + } + } + + if (has_iteration_succeeded) { + has_sb_peb_found1 = true; + goto finish_sb_peb_search; + } + + processed_stripes += jobs_count; + + jobs_count <<= 1; + jobs_count = min_t(u32, jobs_count, threads_count); + jobs_count = min_t(u32, jobs_count, + stripes_count - processed_stripes); + }; + +try_slow_search: + jobs_count = 1; + + processed_stripes = 0; + processed_pebs = 0; + + while (processed_pebs < pebs_per_volume) { + /* Slow search phase */ + has_iteration_succeeded = false; + + if (processed_stripes >= stripes_count) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("processed_stripes %u >= stripes_count %u\n", + processed_stripes, stripes_count); +#endif /* CONFIG_SSDFS_DEBUG */ + goto finish_sb_peb_search; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("SLOW_SEARCH: jobs_count %u\n", jobs_count); +#endif /* CONFIG_SSDFS_DEBUG */ + + for (i = 0; i < jobs_count; i++) { + calculated = + ssdfs_get_pebs_per_stripe(pebs_per_volume, + processed_pebs, + fragments_count, + pebs_per_fragment, + stripes_per_fragment, + pebs_per_stripe); + + if ((processed_pebs + calculated) > pebs_per_volume) + calculated = pebs_per_volume - processed_pebs; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("i %d, start_peb %llu, pebs_count %u\n", + i, processed_pebs, calculated); + SSDFS_DBG("pebs_per_volume %llu, processed_pebs %llu\n", + pebs_per_volume, processed_pebs); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_start_recovery_thread_activity(&array[i], + &found_pebs[processed_stripes + i], + processed_pebs, calculated, + SSDFS_RECOVERY_SLOW_SEARCH); + if (err == -ENODATA) { + /* thread continues to sleep */ + /* continue logic */ + } else if (err) { + SSDFS_ERR("fail to start thread's activity: " + "err %d\n", err); + goto finish_sb_peb_search; + } + + processed_pebs += calculated; + } + + for (i = 0; i < jobs_count; i++) { + err = ssdfs_wait_recovery_thread_finish(fsi, + &array[i], + processed_stripes + i, + &has_iteration_succeeded); + if (unlikely(err)) { + has_sb_peb_found2 = false; + goto finish_sb_peb_search; + } + + switch (array[i].err) { + case 0: + /* SB PEB has been found */ + /* continue logic */ + break; + + case -ENODATA: + case -ENOENT: + case -EAGAIN: + case -E2BIG: + /* SB PEB has not been found */ + /* continue logic */ + break; + + default: + /* Something is going wrong */ + /* stop execution */ + err = array[i].err; + has_sb_peb_found2 = false; + SSDFS_ERR("fail to find valid SB PEB: " + "err %d\n", err); + goto finish_sb_peb_search; + } + } + + if (has_iteration_succeeded) { + has_sb_peb_found2 = true; + goto finish_sb_peb_search; + } + + processed_stripes += jobs_count; + + jobs_count <<= 1; + jobs_count = min_t(u32, jobs_count, threads_count); + jobs_count = min_t(u32, jobs_count, + stripes_count - processed_stripes); + }; + +finish_sb_peb_search: + for (i = 0; i < threads_count; i++) + ssdfs_recovery_stop_thread(&array[i]); + +destruct_sb_info: + for (i = 0; i < threads_count; i++) { + ssdfs_destruct_sb_info(&array[i].sbi); + ssdfs_destruct_sb_info(&array[i].sbi_backup); + } + +free_environment: + if (found_pebs) { + ssdfs_recovery_kfree(found_pebs); + found_pebs = NULL; + } + + if (array) { + ssdfs_recovery_kfree(array); + array = NULL; + } + + switch (err) { + case 0: + /* SB PEB has been found */ + /* continue logic */ + break; + + case -ENODATA: + case -ENOENT: + case -EAGAIN: + case -E2BIG: + /* SB PEB has not been found */ + /* continue logic */ + break; + + default: + /* Something is going wrong */ + /* stop execution */ + SSDFS_ERR("fail to find valid SB PEB: err %d\n", err); + goto forget_buf; + } + + if (has_sb_peb_found1) + SSDFS_DBG("FAST_SEARCH: found SB seg\n"); + else if (has_sb_peb_found2) + SSDFS_DBG("SLOW_SEARCH: found SB seg\n"); + + if (!has_sb_peb_found1 && !has_sb_peb_found2) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_ERR("unable to find latest valid sb segment: " + "trying old algorithm!!!\n"); + BUG(); +#else + SSDFS_ERR("unable to find latest valid sb segment: " + "trying old algorithm!!!\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_find_any_valid_sb_segment(fsi, 0); + if (err) + goto forget_buf; + + err = ssdfs_find_latest_valid_sb_segment(fsi); + if (err) + goto forget_buf; + } + + err = ssdfs_find_latest_valid_sb_info2(fsi); + if (err) { + SSDFS_ERR("unable to find latest valid sb info: " + "trying old algorithm!!!\n"); + + err = ssdfs_find_latest_valid_sb_info(fsi); + if (err) + goto forget_buf; + } + + err = ssdfs_initialize_fs_info(fsi); + if (err && err != -EROFS) + goto forget_buf; + + err = ssdfs_read_maptbl_cache(fsi); + if (err) + goto forget_buf; + + if (is_ssdfs_snapshot_rules_exist(fsi)) { + err = ssdfs_read_snapshot_rules(fsi); + if (err) + goto forget_buf; + } + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("DONE: gather superblock info\n"); +#else + SSDFS_DBG("DONE: gather superblock info\n"); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + return 0; + +forget_buf: + fsi->vh = NULL; + fsi->vs = NULL; + +free_buf: + ssdfs_destruct_sb_info(&fsi->sbi); + ssdfs_destruct_sb_info(&fsi->sbi_backup); + return err; +} diff --git a/fs/ssdfs/recovery.h b/fs/ssdfs/recovery.h new file mode 100644 index 000000000000..aead1ebe29e6 --- /dev/null +++ b/fs/ssdfs/recovery.h @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * SSDFS -- SSD-oriented File System. + * + * fs/ssdfs/recovery.h - recovery logic declarations. + * + * Copyright (c) 2019-2023 Viacheslav Dubeyko <slava@xxxxxxxxxxx> + * http://www.ssdfs.org/ + * All rights reserved. + * + * Authors: Viacheslav Dubeyko <slava@xxxxxxxxxxx> + */ + +#ifndef _SSDFS_RECOVERY_H +#define _SSDFS_RECOVERY_H + +#define SSDFS_RESERVED_SB_SEGS (6) +#define SSDFS_RECOVERY_THREADS (12) + +/* + * struct ssdfs_found_peb - found PEB details + * @peb_id: PEB's ID + * @cno: PEB's starting checkpoint + * @is_superblock_peb: has superblock PEB been found? + * @state: PEB's state + */ +struct ssdfs_found_peb { + u64 peb_id; + u64 cno; + bool is_superblock_peb; + int state; +}; + +/* + * States of found PEB + */ +enum { + SSDFS_PEB_NOT_CHECKED, + SSDFS_FOUND_PEB_VALID, + SSDFS_FOUND_PEB_INVALID, + SSDFS_FOUND_PEB_STATE_MAX +}; + +/* + * struct ssdfs_superblock_pebs_pair - pair of superblock PEBs + * @pair: main and copy superblock PEBs + */ +struct ssdfs_superblock_pebs_pair { + struct ssdfs_found_peb pair[SSDFS_SB_SEG_COPY_MAX]; +}; + +/* + * struct ssdfs_found_superblock_pebs - found superblock PEBs + * sb_pebs: array of superblock PEBs details + */ +struct ssdfs_found_superblock_pebs { + struct ssdfs_superblock_pebs_pair sb_pebs[SSDFS_SB_CHAIN_MAX]; +}; + +/* + * struct ssdfs_found_protected_peb - protected PEB details + * @peb: protected PEB details + * @found: superblock PEBs details + */ +struct ssdfs_found_protected_peb { + struct ssdfs_found_peb peb; + struct ssdfs_found_superblock_pebs found; +}; + +/* + * struct ssdfs_found_protected_pebs - found protected PEBs + * @start_peb: starting PEB ID in fragment + * @pebs_count: PEBs count in fragment + * @lower_offset: lower offset bound + * @middle_offset: middle offset + * @upper_offset: upper offset bound + * @current_offset: current position of the search + * @search_phase: current search phase + * array: array of protected PEBs details + */ +struct ssdfs_found_protected_pebs { + u64 start_peb; + u32 pebs_count; + + u64 lower_offset; + u64 middle_offset; + u64 upper_offset; + u64 current_offset; + int search_phase; + +#define SSDFS_LOWER_PEB_INDEX (0) +#define SSDFS_UPPER_PEB_INDEX (1) +#define SSDFS_LAST_CNO_PEB_INDEX (2) +#define SSDFS_PROTECTED_PEB_CHAIN_MAX (3) + struct ssdfs_found_protected_peb array[SSDFS_PROTECTED_PEB_CHAIN_MAX]; +}; + +/* + * struct ssdfs_recovery_env - recovery environment + * @found: found PEBs' details + * @err: result of the search + * @state: recovery thread's state + * @pebs_per_volume: PEBs number per volume + * @last_vh: buffer for last valid volume header + * @sbi: superblock info + * @sbi_backup: backup copy of superblock info + * @request_wait_queue: request wait queue of recovery thread + * @result_wait_queue: result wait queue of recovery thread + * @thread: descriptor of recovery thread + * @fsi: file system info object + */ +struct ssdfs_recovery_env { + struct ssdfs_found_protected_pebs *found; + + int err; + atomic_t state; + u64 pebs_per_volume; + + struct ssdfs_volume_header last_vh; + struct ssdfs_sb_info sbi; + struct ssdfs_sb_info sbi_backup; + + wait_queue_head_t request_wait_queue; + wait_queue_head_t result_wait_queue; + struct ssdfs_thread_info thread; + struct ssdfs_fs_info *fsi; +}; + +/* + * Search phases + */ +enum { + SSDFS_RECOVERY_NO_SEARCH, + SSDFS_RECOVERY_FAST_SEARCH, + SSDFS_RECOVERY_SLOW_SEARCH, + SSDFS_RECOVERY_FIRST_SLOW_TRY, + SSDFS_RECOVERY_SECOND_SLOW_TRY, + SSDFS_RECOVERY_THIRD_SLOW_TRY, + SSDFS_RECOVERY_SEARCH_PHASES_MAX +}; + +/* + * Recovery thread's state + */ +enum { + SSDFS_RECOVERY_UNKNOWN_STATE, + SSDFS_START_RECOVERY, + SSDFS_RECOVERY_FAILED, + SSDFS_RECOVERY_FINISHED, + SSDFS_RECOVERY_STATE_MAX +}; + +/* + * Operation types + */ +enum { + SSDFS_USE_PEB_ISBAD_OP, + SSDFS_USE_READ_OP, +}; + +/* + * Inline functions + */ + +static inline +struct ssdfs_found_peb * +CUR_MAIN_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_CUR_SB_SEG].pair[SSDFS_MAIN_SB_SEG]; +} + +static inline +struct ssdfs_found_peb * +CUR_COPY_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_CUR_SB_SEG].pair[SSDFS_COPY_SB_SEG]; +} + +static inline +struct ssdfs_found_peb * +NEXT_MAIN_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_NEXT_SB_SEG].pair[SSDFS_MAIN_SB_SEG]; +} + +static inline +struct ssdfs_found_peb * +NEXT_COPY_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_NEXT_SB_SEG].pair[SSDFS_COPY_SB_SEG]; +} + +static inline +struct ssdfs_found_peb * +RESERVED_MAIN_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_RESERVED_SB_SEG].pair[SSDFS_MAIN_SB_SEG]; +} + +static inline +struct ssdfs_found_peb * +RESERVED_COPY_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_RESERVED_SB_SEG].pair[SSDFS_COPY_SB_SEG]; +} + +static inline +struct ssdfs_found_peb * +PREV_MAIN_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_PREV_SB_SEG].pair[SSDFS_MAIN_SB_SEG]; +} + +static inline +struct ssdfs_found_peb * +PREV_COPY_SB_PEB(struct ssdfs_found_superblock_pebs *ptr) +{ + return &ptr->sb_pebs[SSDFS_PREV_SB_SEG].pair[SSDFS_COPY_SB_SEG]; +} + +static inline +bool IS_INSIDE_STRIPE(struct ssdfs_found_protected_pebs *ptr, + struct ssdfs_found_peb *found) +{ + return found->peb_id >= ptr->start_peb && + found->peb_id < (ptr->start_peb + ptr->pebs_count); +} + +static inline +u64 SSDFS_RECOVERY_LOW_OFF(struct ssdfs_recovery_env *env) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->fsi || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (env->found->search_phase) { + case SSDFS_RECOVERY_FAST_SEARCH: + return env->found->lower_offset; + + case SSDFS_RECOVERY_SLOW_SEARCH: + case SSDFS_RECOVERY_FIRST_SLOW_TRY: + return env->found->middle_offset; + + case SSDFS_RECOVERY_SECOND_SLOW_TRY: + return env->found->lower_offset; + + case SSDFS_RECOVERY_THIRD_SLOW_TRY: + if (env->found->start_peb == 0) + return SSDFS_RESERVED_VBR_SIZE; + else + return env->found->start_peb * env->fsi->erasesize; + } + + return U64_MAX; +} + +static inline +u64 SSDFS_RECOVERY_UPPER_OFF(struct ssdfs_recovery_env *env) +{ + u64 calculated_peb; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->fsi || !env->found); + BUG_ON(env->pebs_per_volume == 0); + BUG_ON(env->pebs_per_volume >= U64_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (env->found->search_phase) { + case SSDFS_RECOVERY_FAST_SEARCH: + calculated_peb = div_u64(env->found->middle_offset, + env->fsi->erasesize); + calculated_peb += SSDFS_MAPTBL_PROTECTION_STEP - 1; + if (calculated_peb >= env->pebs_per_volume) + calculated_peb = env->pebs_per_volume - 1; + + return calculated_peb * env->fsi->erasesize; + + case SSDFS_RECOVERY_SLOW_SEARCH: + case SSDFS_RECOVERY_FIRST_SLOW_TRY: + return env->found->upper_offset; + + case SSDFS_RECOVERY_SECOND_SLOW_TRY: + return env->found->middle_offset; + + case SSDFS_RECOVERY_THIRD_SLOW_TRY: + return env->found->lower_offset; + } + + return U64_MAX; +} + +static inline +u64 *SSDFS_RECOVERY_CUR_OFF_PTR(struct ssdfs_recovery_env *env) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + return &env->found->current_offset; +} + +static inline +void SSDFS_RECOVERY_SET_FAST_SEARCH_TRY(struct ssdfs_recovery_env *env) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + *SSDFS_RECOVERY_CUR_OFF_PTR(env) = env->found->lower_offset; + env->found->search_phase = SSDFS_RECOVERY_FAST_SEARCH; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("lower_offset %llu, " + "middle_offset %llu, " + "upper_offset %llu, " + "current_offset %llu, " + "search_phase %#x\n", + env->found->lower_offset, + env->found->middle_offset, + env->found->upper_offset, + env->found->current_offset, + env->found->search_phase); +#endif /* CONFIG_SSDFS_DEBUG */ +} + +static inline +void SSDFS_RECOVERY_SET_FIRST_SLOW_TRY(struct ssdfs_recovery_env *env) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + *SSDFS_RECOVERY_CUR_OFF_PTR(env) = env->found->middle_offset; + env->found->search_phase = SSDFS_RECOVERY_FIRST_SLOW_TRY; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("lower_offset %llu, " + "middle_offset %llu, " + "upper_offset %llu, " + "current_offset %llu, " + "search_phase %#x\n", + env->found->lower_offset, + env->found->middle_offset, + env->found->upper_offset, + env->found->current_offset, + env->found->search_phase); +#endif /* CONFIG_SSDFS_DEBUG */ +} + +static inline +bool is_second_slow_try_possible(struct ssdfs_recovery_env *env) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + return env->found->lower_offset < env->found->middle_offset; +} + +static inline +void SSDFS_RECOVERY_SET_SECOND_SLOW_TRY(struct ssdfs_recovery_env *env) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + *SSDFS_RECOVERY_CUR_OFF_PTR(env) = env->found->lower_offset; + env->found->search_phase = SSDFS_RECOVERY_SECOND_SLOW_TRY; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("lower_offset %llu, " + "middle_offset %llu, " + "upper_offset %llu, " + "current_offset %llu, " + "search_phase %#x\n", + env->found->lower_offset, + env->found->middle_offset, + env->found->upper_offset, + env->found->current_offset, + env->found->search_phase); +#endif /* CONFIG_SSDFS_DEBUG */ +} + +static inline +bool is_third_slow_try_possible(struct ssdfs_recovery_env *env) +{ + u64 offset; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->fsi || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + offset = env->found->start_peb * env->fsi->erasesize; + return offset < env->found->lower_offset; +} + +static inline +void SSDFS_RECOVERY_SET_THIRD_SLOW_TRY(struct ssdfs_recovery_env *env) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!env || !env->fsi || !env->found); +#endif /* CONFIG_SSDFS_DEBUG */ + + *SSDFS_RECOVERY_CUR_OFF_PTR(env) = env->found->lower_offset; + env->found->search_phase = SSDFS_RECOVERY_THIRD_SLOW_TRY; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("lower_offset %llu, " + "middle_offset %llu, " + "upper_offset %llu, " + "current_offset %llu, " + "search_phase %#x\n", + env->found->lower_offset, + env->found->middle_offset, + env->found->upper_offset, + env->found->current_offset, + env->found->search_phase); +#endif /* CONFIG_SSDFS_DEBUG */ +} + +/* + * Recovery API + */ +int ssdfs_recovery_start_thread(struct ssdfs_recovery_env *env, + u32 id); +int ssdfs_recovery_stop_thread(struct ssdfs_recovery_env *env); +void ssdfs_backup_sb_info2(struct ssdfs_recovery_env *env); +void ssdfs_restore_sb_info2(struct ssdfs_recovery_env *env); +int ssdfs_read_checked_sb_info3(struct ssdfs_recovery_env *env, + u64 peb_id, u32 pages_off); +int __ssdfs_find_any_valid_volume_header2(struct ssdfs_recovery_env *env, + u64 start_offset, + u64 end_offset, + u64 step); +int ssdfs_find_any_valid_sb_segment2(struct ssdfs_recovery_env *env, + u64 threshold_peb); +bool is_cur_main_sb_peb_exhausted(struct ssdfs_recovery_env *env); +bool is_cur_copy_sb_peb_exhausted(struct ssdfs_recovery_env *env); +int ssdfs_check_next_sb_pebs_pair(struct ssdfs_recovery_env *env); +int ssdfs_check_reserved_sb_pebs_pair(struct ssdfs_recovery_env *env); +int ssdfs_find_latest_valid_sb_segment2(struct ssdfs_recovery_env *env); +int ssdfs_find_last_sb_seg_outside_fragment(struct ssdfs_recovery_env *env); +int ssdfs_recovery_try_fast_search(struct ssdfs_recovery_env *env); +int ssdfs_recovery_try_slow_search(struct ssdfs_recovery_env *env); + +#endif /* _SSDFS_RECOVERY_H */ -- 2.34.1