new file mode 100644
@@ -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@dubeyko.com>
+ * 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@dubeyko.com>
+ *
+ * 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;
+}
new file mode 100644
@@ -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@dubeyko.com>
+ * http://www.ssdfs.org/
+ * All rights reserved.
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ */
+
+#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 */
This patch implements logic of search/recovery of last actual superblock. Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> CC: Viacheslav Dubeyko <viacheslav.dubeyko@bytedance.com> CC: Luka Perkov <luka.perkov@sartura.hr> CC: Bruno Banelli <bruno.banelli@sartura.hr> --- 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