new file mode 100644
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/fs_error.c - logic for the case of file system errors detection.
+ *
+ * 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/page-flags.h>
+#include <linux/pagevec.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "ssdfs.h"
+
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+atomic64_t ssdfs_fs_error_page_leaks;
+atomic64_t ssdfs_fs_error_memory_leaks;
+atomic64_t ssdfs_fs_error_cache_leaks;
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+/*
+ * void ssdfs_fs_error_cache_leaks_increment(void *kaddr)
+ * void ssdfs_fs_error_cache_leaks_decrement(void *kaddr)
+ * void *ssdfs_fs_error_kmalloc(size_t size, gfp_t flags)
+ * void *ssdfs_fs_error_kzalloc(size_t size, gfp_t flags)
+ * void *ssdfs_fs_error_kcalloc(size_t n, size_t size, gfp_t flags)
+ * void ssdfs_fs_error_kfree(void *kaddr)
+ * struct page *ssdfs_fs_error_alloc_page(gfp_t gfp_mask)
+ * struct page *ssdfs_fs_error_add_pagevec_page(struct pagevec *pvec)
+ * void ssdfs_fs_error_free_page(struct page *page)
+ * void ssdfs_fs_error_pagevec_release(struct pagevec *pvec)
+ */
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ SSDFS_MEMORY_LEAKS_CHECKER_FNS(fs_error)
+#else
+ SSDFS_MEMORY_ALLOCATOR_FNS(fs_error)
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+void ssdfs_fs_error_memory_leaks_init(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ atomic64_set(&ssdfs_fs_error_page_leaks, 0);
+ atomic64_set(&ssdfs_fs_error_memory_leaks, 0);
+ atomic64_set(&ssdfs_fs_error_cache_leaks, 0);
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+void ssdfs_fs_error_check_memory_leaks(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ if (atomic64_read(&ssdfs_fs_error_page_leaks) != 0) {
+ SSDFS_ERR("FS ERROR: "
+ "memory leaks include %lld pages\n",
+ atomic64_read(&ssdfs_fs_error_page_leaks));
+ }
+
+ if (atomic64_read(&ssdfs_fs_error_memory_leaks) != 0) {
+ SSDFS_ERR("FS ERROR: "
+ "memory allocator suffers from %lld leaks\n",
+ atomic64_read(&ssdfs_fs_error_memory_leaks));
+ }
+
+ if (atomic64_read(&ssdfs_fs_error_cache_leaks) != 0) {
+ SSDFS_ERR("FS ERROR: "
+ "caches suffers from %lld leaks\n",
+ atomic64_read(&ssdfs_fs_error_cache_leaks));
+ }
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+static void ssdfs_handle_error(struct super_block *sb)
+{
+ struct ssdfs_fs_info *fsi = SSDFS_FS_I(sb);
+
+ if (sb->s_flags & SB_RDONLY)
+ return;
+
+ spin_lock(&fsi->volume_state_lock);
+ fsi->fs_state = SSDFS_ERROR_FS;
+ spin_unlock(&fsi->volume_state_lock);
+
+ if (ssdfs_test_opt(fsi->mount_opts, ERRORS_PANIC)) {
+ panic("SSDFS (device %s): panic forced after error\n",
+ fsi->devops->device_name(sb));
+ } else if (ssdfs_test_opt(fsi->mount_opts, ERRORS_RO)) {
+ SSDFS_CRIT("Remounting filesystem read-only\n");
+ /*
+ * Make sure updated value of ->s_mount_flags will be visible
+ * before ->s_flags update
+ */
+ smp_wmb();
+ sb->s_flags |= SB_RDONLY;
+ }
+}
+
+void ssdfs_fs_error(struct super_block *sb, const char *file,
+ const char *function, unsigned int line,
+ const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+ vaf.fmt = fmt;
+ vaf.va = &args;
+ pr_crit("SSDFS error (device %s): pid %d:%s:%d %s(): comm %s: %pV",
+ SSDFS_FS_I(sb)->devops->device_name(sb), current->pid,
+ file, line, function, current->comm, &vaf);
+ va_end(args);
+
+ ssdfs_handle_error(sb);
+}
+
+int ssdfs_set_page_dirty(struct page *page)
+{
+ struct address_space *mapping = page->mapping;
+ unsigned long flags;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page_index: %llu, mapping %p\n",
+ (u64)page_index(page), mapping);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (!PageLocked(page)) {
+ SSDFS_WARN("page isn't locked: "
+ "page_index %llu, mapping %p\n",
+ (u64)page_index(page), mapping);
+ return -ERANGE;
+ }
+
+ SetPageDirty(page);
+
+ if (mapping) {
+ xa_lock_irqsave(&mapping->i_pages, flags);
+ __xa_set_mark(&mapping->i_pages, page_index(page),
+ PAGECACHE_TAG_DIRTY);
+ xa_unlock_irqrestore(&mapping->i_pages, flags);
+ }
+
+ return 0;
+}
+
+int __ssdfs_clear_dirty_page(struct page *page)
+{
+ struct address_space *mapping = page->mapping;
+ unsigned long flags;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page_index: %llu, mapping %p\n",
+ (u64)page_index(page), mapping);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (!PageLocked(page)) {
+ SSDFS_WARN("page isn't locked: "
+ "page_index %llu, mapping %p\n",
+ (u64)page_index(page), mapping);
+ return -ERANGE;
+ }
+
+ if (mapping) {
+ xa_lock_irqsave(&mapping->i_pages, flags);
+ if (test_bit(PG_dirty, &page->flags)) {
+ __xa_clear_mark(&mapping->i_pages,
+ page_index(page),
+ PAGECACHE_TAG_DIRTY);
+ }
+ xa_unlock_irqrestore(&mapping->i_pages, flags);
+ }
+
+ TestClearPageDirty(page);
+
+ return 0;
+}
+
+int ssdfs_clear_dirty_page(struct page *page)
+{
+ struct address_space *mapping = page->mapping;
+ unsigned long flags;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page_index: %llu, mapping %p\n",
+ (u64)page_index(page), mapping);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (!PageLocked(page)) {
+ SSDFS_WARN("page isn't locked: "
+ "page_index %llu, mapping %p\n",
+ (u64)page_index(page), mapping);
+ return -ERANGE;
+ }
+
+ if (mapping) {
+ xa_lock_irqsave(&mapping->i_pages, flags);
+ if (test_bit(PG_dirty, &page->flags)) {
+ __xa_clear_mark(&mapping->i_pages,
+ page_index(page),
+ PAGECACHE_TAG_DIRTY);
+ xa_unlock_irqrestore(&mapping->i_pages, flags);
+ return clear_page_dirty_for_io(page);
+ }
+ xa_unlock_irqrestore(&mapping->i_pages, flags);
+ return 0;
+ }
+
+ TestClearPageDirty(page);
+
+ return 0;
+}
+
+/*
+ * ssdfs_clear_dirty_pages - discard dirty pages in address space
+ * @mapping: address space with dirty pages for discarding
+ */
+void ssdfs_clear_dirty_pages(struct address_space *mapping)
+{
+ struct pagevec pvec;
+ unsigned int i;
+ pgoff_t index = 0;
+ int err;
+
+ pagevec_init(&pvec);
+
+ while (pagevec_lookup_tag(&pvec, mapping, &index,
+ PAGECACHE_TAG_DIRTY)) {
+ for (i = 0; i < pagevec_count(&pvec); i++) {
+ struct page *page = pvec.pages[i];
+
+ ssdfs_lock_page(page);
+ err = ssdfs_clear_dirty_page(page);
+ ssdfs_unlock_page(page);
+
+ if (unlikely(err)) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("fail clear page dirty: "
+ "page_index %llu\n",
+ (u64)page_index(page));
+#endif /* CONFIG_SSDFS_DEBUG */
+ }
+ }
+ ssdfs_fs_error_pagevec_release(&pvec);
+ cond_resched();
+ }
+}
new file mode 100644
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/options.c - mount options parsing.
+ *
+ * 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/string.h>
+#include <linux/kernel.h>
+#include <linux/parser.h>
+#include <linux/mount.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/pagevec.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "ssdfs.h"
+#include "segment_bitmap.h"
+
+/*
+ * SSDFS mount options.
+ *
+ * Opt_compr: change default compressor
+ * Opt_fs_err_panic: panic if fs error is detected
+ * Opt_fs_err_ro: remount in RO state if fs error is detected
+ * Opt_fs_err_cont: continue execution if fs error is detected
+ * Opt_ignore_fs_state: ignore on-disk file system state during mount
+ * Opt_err: just end of array marker
+ */
+enum {
+ Opt_compr,
+ Opt_fs_err_panic,
+ Opt_fs_err_ro,
+ Opt_fs_err_cont,
+ Opt_ignore_fs_state,
+ Opt_err,
+};
+
+static const match_table_t tokens = {
+ {Opt_compr, "compr=%s"},
+ {Opt_fs_err_panic, "errors=panic"},
+ {Opt_fs_err_ro, "errors=remount-ro"},
+ {Opt_fs_err_cont, "errors=continue"},
+ {Opt_ignore_fs_state, "fs_state=ignore"},
+ {Opt_err, NULL},
+};
+
+int ssdfs_parse_options(struct ssdfs_fs_info *fs_info, char *data)
+{
+ substring_t args[MAX_OPT_ARGS];
+ char *p, *name;
+
+ if (!data)
+ return 0;
+
+ while ((p = strsep(&data, ","))) {
+ int token;
+
+ if (!*p)
+ continue;
+
+ token = match_token(p, tokens, args);
+ switch (token) {
+ case Opt_compr:
+ name = match_strdup(&args[0]);
+
+ if (!name)
+ return -ENOMEM;
+ if (!strcmp(name, "none"))
+ ssdfs_set_opt(fs_info->mount_opts,
+ COMPR_MODE_NONE);
+#ifdef CONFIG_SSDFS_ZLIB
+ else if (!strcmp(name, "zlib"))
+ ssdfs_set_opt(fs_info->mount_opts,
+ COMPR_MODE_ZLIB);
+#endif
+#ifdef CONFIG_SSDFS_LZO
+ else if (!strcmp(name, "lzo"))
+ ssdfs_set_opt(fs_info->mount_opts,
+ COMPR_MODE_LZO);
+#endif
+ else {
+ SSDFS_ERR("unknown compressor %s\n", name);
+ ssdfs_kfree(name);
+ return -EINVAL;
+ }
+ ssdfs_kfree(name);
+ break;
+
+ case Opt_fs_err_panic:
+ /* Clear possible default initialization */
+ ssdfs_clear_opt(fs_info->mount_opts, ERRORS_RO);
+ ssdfs_clear_opt(fs_info->mount_opts, ERRORS_CONT);
+ ssdfs_set_opt(fs_info->mount_opts, ERRORS_PANIC);
+ break;
+
+ case Opt_fs_err_ro:
+ /* Clear possible default initialization */
+ ssdfs_clear_opt(fs_info->mount_opts, ERRORS_PANIC);
+ ssdfs_clear_opt(fs_info->mount_opts, ERRORS_CONT);
+ ssdfs_set_opt(fs_info->mount_opts, ERRORS_RO);
+ break;
+
+ case Opt_fs_err_cont:
+ /* Clear possible default initialization */
+ ssdfs_clear_opt(fs_info->mount_opts, ERRORS_PANIC);
+ ssdfs_clear_opt(fs_info->mount_opts, ERRORS_RO);
+ ssdfs_set_opt(fs_info->mount_opts, ERRORS_CONT);
+ break;
+
+ case Opt_ignore_fs_state:
+ ssdfs_set_opt(fs_info->mount_opts, IGNORE_FS_STATE);
+ break;
+
+ default:
+ SSDFS_ERR("unrecognized mount option '%s'\n", p);
+ return -EINVAL;
+ }
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("DONE: parse options\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ return 0;
+}
+
+void ssdfs_initialize_fs_errors_option(struct ssdfs_fs_info *fsi)
+{
+ if (fsi->fs_errors == SSDFS_ERRORS_PANIC)
+ ssdfs_set_opt(fsi->mount_opts, ERRORS_PANIC);
+ else if (fsi->fs_errors == SSDFS_ERRORS_RO)
+ ssdfs_set_opt(fsi->mount_opts, ERRORS_RO);
+ else if (fsi->fs_errors == SSDFS_ERRORS_CONTINUE)
+ ssdfs_set_opt(fsi->mount_opts, ERRORS_CONT);
+ else {
+ u16 def_behaviour = SSDFS_ERRORS_DEFAULT;
+
+ switch (def_behaviour) {
+ case SSDFS_ERRORS_PANIC:
+ ssdfs_set_opt(fsi->mount_opts, ERRORS_PANIC);
+ break;
+
+ case SSDFS_ERRORS_RO:
+ ssdfs_set_opt(fsi->mount_opts, ERRORS_RO);
+ break;
+ }
+ }
+}
+
+int ssdfs_show_options(struct seq_file *seq, struct dentry *root)
+{
+ struct ssdfs_fs_info *fsi = SSDFS_FS_I(root->d_sb);
+ char *compress_type;
+
+ if (ssdfs_test_opt(fsi->mount_opts, COMPR_MODE_ZLIB)) {
+ compress_type = "zlib";
+ seq_printf(seq, ",compress=%s", compress_type);
+ } else if (ssdfs_test_opt(fsi->mount_opts, COMPR_MODE_LZO)) {
+ compress_type = "lzo";
+ seq_printf(seq, ",compress=%s", compress_type);
+ }
+
+ if (ssdfs_test_opt(fsi->mount_opts, ERRORS_PANIC))
+ seq_puts(seq, ",errors=panic");
+ else if (ssdfs_test_opt(fsi->mount_opts, ERRORS_RO))
+ seq_puts(seq, ",errors=remount-ro");
+ else if (ssdfs_test_opt(fsi->mount_opts, ERRORS_CONT))
+ seq_puts(seq, ",errors=continue");
+
+ if (ssdfs_test_opt(fsi->mount_opts, IGNORE_FS_STATE))
+ seq_puts(seq, ",fs_state=ignore");
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,651 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/readwrite.c - read/write primitive operations.
+ *
+ * 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/kernel.h>
+#include <linux/rwsem.h>
+#include <linux/pagevec.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "ssdfs.h"
+
+#include <trace/events/ssdfs.h>
+
+/*
+ * ssdfs_read_page_from_volume() - read page from volume
+ * @fsi: pointer on shared file system object
+ * @peb_id: PEB identification number
+ * @bytes_off: offset from PEB's begining in bytes
+ * @page: memory page
+ *
+ * This function tries to read page from the volume.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-EIO - I/O error.
+ */
+int ssdfs_read_page_from_volume(struct ssdfs_fs_info *fsi,
+ u64 peb_id, u32 bytes_off,
+ struct page *page)
+{
+ struct super_block *sb;
+ loff_t offset;
+ u32 peb_size;
+ u32 pagesize;
+ u32 pages_per_peb;
+ u32 pages_off;
+ int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!fsi || !page);
+ BUG_ON(!fsi->devops->readpage);
+
+ SSDFS_DBG("fsi %p, peb_id %llu, bytes_off %u, page %p\n",
+ fsi, peb_id, bytes_off, page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ sb = fsi->sb;
+ pagesize = fsi->pagesize;
+ pages_per_peb = fsi->pages_per_peb;
+ pages_off = bytes_off / pagesize;
+
+ if (pages_per_peb >= (U32_MAX / pagesize)) {
+ SSDFS_ERR("pages_per_peb %u >= U32_MAX / pagesize %u\n",
+ pages_per_peb, pagesize);
+ return -EINVAL;
+ }
+
+ peb_size = pages_per_peb * pagesize;
+
+ if (peb_id >= div_u64(ULLONG_MAX, peb_size)) {
+ SSDFS_ERR("peb_id %llu >= ULLONG_MAX / peb_size %u\n",
+ peb_id, peb_size);
+ return -EINVAL;
+ }
+
+ offset = peb_id * peb_size;
+
+ if (pages_off >= pages_per_peb) {
+ SSDFS_ERR("pages_off %u >= pages_per_peb %u\n",
+ pages_off, pages_per_peb);
+ return -EINVAL;
+ }
+
+ if (pages_off >= (U32_MAX / pagesize)) {
+ SSDFS_ERR("pages_off %u >= U32_MAX / pagesize %u\n",
+ pages_off, fsi->pagesize);
+ return -EINVAL;
+ }
+
+ offset += bytes_off;
+
+ if (fsi->devops->peb_isbad) {
+ err = fsi->devops->peb_isbad(sb, offset);
+ if (err) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("offset %llu is in bad PEB: err %d\n",
+ (unsigned long long)offset, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EIO;
+ }
+ }
+
+ err = fsi->devops->readpage(sb, page, offset);
+ if (unlikely(err)) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("fail to read page: offset %llu, err %d\n",
+ (unsigned long long)offset, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * ssdfs_read_pagevec_from_volume() - read pagevec from volume
+ * @fsi: pointer on shared file system object
+ * @peb_id: PEB identification number
+ * @bytes_off: offset from PEB's begining in bytes
+ * @pvec: pagevec [in|out]
+ *
+ * This function tries to read pages from the volume.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-EIO - I/O error.
+ */
+int ssdfs_read_pagevec_from_volume(struct ssdfs_fs_info *fsi,
+ u64 peb_id, u32 bytes_off,
+ struct pagevec *pvec)
+{
+ struct super_block *sb;
+ loff_t offset;
+ u32 peb_size;
+ u32 pagesize;
+ u32 pages_per_peb;
+ u32 pages_off;
+ int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!fsi || !pvec);
+ BUG_ON(!fsi->devops->readpages);
+
+ SSDFS_DBG("fsi %p, peb_id %llu, bytes_off %u, pvec %p\n",
+ fsi, peb_id, bytes_off, pvec);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ sb = fsi->sb;
+ pagesize = fsi->pagesize;
+ pages_per_peb = fsi->pages_per_peb;
+ pages_off = bytes_off / pagesize;
+
+ if (pages_per_peb >= (U32_MAX / pagesize)) {
+ SSDFS_ERR("pages_per_peb %u >= U32_MAX / pagesize %u\n",
+ pages_per_peb, pagesize);
+ return -EINVAL;
+ }
+
+ peb_size = pages_per_peb * pagesize;
+
+ if (peb_id >= div_u64(ULLONG_MAX, peb_size)) {
+ SSDFS_ERR("peb_id %llu >= ULLONG_MAX / peb_size %u\n",
+ peb_id, peb_size);
+ return -EINVAL;
+ }
+
+ offset = peb_id * peb_size;
+
+ if (pages_off >= pages_per_peb) {
+ SSDFS_ERR("pages_off %u >= pages_per_peb %u\n",
+ pages_off, pages_per_peb);
+ return -EINVAL;
+ }
+
+ if (pages_off >= (U32_MAX / pagesize)) {
+ SSDFS_ERR("pages_off %u >= U32_MAX / pagesize %u\n",
+ pages_off, fsi->pagesize);
+ return -EINVAL;
+ }
+
+ offset += bytes_off;
+
+ if (fsi->devops->peb_isbad) {
+ err = fsi->devops->peb_isbad(sb, offset);
+ if (err) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("offset %llu is in bad PEB: err %d\n",
+ (unsigned long long)offset, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EIO;
+ }
+ }
+
+ err = fsi->devops->readpages(sb, pvec, offset);
+ if (unlikely(err)) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("fail to read pvec: offset %llu, err %d\n",
+ (unsigned long long)offset, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * ssdfs_aligned_read_buffer() - aligned read from volume into buffer
+ * @fsi: pointer on shared file system object
+ * @peb_id: PEB identification number
+ * @bytes_off: offset from PEB's begining in bytes
+ * @buf: buffer
+ * @size: buffer size
+ * @read_bytes: really read bytes
+ *
+ * This function tries to read in buffer by means of page aligned
+ * request. It reads part of requested data in the case of unaligned
+ * request. The @read_bytes returns value of really read data.
+ *
+ * RETURN:
+ * [success] - buffer contains data of @read_bytes in size.
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-EIO - I/O error.
+ */
+int ssdfs_aligned_read_buffer(struct ssdfs_fs_info *fsi,
+ u64 peb_id, u32 bytes_off,
+ void *buf, size_t size,
+ size_t *read_bytes)
+{
+ struct super_block *sb;
+ loff_t offset;
+ u32 peb_size;
+ u32 pagesize;
+ u32 pages_per_peb;
+ u32 pages_off;
+ int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!fsi || !buf);
+ BUG_ON(!fsi->devops->read);
+
+ SSDFS_DBG("fsi %p, peb_id %llu, bytes_off %u, buf %p, size %zu\n",
+ fsi, peb_id, bytes_off, buf, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ sb = fsi->sb;
+ pagesize = fsi->pagesize;
+ pages_per_peb = fsi->pages_per_peb;
+ pages_off = bytes_off / pagesize;
+
+ if (pages_per_peb >= (U32_MAX / pagesize)) {
+ SSDFS_ERR("pages_per_peb %u >= U32_MAX / pagesize %u\n",
+ pages_per_peb, pagesize);
+ return -EINVAL;
+ }
+
+ peb_size = pages_per_peb * pagesize;
+
+ if (peb_id >= div_u64(ULLONG_MAX, peb_size)) {
+ SSDFS_ERR("peb_id %llu >= ULLONG_MAX / peb_size %u\n",
+ peb_id, peb_size);
+ return -EINVAL;
+ }
+
+ offset = peb_id * peb_size;
+
+ if (pages_off >= pages_per_peb) {
+ SSDFS_ERR("pages_off %u >= pages_per_peb %u\n",
+ pages_off, pages_per_peb);
+ return -EINVAL;
+ }
+
+ if (pages_off >= (U32_MAX / pagesize)) {
+ SSDFS_ERR("pages_off %u >= U32_MAX / pagesize %u\n",
+ pages_off, fsi->pagesize);
+ return -EINVAL;
+ }
+
+ if (size > pagesize) {
+ SSDFS_ERR("size %zu > pagesize %u\n",
+ size, fsi->pagesize);
+ return -EINVAL;
+ }
+
+ offset += bytes_off;
+
+ *read_bytes = ((pages_off + 1) * pagesize) - bytes_off;
+ *read_bytes = min_t(size_t, *read_bytes, size);
+
+ if (fsi->devops->peb_isbad) {
+ err = fsi->devops->peb_isbad(sb, offset);
+ if (err) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("offset %llu is in bad PEB: err %d\n",
+ (unsigned long long)offset, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EIO;
+ }
+ }
+
+ err = fsi->devops->read(sb, offset, *read_bytes, buf);
+ if (unlikely(err)) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("fail to read from offset %llu, size %zu, err %d\n",
+ (unsigned long long)offset, *read_bytes, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * ssdfs_unaligned_read_buffer() - unaligned read from volume into buffer
+ * @fsi: pointer on shared file system object
+ * @peb_id: PEB identification number
+ * @bytes_off: offset from PEB's begining in bytes
+ * @buf: buffer
+ * @size: buffer size
+ *
+ * This function tries to read in buffer by means of page unaligned
+ * request.
+ *
+ * RETURN:
+ * [success] - buffer contains data of @size in bytes.
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-EIO - I/O error.
+ */
+int ssdfs_unaligned_read_buffer(struct ssdfs_fs_info *fsi,
+ u64 peb_id, u32 bytes_off,
+ void *buf, size_t size)
+{
+ size_t read_bytes = 0;
+ int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!fsi || !buf);
+ BUG_ON(!fsi->devops->read);
+
+ SSDFS_DBG("fsi %p, peb_id %llu, bytes_off %u, buf %p, size %zu\n",
+ fsi, peb_id, bytes_off, buf, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ do {
+ size_t iter_size = size - read_bytes;
+ size_t iter_read_bytes;
+
+ err = ssdfs_aligned_read_buffer(fsi, peb_id,
+ bytes_off + read_bytes,
+ buf + read_bytes,
+ iter_size,
+ &iter_read_bytes);
+ if (err) {
+ SSDFS_ERR("fail to read from peb_id %llu, offset %zu, "
+ "size %zu, err %d\n",
+ peb_id, (size_t)(bytes_off + read_bytes),
+ iter_size, err);
+ return err;
+ }
+
+ read_bytes += iter_read_bytes;
+ } while (read_bytes < size);
+
+ return 0;
+}
+
+/*
+ * ssdfs_can_write_sb_log() - check that superblock log can be written
+ * @sb: pointer on superblock object
+ * @sb_log: superblock log's extent
+ *
+ * This function checks that superblock log can be written
+ * successfully.
+ *
+ * RETURN:
+ * [success] - superblock log can be written successfully.
+ * [failure] - error code:
+ *
+ * %-ERANGE - invalid extent.
+ */
+int ssdfs_can_write_sb_log(struct super_block *sb,
+ struct ssdfs_peb_extent *sb_log)
+{
+ struct ssdfs_fs_info *fsi;
+ u64 cur_peb;
+ u32 page_offset;
+ u32 log_size;
+ loff_t byte_off;
+ int i;
+ int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!sb || !sb_log);
+
+ SSDFS_DBG("leb_id %llu, peb_id %llu, "
+ "page_offset %u, pages_count %u\n",
+ sb_log->leb_id, sb_log->peb_id,
+ sb_log->page_offset, sb_log->pages_count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ fsi = SSDFS_FS_I(sb);
+
+ if (!fsi->devops->can_write_page)
+ return 0;
+
+ cur_peb = sb_log->peb_id;
+ page_offset = sb_log->page_offset;
+ log_size = sb_log->pages_count;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("cur_peb %llu, page_offset %u, "
+ "log_size %u, pages_per_peb %u\n",
+ cur_peb, page_offset,
+ log_size, fsi->pages_per_peb);
+
+ if (log_size > fsi->pages_per_seg) {
+ SSDFS_ERR("log_size value %u is too big\n",
+ log_size);
+ return -ERANGE;
+ }
+
+ if (cur_peb > div_u64(ULLONG_MAX, fsi->pages_per_seg)) {
+ SSDFS_ERR("cur_peb value %llu is too big\n",
+ cur_peb);
+ return -ERANGE;
+ }
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ byte_off = cur_peb * fsi->pages_per_peb;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ if (byte_off > div_u64(ULLONG_MAX, fsi->pagesize)) {
+ SSDFS_ERR("byte_off value %llu is too big\n",
+ byte_off);
+ return -ERANGE;
+ }
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ byte_off *= fsi->pagesize;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ if ((u64)page_offset > div_u64(ULLONG_MAX, fsi->pagesize)) {
+ SSDFS_ERR("page_offset value %u is too big\n",
+ page_offset);
+ return -ERANGE;
+ }
+
+ if (byte_off > (ULLONG_MAX - ((u64)page_offset * fsi->pagesize))) {
+ SSDFS_ERR("byte_off value %llu is too big\n",
+ byte_off);
+ return -ERANGE;
+ }
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ byte_off += (u64)page_offset * fsi->pagesize;
+
+ for (i = 0; i < log_size; i++) {
+#ifdef CONFIG_SSDFS_DEBUG
+ if (byte_off > (ULLONG_MAX - (i * fsi->pagesize))) {
+ SSDFS_ERR("offset value %llu is too big\n",
+ byte_off);
+ return -ERANGE;
+ }
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ err = fsi->devops->can_write_page(sb, byte_off, true);
+ if (err) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page can't be written: err %d\n", err);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return err;
+ }
+
+ byte_off += fsi->pagesize;
+ }
+
+ return 0;
+}
+
+int ssdfs_unaligned_read_pagevec(struct pagevec *pvec,
+ u32 offset, u32 size,
+ void *buf)
+{
+ struct page *page;
+ u32 page_off;
+ u32 bytes_off;
+ size_t read_bytes = 0;
+ int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!pvec || !buf);
+
+ SSDFS_DBG("pvec %p, offset %u, size %u, buf %p\n",
+ pvec, offset, size, buf);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ do {
+ size_t iter_read_bytes;
+ size_t cur_off;
+
+ bytes_off = offset + read_bytes;
+ page_off = bytes_off / PAGE_SIZE;
+ cur_off = bytes_off % PAGE_SIZE;
+
+ iter_read_bytes = min_t(size_t,
+ (size_t)(size - read_bytes),
+ (size_t)(PAGE_SIZE - cur_off));
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page_off %u, cur_off %zu, "
+ "iter_read_bytes %zu\n",
+ page_off, cur_off,
+ iter_read_bytes);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (page_off >= pagevec_count(pvec)) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page out of range: index %u: "
+ "offset %zu, pagevec_count %u\n",
+ page_off, cur_off,
+ pagevec_count(pvec));
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -E2BIG;
+ }
+
+ page = pvec->pages[page_off];
+
+ ssdfs_lock_page(page);
+ err = ssdfs_memcpy_from_page(buf, read_bytes, size,
+ page, cur_off, PAGE_SIZE,
+ iter_read_bytes);
+ ssdfs_unlock_page(page);
+
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to copy: "
+ "read_bytes %zu, offset %zu, "
+ "iter_read_bytes %zu, err %d\n",
+ read_bytes, cur_off,
+ iter_read_bytes, err);
+ return err;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page %p, count %d\n",
+ page, page_ref_count(page));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ read_bytes += iter_read_bytes;
+ } while (read_bytes < size);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("BUF DUMP\n");
+ print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+ buf, size);
+ SSDFS_DBG("\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ return 0;
+}
+
+int ssdfs_unaligned_write_pagevec(struct pagevec *pvec,
+ u32 offset, u32 size,
+ void *buf)
+{
+ struct page *page;
+ u32 page_off;
+ u32 bytes_off;
+ size_t written_bytes = 0;
+ int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!pvec || !buf);
+
+ SSDFS_DBG("pvec %p, offset %u, size %u, buf %p\n",
+ pvec, offset, size, buf);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ do {
+ size_t iter_write_bytes;
+ size_t cur_off;
+
+ bytes_off = offset + written_bytes;
+ page_off = bytes_off / PAGE_SIZE;
+ cur_off = bytes_off % PAGE_SIZE;
+
+ iter_write_bytes = min_t(size_t,
+ (size_t)(size - written_bytes),
+ (size_t)(PAGE_SIZE - cur_off));
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("bytes_off %u, page_off %u, "
+ "cur_off %zu, written_bytes %zu, "
+ "iter_write_bytes %zu\n",
+ bytes_off, page_off, cur_off,
+ written_bytes, iter_write_bytes);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (page_off >= pagevec_count(pvec)) {
+ SSDFS_ERR("invalid page index %u: "
+ "offset %zu, pagevec_count %u\n",
+ page_off, cur_off,
+ pagevec_count(pvec));
+ return -EINVAL;
+ }
+
+ page = pvec->pages[page_off];
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!page);
+ WARN_ON(!PageLocked(page));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ err = ssdfs_memcpy_to_page(page, cur_off, PAGE_SIZE,
+ buf, written_bytes, size,
+ iter_write_bytes);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to copy: "
+ "written_bytes %zu, offset %zu, "
+ "iter_write_bytes %zu, err %d\n",
+ written_bytes, cur_off,
+ iter_write_bytes, err);
+ return err;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("page %p, count %d\n",
+ page, page_ref_count(page));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ written_bytes += iter_write_bytes;
+ } while (written_bytes < size);
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,1844 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/super.c - module and superblock management.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/super.h>
+#include <linux/exportfs.h>
+#include <linux/pagevec.h>
+#include <linux/blkdev.h>
+#include <linux/backing-dev.h>
+#include <linux/delay.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "ssdfs.h"
+#include "version.h"
+#include "segment_bitmap.h"
+#include "offset_translation_table.h"
+#include "page_array.h"
+#include "page_vector.h"
+#include "peb_container.h"
+#include "segment.h"
+#include "segment_tree.h"
+#include "current_segment.h"
+#include "peb_mapping_table.h"
+#include "extents_queue.h"
+#include "btree_search.h"
+#include "btree_node.h"
+#include "btree.h"
+#include "inodes_tree.h"
+#include "shared_extents_tree.h"
+#include "shared_dictionary.h"
+#include "extents_tree.h"
+#include "dentries_tree.h"
+#include "xattr_tree.h"
+#include "xattr.h"
+#include "acl.h"
+#include "snapshots_tree.h"
+#include "invalidated_extents_tree.h"
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/ssdfs.h>
+
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+atomic64_t ssdfs_allocated_pages;
+atomic64_t ssdfs_memory_leaks;
+atomic64_t ssdfs_super_page_leaks;
+atomic64_t ssdfs_super_memory_leaks;
+atomic64_t ssdfs_super_cache_leaks;
+
+atomic64_t ssdfs_locked_pages;
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+/*
+ * void ssdfs_super_cache_leaks_increment(void *kaddr)
+ * void ssdfs_super_cache_leaks_decrement(void *kaddr)
+ * void *ssdfs_super_kmalloc(size_t size, gfp_t flags)
+ * void *ssdfs_super_kzalloc(size_t size, gfp_t flags)
+ * void *ssdfs_super_kcalloc(size_t n, size_t size, gfp_t flags)
+ * void ssdfs_super_kfree(void *kaddr)
+ * struct page *ssdfs_super_alloc_page(gfp_t gfp_mask)
+ * struct page *ssdfs_super_add_pagevec_page(struct pagevec *pvec)
+ * void ssdfs_super_free_page(struct page *page)
+ * void ssdfs_super_pagevec_release(struct pagevec *pvec)
+ */
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ SSDFS_MEMORY_LEAKS_CHECKER_FNS(super)
+#else
+ SSDFS_MEMORY_ALLOCATOR_FNS(super)
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+void ssdfs_super_memory_leaks_init(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ atomic64_set(&ssdfs_super_page_leaks, 0);
+ atomic64_set(&ssdfs_super_memory_leaks, 0);
+ atomic64_set(&ssdfs_super_cache_leaks, 0);
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+void ssdfs_super_check_memory_leaks(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ if (atomic64_read(&ssdfs_super_page_leaks) != 0) {
+ SSDFS_ERR("SUPER: "
+ "memory leaks include %lld pages\n",
+ atomic64_read(&ssdfs_super_page_leaks));
+ }
+
+ if (atomic64_read(&ssdfs_super_memory_leaks) != 0) {
+ SSDFS_ERR("SUPER: "
+ "memory allocator suffers from %lld leaks\n",
+ atomic64_read(&ssdfs_super_memory_leaks));
+ }
+
+ if (atomic64_read(&ssdfs_super_cache_leaks) != 0) {
+ SSDFS_ERR("SUPER: "
+ "caches suffers from %lld leaks\n",
+ atomic64_read(&ssdfs_super_cache_leaks));
+ }
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+static void init_once(void *foo)
+{
+ struct ssdfs_inode_info *ii = (struct ssdfs_inode_info *)foo;
+
+ inode_init_once(&ii->vfs_inode);
+}
+
+/*
+ * This method is called by inode_alloc() to allocate memory
+ * for struct inode and initialize it
+ */
+struct inode *ssdfs_alloc_inode(struct super_block *sb)
+{
+ struct ssdfs_inode_info *ii;
+
+ ii = alloc_inode_sb(sb, ssdfs_inode_cachep, GFP_KERNEL);
+ if (!ii)
+ return NULL;
+
+ ssdfs_super_cache_leaks_increment(ii);
+
+ init_once((void *)ii);
+
+ atomic_set(&ii->private_flags, 0);
+ init_rwsem(&ii->lock);
+ ii->parent_ino = U64_MAX;
+ ii->flags = 0;
+ ii->name_hash = 0;
+ ii->name_len = 0;
+ ii->extents_tree = NULL;
+ ii->dentries_tree = NULL;
+ ii->xattrs_tree = NULL;
+ ii->inline_file = NULL;
+ memset(&ii->raw_inode, 0, sizeof(struct ssdfs_inode));
+
+ return &ii->vfs_inode;
+}
+
+static void ssdfs_i_callback(struct rcu_head *head)
+{
+ struct inode *inode = container_of(head, struct inode, i_rcu);
+ struct ssdfs_inode_info *ii = SSDFS_I(inode);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("ino %lu\n", inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (ii->extents_tree)
+ ssdfs_extents_tree_destroy(ii);
+
+ if (ii->dentries_tree)
+ ssdfs_dentries_tree_destroy(ii);
+
+ if (ii->xattrs_tree)
+ ssdfs_xattrs_tree_destroy(ii);
+
+ if (ii->inline_file)
+ ssdfs_destroy_inline_file_buffer(inode);
+
+ ssdfs_super_cache_leaks_decrement(ii);
+ kmem_cache_free(ssdfs_inode_cachep, ii);
+}
+
+/*
+ * This method is called by destroy_inode() to release
+ * resources allocated for struct inode
+ */
+static void ssdfs_destroy_inode(struct inode *inode)
+{
+ call_rcu(&inode->i_rcu, ssdfs_i_callback);
+}
+
+static void ssdfs_init_inode_once(void *obj)
+{
+ struct ssdfs_inode_info *ii = obj;
+ inode_init_once(&ii->vfs_inode);
+}
+
+static int ssdfs_remount_fs(struct super_block *sb, int *flags, char *data)
+{
+ struct ssdfs_fs_info *fsi = SSDFS_FS_I(sb);
+ struct ssdfs_peb_extent last_sb_log = {0};
+ struct ssdfs_sb_log_payload payload;
+ unsigned long old_sb_flags;
+ unsigned long old_mount_opts;
+ int err;
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("sb %p, flags %#x, data %p\n", sb, *flags, data);
+#else
+ SSDFS_DBG("sb %p, flags %#x, data %p\n", sb, *flags, data);
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ old_sb_flags = sb->s_flags;
+ old_mount_opts = fsi->mount_opts;
+
+ pagevec_init(&payload.maptbl_cache.pvec);
+
+ err = ssdfs_parse_options(fsi, data);
+ if (err)
+ goto restore_opts;
+
+ set_posix_acl_flag(sb);
+
+ if ((*flags & SB_RDONLY) == (sb->s_flags & SB_RDONLY))
+ goto out;
+
+ if (*flags & SB_RDONLY) {
+ down_write(&fsi->volume_sem);
+
+ err = ssdfs_prepare_sb_log(sb, &last_sb_log);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to prepare sb log: err %d\n",
+ err);
+ }
+
+ err = ssdfs_snapshot_sb_log_payload(sb, &payload);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to snapshot sb log's payload: err %d\n",
+ err);
+ }
+
+ if (!err) {
+ err = ssdfs_commit_super(sb, SSDFS_VALID_FS,
+ &last_sb_log,
+ &payload);
+ } else {
+ SSDFS_ERR("fail to prepare sb log payload: "
+ "err %d\n", err);
+ }
+
+ up_write(&fsi->volume_sem);
+
+ if (err)
+ SSDFS_ERR("fail to commit superblock info\n");
+
+ sb->s_flags |= SB_RDONLY;
+ SSDFS_DBG("remount in RO mode\n");
+ } else {
+ down_write(&fsi->volume_sem);
+
+ err = ssdfs_prepare_sb_log(sb, &last_sb_log);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to prepare sb log: err %d\n",
+ err);
+ }
+
+ err = ssdfs_snapshot_sb_log_payload(sb, &payload);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to snapshot sb log's payload: err %d\n",
+ err);
+ }
+
+ if (!err) {
+ err = ssdfs_commit_super(sb, SSDFS_MOUNTED_FS,
+ &last_sb_log,
+ &payload);
+ } else {
+ SSDFS_ERR("fail to prepare sb log payload: "
+ "err %d\n", err);
+ }
+
+ up_write(&fsi->volume_sem);
+
+ if (err) {
+ SSDFS_NOTICE("fail to commit superblock info\n");
+ goto restore_opts;
+ }
+
+ sb->s_flags &= ~SB_RDONLY;
+ SSDFS_DBG("remount in RW mode\n");
+ }
+out:
+ ssdfs_super_pagevec_release(&payload.maptbl_cache.pvec);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("finished\n");
+#else
+ SSDFS_DBG("finished\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ return 0;
+
+restore_opts:
+ sb->s_flags = old_sb_flags;
+ fsi->mount_opts = old_mount_opts;
+ ssdfs_super_pagevec_release(&payload.maptbl_cache.pvec);
+ return err;
+}
+
+static inline
+bool unfinished_user_data_requests_exist(struct ssdfs_fs_info *fsi)
+{
+ u64 flush_requests = 0;
+
+ spin_lock(&fsi->volume_state_lock);
+ flush_requests = fsi->flushing_user_data_requests;
+ spin_unlock(&fsi->volume_state_lock);
+
+ return flush_requests > 0;
+}
+
+static int ssdfs_sync_fs(struct super_block *sb, int wait)
+{
+ struct ssdfs_fs_info *fsi;
+ int err = 0;
+
+ fsi = SSDFS_FS_I(sb);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("sb %p\n", sb);
+#else
+ SSDFS_DBG("sb %p\n", sb);
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+#ifdef CONFIG_SSDFS_SHOW_CONSUMED_MEMORY
+ SSDFS_ERR("SYNCFS is starting...\n");
+ ssdfs_check_memory_leaks();
+#endif /* CONFIG_SSDFS_SHOW_CONSUMED_MEMORY */
+
+ atomic_set(&fsi->global_fs_state, SSDFS_METADATA_GOING_FLUSHING);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("SSDFS_METADATA_GOING_FLUSHING\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ wake_up_all(&fsi->pending_wq);
+
+ if (unfinished_user_data_requests_exist(fsi)) {
+ wait_queue_head_t *wq = &fsi->finish_user_data_flush_wq;
+
+ err = wait_event_killable_timeout(*wq,
+ !unfinished_user_data_requests_exist(fsi),
+ SSDFS_DEFAULT_TIMEOUT);
+ if (err < 0)
+ WARN_ON(err < 0);
+ else
+ err = 0;
+
+ if (unfinished_user_data_requests_exist(fsi))
+ BUG();
+ }
+
+ atomic_set(&fsi->global_fs_state, SSDFS_METADATA_UNDER_FLUSH);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("SSDFS_METADATA_UNDER_FLUSH\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_write(&fsi->volume_sem);
+
+ if (fsi->fs_feature_compat &
+ SSDFS_HAS_INVALID_EXTENTS_TREE_COMPAT_FLAG) {
+ err = ssdfs_invextree_flush(fsi);
+ if (err) {
+ SSDFS_ERR("fail to flush invalidated extents btree: "
+ "err %d\n", err);
+ }
+ }
+
+ if (fsi->fs_feature_compat & SSDFS_HAS_SHARED_EXTENTS_COMPAT_FLAG) {
+ err = ssdfs_shextree_flush(fsi);
+ if (err) {
+ SSDFS_ERR("fail to flush shared extents btree: "
+ "err %d\n", err);
+ }
+ }
+
+ if (fsi->fs_feature_compat & SSDFS_HAS_INODES_TREE_COMPAT_FLAG) {
+ err = ssdfs_inodes_btree_flush(fsi->inodes_tree);
+ if (err) {
+ SSDFS_ERR("fail to flush inodes btree: "
+ "err %d\n", err);
+ }
+ }
+
+ if (fsi->fs_feature_compat & SSDFS_HAS_SHARED_DICT_COMPAT_FLAG) {
+ err = ssdfs_shared_dict_btree_flush(fsi->shdictree);
+ if (err) {
+ SSDFS_ERR("fail to flush shared dictionary: "
+ "err %d\n", err);
+ }
+ }
+
+ err = ssdfs_execute_create_snapshots(fsi);
+ if (err) {
+ SSDFS_ERR("fail to process the snapshots creation\n");
+ }
+
+ if (fsi->fs_feature_compat & SSDFS_HAS_SNAPSHOTS_TREE_COMPAT_FLAG) {
+ err = ssdfs_snapshots_btree_flush(fsi);
+ if (err) {
+ SSDFS_ERR("fail to flush snapshots btree: "
+ "err %d\n", err);
+ }
+ }
+
+ if (fsi->fs_feature_compat & SSDFS_HAS_SEGBMAP_COMPAT_FLAG) {
+ err = ssdfs_segbmap_flush(fsi->segbmap);
+ if (err) {
+ SSDFS_ERR("fail to flush segment bitmap: "
+ "err %d\n", err);
+ }
+ }
+
+ if (fsi->fs_feature_compat & SSDFS_HAS_MAPTBL_COMPAT_FLAG) {
+ err = ssdfs_maptbl_flush(fsi->maptbl);
+ if (err) {
+ SSDFS_ERR("fail to flush mapping table: "
+ "err %d\n", err);
+ }
+ }
+
+ up_write(&fsi->volume_sem);
+
+ atomic_set(&fsi->global_fs_state, SSDFS_REGULAR_FS_OPERATIONS);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("SSDFS_REGULAR_FS_OPERATIONS\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+#ifdef CONFIG_SSDFS_SHOW_CONSUMED_MEMORY
+ SSDFS_ERR("SYNCFS has been finished...\n");
+ ssdfs_check_memory_leaks();
+#endif /* CONFIG_SSDFS_SHOW_CONSUMED_MEMORY */
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("finished\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (unlikely(err))
+ goto fail_sync_fs;
+
+ trace_ssdfs_sync_fs(sb, wait);
+
+ return 0;
+
+fail_sync_fs:
+ trace_ssdfs_sync_fs_exit(sb, wait, err);
+ return err;
+}
+
+static struct inode *ssdfs_nfs_get_inode(struct super_block *sb,
+ u64 ino, u32 generation)
+{
+ struct inode *inode;
+
+ if (ino < SSDFS_ROOT_INO)
+ return ERR_PTR(-ESTALE);
+
+ inode = ssdfs_iget(sb, ino);
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+ if (generation && inode->i_generation != generation) {
+ iput(inode);
+ return ERR_PTR(-ESTALE);
+ }
+ return inode;
+}
+
+static struct dentry *ssdfs_fh_to_dentry(struct super_block *sb,
+ struct fid *fid,
+ int fh_len, int fh_type)
+{
+ return generic_fh_to_dentry(sb, fid, fh_len, fh_type,
+ ssdfs_nfs_get_inode);
+}
+
+static struct dentry *ssdfs_fh_to_parent(struct super_block *sb,
+ struct fid *fid,
+ int fh_len, int fh_type)
+{
+ return generic_fh_to_parent(sb, fid, fh_len, fh_type,
+ ssdfs_nfs_get_inode);
+}
+
+static struct dentry *ssdfs_get_parent(struct dentry *child)
+{
+ struct qstr dotdot = QSTR_INIT("..", 2);
+ ino_t ino;
+ int err;
+
+ err = ssdfs_inode_by_name(d_inode(child), &dotdot, &ino);
+ if (unlikely(err))
+ return ERR_PTR(err);
+
+ return d_obtain_alias(ssdfs_iget(child->d_sb, ino));
+}
+
+static const struct export_operations ssdfs_export_ops = {
+ .get_parent = ssdfs_get_parent,
+ .fh_to_dentry = ssdfs_fh_to_dentry,
+ .fh_to_parent = ssdfs_fh_to_parent,
+};
+
+static const struct super_operations ssdfs_super_operations = {
+ .alloc_inode = ssdfs_alloc_inode,
+ .destroy_inode = ssdfs_destroy_inode,
+ .evict_inode = ssdfs_evict_inode,
+ .write_inode = ssdfs_write_inode,
+ .statfs = ssdfs_statfs,
+ .show_options = ssdfs_show_options,
+ .put_super = ssdfs_put_super,
+ .remount_fs = ssdfs_remount_fs,
+ .sync_fs = ssdfs_sync_fs,
+};
+
+static void ssdfs_memory_page_locks_checker_init(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ atomic64_set(&ssdfs_locked_pages, 0);
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+static void ssdfs_check_memory_page_locks(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ if (atomic64_read(&ssdfs_locked_pages) != 0) {
+ SSDFS_WARN("Lock keeps %lld memory pages\n",
+ atomic64_read(&ssdfs_locked_pages));
+ }
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+static void ssdfs_memory_leaks_checker_init(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+ atomic64_set(&ssdfs_allocated_pages, 0);
+ atomic64_set(&ssdfs_memory_leaks, 0);
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+#ifdef CONFIG_SSDFS_POSIX_ACL
+ ssdfs_acl_memory_leaks_init();
+#endif /* CONFIG_SSDFS_POSIX_ACL */
+
+ ssdfs_block_bmap_memory_leaks_init();
+ ssdfs_btree_memory_leaks_init();
+ ssdfs_btree_hierarchy_memory_leaks_init();
+ ssdfs_btree_node_memory_leaks_init();
+ ssdfs_btree_search_memory_leaks_init();
+
+#ifdef CONFIG_SSDFS_ZLIB
+ ssdfs_zlib_memory_leaks_init();
+#endif /* CONFIG_SSDFS_ZLIB */
+
+#ifdef CONFIG_SSDFS_LZO
+ ssdfs_lzo_memory_leaks_init();
+#endif /* CONFIG_SSDFS_LZO */
+
+ ssdfs_compr_memory_leaks_init();
+ ssdfs_cur_seg_memory_leaks_init();
+ ssdfs_dentries_memory_leaks_init();
+
+#ifdef CONFIG_SSDFS_MTD_DEVICE
+ ssdfs_dev_mtd_memory_leaks_init();
+#elif defined(CONFIG_SSDFS_BLOCK_DEVICE)
+ ssdfs_dev_bdev_memory_leaks_init();
+ ssdfs_dev_zns_memory_leaks_init();
+#else
+ BUILD_BUG();
+#endif
+
+ ssdfs_dir_memory_leaks_init();
+
+#ifdef CONFIG_SSDFS_DIFF_ON_WRITE_USER_DATA
+ ssdfs_diff_memory_leaks_init();
+#endif /* CONFIG_SSDFS_DIFF_ON_WRITE_USER_DATA */
+
+ ssdfs_ext_queue_memory_leaks_init();
+ ssdfs_ext_tree_memory_leaks_init();
+ ssdfs_file_memory_leaks_init();
+ ssdfs_fs_error_memory_leaks_init();
+ ssdfs_inode_memory_leaks_init();
+ ssdfs_ino_tree_memory_leaks_init();
+ ssdfs_invext_tree_memory_leaks_init();
+ ssdfs_blk2off_memory_leaks_init();
+ ssdfs_parray_memory_leaks_init();
+ ssdfs_page_vector_memory_leaks_init();
+ ssdfs_flush_memory_leaks_init();
+ ssdfs_gc_memory_leaks_init();
+ ssdfs_map_queue_memory_leaks_init();
+ ssdfs_map_tbl_memory_leaks_init();
+ ssdfs_map_cache_memory_leaks_init();
+ ssdfs_map_thread_memory_leaks_init();
+ ssdfs_migration_memory_leaks_init();
+ ssdfs_peb_memory_leaks_init();
+ ssdfs_read_memory_leaks_init();
+ ssdfs_recovery_memory_leaks_init();
+ ssdfs_req_queue_memory_leaks_init();
+ ssdfs_seg_obj_memory_leaks_init();
+ ssdfs_seg_bmap_memory_leaks_init();
+ ssdfs_seg_blk_memory_leaks_init();
+ ssdfs_seg_tree_memory_leaks_init();
+ ssdfs_seq_arr_memory_leaks_init();
+ ssdfs_dict_memory_leaks_init();
+ ssdfs_shextree_memory_leaks_init();
+ ssdfs_super_memory_leaks_init();
+ ssdfs_xattr_memory_leaks_init();
+ ssdfs_snap_reqs_queue_memory_leaks_init();
+ ssdfs_snap_rules_list_memory_leaks_init();
+ ssdfs_snap_tree_memory_leaks_init();
+}
+
+static void ssdfs_check_memory_leaks(void)
+{
+#ifdef CONFIG_SSDFS_POSIX_ACL
+ ssdfs_acl_check_memory_leaks();
+#endif /* CONFIG_SSDFS_POSIX_ACL */
+
+ ssdfs_block_bmap_check_memory_leaks();
+ ssdfs_btree_check_memory_leaks();
+ ssdfs_btree_hierarchy_check_memory_leaks();
+ ssdfs_btree_node_check_memory_leaks();
+ ssdfs_btree_search_check_memory_leaks();
+
+#ifdef CONFIG_SSDFS_ZLIB
+ ssdfs_zlib_check_memory_leaks();
+#endif /* CONFIG_SSDFS_ZLIB */
+
+#ifdef CONFIG_SSDFS_LZO
+ ssdfs_lzo_check_memory_leaks();
+#endif /* CONFIG_SSDFS_LZO */
+
+ ssdfs_compr_check_memory_leaks();
+ ssdfs_cur_seg_check_memory_leaks();
+ ssdfs_dentries_check_memory_leaks();
+
+#ifdef CONFIG_SSDFS_MTD_DEVICE
+ ssdfs_dev_mtd_check_memory_leaks();
+#elif defined(CONFIG_SSDFS_BLOCK_DEVICE)
+ ssdfs_dev_bdev_check_memory_leaks();
+ ssdfs_dev_zns_check_memory_leaks();
+#else
+ BUILD_BUG();
+#endif
+
+ ssdfs_dir_check_memory_leaks();
+
+#ifdef CONFIG_SSDFS_DIFF_ON_WRITE_USER_DATA
+ ssdfs_diff_check_memory_leaks();
+#endif /* CONFIG_SSDFS_DIFF_ON_WRITE_USER_DATA */
+
+ ssdfs_ext_queue_check_memory_leaks();
+ ssdfs_ext_tree_check_memory_leaks();
+ ssdfs_file_check_memory_leaks();
+ ssdfs_fs_error_check_memory_leaks();
+ ssdfs_inode_check_memory_leaks();
+ ssdfs_ino_tree_check_memory_leaks();
+ ssdfs_invext_tree_check_memory_leaks();
+ ssdfs_blk2off_check_memory_leaks();
+ ssdfs_parray_check_memory_leaks();
+ ssdfs_page_vector_check_memory_leaks();
+ ssdfs_flush_check_memory_leaks();
+ ssdfs_gc_check_memory_leaks();
+ ssdfs_map_queue_check_memory_leaks();
+ ssdfs_map_tbl_check_memory_leaks();
+ ssdfs_map_cache_check_memory_leaks();
+ ssdfs_map_thread_check_memory_leaks();
+ ssdfs_migration_check_memory_leaks();
+ ssdfs_peb_check_memory_leaks();
+ ssdfs_read_check_memory_leaks();
+ ssdfs_recovery_check_memory_leaks();
+ ssdfs_req_queue_check_memory_leaks();
+ ssdfs_seg_obj_check_memory_leaks();
+ ssdfs_seg_bmap_check_memory_leaks();
+ ssdfs_seg_blk_check_memory_leaks();
+ ssdfs_seg_tree_check_memory_leaks();
+ ssdfs_seq_arr_check_memory_leaks();
+ ssdfs_dict_check_memory_leaks();
+ ssdfs_shextree_check_memory_leaks();
+ ssdfs_super_check_memory_leaks();
+ ssdfs_xattr_check_memory_leaks();
+ ssdfs_snap_reqs_queue_check_memory_leaks();
+ ssdfs_snap_rules_list_check_memory_leaks();
+ ssdfs_snap_tree_check_memory_leaks();
+
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+#ifdef CONFIG_SSDFS_SHOW_CONSUMED_MEMORY
+ if (atomic64_read(&ssdfs_allocated_pages) != 0) {
+ SSDFS_ERR("Memory leaks include %lld pages\n",
+ atomic64_read(&ssdfs_allocated_pages));
+ }
+
+ if (atomic64_read(&ssdfs_memory_leaks) != 0) {
+ SSDFS_ERR("Memory allocator suffers from %lld leaks\n",
+ atomic64_read(&ssdfs_memory_leaks));
+ }
+#else
+ if (atomic64_read(&ssdfs_allocated_pages) != 0) {
+ SSDFS_WARN("Memory leaks include %lld pages\n",
+ atomic64_read(&ssdfs_allocated_pages));
+ }
+
+ if (atomic64_read(&ssdfs_memory_leaks) != 0) {
+ SSDFS_WARN("Memory allocator suffers from %lld leaks\n",
+ atomic64_read(&ssdfs_memory_leaks));
+ }
+#endif /* CONFIG_SSDFS_SHOW_CONSUMED_MEMORY */
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+static int ssdfs_fill_super(struct super_block *sb, void *data, int silent)
+{
+ struct ssdfs_fs_info *fs_info;
+ struct ssdfs_peb_extent last_sb_log = {0};
+ struct ssdfs_sb_log_payload payload;
+ struct inode *root_i;
+ u64 fs_feature_compat;
+ int i;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("sb %p, data %p, silent %#x\n", sb, data, silent);
+#else
+ SSDFS_DBG("sb %p, data %p, silent %#x\n", sb, data, silent);
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("segment header size %zu, "
+ "partial log header size %zu, "
+ "footer size %zu\n",
+ sizeof(struct ssdfs_segment_header),
+ sizeof(struct ssdfs_partial_log_header),
+ sizeof(struct ssdfs_log_footer));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ ssdfs_memory_page_locks_checker_init();
+ ssdfs_memory_leaks_checker_init();
+
+ fs_info = ssdfs_super_kzalloc(sizeof(*fs_info), GFP_KERNEL);
+ if (!fs_info)
+ return -ENOMEM;
+
+#ifdef CONFIG_SSDFS_TESTING
+ fs_info->do_fork_invalidation = true;
+#endif /* CONFIG_SSDFS_TESTING */
+
+ fs_info->max_open_zones = 0;
+ fs_info->is_zns_device = false;
+ fs_info->zone_size = U64_MAX;
+ fs_info->zone_capacity = U64_MAX;
+ atomic_set(&fs_info->open_zones, 0);
+
+#ifdef CONFIG_SSDFS_MTD_DEVICE
+ fs_info->mtd = sb->s_mtd;
+ fs_info->devops = &ssdfs_mtd_devops;
+ sb->s_bdi = sb->s_mtd->backing_dev_info;
+#elif defined(CONFIG_SSDFS_BLOCK_DEVICE)
+ if (bdev_is_zoned(sb->s_bdev)) {
+ fs_info->devops = &ssdfs_zns_devops;
+ fs_info->is_zns_device = true;
+ fs_info->max_open_zones = bdev_max_open_zones(sb->s_bdev);
+
+ fs_info->zone_size = ssdfs_zns_zone_size(sb,
+ SSDFS_RESERVED_VBR_SIZE);
+ if (fs_info->zone_size >= U64_MAX) {
+ SSDFS_ERR("fail to get zone size\n");
+ return -ERANGE;
+ }
+
+ fs_info->zone_capacity = ssdfs_zns_zone_capacity(sb,
+ SSDFS_RESERVED_VBR_SIZE);
+ if (fs_info->zone_capacity >= U64_MAX) {
+ SSDFS_ERR("fail to get zone capacity\n");
+ return -ERANGE;
+ } else if (fs_info->zone_capacity > fs_info->zone_size) {
+ SSDFS_ERR("invalid zone capacity: "
+ "capacity %llu, size %llu\n",
+ fs_info->zone_capacity,
+ fs_info->zone_size);
+ return -ERANGE;
+ }
+ } else
+ fs_info->devops = &ssdfs_bdev_devops;
+
+ sb->s_bdi = bdi_get(sb->s_bdev->bd_disk->bdi);
+ atomic_set(&fs_info->pending_bios, 0);
+ fs_info->erase_page = ssdfs_super_alloc_page(GFP_KERNEL);
+ if (IS_ERR_OR_NULL(fs_info->erase_page)) {
+ err = (fs_info->erase_page == NULL ?
+ -ENOMEM : PTR_ERR(fs_info->erase_page));
+ SSDFS_ERR("unable to allocate memory page\n");
+ goto free_erase_page;
+ }
+ memset(page_address(fs_info->erase_page), 0xFF, PAGE_SIZE);
+#else
+ BUILD_BUG();
+#endif
+
+ fs_info->sb = sb;
+ sb->s_fs_info = fs_info;
+ atomic64_set(&fs_info->flush_reqs, 0);
+ init_waitqueue_head(&fs_info->pending_wq);
+ init_waitqueue_head(&fs_info->finish_user_data_flush_wq);
+ atomic_set(&fs_info->global_fs_state, SSDFS_UNKNOWN_GLOBAL_FS_STATE);
+
+ for (i = 0; i < SSDFS_GC_THREAD_TYPE_MAX; i++) {
+ init_waitqueue_head(&fs_info->gc_wait_queue[i]);
+ atomic_set(&fs_info->gc_should_act[i], 1);
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("parse options started...\n");
+#else
+ SSDFS_DBG("parse options started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ err = ssdfs_parse_options(fs_info, data);
+ if (err)
+ goto free_erase_page;
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("gather superblock info started...\n");
+#else
+ SSDFS_DBG("gather superblock info started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ err = ssdfs_gather_superblock_info(fs_info, silent);
+ if (err)
+ goto free_erase_page;
+
+ spin_lock(&fs_info->volume_state_lock);
+ fs_feature_compat = fs_info->fs_feature_compat;
+ spin_unlock(&fs_info->volume_state_lock);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create device group started...\n");
+#else
+ SSDFS_DBG("create device group started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ err = ssdfs_sysfs_create_device_group(sb);
+ if (err)
+ goto release_maptbl_cache;
+
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+ sb->s_magic = SSDFS_SUPER_MAGIC;
+ sb->s_op = &ssdfs_super_operations;
+ sb->s_export_op = &ssdfs_export_ops;
+
+ sb->s_xattr = ssdfs_xattr_handlers;
+ set_posix_acl_flag(sb);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create snapshots subsystem started...\n");
+#else
+ SSDFS_DBG("create snapshots subsystem started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ err = ssdfs_snapshot_subsystem_init(fs_info);
+ if (err == -EINTR) {
+ /*
+ * Ignore this error.
+ */
+ err = 0;
+ goto destroy_sysfs_device_group;
+ } else if (err)
+ goto destroy_sysfs_device_group;
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create segment tree started...\n");
+#else
+ SSDFS_DBG("create segment tree started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ down_write(&fs_info->volume_sem);
+ err = ssdfs_segment_tree_create(fs_info);
+ up_write(&fs_info->volume_sem);
+ if (err)
+ goto destroy_snapshot_subsystem;
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create mapping table started...\n");
+#else
+ SSDFS_DBG("create mapping table started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_MAPTBL_COMPAT_FLAG) {
+ down_write(&fs_info->volume_sem);
+ err = ssdfs_maptbl_create(fs_info);
+ up_write(&fs_info->volume_sem);
+
+ if (err == -EINTR) {
+ /*
+ * Ignore this error.
+ */
+ err = 0;
+ goto destroy_segments_tree;
+ } else if (err)
+ goto destroy_segments_tree;
+ } else {
+ err = -EIO;
+ SSDFS_WARN("volume hasn't mapping table\n");
+ goto destroy_segments_tree;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create segment bitmap started...\n");
+#else
+ SSDFS_DBG("create segment bitmap started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_SEGBMAP_COMPAT_FLAG) {
+ down_write(&fs_info->volume_sem);
+ err = ssdfs_segbmap_create(fs_info);
+ up_write(&fs_info->volume_sem);
+
+ if (err == -EINTR) {
+ /*
+ * Ignore this error.
+ */
+ err = 0;
+ goto destroy_maptbl;
+ } else if (err)
+ goto destroy_maptbl;
+ } else {
+ err = -EIO;
+ SSDFS_WARN("volume hasn't segment bitmap\n");
+ goto destroy_maptbl;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create shared extents tree started...\n");
+#else
+ SSDFS_DBG("create shared extents tree started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_info->fs_feature_compat & SSDFS_HAS_SHARED_EXTENTS_COMPAT_FLAG) {
+ down_write(&fs_info->volume_sem);
+ err = ssdfs_shextree_create(fs_info);
+ up_write(&fs_info->volume_sem);
+ if (err)
+ goto destroy_segbmap;
+ } else {
+ err = -EIO;
+ SSDFS_WARN("volume hasn't shared extents tree\n");
+ goto destroy_segbmap;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create invalidated extents btree started...\n");
+#else
+ SSDFS_DBG("create invalidated extents btree started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_INVALID_EXTENTS_TREE_COMPAT_FLAG) {
+ down_write(&fs_info->volume_sem);
+ err = ssdfs_invextree_create(fs_info);
+ up_write(&fs_info->volume_sem);
+ if (err)
+ goto destroy_shextree;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create current segment array started...\n");
+#else
+ SSDFS_DBG("create current segment array started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ down_write(&fs_info->volume_sem);
+ err = ssdfs_current_segment_array_create(fs_info);
+ up_write(&fs_info->volume_sem);
+ if (err)
+ goto destroy_invext_btree;
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create shared dictionary started...\n");
+#else
+ SSDFS_DBG("create shared dictionary started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_SHARED_DICT_COMPAT_FLAG) {
+ down_write(&fs_info->volume_sem);
+
+ err = ssdfs_shared_dict_btree_create(fs_info);
+ if (err) {
+ up_write(&fs_info->volume_sem);
+ goto destroy_current_segment_array;
+ }
+
+ err = ssdfs_shared_dict_btree_init(fs_info);
+ if (err) {
+ up_write(&fs_info->volume_sem);
+ goto destroy_shdictree;
+ }
+
+ up_write(&fs_info->volume_sem);
+ } else {
+ err = -EIO;
+ SSDFS_WARN("volume hasn't shared dictionary\n");
+ goto destroy_current_segment_array;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("create inodes btree started...\n");
+#else
+ SSDFS_DBG("create inodes btree started...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_INODES_TREE_COMPAT_FLAG) {
+ down_write(&fs_info->volume_sem);
+ err = ssdfs_inodes_btree_create(fs_info);
+ up_write(&fs_info->volume_sem);
+ if (err)
+ goto destroy_shdictree;
+ } else {
+ err = -EIO;
+ SSDFS_WARN("volume hasn't inodes btree\n");
+ goto destroy_shdictree;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("getting root inode...\n");
+#else
+ SSDFS_DBG("getting root inode...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ root_i = ssdfs_iget(sb, SSDFS_ROOT_INO);
+ if (IS_ERR(root_i)) {
+ SSDFS_DBG("getting root inode failed\n");
+ err = PTR_ERR(root_i);
+ goto destroy_inodes_btree;
+ }
+
+ if (!S_ISDIR(root_i->i_mode) || !root_i->i_blocks || !root_i->i_size) {
+ err = -ERANGE;
+ iput(root_i);
+ SSDFS_ERR("corrupted root inode\n");
+ goto destroy_inodes_btree;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("d_make_root()\n");
+#else
+ SSDFS_DBG("d_make_root()\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ sb->s_root = d_make_root(root_i);
+ if (!sb->s_root) {
+ err = -ENOMEM;
+ goto put_root_inode;
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("starting GC threads...\n");
+#else
+ SSDFS_DBG("starting GC threads...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ err = ssdfs_start_gc_thread(fs_info, SSDFS_SEG_USING_GC_THREAD);
+ if (err == -EINTR) {
+ /*
+ * Ignore this error.
+ */
+ err = 0;
+ goto put_root_inode;
+ } else if (unlikely(err)) {
+ SSDFS_ERR("fail to start GC-using-seg thread: "
+ "err %d\n", err);
+ goto put_root_inode;
+ }
+
+ err = ssdfs_start_gc_thread(fs_info, SSDFS_SEG_USED_GC_THREAD);
+ if (err == -EINTR) {
+ /*
+ * Ignore this error.
+ */
+ err = 0;
+ goto stop_gc_using_seg_thread;
+ } else if (unlikely(err)) {
+ SSDFS_ERR("fail to start GC-used-seg thread: "
+ "err %d\n", err);
+ goto stop_gc_using_seg_thread;
+ }
+
+ err = ssdfs_start_gc_thread(fs_info, SSDFS_SEG_PRE_DIRTY_GC_THREAD);
+ if (err == -EINTR) {
+ /*
+ * Ignore this error.
+ */
+ err = 0;
+ goto stop_gc_used_seg_thread;
+ } else if (unlikely(err)) {
+ SSDFS_ERR("fail to start GC-pre-dirty-seg thread: "
+ "err %d\n", err);
+ goto stop_gc_used_seg_thread;
+ }
+
+ err = ssdfs_start_gc_thread(fs_info, SSDFS_SEG_DIRTY_GC_THREAD);
+ if (err == -EINTR) {
+ /*
+ * Ignore this error.
+ */
+ err = 0;
+ goto stop_gc_pre_dirty_seg_thread;
+ } else if (unlikely(err)) {
+ SSDFS_ERR("fail to start GC-dirty-seg thread: "
+ "err %d\n", err);
+ goto stop_gc_pre_dirty_seg_thread;
+ }
+
+ if (!(sb->s_flags & SB_RDONLY)) {
+ pagevec_init(&payload.maptbl_cache.pvec);
+
+ down_write(&fs_info->volume_sem);
+
+ err = ssdfs_prepare_sb_log(sb, &last_sb_log);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to prepare sb log: err %d\n",
+ err);
+ }
+
+ err = ssdfs_snapshot_sb_log_payload(sb, &payload);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to snapshot sb log's payload: err %d\n",
+ err);
+ }
+
+ if (!err) {
+ err = ssdfs_commit_super(sb, SSDFS_MOUNTED_FS,
+ &last_sb_log,
+ &payload);
+ } else {
+ SSDFS_ERR("fail to prepare sb log payload: "
+ "err %d\n", err);
+ }
+
+ up_write(&fs_info->volume_sem);
+
+ ssdfs_super_pagevec_release(&payload.maptbl_cache.pvec);
+
+ if (err) {
+ SSDFS_NOTICE("fail to commit superblock info: "
+ "remount filesystem in RO mode\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+ }
+
+ atomic_set(&fs_info->global_fs_state, SSDFS_REGULAR_FS_OPERATIONS);
+
+ SSDFS_INFO("%s has been mounted on device %s\n",
+ SSDFS_VERSION, fs_info->devops->device_name(sb));
+
+ return 0;
+
+stop_gc_pre_dirty_seg_thread:
+ ssdfs_stop_gc_thread(fs_info, SSDFS_SEG_PRE_DIRTY_GC_THREAD);
+
+stop_gc_used_seg_thread:
+ ssdfs_stop_gc_thread(fs_info, SSDFS_SEG_USED_GC_THREAD);
+
+stop_gc_using_seg_thread:
+ ssdfs_stop_gc_thread(fs_info, SSDFS_SEG_USING_GC_THREAD);
+
+put_root_inode:
+ iput(root_i);
+
+destroy_inodes_btree:
+ ssdfs_inodes_btree_destroy(fs_info);
+
+destroy_shdictree:
+ ssdfs_shared_dict_btree_destroy(fs_info);
+
+destroy_current_segment_array:
+ ssdfs_destroy_all_curent_segments(fs_info);
+
+destroy_invext_btree:
+ ssdfs_invextree_destroy(fs_info);
+
+destroy_shextree:
+ ssdfs_shextree_destroy(fs_info);
+
+destroy_segbmap:
+ ssdfs_segbmap_destroy(fs_info);
+
+destroy_maptbl:
+ ssdfs_maptbl_stop_thread(fs_info->maptbl);
+ ssdfs_maptbl_destroy(fs_info);
+
+destroy_segments_tree:
+ ssdfs_segment_tree_destroy(fs_info);
+ ssdfs_current_segment_array_destroy(fs_info);
+
+destroy_snapshot_subsystem:
+ ssdfs_snapshot_subsystem_destroy(fs_info);
+
+destroy_sysfs_device_group:
+ ssdfs_sysfs_delete_device_group(fs_info);
+
+release_maptbl_cache:
+ ssdfs_maptbl_cache_destroy(&fs_info->maptbl_cache);
+
+free_erase_page:
+ if (fs_info->erase_page)
+ ssdfs_super_free_page(fs_info->erase_page);
+
+ ssdfs_destruct_sb_info(&fs_info->sbi);
+ ssdfs_destruct_sb_info(&fs_info->sbi_backup);
+
+ ssdfs_free_workspaces();
+
+ ssdfs_super_kfree(fs_info);
+
+ rcu_barrier();
+
+ ssdfs_check_memory_page_locks();
+ ssdfs_check_memory_leaks();
+ return err;
+}
+
+static void ssdfs_put_super(struct super_block *sb)
+{
+ struct ssdfs_fs_info *fsi = SSDFS_FS_I(sb);
+ struct ssdfs_peb_extent last_sb_log = {0};
+ struct ssdfs_sb_log_payload payload;
+ u64 fs_feature_compat;
+ u16 fs_state;
+ bool can_commit_super = true;
+ int i;
+ int err;
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("sb %p\n", sb);
+#else
+ SSDFS_DBG("sb %p\n", sb);
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ atomic_set(&fsi->global_fs_state, SSDFS_METADATA_GOING_FLUSHING);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("SSDFS_METADATA_GOING_FLUSHING\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ wake_up_all(&fsi->pending_wq);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("STOP THREADS...\n");
+#else
+ SSDFS_DBG("STOP THREADS...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ err = ssdfs_stop_gc_thread(fsi, SSDFS_SEG_USING_GC_THREAD);
+ if (err) {
+ SSDFS_ERR("fail to stop GC using seg thread: "
+ "err %d\n", err);
+ }
+
+ err = ssdfs_stop_gc_thread(fsi, SSDFS_SEG_USED_GC_THREAD);
+ if (err) {
+ SSDFS_ERR("fail to stop GC used seg thread: "
+ "err %d\n", err);
+ }
+
+ err = ssdfs_stop_gc_thread(fsi, SSDFS_SEG_PRE_DIRTY_GC_THREAD);
+ if (err) {
+ SSDFS_ERR("fail to stop GC pre-dirty seg thread: "
+ "err %d\n", err);
+ }
+
+ err = ssdfs_stop_gc_thread(fsi, SSDFS_SEG_DIRTY_GC_THREAD);
+ if (err) {
+ SSDFS_ERR("fail to stop GC dirty seg thread: "
+ "err %d\n", err);
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("GC threads have been stoped\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ err = ssdfs_shared_dict_stop_thread(fsi->shdictree);
+ if (err == -EIO) {
+ ssdfs_fs_error(fsi->sb,
+ __FILE__, __func__, __LINE__,
+ "thread I/O issue\n");
+ } else if (unlikely(err)) {
+ SSDFS_WARN("thread stopping issue: err %d\n",
+ err);
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("shared dictionary thread has been stoped\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ for (i = 0; i < SSDFS_INVALIDATION_QUEUE_NUMBER; i++) {
+ err = ssdfs_shextree_stop_thread(fsi->shextree, i);
+ if (err == -EIO) {
+ ssdfs_fs_error(fsi->sb,
+ __FILE__, __func__, __LINE__,
+ "thread I/O issue\n");
+ } else if (unlikely(err)) {
+ SSDFS_WARN("thread stopping issue: ID %d, err %d\n",
+ i, err);
+ }
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("shared extents threads have been stoped\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ err = ssdfs_stop_snapshots_btree_thread(fsi);
+ if (err == -EIO) {
+ ssdfs_fs_error(fsi->sb,
+ __FILE__, __func__, __LINE__,
+ "thread I/O issue\n");
+ } else if (unlikely(err)) {
+ SSDFS_WARN("thread stopping issue: err %d\n",
+ err);
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("snaphots btree thread has been stoped\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ err = ssdfs_maptbl_stop_thread(fsi->maptbl);
+ if (unlikely(err)) {
+ SSDFS_WARN("maptbl thread stopping issue: err %d\n",
+ err);
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("mapping table thread has been stoped\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ spin_lock(&fsi->volume_state_lock);
+ fs_feature_compat = fsi->fs_feature_compat;
+ fs_state = fsi->fs_state;
+ spin_unlock(&fsi->volume_state_lock);
+
+ pagevec_init(&payload.maptbl_cache.pvec);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("Wait unfinished user data requests...\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (unfinished_user_data_requests_exist(fsi)) {
+ wait_queue_head_t *wq = &fsi->finish_user_data_flush_wq;
+
+ err = wait_event_killable_timeout(*wq,
+ !unfinished_user_data_requests_exist(fsi),
+ SSDFS_DEFAULT_TIMEOUT);
+ if (err < 0)
+ WARN_ON(err < 0);
+ else
+ err = 0;
+
+ if (unfinished_user_data_requests_exist(fsi))
+ BUG();
+ }
+
+ atomic_set(&fsi->global_fs_state, SSDFS_METADATA_UNDER_FLUSH);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("SSDFS_METADATA_UNDER_FLUSH\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (!(sb->s_flags & SB_RDONLY)) {
+ down_write(&fsi->volume_sem);
+
+ err = ssdfs_prepare_sb_log(sb, &last_sb_log);
+ if (unlikely(err)) {
+ can_commit_super = false;
+ SSDFS_ERR("fail to prepare sb log: err %d\n",
+ err);
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Flush invalidated extents b-tree...\n");
+#else
+ SSDFS_DBG("Flush invalidated extents b-tree...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fsi->fs_feature_compat &
+ SSDFS_HAS_INVALID_EXTENTS_TREE_COMPAT_FLAG) {
+ err = ssdfs_invextree_flush(fsi);
+ if (err) {
+ SSDFS_ERR("fail to flush invalidated extents btree: "
+ "err %d\n", err);
+ }
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Flush shared extents b-tree...\n");
+#else
+ SSDFS_DBG("Flush shared extents b-tree...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fsi->fs_feature_compat &
+ SSDFS_HAS_SHARED_EXTENTS_COMPAT_FLAG) {
+ err = ssdfs_shextree_flush(fsi);
+ if (err) {
+ SSDFS_ERR("fail to flush shared extents btree: "
+ "err %d\n", err);
+ }
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Flush inodes b-tree...\n");
+#else
+ SSDFS_DBG("Flush inodes b-tree...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_INODES_TREE_COMPAT_FLAG) {
+ err = ssdfs_inodes_btree_flush(fsi->inodes_tree);
+ if (err) {
+ SSDFS_ERR("fail to flush inodes btree: "
+ "err %d\n", err);
+ }
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Flush shared dictionary b-tree...\n");
+#else
+ SSDFS_DBG("Flush shared dictionary b-tree...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_SHARED_DICT_COMPAT_FLAG) {
+ err = ssdfs_shared_dict_btree_flush(fsi->shdictree);
+ if (err) {
+ SSDFS_ERR("fail to flush shared dictionary: "
+ "err %d\n", err);
+ }
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Execute create snapshots...\n");
+#else
+ SSDFS_DBG("Execute create snapshots...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ err = ssdfs_execute_create_snapshots(fsi);
+ if (err) {
+ SSDFS_ERR("fail to process the snapshots creation\n");
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Flush snapshots b-tree...\n");
+#else
+ SSDFS_DBG("Flush snapshots b-tree...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fsi->fs_feature_compat &
+ SSDFS_HAS_SNAPSHOTS_TREE_COMPAT_FLAG) {
+ err = ssdfs_snapshots_btree_flush(fsi);
+ if (err) {
+ SSDFS_ERR("fail to flush snapshots btree: "
+ "err %d\n", err);
+ }
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Flush segment bitmap...\n");
+#else
+ SSDFS_DBG("Flush segment bitmap...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_SEGBMAP_COMPAT_FLAG) {
+ err = ssdfs_segbmap_flush(fsi->segbmap);
+ if (err) {
+ SSDFS_ERR("fail to flush segbmap: "
+ "err %d\n", err);
+ }
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Flush PEB mapping table...\n");
+#else
+ SSDFS_DBG("Flush PEB mapping table...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (fs_feature_compat & SSDFS_HAS_MAPTBL_COMPAT_FLAG) {
+ err = ssdfs_maptbl_flush(fsi->maptbl);
+ if (err) {
+ SSDFS_ERR("fail to flush maptbl: "
+ "err %d\n", err);
+ }
+
+ set_maptbl_going_to_be_destroyed(fsi);
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Commit superblock...\n");
+#else
+ SSDFS_DBG("Commit superblock...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (can_commit_super) {
+ err = ssdfs_snapshot_sb_log_payload(sb, &payload);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to snapshot log's payload: "
+ "err %d\n", err);
+ } else {
+ err = ssdfs_commit_super(sb, SSDFS_VALID_FS,
+ &last_sb_log,
+ &payload);
+ }
+ } else {
+ /* prepare error code */
+ err = -ERANGE;
+ }
+
+ if (err) {
+ SSDFS_ERR("fail to commit superblock info: "
+ "err %d\n", err);
+ }
+
+ up_write(&fsi->volume_sem);
+ } else {
+ if (fs_state == SSDFS_ERROR_FS) {
+ down_write(&fsi->volume_sem);
+
+ err = ssdfs_prepare_sb_log(sb, &last_sb_log);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to prepare sb log: err %d\n",
+ err);
+ }
+
+ err = ssdfs_snapshot_sb_log_payload(sb, &payload);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to snapshot log's payload: "
+ "err %d\n", err);
+ }
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Commit superblock...\n");
+#else
+ SSDFS_DBG("Commit superblock...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ if (!err) {
+ err = ssdfs_commit_super(sb, SSDFS_ERROR_FS,
+ &last_sb_log,
+ &payload);
+ }
+
+ up_write(&fsi->volume_sem);
+
+ if (err) {
+ SSDFS_ERR("fail to commit superblock info: "
+ "err %d\n", err);
+ }
+ }
+ }
+
+ atomic_set(&fsi->global_fs_state, SSDFS_UNKNOWN_GLOBAL_FS_STATE);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("SSDFS_UNKNOWN_GLOBAL_FS_STATE\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("Starting destroy the metadata structures...\n");
+#else
+ SSDFS_DBG("Starting destroy the metadata structures...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ ssdfs_super_pagevec_release(&payload.maptbl_cache.pvec);
+ fsi->devops->sync(sb);
+ ssdfs_snapshot_subsystem_destroy(fsi);
+ ssdfs_invextree_destroy(fsi);
+ ssdfs_shextree_destroy(fsi);
+ ssdfs_inodes_btree_destroy(fsi);
+ ssdfs_shared_dict_btree_destroy(fsi);
+ ssdfs_segbmap_destroy(fsi);
+ ssdfs_destroy_all_curent_segments(fsi);
+ ssdfs_segment_tree_destroy(fsi);
+ ssdfs_current_segment_array_destroy(fsi);
+ ssdfs_maptbl_destroy(fsi);
+ ssdfs_sysfs_delete_device_group(fsi);
+
+ SSDFS_INFO("%s has been unmounted from device %s\n",
+ SSDFS_VERSION, fsi->devops->device_name(sb));
+
+ if (fsi->erase_page)
+ ssdfs_super_free_page(fsi->erase_page);
+
+ ssdfs_maptbl_cache_destroy(&fsi->maptbl_cache);
+ ssdfs_destruct_sb_info(&fsi->sbi);
+ ssdfs_destruct_sb_info(&fsi->sbi_backup);
+
+ ssdfs_free_workspaces();
+
+ ssdfs_super_kfree(fsi);
+ sb->s_fs_info = NULL;
+
+ rcu_barrier();
+
+ ssdfs_check_memory_page_locks();
+ ssdfs_check_memory_leaks();
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("All metadata structures have been destroyed...\n");
+#else
+ SSDFS_DBG("All metadata structures have been destroyed...\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+}
+
+static struct dentry *ssdfs_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name,
+ void *data)
+{
+#ifdef CONFIG_SSDFS_MTD_DEVICE
+ return mount_mtd(fs_type, flags, dev_name, data, ssdfs_fill_super);
+#elif defined(CONFIG_SSDFS_BLOCK_DEVICE)
+ return mount_bdev(fs_type, flags, dev_name, data, ssdfs_fill_super);
+#else
+ BUILD_BUG();
+ return NULL;
+#endif
+}
+
+static void kill_ssdfs_sb(struct super_block *sb)
+{
+#ifdef CONFIG_SSDFS_MTD_DEVICE
+ kill_mtd_super(sb);
+#elif defined(CONFIG_SSDFS_BLOCK_DEVICE)
+ kill_block_super(sb);
+#else
+ BUILD_BUG();
+#endif
+}
+
+static struct file_system_type ssdfs_fs_type = {
+ .name = "ssdfs",
+ .owner = THIS_MODULE,
+ .mount = ssdfs_mount,
+ .kill_sb = kill_ssdfs_sb,
+#ifdef CONFIG_SSDFS_BLOCK_DEVICE
+ .fs_flags = FS_REQUIRES_DEV,
+#endif
+};
+MODULE_ALIAS_FS(SSDFS_VERSION);
+
+static void ssdfs_destroy_caches(void)
+{
+ /*
+ * Make sure all delayed rcu free inodes are flushed before we
+ * destroy cache.
+ */
+ rcu_barrier();
+
+ if (ssdfs_inode_cachep)
+ kmem_cache_destroy(ssdfs_inode_cachep);
+
+ ssdfs_destroy_seg_req_obj_cache();
+ ssdfs_destroy_btree_search_obj_cache();
+ ssdfs_destroy_free_ino_desc_cache();
+ ssdfs_destroy_btree_node_obj_cache();
+ ssdfs_destroy_seg_obj_cache();
+ ssdfs_destroy_extent_info_cache();
+ ssdfs_destroy_peb_mapping_info_cache();
+ ssdfs_destroy_blk2off_frag_obj_cache();
+ ssdfs_destroy_name_info_cache();
+}
+
+static int ssdfs_init_caches(void)
+{
+ int err;
+
+ ssdfs_zero_seg_obj_cache_ptr();
+ ssdfs_zero_seg_req_obj_cache_ptr();
+ ssdfs_zero_extent_info_cache_ptr();
+ ssdfs_zero_btree_node_obj_cache_ptr();
+ ssdfs_zero_btree_search_obj_cache_ptr();
+ ssdfs_zero_free_ino_desc_cache_ptr();
+ ssdfs_zero_peb_mapping_info_cache_ptr();
+ ssdfs_zero_blk2off_frag_obj_cache_ptr();
+ ssdfs_zero_name_info_cache_ptr();
+
+ ssdfs_inode_cachep = kmem_cache_create("ssdfs_inode_cache",
+ sizeof(struct ssdfs_inode_info), 0,
+ SLAB_RECLAIM_ACCOUNT |
+ SLAB_MEM_SPREAD |
+ SLAB_ACCOUNT,
+ ssdfs_init_inode_once);
+ if (!ssdfs_inode_cachep) {
+ SSDFS_ERR("unable to create inode cache\n");
+ return -ENOMEM;
+ }
+
+ err = ssdfs_init_seg_obj_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create segment object cache: err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_seg_req_obj_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create segment request object cache: "
+ "err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_extent_info_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create extent info object cache: "
+ "err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_btree_node_obj_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create btree node object cache: err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_btree_search_obj_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create btree search object cache: "
+ "err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_free_ino_desc_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create free inode descriptors cache: "
+ "err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_peb_mapping_info_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create PEB mapping descriptors cache: "
+ "err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_blk2off_frag_obj_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create blk2off fragments cache: "
+ "err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ err = ssdfs_init_name_info_cache();
+ if (unlikely(err)) {
+ SSDFS_ERR("unable to create name info cache: "
+ "err %d\n",
+ err);
+ goto destroy_caches;
+ }
+
+ return 0;
+
+destroy_caches:
+ ssdfs_destroy_caches();
+ return -ENOMEM;
+}
+
+static inline void ssdfs_print_info(void)
+{
+ SSDFS_INFO("%s loaded\n", SSDFS_VERSION);
+}
+
+static int __init ssdfs_init(void)
+{
+ int err;
+
+ err = ssdfs_init_caches();
+ if (err) {
+ SSDFS_ERR("failed to initialize caches\n");
+ goto failed_init;
+ }
+
+ err = ssdfs_compressors_init();
+ if (err) {
+ SSDFS_ERR("failed to initialize compressors\n");
+ goto free_caches;
+ }
+
+ err = ssdfs_sysfs_init();
+ if (err) {
+ SSDFS_ERR("failed to initialize sysfs subsystem\n");
+ goto stop_compressors;
+ }
+
+ err = register_filesystem(&ssdfs_fs_type);
+ if (err) {
+ SSDFS_ERR("failed to register filesystem\n");
+ goto sysfs_exit;
+ }
+
+ ssdfs_print_info();
+
+ return 0;
+
+sysfs_exit:
+ ssdfs_sysfs_exit();
+
+stop_compressors:
+ ssdfs_compressors_exit();
+
+free_caches:
+ ssdfs_destroy_caches();
+
+failed_init:
+ return err;
+}
+
+static void __exit ssdfs_exit(void)
+{
+ ssdfs_destroy_caches();
+ unregister_filesystem(&ssdfs_fs_type);
+ ssdfs_sysfs_exit();
+ ssdfs_compressors_exit();
+}
+
+module_init(ssdfs_init);
+module_exit(ssdfs_exit);
+
+MODULE_DESCRIPTION("SSDFS -- SSD-oriented File System");
+MODULE_AUTHOR("HGST, San Jose Research Center, Storage Architecture Group");
+MODULE_AUTHOR("Viacheslav Dubeyko <slava@dubeyko.com>");
+MODULE_LICENSE("Dual BSD/GPL");
Patch implements register/unregister file system logic. The register FS logic includes caches creation/initialization, compression support initialization, sysfs subsystem initialization. Oppositely, unregister FS logic executes destruction of caches, compression subsystem, and sysfs entries. Also, patch implements basic mount/unmount logic. The ssdfs_fill_super() implements mount logic that includes: (1) parsing mount options, (2) extract superblock info, (3) create key in-core metadata structures (mapping table, segment bitmap, b-trees), (4) create root inode, (5) start metadata structures' threads, (6) commit superblock on finish of mount operation. The ssdfs_put_super() implements unmount logic: (1) stop metadata threads, (2) wait unfinished user data requests, (3) flush dirty metadata structures, (4) commit superblock, (5) destroy in-core metadata structures. 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/fs_error.c | 257 ++++++ fs/ssdfs/options.c | 190 +++++ fs/ssdfs/readwrite.c | 651 +++++++++++++++ fs/ssdfs/super.c | 1844 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2942 insertions(+) create mode 100644 fs/ssdfs/fs_error.c create mode 100644 fs/ssdfs/options.c create mode 100644 fs/ssdfs/readwrite.c create mode 100644 fs/ssdfs/super.c