[PATCH v2 77/79] ssdfs: implement file operations support

Viacheslav Dubeyko posted 79 patches 3 weeks, 1 day ago
Only 33 patches received!
[PATCH v2 77/79] ssdfs: implement file operations support
Posted by Viacheslav Dubeyko 3 weeks, 1 day ago
Complete patchset is available here:
https://github.com/dubeyko/ssdfs-driver/tree/master/patchset/linux-kernel-6.18.0

Implement file operations support.

Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
---
 fs/ssdfs/file.c | 4341 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 4341 insertions(+)
 create mode 100644 fs/ssdfs/file.c

diff --git a/fs/ssdfs/file.c b/fs/ssdfs/file.c
new file mode 100644
index 000000000000..79179e221004
--- /dev/null
+++ b/fs/ssdfs/file.c
@@ -0,0 +1,4341 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/file.c - file operations.
+ *
+ * Copyright (c) 2019-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ * All rights reserved.
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/highmem.h>
+#include <linux/pagemap.h>
+#include <linux/writeback.h>
+#include <linux/pagevec.h>
+#include <linux/blkdev.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "folio_vector.h"
+#include "ssdfs.h"
+#include "request_queue.h"
+#include "folio_array.h"
+#include "peb.h"
+#include "offset_translation_table.h"
+#include "peb_container.h"
+#include "segment_bitmap.h"
+#include "segment.h"
+#include "btree_search.h"
+#include "btree_node.h"
+#include "btree.h"
+#include "inodes_tree.h"
+#include "extents_tree.h"
+#include "xattr.h"
+#include "acl.h"
+#include "peb_mapping_table.h"
+
+#include <trace/events/ssdfs.h>
+
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+atomic64_t ssdfs_file_folio_leaks;
+atomic64_t ssdfs_file_memory_leaks;
+atomic64_t ssdfs_file_cache_leaks;
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+/*
+ * void ssdfs_file_cache_leaks_increment(void *kaddr)
+ * void ssdfs_file_cache_leaks_decrement(void *kaddr)
+ * void *ssdfs_file_kmalloc(size_t size, gfp_t flags)
+ * void *ssdfs_file_kzalloc(size_t size, gfp_t flags)
+ * void *ssdfs_file_kcalloc(size_t n, size_t size, gfp_t flags)
+ * void ssdfs_file_kfree(void *kaddr)
+ * struct folio *ssdfs_file_alloc_folio(gfp_t gfp_mask,
+ *                                      unsigned int order)
+ * struct folio *ssdfs_file_add_batch_folio(struct folio_batch *batch,
+ *                                          unsigned int order)
+ * void ssdfs_file_free_folio(struct folio *folio)
+ * void ssdfs_file_folio_batch_release(struct folio_batch *batch)
+ */
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+	SSDFS_MEMORY_LEAKS_CHECKER_FNS(file)
+#else
+	SSDFS_MEMORY_ALLOCATOR_FNS(file)
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+void ssdfs_file_memory_leaks_init(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+	atomic64_set(&ssdfs_file_folio_leaks, 0);
+	atomic64_set(&ssdfs_file_memory_leaks, 0);
+	atomic64_set(&ssdfs_file_cache_leaks, 0);
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+void ssdfs_file_check_memory_leaks(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+	if (atomic64_read(&ssdfs_file_folio_leaks) != 0) {
+		SSDFS_ERR("FILE: "
+			  "memory leaks include %lld folios\n",
+			  atomic64_read(&ssdfs_file_folio_leaks));
+	}
+
+	if (atomic64_read(&ssdfs_file_memory_leaks) != 0) {
+		SSDFS_ERR("FILE: "
+			  "memory allocator suffers from %lld leaks\n",
+			  atomic64_read(&ssdfs_file_memory_leaks));
+	}
+
+	if (atomic64_read(&ssdfs_file_cache_leaks) != 0) {
+		SSDFS_ERR("FILE: "
+			  "caches suffers from %lld leaks\n",
+			  atomic64_read(&ssdfs_file_cache_leaks));
+	}
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+enum {
+	SSDFS_BLOCK_BASED_REQUEST,
+	SSDFS_EXTENT_BASED_REQUEST,
+};
+
+enum {
+	SSDFS_CURRENT_THREAD_READ,
+	SSDFS_DELEGATE_TO_READ_THREAD,
+};
+
+static inline
+bool can_file_be_inline(struct inode *inode, loff_t new_size)
+{
+	size_t capacity = ssdfs_inode_inline_file_capacity(inode);
+
+	if (capacity == 0)
+		return false;
+
+	if (capacity < new_size)
+		return false;
+
+	return true;
+}
+
+static inline
+size_t ssdfs_inode_size_threshold(void)
+{
+	return sizeof(struct ssdfs_inode) -
+			offsetof(struct ssdfs_inode, internal);
+}
+
+int ssdfs_allocate_inline_file_buffer(struct inode *inode)
+{
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	size_t threshold = ssdfs_inode_size_threshold();
+	size_t inline_capacity;
+
+	if (ii->inline_file)
+		return 0;
+
+	inline_capacity = ssdfs_inode_inline_file_capacity(inode);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("inline_capacity %zu, threshold %zu\n",
+		  inline_capacity, threshold);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (inline_capacity < threshold) {
+		SSDFS_ERR("inline_capacity %zu < threshold %zu\n",
+			  inline_capacity, threshold);
+		return -ERANGE;
+	} else if (inline_capacity == threshold) {
+		ii->inline_file = ii->raw_inode.internal;
+	} else {
+		ii->inline_file =
+			ssdfs_file_kzalloc(inline_capacity, GFP_KERNEL);
+		if (!ii->inline_file) {
+			SSDFS_ERR("fail to allocate inline buffer: "
+				  "ino %lu, inline_capacity %zu\n",
+				  inode->i_ino, inline_capacity);
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+void ssdfs_destroy_inline_file_buffer(struct inode *inode)
+{
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	size_t threshold = ssdfs_inode_size_threshold();
+	size_t inline_capacity;
+
+	if (!ii->inline_file)
+		return;
+
+	inline_capacity = ssdfs_inode_inline_file_capacity(inode);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("inline_capacity %zu, threshold %zu\n",
+		  inline_capacity, threshold);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (inline_capacity <= threshold) {
+		ii->inline_file = NULL;
+	} else {
+		ssdfs_file_kfree(ii->inline_file);
+		ii->inline_file = NULL;
+	}
+}
+
+/*
+ * ssdfs_read_block_async() - read block async
+ * @fsi: pointer on shared file system object
+ * @req: request object
+ */
+static
+int ssdfs_read_block_async(struct ssdfs_fs_info *fsi,
+			   struct ssdfs_segment_request *req)
+{
+	struct ssdfs_segment_info *si;
+	struct ssdfs_segment_search_state seg_search;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!fsi || !req);
+	BUG_ON((req->extent.logical_offset >> fsi->log_pagesize) >= U32_MAX);
+
+	SSDFS_DBG("fsi %p, req %p\n", fsi, req);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = ssdfs_prepare_volume_extent(fsi, req);
+	if (err == -EAGAIN) {
+		err = 0;
+		SSDFS_DBG("logical extent processed partially\n");
+	} else if (unlikely(err)) {
+		SSDFS_ERR("fail to prepare volume extent: "
+			  "ino %llu, logical_offset %llu, "
+			  "data_bytes %u, cno %llu, "
+			  "parent_snapshot %llu, err %d\n",
+			  req->extent.ino,
+			  req->extent.logical_offset,
+			  req->extent.data_bytes,
+			  req->extent.cno,
+			  req->extent.parent_snapshot,
+			  err);
+		return err;
+	}
+
+	req->place.len = 1;
+
+	ssdfs_segment_search_state_init(&seg_search,
+					SSDFS_USER_DATA_SEG_TYPE,
+					req->place.start.seg_id, U64_MAX);
+
+	si = ssdfs_grab_segment(fsi, &seg_search);
+	if (unlikely(IS_ERR_OR_NULL(si))) {
+		SSDFS_ERR("fail to grab segment object: "
+			  "seg %llu, err %ld\n",
+			  req->place.start.seg_id,
+			  PTR_ERR(si));
+		return PTR_ERR(si);
+	}
+
+	if (!is_ssdfs_segment_ready_for_requests(si)) {
+		err = ssdfs_wait_segment_init_end(si);
+		if (unlikely(err)) {
+			SSDFS_ERR("segment initialization failed: "
+				  "seg %llu, ino %llu, err %d\n",
+				  si->seg_id, req->extent.ino, err);
+			return err;
+		}
+	}
+
+	err = ssdfs_segment_read_block_async(si, SSDFS_REQ_ASYNC, req);
+	if (unlikely(err)) {
+		SSDFS_ERR("read request failed: "
+			  "ino %llu, logical_offset %llu, size %u, err %d\n",
+			  req->extent.ino, req->extent.logical_offset,
+			  req->extent.data_bytes, err);
+		return err;
+	}
+
+	ssdfs_segment_put_object(si);
+
+	return 0;
+}
+
+/*
+ * ssdfs_read_block_by_current_thread() - read block by current thread
+ * @fsi: pointer on shared file system object
+ * @req: request object
+ */
+static
+int ssdfs_read_block_by_current_thread(struct ssdfs_fs_info *fsi,
+					struct ssdfs_segment_request *req)
+{
+	struct ssdfs_segment_info *si;
+	struct ssdfs_peb_container *pebc;
+	struct ssdfs_blk2off_table *table;
+	struct ssdfs_offset_position pos;
+	struct ssdfs_segment_search_state seg_search;
+	u16 logical_blk;
+	struct completion *end;
+	int i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!fsi || !req);
+	BUG_ON((req->extent.logical_offset >> fsi->log_pagesize) >= U32_MAX);
+
+	SSDFS_DBG("fsi %p, req %p\n", fsi, req);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = ssdfs_prepare_volume_extent(fsi, req);
+	if (err == -EAGAIN) {
+		err = 0;
+		SSDFS_DBG("logical extent processed partially\n");
+	} else if (err == -ENOENT) {
+		SSDFS_DBG("fork is absent: "
+			  "ino %llu, logical_offset %llu, "
+			  "data_bytes %u, cno %llu, "
+			  "parent_snapshot %llu, err %d\n",
+			  req->extent.ino,
+			  req->extent.logical_offset,
+			  req->extent.data_bytes,
+			  req->extent.cno,
+			  req->extent.parent_snapshot,
+			  err);
+		return err;
+	} else if (unlikely(err)) {
+		SSDFS_ERR("fail to prepare volume extent: "
+			  "ino %llu, logical_offset %llu, "
+			  "data_bytes %u, cno %llu, "
+			  "parent_snapshot %llu, err %d\n",
+			  req->extent.ino,
+			  req->extent.logical_offset,
+			  req->extent.data_bytes,
+			  req->extent.cno,
+			  req->extent.parent_snapshot,
+			  err);
+		return err;
+	}
+
+	req->place.len = 1;
+
+	ssdfs_segment_search_state_init(&seg_search,
+					SSDFS_USER_DATA_SEG_TYPE,
+					req->place.start.seg_id, U64_MAX);
+
+	si = ssdfs_grab_segment(fsi, &seg_search);
+	if (unlikely(IS_ERR_OR_NULL(si))) {
+		SSDFS_ERR("fail to grab segment object: "
+			  "seg %llu, err %d\n",
+			  req->place.start.seg_id, err);
+		return PTR_ERR(si);
+	}
+
+	if (!is_ssdfs_segment_ready_for_requests(si)) {
+		err = ssdfs_wait_segment_init_end(si);
+		if (unlikely(err)) {
+			SSDFS_ERR("segment initialization failed: "
+				  "seg %llu, err %d\n",
+				  si->seg_id, err);
+			goto finish_read_block;
+		}
+	}
+
+	ssdfs_request_prepare_internal_data(SSDFS_PEB_READ_REQ,
+					    SSDFS_READ_PAGE,
+					    SSDFS_REQ_SYNC,
+					    req);
+	ssdfs_request_define_segment(si->seg_id, req);
+
+	table = si->blk2off_table;
+	logical_blk = req->place.start.blk_index;
+
+	err = ssdfs_blk2off_table_get_offset_position(table, logical_blk, &pos);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to convert: "
+			  "logical_blk %u, err %d\n",
+			  logical_blk, err);
+		goto finish_read_block;
+	}
+
+	pebc = &si->peb_array[pos.peb_index];
+
+	ssdfs_peb_read_request_cno(pebc);
+
+	err = ssdfs_peb_read_page(pebc, req, &end);
+	if (err == -EAGAIN) {
+		err = SSDFS_WAIT_COMPLETION(end);
+		if (unlikely(err)) {
+			SSDFS_ERR("PEB init failed: "
+				  "err %d\n", err);
+			goto forget_request_cno;
+		}
+
+		err = ssdfs_peb_read_page(pebc, req, &end);
+	}
+
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to read block: err %d\n",
+			  err);
+		goto forget_request_cno;
+	}
+
+	for (i = 0; i < req->result.processed_blks; i++)
+		ssdfs_peb_mark_request_block_uptodate(pebc, req, i);
+
+forget_request_cno:
+	ssdfs_peb_finish_read_request_cno(pebc);
+
+finish_read_block:
+	req->result.err = err;
+	complete(&req->result.wait);
+	ssdfs_segment_put_object(si);
+
+	return 0;
+}
+
+static
+int ssdfs_read_block_nolock(struct file *file, struct folio_batch *batch,
+			    int read_mode)
+{
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(file_inode(file)->i_sb);
+	struct inode *inode = file_inode(file);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct folio *folio;
+	struct ssdfs_segment_request *req = NULL;
+	ino_t ino = file_inode(file)->i_ino;
+	loff_t logical_offset;
+	loff_t data_bytes = 0;
+	loff_t file_size;
+	int i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, read_mode %#x\n",
+		  ino, read_mode);
+
+	BUG_ON(folio_batch_count(batch) == 0);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	folio = batch->folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	logical_offset = (loff_t)folio->index << PAGE_SHIFT;
+
+	for (i = 0; i < folio_batch_count(batch); i++) {
+		folio = batch->folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		__ssdfs_memzero_folio(folio, 0, folio_size(folio),
+				      folio_size(folio));
+
+		data_bytes += folio_size(folio);
+	}
+
+	file_size = i_size_read(file_inode(file));
+	data_bytes = min_t(loff_t, file_size - logical_offset, data_bytes);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(data_bytes > U32_MAX);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (logical_offset >= file_size) {
+		/* Reading beyond inode */
+		for (i = 0; i < folio_batch_count(batch); i++) {
+			folio = batch->folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			folio_mark_uptodate(folio);
+			flush_dcache_folio(folio);
+		}
+
+		goto finish_read_block;
+	} else if (is_ssdfs_file_inline(ii)) {
+		loff_t byte_offset = 0;
+		loff_t iter_bytes;
+		size_t inline_capacity =
+				ssdfs_inode_inline_file_capacity(inode);
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("inline_capacity %zu, file_size %llu\n",
+			  inline_capacity, file_size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		if (file_size > inline_capacity) {
+			err = -E2BIG;
+			SSDFS_ERR("file_size %llu is greater than capacity %zu\n",
+				  file_size, inline_capacity);
+			goto fail_read_block;
+		} else if (data_bytes > inline_capacity) {
+			err = -ERANGE;
+			SSDFS_ERR("data_bytes %llu is greater than capacity %zu\n",
+				  data_bytes, inline_capacity);
+			goto fail_read_block;
+		}
+
+		for (i = 0; i < folio_batch_count(batch); i++) {
+			folio = batch->folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			if (byte_offset < inline_capacity) {
+#ifdef CONFIG_SSDFS_DEBUG
+				BUG_ON(data_bytes <= byte_offset);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+				iter_bytes = min_t(loff_t, folio_size(folio),
+						   data_bytes - byte_offset);
+
+				err = __ssdfs_memcpy_to_folio(folio,
+							      0,
+							      folio_size(folio),
+							      ii->inline_file,
+							      byte_offset,
+							      inline_capacity,
+							      iter_bytes);
+				if (unlikely(err)) {
+					SSDFS_ERR("fail to copy file's content: "
+						  "err %d\n", err);
+					goto fail_read_block;
+				}
+			}
+
+			folio_mark_uptodate(folio);
+			flush_dcache_folio(folio);
+
+			byte_offset += folio_size(folio);
+		}
+
+		goto finish_read_block;
+	}
+
+	req = ssdfs_request_alloc();
+	if (IS_ERR_OR_NULL(req)) {
+		err = (req == NULL ? -ENOMEM : PTR_ERR(req));
+		SSDFS_ERR("fail to allocate segment request: err %d\n",
+			  err);
+		return err;
+	}
+
+	ssdfs_request_init(req, fsi->pagesize);
+	ssdfs_get_request(req);
+
+	ssdfs_request_prepare_logical_extent(ino,
+					     (u64)logical_offset,
+					     (u32)data_bytes,
+					     0, 0, req);
+
+	for (i = 0; i < folio_batch_count(batch); i++) {
+		folio = batch->folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		err = ssdfs_request_add_folio(folio, 0, req);
+		if (err) {
+			SSDFS_ERR("fail to add folio into request: "
+				  "ino %lu, folio_index %lu, err %d\n",
+				  ino, folio->index, err);
+			goto fail_read_block;
+		}
+	}
+
+	switch (read_mode) {
+	case SSDFS_CURRENT_THREAD_READ:
+		err = ssdfs_read_block_by_current_thread(fsi, req);
+		if (err == -ENOENT) {
+			SSDFS_DBG("empty block has been prepared\n");
+
+			for (i = 0; i < folio_batch_count(batch); i++) {
+				folio = batch->folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+				BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+				folio_mark_uptodate(folio);
+				flush_dcache_folio(folio);
+			}
+
+			ssdfs_put_request(req);
+			ssdfs_request_free(req, NULL);
+			goto finish_read_block;
+		} else if (err) {
+			SSDFS_ERR("fail to read block: err %d\n", err);
+			goto fail_read_block;
+		}
+
+		err = SSDFS_WAIT_COMPLETION(&req->result.wait);
+		if (unlikely(err)) {
+			SSDFS_ERR("read request failed: "
+				  "ino %lu, logical_offset %llu, "
+				  "size %u, err %d\n",
+				  ino, (u64)logical_offset,
+				  (u32)data_bytes, err);
+			goto fail_read_block;
+		}
+
+		if (req->result.err) {
+			SSDFS_ERR("read request failed: "
+				  "ino %lu, logical_offset %llu, "
+				  "size %u, err %d\n",
+				  ino, (u64)logical_offset,
+				  (u32)data_bytes,
+				  req->result.err);
+			goto fail_read_block;
+		}
+
+		ssdfs_put_request(req);
+		ssdfs_request_free(req, NULL);
+		break;
+
+	case SSDFS_DELEGATE_TO_READ_THREAD:
+		err = ssdfs_read_block_async(fsi, req);
+		if (err) {
+			SSDFS_ERR("fail to read block: err %d\n", err);
+			goto fail_read_block;
+		}
+		break;
+
+	default:
+		BUG();
+	}
+
+finish_read_block:
+	return 0;
+
+fail_read_block:
+	for (i = 0; i < folio_batch_count(batch); i++) {
+		folio = batch->folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		folio_clear_uptodate(folio);
+	}
+
+	if (req) {
+		ssdfs_put_request(req);
+		ssdfs_request_free(req, NULL);
+	}
+
+	return err;
+}
+
+static
+int ssdfs_read_block(struct file *file, struct folio *folio)
+{
+	struct inode *inode = file_inode(file);
+	struct address_space *mapping = file->f_mapping;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct folio_batch fbatch;
+	struct folio *cur_folio;
+	loff_t logical_offset;
+	pgoff_t index;
+	fgf_t fgp_flags = FGP_CREAT | FGP_LOCK;
+	u32 processed_bytes = 0;
+	bool need_read_block = false;
+	int i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, folio_index %lu, "
+		  "folio_size %zu\n",
+		  file_inode(file)->i_ino, folio->index,
+		  folio_size(folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	folio_batch_init(&fbatch);
+
+	logical_offset = (loff_t)folio->index << PAGE_SHIFT;
+	logical_offset >>= fsi->log_pagesize;
+	logical_offset <<= fsi->log_pagesize;
+
+	index = logical_offset >> PAGE_SHIFT;
+
+	while (processed_bytes < fsi->pagesize) {
+		if (index == folio->index) {
+			cur_folio = folio;
+			ssdfs_account_locked_folio(folio);
+		} else {
+			fgp_flags |= fgf_set_order(fsi->pagesize -
+						   processed_bytes);
+
+			cur_folio = __filemap_get_folio(mapping,
+						    index,
+						    fgp_flags,
+						    mapping_gfp_mask(mapping));
+			if (!cur_folio) {
+				SSDFS_ERR("fail to grab folio: page_index %lu\n",
+					  index);
+				return -ENOMEM;
+			} else if (IS_ERR(cur_folio)) {
+				SSDFS_ERR("fail to grab folio: "
+					  "page_index %lu, err %ld\n",
+					  index, PTR_ERR(cur_folio));
+				return PTR_ERR(cur_folio);
+			}
+
+			ssdfs_account_locked_folio(cur_folio);
+		}
+
+		folio_batch_add(&fbatch, cur_folio);
+
+		if (!folio_test_uptodate(cur_folio))
+			need_read_block = true;
+
+		index += folio_size(cur_folio) >> PAGE_SHIFT;
+		processed_bytes += folio_size(cur_folio);
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	processed_bytes = 0;
+
+	for (i = 0; i < folio_batch_count(&fbatch); i++) {
+		cur_folio = fbatch.folios[i];
+
+		if (!cur_folio)
+			continue;
+
+		processed_bytes += folio_size(cur_folio);
+	}
+
+	if (processed_bytes != fsi->pagesize) {
+		SSDFS_ERR("invalid block batch: "
+			  "ino %lu, folio_index %lu, "
+			  "folio_size %zu, processed_bytes %u, "
+			  "pagesize %u\n",
+			  file_inode(file)->i_ino, folio->index,
+			  folio_size(folio), processed_bytes,
+			  fsi->pagesize);
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (need_read_block) {
+		err = ssdfs_read_block_nolock(file, &fbatch,
+						SSDFS_CURRENT_THREAD_READ);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to read folio: "
+				  "index %lu, err %d\n",
+				  folio->index, err);
+		}
+	}
+
+	for (i = 0; i < folio_batch_count(&fbatch); i++) {
+		cur_folio = fbatch.folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!cur_folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_folio_unlock(cur_folio);
+	}
+
+	return err;
+}
+
+static
+int ssdfs_check_read_request(struct ssdfs_segment_request *req)
+{
+	wait_queue_head_t *wq = NULL;
+	int res;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!req);
+
+	SSDFS_DBG("req %p\n", req);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+check_req_state:
+	switch (atomic_read(&req->result.state)) {
+	case SSDFS_REQ_CREATED:
+	case SSDFS_REQ_STARTED:
+		wq = &req->private.wait_queue;
+
+		res = wait_event_killable_timeout(*wq,
+					has_request_been_executed(req),
+					SSDFS_DEFAULT_TIMEOUT);
+		if (res < 0) {
+			err = res;
+			WARN_ON(1);
+		} else if (res > 1) {
+			/*
+			 * Condition changed before timeout
+			 */
+			goto check_req_state;
+		} else {
+			/* timeout is elapsed */
+			err = -ERANGE;
+			WARN_ON(1);
+		}
+		break;
+
+	case SSDFS_REQ_FINISHED:
+		/* do nothing */
+		break;
+
+	case SSDFS_REQ_FAILED:
+		err = req->result.err;
+
+		if (!err) {
+			SSDFS_ERR("error code is absent: "
+				  "req %p, err %d\n",
+				  req, err);
+			err = -ERANGE;
+		}
+
+		SSDFS_ERR("read request is failed: "
+			  "err %d\n", err);
+		goto finish_check;
+
+	default:
+		err = -ERANGE;
+		SSDFS_ERR("invalid result's state %#x\n",
+			  atomic_read(&req->result.state));
+		goto finish_check;
+	}
+
+finish_check:
+	return err;
+}
+
+static
+int ssdfs_wait_read_request_end(struct ssdfs_fs_info *fsi,
+				struct ssdfs_segment_request *req)
+{
+	struct ssdfs_segment_info *si;
+	struct ssdfs_segment_search_state seg_search;
+	wait_queue_head_t *wait;
+	int res;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("req %p\n", req);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!req)
+		return 0;
+
+	err = ssdfs_check_read_request(req);
+	if (unlikely(err)) {
+		SSDFS_ERR("read request failed: "
+			  "err %d\n", err);
+		goto free_request;
+	}
+
+	ssdfs_segment_search_state_init(&seg_search,
+					SSDFS_USER_DATA_SEG_TYPE,
+					req->place.start.seg_id, U64_MAX);
+
+	si = ssdfs_grab_segment(fsi, &seg_search);
+	if (unlikely(IS_ERR_OR_NULL(si))) {
+		err = (si == NULL ? -ENOMEM : PTR_ERR(si));
+		SSDFS_ERR("fail to grab segment object: "
+			  "seg %llu, err %d\n",
+			  req->place.start.seg_id,
+			  err);
+		goto finish_wait;
+	}
+
+	wait = &si->wait_queue[SSDFS_PEB_READ_THREAD];
+
+	if (atomic_read(&req->private.refs_count) != 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("start waiting: refs_count %d\n",
+			   atomic_read(&req->private.refs_count));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		res = wait_event_killable_timeout(*wait,
+			    atomic_read(&req->private.refs_count) == 0,
+			    SSDFS_DEFAULT_TIMEOUT);
+		if (res < 0) {
+			err = res;
+			WARN_ON(1);
+		} else if (res > 1) {
+			/*
+			 * Condition changed before timeout
+			 */
+		} else {
+			/* timeout is elapsed */
+			err = -ERANGE;
+			WARN_ON(1);
+		}
+	}
+
+	ssdfs_segment_put_object(si);
+
+free_request:
+	ssdfs_request_free(req, si);
+
+finish_wait:
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("finished\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+	return err;
+}
+
+struct ssdfs_readahead_env {
+	struct file *file;
+	struct ssdfs_segment_request **reqs;
+	unsigned count;
+	unsigned capacity;
+
+	struct folio_batch batch;
+	struct ssdfs_logical_extent requested;
+	struct ssdfs_volume_extent place;
+	struct ssdfs_volume_extent cur_extent;
+};
+
+static
+struct ssdfs_segment_request *
+ssdfs_issue_read_request(struct ssdfs_readahead_env *env)
+{
+	struct ssdfs_fs_info *fsi;
+	struct ssdfs_segment_request *req = NULL;
+	struct ssdfs_segment_info *si;
+	struct ssdfs_segment_search_state seg_search;
+	loff_t data_bytes = 0;
+	int i;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!env);
+
+	SSDFS_DBG("requested (ino %llu, logical_offset %llu, "
+		  "cno %llu, parent_snapshot %llu), "
+		  "current extent (seg_id %llu, logical_blk %u, len %u)\n",
+		  env->requested.ino,
+		  env->requested.logical_offset,
+		  env->requested.cno,
+		  env->requested.parent_snapshot,
+		  env->cur_extent.start.seg_id,
+		  env->cur_extent.start.blk_index,
+		  env->cur_extent.len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	fsi = SSDFS_FS_I(file_inode(env->file)->i_sb);
+
+	req = ssdfs_request_alloc();
+	if (IS_ERR_OR_NULL(req)) {
+		err = (req == NULL ? -ENOMEM : PTR_ERR(req));
+		SSDFS_ERR("fail to allocate segment request: err %d\n",
+			  err);
+		return req;
+	}
+
+	ssdfs_request_init(req, fsi->pagesize);
+	ssdfs_get_request(req);
+
+	for (i = 0; i < folio_batch_count(&env->batch); i++) {
+		struct folio *folio;
+
+		folio = env->batch.folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		data_bytes += folio_size(folio);
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("folio_index %d, folio_size %zu, "
+			  "data_bytes %llu\n",
+			  i, folio_size(folio),
+			  (u64)data_bytes);
+#endif /* CONFIG_SSDFS_DEBUG */
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(data_bytes == 0);
+	BUG_ON(data_bytes > fsi->pagesize);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	ssdfs_request_prepare_logical_extent(env->requested.ino,
+					     env->requested.logical_offset,
+					     (u32)data_bytes,
+					     env->requested.cno,
+					     env->requested.parent_snapshot,
+					     req);
+
+	ssdfs_request_define_segment(env->cur_extent.start.seg_id, req);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(env->cur_extent.start.blk_index >= U16_MAX);
+	BUG_ON(env->cur_extent.len >= U16_MAX);
+#endif /* CONFIG_SSDFS_DEBUG */
+	ssdfs_request_define_volume_extent(env->cur_extent.start.blk_index,
+					   env->cur_extent.len,
+					   req);
+
+	for (i = 0; i < folio_batch_count(&env->batch); i++) {
+		err = ssdfs_request_add_folio(env->batch.folios[i], 0, req);
+		if (err) {
+			SSDFS_ERR("fail to add folio into request: "
+				  "ino %llu, err %d\n",
+				  env->requested.ino, err);
+			goto fail_issue_read_request;
+		}
+
+		env->batch.folios[i] = NULL;
+	}
+
+	ssdfs_segment_search_state_init(&seg_search,
+					SSDFS_USER_DATA_SEG_TYPE,
+					req->place.start.seg_id, U64_MAX);
+
+	si = ssdfs_grab_segment(fsi, &seg_search);
+	if (unlikely(IS_ERR_OR_NULL(si))) {
+		err = (si == NULL ? -ENOMEM : PTR_ERR(si));
+		SSDFS_ERR("fail to grab segment object: "
+			  "seg %llu, err %d\n",
+			  req->place.start.seg_id,
+			  err);
+		goto fail_issue_read_request;
+	}
+
+	if (!is_ssdfs_segment_ready_for_requests(si)) {
+		err = ssdfs_wait_segment_init_end(si);
+		if (unlikely(err)) {
+			SSDFS_ERR("segment initialization failed: "
+				  "seg %llu, ino %llu, err %d\n",
+				  si->seg_id, req->extent.ino, err);
+			goto fail_issue_read_request;
+		}
+	}
+
+	err = ssdfs_segment_read_block_async(si, SSDFS_REQ_ASYNC_NO_FREE, req);
+	if (unlikely(err)) {
+		SSDFS_ERR("read request failed: "
+			  "ino %llu, logical_offset %llu, size %u, err %d\n",
+			  req->extent.ino, req->extent.logical_offset,
+			  req->extent.data_bytes, err);
+		goto fail_issue_read_request;
+	}
+
+	ssdfs_segment_put_object(si);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("finished\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return req;
+
+fail_issue_read_request:
+	ssdfs_put_request(req);
+	ssdfs_request_free(req, si);
+
+	return ERR_PTR(err);
+}
+
+static
+int ssdfs_readahead_block(struct ssdfs_readahead_env *env)
+{
+	struct ssdfs_fs_info *fsi;
+	struct inode *inode;
+	struct folio *folio;
+	ino_t ino;
+	pgoff_t index;
+	loff_t logical_offset;
+	loff_t data_bytes;
+	loff_t file_size;
+	int i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!env);
+
+	SSDFS_DBG("folios_count %u\n",
+		  folio_batch_count(&env->batch));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = file_inode(env->file);
+	fsi = SSDFS_FS_I(inode->i_sb);
+	ino = inode->i_ino;
+
+	if (folio_batch_count(&env->batch) == 0) {
+		SSDFS_ERR("empty batch\n");
+		return -ERANGE;
+	}
+
+	folio = env->batch.folios[0];
+
+	index = folio->index;
+	logical_offset = (loff_t)index << PAGE_SHIFT;
+
+	file_size = i_size_read(inode);
+	data_bytes = file_size - logical_offset;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(data_bytes > U32_MAX);
+
+	SSDFS_DBG("folio_index %llu, logical_offset %llu, "
+		  "file_size %llu, data_bytes %llu\n",
+		  (u64)index, (u64)logical_offset,
+		  (u64)file_size, (u64)data_bytes);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	env->requested.ino = ino;
+	env->requested.logical_offset = logical_offset;
+	env->requested.data_bytes = data_bytes;
+	env->requested.cno = 0;
+	env->requested.parent_snapshot = 0;
+
+	if (env->place.len == 0) {
+		err = __ssdfs_prepare_volume_extent(fsi, inode,
+						    &env->requested,
+						    &env->place);
+		if (err == -EAGAIN) {
+			err = 0;
+			SSDFS_DBG("logical extent processed partially\n");
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to prepare volume extent: "
+				  "ino %llu, logical_offset %llu, "
+				  "data_bytes %u, cno %llu, "
+				  "parent_snapshot %llu, err %d\n",
+				  env->requested.ino,
+				  env->requested.logical_offset,
+				  env->requested.data_bytes,
+				  env->requested.cno,
+				  env->requested.parent_snapshot,
+				  err);
+			goto fail_readahead_block;
+		}
+	}
+
+	if (env->place.len == 0) {
+		err = -ERANGE;
+		SSDFS_ERR("found empty extent\n");
+		goto fail_readahead_block;
+	}
+
+	env->cur_extent.start.seg_id = env->place.start.seg_id;
+	env->cur_extent.start.blk_index = env->place.start.blk_index;
+	env->cur_extent.len = 1;
+
+	env->place.start.blk_index++;
+	env->place.len--;
+
+	env->reqs[env->count] = ssdfs_issue_read_request(env);
+	if (IS_ERR_OR_NULL(env->reqs[env->count])) {
+		err = (env->reqs[env->count] == NULL ? -ENOMEM :
+					PTR_ERR(env->reqs[env->count]));
+		env->reqs[env->count] = NULL;
+
+		if (err == -ENODATA) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("no data for the block: "
+				  "index %d\n", env->count);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto fail_readahead_block;
+		} else {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("unable to issue request: "
+				  "index %d, err %d\n",
+				  env->count, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto fail_readahead_block;
+		}
+	} else
+		env->count++;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("finished\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return 0;
+
+fail_readahead_block:
+	for (i = 0; i < folio_batch_count(&env->batch); i++) {
+		folio = env->batch.folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		__ssdfs_memzero_folio(folio, 0, folio_size(folio),
+					folio_size(folio));
+
+		folio_clear_uptodate(folio);
+		ssdfs_folio_unlock(folio);
+		ssdfs_folio_put(folio);
+	}
+
+	return err;
+}
+
+/*
+ * The ssdfs_readahead() is called by the VM to read pages
+ * associated with the address_space object. The pages are
+ * consecutive in the page cache and are locked.
+ * The implementation should decrement the page refcount
+ * after starting I/O on each page. Usually the page will be
+ * unlocked by the I/O completion handler. The ssdfs_readahead()
+ * is only used for read-ahead, so read errors are ignored.
+ */
+static
+void ssdfs_readahead(struct readahead_control *rac)
+{
+	struct inode *inode = file_inode(rac->file);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_readahead_env env;
+	struct folio *folio;
+	pgoff_t index;
+	loff_t logical_offset;
+	loff_t file_size;
+	unsigned i;
+	int res;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, nr_pages %u\n",
+		  file_inode(rac->file)->i_ino,
+		  readahead_count(rac));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (is_ssdfs_file_inline(ii)) {
+		/* do nothing */
+		return;
+	}
+
+	env.file = rac->file;
+	env.count = 0;
+	env.capacity = readahead_count(rac);
+
+	env.reqs = ssdfs_file_kcalloc(env.capacity,
+				  sizeof(struct ssdfs_segment_request *),
+				  GFP_KERNEL);
+	if (!env.reqs) {
+		SSDFS_ERR("fail to allocate requests array\n");
+		return;
+	}
+
+	folio_batch_init(&env.batch);
+	memset(&env.requested, 0, sizeof(struct ssdfs_logical_extent));
+	memset(&env.place, 0, sizeof(struct ssdfs_volume_extent));
+	memset(&env.cur_extent, 0, sizeof(struct ssdfs_volume_extent));
+
+	for (i = 0; i < env.capacity; i++) {
+		u32 processed_bytes = 0;
+
+		folio_batch_reinit(&env.batch);
+
+		while (processed_bytes < fsi->pagesize) {
+			folio = readahead_folio(rac);
+			if (!folio) {
+				SSDFS_DBG("no more folios\n");
+
+				if (processed_bytes > 0)
+					goto try_readahead_block;
+				else
+					goto finish_requests_processing;
+			}
+
+			prefetchw(&folio->flags);
+
+			index = folio->index;
+			logical_offset = (loff_t)index << PAGE_SHIFT;
+			file_size = i_size_read(file_inode(env.file));
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("index %lu, folio_size %zu, "
+				  "logical_offset %llu, file_size %llu\n",
+				  index, folio_size(folio),
+				  logical_offset, file_size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			if (logical_offset >= file_size) {
+				/* Reading beyond inode */
+				err = -ENODATA;
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("Reading beyond inode: "
+					  "index %lu, folio_size %zu, "
+					  "logical_offset %llu, file_size %llu\n",
+					  index, folio_size(folio),
+					  logical_offset, file_size);
+#endif /* CONFIG_SSDFS_DEBUG */
+				folio_mark_uptodate(folio);
+				flush_dcache_folio(folio);
+
+				if (processed_bytes > 0)
+					goto try_readahead_block;
+				else
+					goto finish_requests_processing;
+			}
+
+			ssdfs_folio_get(folio);
+			ssdfs_account_locked_folio(folio);
+
+			__ssdfs_memzero_folio(folio, 0, folio_size(folio),
+					      folio_size(folio));
+
+			folio_batch_add(&env.batch, folio);
+
+			processed_bytes += folio_size(folio);
+		}
+
+try_readahead_block:
+		err = ssdfs_readahead_block(&env);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to process block: "
+				  "index %u, err %d\n",
+				  env.count, err);
+			break;
+		}
+	}
+
+finish_requests_processing:
+	for (i = 0; i < env.count; i++) {
+		res = ssdfs_wait_read_request_end(fsi, env.reqs[i]);
+		if (res) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("waiting has finished with issue: "
+				  "index %u, err %d\n",
+				  i, res);
+#endif /* CONFIG_SSDFS_DEBUG */
+		}
+
+		if (err == 0)
+			err = res;
+
+		env.reqs[i] = NULL;
+	}
+
+	if (env.reqs)
+		ssdfs_file_kfree(env.reqs);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	if (err) {
+		SSDFS_DBG("readahead fails: "
+			  "ino %lu, nr_pages %u, err %d\n",
+			  file_inode(rac->file)->i_ino,
+			  readahead_count(rac), err);
+	} else {
+		SSDFS_DBG("readahead finished: "
+			  "ino %lu, nr_pages %u, err %d\n",
+			  file_inode(rac->file)->i_ino,
+			  readahead_count(rac), err);
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return;
+}
+
+static ssize_t ssdfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+	struct folio *folio;
+	struct file *file = iocb->ki_filp;
+	struct inode *inode = file_inode(file);
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct address_space *mapping = file->f_mapping;
+	pgoff_t start_index = iocb->ki_pos >> PAGE_SHIFT;
+	size_t iter_bytes = iov_iter_count(iter);
+	u32 processed_bytes;
+	size_t folios_count;
+	int pages_per_folio = fsi->pagesize >> PAGE_SHIFT;
+	fgf_t fgp_flags = FGP_CREAT | FGP_LOCK;
+	int i;
+	ssize_t res = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, pos %llu, iter_bytes %zu\n",
+		  inode->i_ino, iocb->ki_pos, iter_bytes);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!iter_bytes)
+		return 0;
+
+	if (iocb->ki_pos >= i_size_read(inode))
+		return 0;
+
+	iter_bytes = min_t(size_t,
+			   iter_bytes, i_size_read(inode) - iocb->ki_pos);
+	folios_count = (iter_bytes + fsi->pagesize - 1) >> fsi->log_pagesize;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("iter_bytes %zu, folios_count %zu\n",
+		  iter_bytes, folios_count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!mapping_large_folio_support(mapping))
+		goto read_file_content_now;
+
+	if (iter_bytes == 0) {
+		SSDFS_DBG("nothing to read: iter_bytes %zu\n",
+			  iter_bytes);
+		return 0;
+	}
+
+	for (i = 0; i < folios_count; i++) {
+		pgoff_t cur_index = start_index + (i * pages_per_folio);
+		processed_bytes = 0;
+
+		while (processed_bytes < fsi->pagesize) {
+			pgoff_t page_index = cur_index +
+						(processed_bytes >> PAGE_SHIFT);
+			fgp_flags |= fgf_set_order(fsi->pagesize -
+						   processed_bytes);
+
+			folio = __filemap_get_folio(mapping,
+						    page_index,
+						    fgp_flags,
+						    mapping_gfp_mask(mapping));
+			if (!folio) {
+				SSDFS_ERR("fail to grab folio: page_index %lu\n",
+					  page_index);
+				return -ENOMEM;
+			} else if (IS_ERR(folio)) {
+				SSDFS_ERR("fail to grab folio: "
+					  "page_index %lu, err %ld\n",
+					  page_index, PTR_ERR(folio));
+				return PTR_ERR(folio);
+			}
+
+			ssdfs_account_locked_folio(folio);
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("folio %p, page_index %lu, count %d, "
+				  "folio_size %zu, page_size %u, "
+				  "fgp_flags %#x, order %u\n",
+				  folio, page_index,
+				  folio_ref_count(folio),
+				  folio_size(folio),
+				  fsi->pagesize,
+				  fgp_flags,
+				  FGF_GET_ORDER(fgp_flags));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			processed_bytes += folio_size(folio);
+
+			ssdfs_folio_unlock(folio);
+			ssdfs_folio_put(folio);
+		}
+	}
+
+read_file_content_now:
+	res = generic_file_read_iter(iocb, iter);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("finished: res %zd\n", res);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return res;
+}
+
+/*
+ * ssdfs_check_async_write_request() - check user data write request
+ * @req: segment request
+ *
+ * This method tries to check the state of request.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE     - internal error.
+ */
+static
+int ssdfs_check_async_write_request(struct ssdfs_segment_request *req)
+{
+	wait_queue_head_t *wq = NULL;
+	int res;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!req);
+
+	SSDFS_DBG("req %p\n", req);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+check_req_state:
+	switch (atomic_read(&req->result.state)) {
+	case SSDFS_REQ_CREATED:
+	case SSDFS_REQ_STARTED:
+		wq = &req->private.wait_queue;
+
+		res = wait_event_killable_timeout(*wq,
+					has_request_been_executed(req),
+					SSDFS_DEFAULT_TIMEOUT);
+		if (res < 0) {
+			err = res;
+			WARN_ON(1);
+		} else if (res > 1) {
+			/*
+			 * Condition changed before timeout
+			 */
+			goto check_req_state;
+		} else {
+			/* timeout is elapsed */
+			err = -ERANGE;
+			WARN_ON(1);
+		}
+		break;
+
+	case SSDFS_REQ_FINISHED:
+		/* do nothing */
+		break;
+
+	case SSDFS_REQ_FAILED:
+		err = req->result.err;
+
+		if (!err) {
+			SSDFS_ERR("error code is absent: "
+				  "req %p, err %d\n",
+				  req, err);
+			err = -ERANGE;
+		}
+
+		SSDFS_ERR("write request is failed: "
+			  "err %d\n", err);
+		goto finish_check;
+
+	default:
+		err = -ERANGE;
+		SSDFS_ERR("invalid result's state %#x\n",
+			  atomic_read(&req->result.state));
+		goto finish_check;
+	}
+
+finish_check:
+	return err;
+}
+
+/*
+ * ssdfs_check_sync_write_request() - check user data write request
+ * @req: segment request
+ *
+ * This method tries to check the state of request.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE     - internal error.
+ */
+static
+int ssdfs_check_sync_write_request(struct ssdfs_fs_info *fsi,
+				   struct ssdfs_segment_request *req)
+{
+	int i, j;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!req);
+
+	SSDFS_DBG("req %p\n", req);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = SSDFS_WAIT_COMPLETION(&req->result.wait);
+	if (unlikely(err)) {
+		SSDFS_ERR("write request failed: err %d\n",
+			  err);
+		return err;
+	}
+
+	switch (atomic_read(&req->result.state)) {
+	case SSDFS_REQ_FINISHED:
+		/* do nothing */
+		break;
+
+	case SSDFS_REQ_FAILED:
+		err = req->result.err;
+
+		if (!err) {
+			SSDFS_ERR("error code is absent: "
+				  "req %p, err %d\n",
+				  req, err);
+			err = -ERANGE;
+		}
+
+		SSDFS_ERR("write request is failed: "
+			  "err %d\n", err);
+		return err;
+
+	default:
+		SSDFS_ERR("unexpected result state %#x\n",
+			  atomic_read(&req->result.state));
+		return -ERANGE;
+	}
+
+	if (req->result.err) {
+		err = req->result.err;
+		SSDFS_ERR("write request failed: err %d\n",
+			  req->result.err);
+		return req->result.err;
+	}
+
+	for (i = 0; i < req->result.content.count; i++) {
+		struct ssdfs_request_content_block *block;
+		struct ssdfs_content_block *blk_state;
+
+		block = &req->result.content.blocks[i];
+		blk_state = &block->new_state;
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(folio_batch_count(&blk_state->batch) == 0);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		for (j = 0; j < folio_batch_count(&blk_state->batch); j++) {
+			struct folio *folio = blk_state->batch.folios[j];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			clear_folio_new(folio);
+			folio_mark_uptodate(folio);
+			ssdfs_clear_dirty_folio(folio);
+
+			ssdfs_folio_unlock(folio);
+			ssdfs_folio_end_writeback(fsi, U64_MAX, 0, folio);
+			ssdfs_request_writeback_folios_dec(req);
+		}
+	}
+
+	return 0;
+}
+
+static
+int ssdfs_wait_write_pool_requests_end(struct ssdfs_fs_info *fsi,
+					struct ssdfs_segment_request_pool *pool)
+{
+	struct ssdfs_segment_request *req;
+	struct ssdfs_segment_info *si;
+	struct ssdfs_segment_search_state seg_search;
+	wait_queue_head_t *wait;
+	bool has_request_failed = false;
+	int i;
+	int res;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("pool %p\n", pool);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!pool)
+		return 0;
+
+	if (pool->count == 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("request pool is empty\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+		return 0;
+	}
+
+	switch (pool->req_class) {
+	case SSDFS_PEB_CREATE_DATA_REQ:
+	case SSDFS_PEB_UPDATE_REQ:
+		/* expected class */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected class of request %#x\n",
+			  pool->req_class);
+		return -ERANGE;
+	}
+
+	switch (pool->req_command) {
+	case SSDFS_CREATE_BLOCK:
+	case SSDFS_CREATE_EXTENT:
+	case SSDFS_UPDATE_BLOCK:
+	case SSDFS_UPDATE_EXTENT:
+		/* expected class */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected command of request %#x\n",
+			  pool->req_command);
+		return -ERANGE;
+	}
+
+	switch (pool->req_type) {
+	case SSDFS_REQ_SYNC:
+		for (i = 0; i < pool->count; i++) {
+			req = pool->pointers[i];
+
+			if (!req) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("request %d is empty\n", i);
+#endif /* CONFIG_SSDFS_DEBUG */
+				continue;
+			}
+
+			err = ssdfs_check_sync_write_request(fsi, req);
+			if (unlikely(err)) {
+				SSDFS_ERR("request %d is failed: err %d\n",
+					  i, err);
+				has_request_failed = true;
+			}
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("seg_id %llu, i %d, pool->count %u, "
+				  "request's reference count %d\n",
+				  si->seg_id, i, pool->count,
+				  atomic_read(&req->private.refs_count));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_put_request(req);
+			ssdfs_request_free(req, NULL);
+			pool->pointers[i] = NULL;
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("request %d is freed\n", i);
+#endif /* CONFIG_SSDFS_DEBUG */
+		}
+
+		ssdfs_segment_request_pool_init(pool);
+		break;
+
+	case SSDFS_REQ_ASYNC_NO_FREE:
+		for (i = 0; i < pool->count; i++) {
+			req = pool->pointers[i];
+
+			if (!req) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("request %d is empty\n", i);
+#endif /* CONFIG_SSDFS_DEBUG */
+				continue;
+			}
+
+			err = ssdfs_check_async_write_request(req);
+			if (unlikely(err)) {
+				SSDFS_ERR("request %d is failed: err %d\n",
+					  i, err);
+				has_request_failed = true;
+			}
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("seg_id %llu, i %d, pool->count %u, "
+				  "request's reference count %d\n",
+				  si->seg_id, i, pool->count,
+				  atomic_read(&req->private.refs_count));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_put_request(req);
+
+			ssdfs_segment_search_state_init(&seg_search,
+						SSDFS_USER_DATA_SEG_TYPE,
+						req->place.start.seg_id,
+						U64_MAX);
+
+			si = ssdfs_grab_segment(fsi, &seg_search);
+			if (unlikely(IS_ERR_OR_NULL(si))) {
+				err = (si == NULL ? -ENOMEM : PTR_ERR(si));
+				SSDFS_ERR("fail to grab segment object: "
+					  "seg %llu, err %d\n",
+					  req->place.start.seg_id,
+					  err);
+				continue;
+			}
+
+			wait = &si->wait_queue[SSDFS_PEB_READ_THREAD];
+
+			if (atomic_read(&req->private.refs_count) != 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("start waiting: refs_count %d\n",
+					   atomic_read(&req->private.refs_count));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+				res = wait_event_killable_timeout(*wait,
+				    atomic_read(&req->private.refs_count) == 0,
+				    SSDFS_DEFAULT_TIMEOUT);
+				if (res < 0) {
+					err = res;
+					WARN_ON(1);
+				} else if (res > 1) {
+					/*
+					 * Condition changed before timeout
+					 */
+				} else {
+					/* timeout is elapsed */
+					err = -ERANGE;
+					WARN_ON(1);
+				}
+			}
+
+			ssdfs_segment_put_object(si);
+
+			ssdfs_request_free(req, si);
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("request %d is freed\n", i);
+#endif /* CONFIG_SSDFS_DEBUG */
+		}
+
+		ssdfs_segment_request_pool_init(pool);
+		break;
+
+	case SSDFS_REQ_ASYNC:
+		ssdfs_segment_request_pool_init(pool);
+		break;
+
+	default:
+		SSDFS_ERR("unknown request type %#x\n",
+			  pool->req_type);
+		return -ERANGE;
+	}
+
+	if (has_request_failed)
+		return -ERANGE;
+
+	return 0;
+}
+
+static
+void ssdfs_clean_failed_request_pool(struct ssdfs_segment_request_pool *pool)
+{
+	struct ssdfs_segment_request *req;
+	int i;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("pool %p\n", pool);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!pool)
+		return;
+
+	if (pool->count == 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("request pool is empty\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+		return;
+	}
+
+	switch (pool->req_type) {
+	case SSDFS_REQ_SYNC:
+		for (i = 0; i < pool->count; i++) {
+			req = pool->pointers[i];
+
+			if (!req) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("request %d is empty\n", i);
+#endif /* CONFIG_SSDFS_DEBUG */
+				continue;
+			}
+
+			ssdfs_put_request(req);
+			ssdfs_request_free(req, NULL);
+		}
+		break;
+
+	case SSDFS_REQ_ASYNC_NO_FREE:
+		for (i = 0; i < pool->count; i++) {
+			req = pool->pointers[i];
+
+			if (!req) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("request %d is empty\n", i);
+#endif /* CONFIG_SSDFS_DEBUG */
+				continue;
+			}
+
+			ssdfs_request_free(req, NULL);
+		}
+		break;
+
+	case SSDFS_REQ_ASYNC:
+		/* do nothing */
+		break;
+
+	default:
+		SSDFS_ERR("unknown request type %#x\n",
+			  pool->req_type);
+	}
+}
+
+/*
+ * ssdfs_update_block() - update block.
+ * @fsi: pointer on shared file system object
+ * @pool: segment request pool
+ * @batch: dirty memory folios batch
+ */
+static
+int ssdfs_update_block(struct ssdfs_fs_info *fsi,
+		       struct ssdfs_segment_request_pool *pool,
+		       struct ssdfs_dirty_folios_batch *batch,
+		       struct writeback_control *wbc)
+{
+	struct ssdfs_segment_info *si;
+	struct ssdfs_segment_search_state seg_search;
+	struct folio *folio;
+	struct inode *inode;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!fsi || !pool || !batch);
+
+	if (batch->content.count == 0) {
+		SSDFS_ERR("batch is empty\n");
+		return -ERANGE;
+	}
+
+	SSDFS_DBG("fsi %p, pool %p, batch %p\n",
+		  fsi, pool, batch);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (batch->processed_blks >= batch->content.count) {
+		SSDFS_ERR("processed_blks %u >= batch_size %u\n",
+			  batch->processed_blks,
+			  batch->content.count);
+		return -ERANGE;
+	}
+
+	folio = batch->content.blocks[batch->processed_blks].batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = folio->mapping->host;
+
+	err = __ssdfs_prepare_volume_extent(fsi, inode,
+					    &batch->requested_extent,
+					    &batch->place);
+	if (err == -EAGAIN) {
+		err = 0;
+		SSDFS_DBG("logical extent processed partially\n");
+	} else if (unlikely(err)) {
+		SSDFS_ERR("fail to prepare volume extent: "
+			  "ino %llu, logical_offset %llu, "
+			  "data_bytes %u, cno %llu, "
+			  "parent_snapshot %llu, err %d\n",
+			  batch->requested_extent.ino,
+			  batch->requested_extent.logical_offset,
+			  batch->requested_extent.data_bytes,
+			  batch->requested_extent.cno,
+			  batch->requested_extent.parent_snapshot,
+			  err);
+		return err;
+	}
+
+	ssdfs_segment_search_state_init(&seg_search,
+					SSDFS_USER_DATA_SEG_TYPE,
+					batch->place.start.seg_id,
+					U64_MAX);
+
+	si = ssdfs_grab_segment(fsi, &seg_search);
+	if (unlikely(IS_ERR_OR_NULL(si))) {
+		SSDFS_ERR("fail to grab segment object: "
+			  "seg %llu, err %d\n",
+			  batch->place.start.seg_id, err);
+		return PTR_ERR(si);
+	}
+
+	if (!is_ssdfs_segment_ready_for_requests(si)) {
+		err = ssdfs_wait_segment_init_end(si);
+		if (unlikely(err)) {
+			SSDFS_ERR("segment initialization failed: "
+				  "seg %llu, err %d\n",
+				  si->seg_id, err);
+			return err;
+		}
+	}
+
+	if (wbc->sync_mode == WB_SYNC_NONE) {
+		err = ssdfs_segment_update_data_block_async(si,
+						       SSDFS_REQ_ASYNC,
+						       pool, batch);
+	} else if (wbc->sync_mode == WB_SYNC_ALL)
+		err = ssdfs_segment_update_data_block_sync(si, pool, batch);
+	else
+		BUG();
+
+	if (err == -EAGAIN) {
+		SSDFS_DBG("wait finishing requests in pool\n");
+	} else if (unlikely(err)) {
+		SSDFS_ERR("update request failed: "
+			  "ino %llu, logical_offset %llu, size %u, err %d\n",
+			  batch->requested_extent.ino,
+			  batch->requested_extent.logical_offset,
+			  batch->requested_extent.data_bytes,
+			  err);
+	}
+
+	ssdfs_segment_put_object(si);
+
+	return err;
+}
+
+/*
+ * ssdfs_update_extent() - update extent.
+ * @fsi: pointer on shared file system object
+ * @pool: segment request pool
+ * @batch: dirty memory folios batch
+ */
+static
+int ssdfs_update_extent(struct ssdfs_fs_info *fsi,
+			struct ssdfs_segment_request_pool *pool,
+			struct ssdfs_dirty_folios_batch *batch,
+			struct writeback_control *wbc)
+{
+	struct ssdfs_segment_info *si;
+	struct ssdfs_segment_search_state seg_search;
+	struct folio *folio;
+	struct inode *inode;
+	u32 batch_size;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!fsi || !pool || !batch);
+
+	if (batch->content.count == 0) {
+		SSDFS_ERR("batch is empty\n");
+		return -ERANGE;
+	}
+
+	SSDFS_DBG("fsi %p, pool %p, batch %p\n",
+		  fsi, pool, batch);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	batch_size = batch->content.count;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("batch_size %u, batch->processed_blks %u\n",
+		  batch_size,
+		  batch->processed_blks);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (batch->processed_blks >= batch_size) {
+		SSDFS_ERR("processed_blks %u >= batch_size %u\n",
+			  batch->processed_blks, batch_size);
+		return -ERANGE;
+	}
+
+	folio = batch->content.blocks[batch->processed_blks].batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = folio->mapping->host;
+
+	while (batch->processed_blks < batch_size) {
+		err = __ssdfs_prepare_volume_extent(fsi, inode,
+						    &batch->requested_extent,
+						    &batch->place);
+		if (err == -EAGAIN) {
+			err = 0;
+			SSDFS_DBG("logical extent processed partially\n");
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to prepare volume extent: "
+				  "ino %llu, logical_offset %llu, "
+				  "data_bytes %u, cno %llu, "
+				  "parent_snapshot %llu, err %d\n",
+				  batch->requested_extent.ino,
+				  batch->requested_extent.logical_offset,
+				  batch->requested_extent.data_bytes,
+				  batch->requested_extent.cno,
+				  batch->requested_extent.parent_snapshot,
+				  err);
+			return err;
+		}
+
+		ssdfs_segment_search_state_init(&seg_search,
+						SSDFS_USER_DATA_SEG_TYPE,
+						batch->place.start.seg_id,
+						U64_MAX);
+
+		si = ssdfs_grab_segment(fsi, &seg_search);
+		if (unlikely(IS_ERR_OR_NULL(si))) {
+			SSDFS_ERR("fail to grab segment object: "
+				  "seg %llu, err %d\n",
+				  batch->place.start.seg_id, err);
+			return PTR_ERR(si);
+		}
+
+		if (!is_ssdfs_segment_ready_for_requests(si)) {
+			err = ssdfs_wait_segment_init_end(si);
+			if (unlikely(err)) {
+				SSDFS_ERR("segment initialization failed: "
+					  "seg %llu, err %d\n",
+					  si->seg_id, err);
+				return err;
+			}
+		}
+
+		if (wbc->sync_mode == WB_SYNC_NONE) {
+			err = ssdfs_segment_update_data_extent_async(si,
+							    SSDFS_REQ_ASYNC,
+							    pool, batch);
+		} else if (wbc->sync_mode == WB_SYNC_ALL)
+			err = ssdfs_segment_update_data_extent_sync(si,
+							    pool, batch);
+		else
+			BUG();
+
+		ssdfs_segment_put_object(si);
+
+		if (err == -EAGAIN) {
+			if (batch->processed_blks >= batch_size) {
+				err = -ERANGE;
+				SSDFS_ERR("processed_blks %u >= batch_size %u\n",
+					  batch->processed_blks, batch_size);
+				goto finish_update_extent;
+			} else {
+				err = 0;
+				/* process the rest of memory pages */
+				continue;
+			}
+		} else if (err == -ENOSPC) {
+			err = -EAGAIN;
+			SSDFS_DBG("wait finishing requests in pool\n");
+			goto finish_update_extent;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("update request failed: "
+				  "ino %llu, logical_offset %llu, "
+				  "size %u, err %d\n",
+				  batch->requested_extent.ino,
+				  batch->requested_extent.logical_offset,
+				  batch->requested_extent.data_bytes,
+				  err);
+			goto finish_update_extent;
+		}
+	}
+
+finish_update_extent:
+	return err;
+}
+
+static
+int ssdfs_issue_async_block_write_request(struct writeback_control *wbc,
+					  struct ssdfs_segment_request_pool *pool,
+					  struct ssdfs_dirty_folios_batch *batch)
+{
+	struct folio *folio;
+	struct inode *inode;
+	struct ssdfs_inode_info *ii;
+	struct ssdfs_fs_info *fsi;
+	ino_t ino;
+	u64 logical_offset;
+	u32 data_bytes;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!wbc || !pool || !batch);
+
+	if (batch->content.count == 0) {
+		SSDFS_ERR("batch is empty\n");
+		return -ERANGE;
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (batch->processed_blks >= batch->content.count) {
+		SSDFS_ERR("processed_blks %u >= batch_size %u\n",
+			  batch->processed_blks,
+			  batch->content.count);
+		return -ERANGE;
+	}
+
+	folio = batch->content.blocks[batch->processed_blks].batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = folio->mapping->host;
+	ii = SSDFS_I(inode);
+	fsi = SSDFS_FS_I(inode->i_sb);
+	ino = inode->i_ino;
+	logical_offset = batch->requested_extent.logical_offset;
+	data_bytes = batch->requested_extent.data_bytes;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, logical_offset %llu, "
+		  "data_bytes %u, sync_mode %#x\n",
+		  ino, logical_offset, data_bytes, wbc->sync_mode);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (need_add_block(folio)) {
+		err = ssdfs_segment_add_data_block_async(fsi, pool, batch);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	} else {
+		err = ssdfs_update_block(fsi, pool, batch, wbc);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	}
+
+	if (err) {
+		SSDFS_ERR("fail to write folio async: "
+			  "ino %lu, folio_index %llu, err %d\n",
+			  ino, (u64)folio->index, err);
+		return err;
+	}
+
+	return 0;
+}
+
+static
+int ssdfs_issue_sync_block_write_request(struct writeback_control *wbc,
+					 struct ssdfs_segment_request_pool *pool,
+					 struct ssdfs_dirty_folios_batch *batch)
+{
+	struct folio *folio;
+	struct inode *inode;
+	struct ssdfs_inode_info *ii;
+	struct ssdfs_fs_info *fsi;
+	ino_t ino;
+	u64 logical_offset;
+	u32 data_bytes;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!wbc || !pool || !batch);
+
+	if (batch->content.count == 0) {
+		SSDFS_ERR("batch is empty\n");
+		return -ERANGE;
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (batch->processed_blks >= batch->content.count) {
+		SSDFS_ERR("processed_blks %u >= batch_size %u\n",
+			  batch->processed_blks,
+			  batch->content.count);
+		return -ERANGE;
+	}
+
+	folio = batch->content.blocks[batch->processed_blks].batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = folio->mapping->host;
+	ii = SSDFS_I(inode);
+	fsi = SSDFS_FS_I(inode->i_sb);
+	ino = inode->i_ino;
+	logical_offset = batch->requested_extent.logical_offset;
+	data_bytes = batch->requested_extent.data_bytes;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, logical_offset %llu, "
+		  "data_bytes %u, sync_mode %#x\n",
+		  ino, logical_offset, data_bytes, wbc->sync_mode);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (need_add_block(folio)) {
+		err = ssdfs_segment_add_data_block_sync(fsi, pool, batch);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	} else {
+		err = ssdfs_update_block(fsi, pool, batch, wbc);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	}
+
+	if (err) {
+		SSDFS_ERR("fail to write folio sync: "
+			  "ino %lu, folio_index %llu, err %d\n",
+			  ino, (u64)folio->index, err);
+		return err;
+	}
+
+	return 0;
+}
+
+static
+int ssdfs_issue_async_extent_write_request(struct writeback_control *wbc,
+					   struct ssdfs_segment_request_pool *pool,
+					   struct ssdfs_dirty_folios_batch *batch)
+{
+	struct folio *folio;
+	struct inode *inode;
+	struct ssdfs_inode_info *ii;
+	struct ssdfs_fs_info *fsi;
+	ino_t ino;
+	u64 logical_offset;
+	u32 data_bytes;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!wbc || !pool || !batch);
+
+	if (batch->content.count == 0) {
+		SSDFS_ERR("batch is empty\n");
+		return -ERANGE;
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (batch->processed_blks >= batch->content.count) {
+		SSDFS_ERR("processed_blks %u >= batch_size %u\n",
+			  batch->processed_blks,
+			  batch->content.count);
+		return -ERANGE;
+	}
+
+	folio = batch->content.blocks[batch->processed_blks].batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = folio->mapping->host;
+	ii = SSDFS_I(inode);
+	fsi = SSDFS_FS_I(inode->i_sb);
+	ino = inode->i_ino;
+	logical_offset = batch->requested_extent.logical_offset;
+	data_bytes = batch->requested_extent.data_bytes;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, logical_offset %llu, "
+		  "data_bytes %u, sync_mode %#x\n",
+		  ino, logical_offset, data_bytes, wbc->sync_mode);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (need_add_block(folio)) {
+		err = ssdfs_segment_add_data_extent_async(fsi, pool, batch);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	} else {
+		err = ssdfs_update_extent(fsi, pool, batch, wbc);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	}
+
+	if (err) {
+		SSDFS_ERR("fail to write extent async: "
+			  "ino %lu, folio_index %llu, err %d\n",
+			  ino, (u64)folio->index, err);
+		return err;
+	}
+
+	return 0;
+}
+
+static
+int ssdfs_issue_sync_extent_write_request(struct writeback_control *wbc,
+					struct ssdfs_segment_request_pool *pool,
+					struct ssdfs_dirty_folios_batch *batch)
+{
+	struct folio *folio;
+	struct inode *inode;
+	struct ssdfs_inode_info *ii;
+	struct ssdfs_fs_info *fsi;
+	ino_t ino;
+	u64 logical_offset;
+	u32 data_bytes;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!wbc || !pool || !batch);
+
+	if (batch->content.count == 0) {
+		SSDFS_ERR("batch is empty\n");
+		return -ERANGE;
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (batch->processed_blks >= batch->content.count) {
+		SSDFS_ERR("processed_blks %u >= batch_size %u\n",
+			  batch->processed_blks,
+			  batch->content.count);
+		return -ERANGE;
+	}
+
+	folio = batch->content.blocks[batch->processed_blks].batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = folio->mapping->host;
+	ii = SSDFS_I(inode);
+	fsi = SSDFS_FS_I(inode->i_sb);
+	ino = inode->i_ino;
+	logical_offset = batch->requested_extent.logical_offset;
+	data_bytes = batch->requested_extent.data_bytes;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, logical_offset %llu, "
+		  "data_bytes %u, sync_mode %#x\n",
+		  ino, logical_offset, data_bytes, wbc->sync_mode);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (need_add_block(folio)) {
+		err = ssdfs_segment_add_data_extent_sync(fsi, pool, batch);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	} else {
+		err = ssdfs_update_extent(fsi, pool, batch, wbc);
+		if (err == -EAGAIN) {
+			SSDFS_DBG("wait finishing requests in pool\n");
+			return err;
+		}
+	}
+
+	if (err) {
+		SSDFS_ERR("fail to write folio sync: "
+			  "ino %lu, folio_index %llu, err %d\n",
+			  ino, (u64)folio->index, err);
+		return err;
+	}
+
+	return 0;
+}
+
+static
+int ssdfs_issue_async_write_request(struct ssdfs_fs_info *fsi,
+			      struct writeback_control *wbc,
+			      struct ssdfs_segment_request_pool *pool,
+			      struct ssdfs_dirty_folios_batch *batch,
+			      int req_type)
+{
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!wbc || !pool || !batch);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (req_type == SSDFS_BLOCK_BASED_REQUEST) {
+		err = ssdfs_issue_async_block_write_request(wbc, pool, batch);
+		if (err == -EAGAIN) {
+			wake_up_all(&fsi->pending_wq);
+
+			err = ssdfs_wait_write_pool_requests_end(fsi, pool);
+			if (unlikely(err)) {
+				SSDFS_ERR("write request failed: err %d\n",
+					  err);
+				return err;
+			}
+
+			err = ssdfs_issue_async_block_write_request(wbc,
+								pool, batch);
+		}
+	} else if (req_type == SSDFS_EXTENT_BASED_REQUEST) {
+		err = ssdfs_issue_async_extent_write_request(wbc, pool, batch);
+		if (err == -EAGAIN) {
+			wake_up_all(&fsi->pending_wq);
+
+			err = ssdfs_wait_write_pool_requests_end(fsi, pool);
+			if (unlikely(err)) {
+				SSDFS_ERR("write request failed: err %d\n",
+					  err);
+				return err;
+			}
+
+			err = ssdfs_issue_async_extent_write_request(wbc,
+								pool, batch);
+		}
+	} else
+		BUG();
+
+	if (err) {
+		SSDFS_ERR("fail to write async: err %d\n",
+			  err);
+	}
+
+	wake_up_all(&fsi->pending_wq);
+
+	return err;
+}
+
+static
+int ssdfs_issue_sync_write_request(struct ssdfs_fs_info *fsi,
+				   struct writeback_control *wbc,
+				   struct ssdfs_segment_request_pool *pool,
+				   struct ssdfs_dirty_folios_batch *batch,
+				   int req_type)
+{
+	int i, j;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!wbc || !pool || !batch);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (req_type == SSDFS_BLOCK_BASED_REQUEST) {
+		err = ssdfs_issue_sync_block_write_request(wbc, pool, batch);
+		if (err == -EAGAIN) {
+			wake_up_all(&fsi->pending_wq);
+
+			err = ssdfs_wait_write_pool_requests_end(fsi, pool);
+			if (unlikely(err)) {
+				SSDFS_ERR("write request failed: err %d\n",
+					  err);
+				return err;
+			}
+
+			err = ssdfs_issue_sync_block_write_request(wbc,
+								pool, batch);
+		}
+	} else if (req_type == SSDFS_EXTENT_BASED_REQUEST) {
+		err = ssdfs_issue_sync_extent_write_request(wbc, pool, batch);
+		if (err == -EAGAIN) {
+			wake_up_all(&fsi->pending_wq);
+
+			err = ssdfs_wait_write_pool_requests_end(fsi, pool);
+			if (unlikely(err)) {
+				SSDFS_ERR("write request failed: err %d\n",
+					  err);
+				return err;
+			}
+
+			err = ssdfs_issue_sync_extent_write_request(wbc,
+								pool, batch);
+		}
+	} else
+		BUG();
+
+	if (err) {
+		SSDFS_ERR("fail to write sync: err %d\n",
+			  err);
+
+		for (i = 0; i < batch->content.count; i++) {
+			struct ssdfs_content_block *blk_state;
+			u32 batch_size;
+
+			blk_state = &batch->content.blocks[i];
+			batch_size = folio_batch_count(&blk_state->batch);
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(batch_size == 0);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			for (j = 0; j < batch_size; j++) {
+				struct folio *folio;
+
+				folio = blk_state->batch.folios[j];
+
+#ifdef CONFIG_SSDFS_DEBUG
+				BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+				if (!folio_test_locked(folio)) {
+					SSDFS_WARN("folio %p, folio_test_locked %#x\n",
+						   folio, folio_test_locked(folio));
+					ssdfs_folio_lock(folio);
+				}
+
+				clear_folio_new(folio);
+				folio_mark_uptodate(folio);
+				folio_clear_dirty(folio);
+
+				ssdfs_folio_unlock(folio);
+				ssdfs_folio_end_writeback(fsi, U64_MAX, 0, folio);
+			}
+		}
+	}
+
+	wake_up_all(&fsi->pending_wq);
+
+	return err;
+}
+
+static
+int ssdfs_issue_write_request(struct writeback_control *wbc,
+			      struct ssdfs_segment_request_pool *pool,
+			      struct ssdfs_dirty_folios_batch *batch,
+			      int req_type)
+{
+	struct ssdfs_fs_info *fsi;
+	struct inode *inode;
+	struct address_space *mapping;
+	struct folio *folio;
+	struct ssdfs_content_block *blk_state;
+	ino_t ino;
+	u64 logical_offset;
+	u32 data_bytes;
+	u64 start_index;
+	int i, j;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!wbc || !pool || !batch);
+
+	if (batch->content.count == 0) {
+		SSDFS_WARN("batch is empty\n");
+		return -ERANGE;
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	folio = batch->content.blocks[0].batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	inode = folio->mapping->host;
+	mapping = folio->mapping;
+	fsi = SSDFS_FS_I(inode->i_sb);
+	ino = inode->i_ino;
+	logical_offset = batch->requested_extent.logical_offset;
+	data_bytes = batch->requested_extent.data_bytes;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, logical_offset %llu, "
+		  "data_bytes %u, sync_mode %#x\n",
+		  ino, logical_offset, data_bytes, wbc->sync_mode);
+
+	if (logical_offset % fsi->pagesize) {
+		SSDFS_ERR("logical_offset %llu, pagesize %u\n",
+			  logical_offset, fsi->pagesize);
+		BUG();
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	for (i = 0; i < batch->content.count; i++) {
+		size_t block_bytes = 0;
+		pgoff_t index;
+		pgoff_t last_folio_index;
+
+		blk_state = &batch->content.blocks[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(folio_batch_count(&blk_state->batch) == 0);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		for (j = 0; j < folio_batch_count(&blk_state->batch); j++) {
+			folio = blk_state->batch.folios[j];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!folio);
+
+			SSDFS_DBG("folio_index %llu\n",
+				  (u64)folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_folio_start_writeback(fsi, U64_MAX,
+						    logical_offset, folio);
+			ssdfs_clear_dirty_folio(folio);
+
+			block_bytes += folio_size(folio);
+		}
+
+		if (block_bytes != fsi->pagesize) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("ino %lu, logical_offset %llu, "
+				  "data_bytes %u, blk_index %d, "
+				  "block_bytes %zu, pagesize %u\n",
+				  ino, logical_offset, data_bytes, i,
+				  block_bytes, fsi->pagesize);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			last_folio_index = folio_batch_count(&blk_state->batch);
+
+			if (last_folio_index == 0) {
+				err = -ERANGE;
+				SSDFS_ERR("empty block: blk_index %d\n", i);
+				goto finish_issue_write_request;
+			}
+
+			folio = blk_state->batch.folios[0];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			start_index = logical_offset + (i * fsi->pagesize);
+			start_index >>= PAGE_SHIFT;
+			index = folio->index;
+
+			if (start_index != index) {
+				err = -ERANGE;
+				SSDFS_WARN("block batch hasn't first folio: "
+					   "ino %lu, logical_offset %llu, "
+					   "data_bytes %u, blk_index %d, "
+					   "block_bytes %zu, pagesize %u, "
+					   "start_index %llu, index %lu\n",
+					   ino, logical_offset, data_bytes, i,
+					   block_bytes, fsi->pagesize,
+					   start_index, index);
+				goto finish_issue_write_request;
+			}
+
+			last_folio_index--;
+
+			folio = blk_state->batch.folios[last_folio_index];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			while (block_bytes < fsi->pagesize) {
+				pgoff_t mem_pages_per_folio =
+						folio_size(folio) / PAGE_SIZE;
+
+				last_folio_index = folio->index;
+				last_folio_index += mem_pages_per_folio;
+
+				folio = filemap_get_folio(mapping,
+							   last_folio_index);
+				if (IS_ERR(folio) && PTR_ERR(folio) == -ENOENT) {
+					folio = filemap_grab_folio(mapping,
+							    last_folio_index);
+					if (!folio) {
+						err = -ERANGE;
+						SSDFS_ERR("empty folio: "
+							  "folio_index %lu\n",
+							  last_folio_index);
+						goto finish_issue_write_request;
+					} else if (IS_ERR(folio)) {
+						err = PTR_ERR(folio);
+						SSDFS_ERR("fail to grab folio: "
+							  "folio_index %lu, err %d\n",
+							  last_folio_index,
+							  err);
+						goto finish_issue_write_request;
+					}
+
+					__ssdfs_memzero_folio(folio, 0,
+							      folio_size(folio),
+							      folio_size(folio));
+
+					ssdfs_account_locked_folio(folio);
+					folio_mark_uptodate(folio);
+					folio_mark_dirty(folio);
+					ssdfs_folio_start_writeback(fsi, U64_MAX,
+								    logical_offset, folio);
+					ssdfs_clear_dirty_folio(folio);
+					folio_batch_add(&blk_state->batch,
+							folio);
+				} else if (IS_ERR(folio)) {
+					err = PTR_ERR(folio);
+					SSDFS_ERR("fail to get folio: "
+						  "folio_index %lu, err %d\n",
+						  last_folio_index, err);
+					goto finish_issue_write_request;
+				} else {
+					if (!folio_test_locked(folio))
+						ssdfs_folio_lock(folio);
+					else
+						ssdfs_account_locked_folio(folio);
+
+					folio_mark_uptodate(folio);
+					folio_mark_dirty(folio);
+					ssdfs_folio_start_writeback(fsi, U64_MAX,
+								    logical_offset, folio);
+					ssdfs_clear_dirty_folio(folio);
+					folio_batch_add(&blk_state->batch,
+							folio);
+				}
+
+				block_bytes += folio_size(folio);
+			}
+		}
+	}
+
+	if (wbc->sync_mode == WB_SYNC_NONE) {
+		err = ssdfs_issue_async_write_request(fsi, wbc, pool,
+							batch, req_type);
+		if (err) {
+			SSDFS_ERR("fail to write async: "
+				  "ino %lu, err %d\n",
+				  ino, err);
+			goto finish_issue_write_request;
+		}
+	} else if (wbc->sync_mode == WB_SYNC_ALL) {
+		err = ssdfs_issue_sync_write_request(fsi, wbc, pool,
+						     batch, req_type);
+		if (err) {
+			SSDFS_ERR("fail to write sync: "
+				  "ino %lu, err %d\n",
+				  ino, err);
+			goto finish_issue_write_request;
+		}
+	} else
+		BUG();
+
+finish_issue_write_request:
+	if (unlikely(err)) {
+		for (i = 0; i < batch->content.count; i++) {
+			blk_state = &batch->content.blocks[i];
+
+			for (j = 0; j < folio_batch_count(&blk_state->batch); j++) {
+				folio = blk_state->batch.folios[j];
+
+				if (!folio)
+					continue;
+
+				SSDFS_ERR("BLK[%d][%d] folio_index %llu, folio_size %zu\n",
+					  i, j,
+					  (u64)folio->index,
+					  folio_size(folio));
+			}
+
+		}
+	}
+
+	ssdfs_dirty_folios_batch_init(batch);
+
+	return err;
+}
+
+static
+int __ssdfs_writepages(struct folio *folio, u32 len,
+			struct writeback_control *wbc,
+			struct ssdfs_segment_request_pool *pool,
+			struct ssdfs_dirty_folios_batch *batch)
+{
+	struct inode *inode = folio->mapping->host;
+	struct address_space *mapping = folio->mapping;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	ino_t ino = inode->i_ino;
+	pgoff_t start_index;
+	pgoff_t index = folio->index;
+	loff_t logical_offset;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, folio_index %llu, len %u, sync_mode %#x\n",
+		  ino, (u64)index, len, wbc->sync_mode);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	logical_offset = (loff_t)index << PAGE_SHIFT;
+
+try_add_folio_into_request:
+	if (is_ssdfs_logical_extent_invalid(&batch->requested_extent)) {
+		if (logical_offset % fsi->pagesize) {
+			struct folio *cur_folio;
+			pgoff_t cur_blk;
+			pgoff_t cur_index;
+			pgoff_t mem_pages_per_folio;
+			u32 processed_bytes = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("logical_offset %llu, pagesize %u\n",
+				  logical_offset, fsi->pagesize);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			cur_blk = batch->content.count;
+
+			start_index = logical_offset >> fsi->log_pagesize;
+			start_index <<= fsi->log_pagesize;
+			start_index >>= PAGE_SHIFT;
+
+			cur_index = start_index;
+			while (cur_index < index) {
+				cur_folio = filemap_get_folio(mapping,
+							      cur_index);
+				if (IS_ERR_OR_NULL(cur_folio)) {
+					err = IS_ERR(cur_folio) ?
+						PTR_ERR(cur_folio) : -ERANGE;
+					SSDFS_ERR("fail to get folio: "
+						  "folio_index %lu, err %d\n",
+						  cur_index, err);
+					goto fail_write_folios;
+				}
+
+				if (!folio_test_locked(cur_folio))
+					ssdfs_folio_lock(cur_folio);
+				else
+					ssdfs_account_locked_folio(cur_folio);
+
+#ifdef CONFIG_SSDFS_DEBUG
+				BUG_ON(!folio_test_uptodate(cur_folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+				folio_mark_dirty(cur_folio);
+				ssdfs_folio_start_writeback(fsi, U64_MAX,
+						    logical_offset, cur_folio);
+				ssdfs_clear_dirty_folio(cur_folio);
+
+				err =
+				    ssdfs_dirty_folios_batch_add_folio(cur_folio,
+									cur_blk,
+									batch);
+				if (err) {
+					SSDFS_ERR("fail to add folio into batch: "
+						  "ino %lu, folio_index %lu, err %d\n",
+						  ino, index, err);
+					goto fail_write_folios;
+				}
+
+				mem_pages_per_folio =
+					folio_size(cur_folio) >> PAGE_SHIFT;
+				cur_index = cur_folio->index;
+				cur_index += mem_pages_per_folio;
+
+				processed_bytes += folio_size(cur_folio);
+
+#ifdef CONFIG_SSDFS_DEBUG
+				BUG_ON(cur_index > index);
+#endif /* CONFIG_SSDFS_DEBUG */
+			}
+
+			logical_offset = (loff_t)start_index << PAGE_SHIFT;
+			len += processed_bytes;
+
+			err = ssdfs_dirty_folios_batch_add_folio(folio,
+								 cur_blk,
+								 batch);
+			if (err) {
+				SSDFS_ERR("fail to add folio into batch: "
+					  "ino %lu, folio_index %lu, err %d\n",
+					  ino, index, err);
+				goto fail_write_folios;
+			}
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("logical_offset %llu, len %u\n",
+				  (u64)logical_offset, len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_dirty_folios_batch_prepare_logical_extent(ino,
+							(u64)logical_offset,
+							len, 0, 0,
+							batch);
+
+			err = ssdfs_issue_write_request(wbc, pool, batch,
+						    SSDFS_EXTENT_BASED_REQUEST);
+			if (err)
+				goto fail_write_folios;
+		} else {
+			err = ssdfs_dirty_folios_batch_add_folio(folio,
+							 batch->content.count,
+							 batch);
+			if (err) {
+				SSDFS_ERR("fail to add folio into batch: "
+					  "ino %lu, folio_index %lu, err %d\n",
+					  ino, index, err);
+				goto fail_write_folios;
+			}
+
+			ssdfs_dirty_folios_batch_prepare_logical_extent(ino,
+							(u64)logical_offset,
+							len, 0, 0,
+							batch);
+		}
+	} else {
+		struct ssdfs_content_block *blk_state;
+		struct folio *last_folio;
+		u64 upper_bound = batch->requested_extent.logical_offset +
+					batch->requested_extent.data_bytes;
+		u32 last_blk;
+		u32 last_index;
+
+		if (batch->content.count == 0) {
+			err = -ERANGE;
+			SSDFS_WARN("batch is empty\n");
+			goto fail_write_folios;
+		}
+
+		last_blk = batch->content.count - 1;
+		blk_state = &batch->content.blocks[last_blk];
+
+		last_index = folio_batch_count(&blk_state->batch);
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(last_index == 0);
+#endif /* CONFIG_SSDFS_DEBUG */
+		last_index -= 1;
+
+		last_folio = blk_state->batch.folios[last_index];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("logical_offset %llu, upper_bound %llu, "
+			  "last_blk %u, content.count %u, "
+			  "last_index %u, folio_batch_count %u\n",
+			  (u64)logical_offset, upper_bound,
+			  last_blk, batch->content.count,
+			  last_index,
+			  folio_batch_count(&blk_state->batch));
+
+		BUG_ON(!last_folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		last_index = last_folio->index;
+
+		if (last_index == index) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("last_index %u == index %lu\n",
+				  last_index, index);
+#endif /* CONFIG_SSDFS_DEBUG */
+			return 0;
+		}
+
+		if (logical_offset == upper_bound &&
+		    can_be_merged_into_extent(last_folio, folio)) {
+			pgoff_t logical_blk1, logical_blk2;
+			pgoff_t cur_blk;
+
+			logical_blk1 = last_folio->index;
+			logical_blk1 <<= PAGE_SHIFT;
+			logical_blk1 >>= fsi->log_pagesize;
+
+			logical_blk2 = folio->index;
+			logical_blk2 <<= PAGE_SHIFT;
+			logical_blk2 >>= fsi->log_pagesize;
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("folio can be merged into extent: "
+				  "LAST FOLIO: (folio_index %lu, "
+				  "logical_blk %lu), "
+				  "CURRENT FOLIO: (folio_index %lu, "
+				  "logical_blk %lu)\n",
+				  last_folio->index,
+				  logical_blk1,
+				  folio->index,
+				  logical_blk2);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			if (logical_blk1 == logical_blk2)
+				cur_blk = last_blk;
+			else
+				cur_blk = batch->content.count;
+
+			err = ssdfs_dirty_folios_batch_add_folio(folio,
+								 cur_blk,
+								 batch);
+			if (err) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("unable to add folio: "
+					  "cur_blk %lu, folio_index %lu, "
+					  "err %d\n",
+					  cur_blk,
+					  folio->index,
+					  err);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+				err = ssdfs_issue_write_request(wbc,
+						    pool, batch,
+						    SSDFS_EXTENT_BASED_REQUEST);
+				if (err)
+					goto fail_write_folios;
+				else
+					goto try_add_folio_into_request;
+			}
+
+			batch->requested_extent.data_bytes += len;
+		} else {
+			err = ssdfs_issue_write_request(wbc, pool, batch,
+						    SSDFS_EXTENT_BASED_REQUEST);
+			if (err)
+				goto fail_write_folios;
+			else
+				goto try_add_folio_into_request;
+		}
+	}
+
+	return 0;
+
+fail_write_folios:
+	return err;
+}
+
+/* writepage function prototype */
+typedef int (*ssdfs_writepagefn)(struct folio *folio, u32 len,
+				 struct writeback_control *wbc,
+				 struct ssdfs_segment_request_pool *pool,
+				 struct ssdfs_dirty_folios_batch *batch);
+
+static
+int ssdfs_writepage_wrapper(struct folio *folio,
+			    struct writeback_control *wbc,
+			    struct ssdfs_segment_request_pool *pool,
+			    struct ssdfs_dirty_folios_batch *batch,
+			    ssdfs_writepagefn writepage)
+{
+	struct inode *inode = folio->mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	ino_t ino = inode->i_ino;
+	pgoff_t index = folio->index;
+	loff_t i_size = i_size_read(inode);
+	pgoff_t end_index = i_size >> PAGE_SHIFT;
+	int len = i_size & (folio_size(folio) - 1);
+	loff_t cur_blk;
+	u32 offset_inside_block;
+	bool is_new_blk = false;
+#ifdef CONFIG_SSDFS_DEBUG
+	u32 folio_processed_bytes = 0;
+	void *kaddr;
+#endif /* CONFIG_SSDFS_DEBUG */
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, page_index %llu, "
+		  "i_size %llu, len %d\n",
+		  ino, (u64)index,
+		  (u64)i_size, len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (inode->i_sb->s_flags & SB_RDONLY) {
+		/*
+		 * It means that filesystem was remounted in read-only
+		 * mode because of error or metadata corruption. But we
+		 * have dirty pages that try to be flushed in background.
+		 * So, here we simply discard this dirty page.
+		 */
+		err = -EROFS;
+		goto discard_folio;
+	}
+
+	/* Is the page fully outside @i_size? (truncate in progress) */
+	if (index > end_index) {
+		err = 0;
+		goto finish_write_folio;
+	}
+
+	if (is_ssdfs_file_inline(ii)) {
+		size_t inline_capacity =
+				ssdfs_inode_inline_file_capacity(inode);
+
+		if (len > inline_capacity) {
+			err = -ENOSPC;
+			SSDFS_ERR("len %d is greater capacity %zu\n",
+				  len, inline_capacity);
+			goto discard_folio;
+		}
+
+		ssdfs_folio_start_writeback(fsi, U64_MAX, 0, folio);
+
+		err = __ssdfs_memcpy_from_folio(ii->inline_file,
+						0, inline_capacity,
+						folio,
+						0, folio_size(folio),
+						len);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to copy file's content: "
+				  "err %d\n", err);
+			goto discard_folio;
+		}
+
+		inode_add_bytes(inode, len);
+
+		clear_folio_new(folio);
+		folio_mark_uptodate(folio);
+		folio_clear_dirty(folio);
+
+		ssdfs_folio_unlock(folio);
+		ssdfs_folio_end_writeback(fsi, U64_MAX, 0, folio);
+
+		return 0;
+	}
+
+	cur_blk = ((u64)index << PAGE_SHIFT) >> fsi->log_pagesize;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("cur_blk %llu\n", (u64)cur_blk);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!need_add_block(folio)) {
+		is_new_blk = !ssdfs_extents_tree_has_logical_block(cur_blk,
+								   inode);
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("cur_blk %llu, is_new_blk %#x\n",
+			  (u64)cur_blk, is_new_blk);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		if (is_new_blk)
+			set_folio_new(folio);
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	do {
+		kaddr = kmap_local_folio(folio, folio_processed_bytes);
+		SSDFS_DBG("PAGE DUMP: "
+			  "folio_processed_bytes %u\n",
+			  folio_processed_bytes);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     kaddr,
+				     PAGE_SIZE);
+		SSDFS_DBG("\n");
+		kunmap_local(kaddr);
+
+		folio_processed_bytes += PAGE_SIZE;
+	} while (folio_processed_bytes < folio_size(folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	/* Is the page fully inside @i_size? */
+	if (index < end_index) {
+		err = (*writepage)(folio, folio_size(folio), wbc, pool, batch);
+		if (unlikely(err)) {
+			ssdfs_fs_error(inode->i_sb, __FILE__,
+					__func__, __LINE__,
+					"fail to write block: "
+					"ino %lu, page_index %llu, err %d\n",
+					ino, (u64)index, err);
+			goto discard_folio;
+		}
+	} else if (len > 0) {
+		/*
+		 * The page straddles @i_size. It must be zeroed out on each and every
+		 * writepage invocation because it may be mmapped. "A file is mapped
+		 * in multiples of the page size. For a file that is not a multiple of
+		 * the page size, the remaining memory is zeroed when mapped, and
+		 * writes to that region are not written out to the file."
+		 */
+		folio_zero_segment(folio, len, folio_size(folio));
+
+		err = (*writepage)(folio, len, wbc, pool, batch);
+		if (unlikely(err)) {
+			ssdfs_fs_error(inode->i_sb, __FILE__,
+					__func__, __LINE__,
+					"fail to write block: "
+					"ino %lu, page_index %llu, err %d\n",
+					ino, (u64)index, err);
+			goto discard_folio;
+		}
+	} else {
+		/* Write out the whole last folio (len == 0) */
+		err = (*writepage)(folio, folio_size(folio), wbc, pool, batch);
+		if (unlikely(err)) {
+			ssdfs_fs_error(inode->i_sb, __FILE__,
+					__func__, __LINE__,
+					"fail to write block: "
+					"ino %lu, page_index %llu, err %d\n",
+					ino, (u64)index, err);
+			goto discard_folio;
+		}
+	}
+
+	offset_inside_block = index << PAGE_SHIFT;
+	offset_inside_block %= fsi->pagesize;
+
+	if ((offset_inside_block + folio_size(folio)) < fsi->pagesize) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("NOT WHOLE BLOCK IS PROCESSED: "
+			  "ino %lu, cur_blk %llu, "
+			  "page_index %llu, "
+			  "offset_inside_block %u, "
+			  "folio_size %zu, block_size %u\n",
+			  ino, (u64)cur_blk, (u64)index,
+			  offset_inside_block,
+			  folio_size(folio),
+			  fsi->pagesize);
+#endif /* CONFIG_SSDFS_DEBUG */
+		return -EAGAIN;
+	}
+
+	return 0;
+
+finish_write_folio:
+	ssdfs_folio_unlock(folio);
+
+discard_folio:
+	return err;
+}
+
+/*
+ * The ssdfs_writepages() is called by the VM to write out pages associated
+ * with the address_space object. If wbc->sync_mode is WBC_SYNC_ALL, then
+ * the writeback_control will specify a range of pages that must be
+ * written out.  If it is WBC_SYNC_NONE, then a nr_to_write is given
+ * and that many pages should be written if possible.
+ * If no ->writepages is given, then mpage_writepages is used
+ * instead.  This will choose pages from the address space that are
+ * tagged as DIRTY and will pass them to ->writepage.
+ */
+static
+int ssdfs_writepages(struct address_space *mapping,
+		     struct writeback_control *wbc)
+{
+	struct inode *inode = mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	ino_t ino = inode->i_ino;
+	struct ssdfs_segment_request_pool pool;
+	struct ssdfs_dirty_folios_batch *batch;
+	struct folio_batch fvec;
+	struct folio_batch block_vec;
+	int folios_count;
+	pgoff_t index = 0;
+	pgoff_t end;		/* Inclusive */
+	pgoff_t done_index = 0;
+	int range_whole = 0;
+	int tag;
+	int i;
+	int done = 0;
+	int ret = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, nr_to_write %lu, "
+		  "range_start %llu, range_end %llu, "
+		  "writeback_index %llu, "
+		  "wbc->range_cyclic %#x\n",
+		  ino, wbc->nr_to_write,
+		  (u64)wbc->range_start,
+		  (u64)wbc->range_end,
+		  (u64)mapping->writeback_index,
+		  wbc->range_cyclic);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	batch = ssdfs_dirty_folios_batch_alloc();
+	if (IS_ERR_OR_NULL(batch)) {
+		ret = (batch == NULL ? -ENOMEM : PTR_ERR(batch));
+		SSDFS_ERR("unable to allocate dirty folios batch\n");
+		return ret;
+	}
+
+	ssdfs_segment_request_pool_init(&pool);
+	ssdfs_dirty_folios_batch_init(batch);
+
+	/*
+	 * No folios to write?
+	 */
+	if (!mapping->nrpages || !mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
+		goto out_writepages;
+
+	folio_batch_init(&fvec);
+	folio_batch_init(&block_vec);
+
+	if (wbc->range_cyclic) {
+		index = mapping->writeback_index; /* prev offset */
+		end = -1;
+	} else {
+		index = wbc->range_start >> PAGE_SHIFT;
+		end = wbc->range_end >> PAGE_SHIFT;
+
+		if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
+			range_whole = 1;
+	}
+
+	if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages) {
+		tag = PAGECACHE_TAG_TOWRITE;
+		tag_pages_for_writeback(mapping, index, end);
+	} else
+		tag = PAGECACHE_TAG_DIRTY;
+
+	done_index = index;
+
+	while (!done && (index <= end)) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("index %llu, end %llu, done_index %llu, "
+			  "done %#x, tag %#x\n",
+			  (u64)index, (u64)end, (u64)done_index, done, tag);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		folios_count = filemap_get_folios_tag(mapping, &index, end,
+							tag, &fvec);
+		if (folios_count == 0) {
+			if (!is_ssdfs_file_inline(ii) &&
+			    is_ssdfs_dirty_batch_not_processed(batch)) {
+				ret = ssdfs_issue_write_request(wbc,
+						&pool, batch,
+						SSDFS_EXTENT_BASED_REQUEST);
+				if (ret < 0) {
+					SSDFS_ERR("ino %lu, nr_to_write %lu, "
+						  "range_start %llu, "
+						  "range_end %llu, "
+						  "writeback_index %llu, "
+						  "wbc->range_cyclic %#x, "
+						  "index %llu, end %llu, "
+						  "done_index %llu\n",
+						  ino, wbc->nr_to_write,
+						  (u64)wbc->range_start,
+						  (u64)wbc->range_end,
+						  (u64)mapping->writeback_index,
+						  wbc->range_cyclic,
+						  (u64)index, (u64)end,
+						  (u64)done_index);
+					goto out_writepages;
+				}
+			}
+
+			break;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("FOUND: folios_count %d\n", folios_count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		for (i = 0; i < folios_count; i++) {
+			struct folio *folio = fvec.folios[i];
+			unsigned long nr;
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("folio %p, index %d, "
+				  "folio->index %ld, end %llu\n",
+				  folio, i, folio->index, (u64)end);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ret = 0;
+
+			/*
+			 * At this point, the page may be truncated or
+			 * invalidated (changing page->mapping to NULL), or
+			 * even swizzled back from swapper_space to tmpfs file
+			 * mapping. However, page->index will not change
+			 * because we have a reference on the page.
+			 */
+			if (folio->index > end) {
+				/*
+				 * can't be range_cyclic (1st pass) because
+				 * end == -1 in that case.
+				 */
+				done = 1;
+				break;
+			}
+
+			done_index = folio->index + 1;
+
+			ssdfs_folio_lock(folio);
+
+			/*
+			 * Page truncated or invalidated. We can freely skip it
+			 * then, even for data integrity operations: the page
+			 * has disappeared concurrently, so there could be no
+			 * real expectation of this data interity operation
+			 * even if there is now a new, dirty page at the same
+			 * pagecache address.
+			 */
+			if (unlikely(folio->mapping != mapping)) {
+continue_unlock:
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("UNLOCK FOLIO: index %ld\n",
+					  folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+				ssdfs_folio_unlock(folio);
+				continue;
+			}
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("folio %p, index %d, folio->index %ld, "
+				  "folio_test_locked %#x, "
+				  "folio_test_dirty %#x, "
+				  "folio_test_writeback %#x\n",
+				  folio, i, folio->index,
+				  folio_test_locked(folio),
+				  folio_test_dirty(folio),
+				  folio_test_writeback(folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			if (!folio_test_dirty(folio)) {
+				/* someone wrote it for us */
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("FOLIO IS NOT DIRTY: index %ld\n",
+					  folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+				goto continue_unlock;
+			}
+
+			if (folio_test_writeback(folio)) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("FOLIO IS UNDER WRITEBACK: "
+					  "index %ld\n",
+					  folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+				if (wbc->sync_mode != WB_SYNC_NONE) {
+#ifdef CONFIG_SSDFS_DEBUG
+					SSDFS_DBG("WAIT WRITEBACK: "
+						  "folio_index %ld\n",
+						  folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+					folio_wait_writeback(folio);
+				} else
+					goto continue_unlock;
+			}
+
+			BUG_ON(folio_test_writeback(folio));
+			if (!folio_clear_dirty_for_io(folio))
+				goto continue_unlock;
+
+			ret = ssdfs_writepage_wrapper(folio, wbc,
+						      &pool, batch,
+						      __ssdfs_writepages);
+			nr = folio_nr_pages(folio);
+
+			if (ret) {
+				if (ret == -EAGAIN) {
+					/*
+					 * Not all folios of the logical block
+					 * is processed: continue processing folios
+					 */
+					done_index = folio->index + nr;
+				} else if (ret == -EROFS) {
+					/*
+					 * continue to discard folios
+					 */
+					done_index = folio->index + nr;
+				} else {
+					/*
+					 * done_index is set past this page,
+					 * so media errors will not choke
+					 * background writeout for the entire
+					 * file. This has consequences for
+					 * range_cyclic semantics (ie. it may
+					 * not be suitable for data integrity
+					 * writeout).
+					 */
+					done_index = folio->index + nr;
+					done = 1;
+					break;
+				}
+			}
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("folio %p, index %d, folio->index %ld, "
+				  "folio_test_locked %#x, "
+				  "folio_test_dirty %#x, "
+				  "folio_test_writeback %#x\n",
+				  folio, i, folio->index,
+				  folio_test_locked(folio),
+				  folio_test_dirty(folio),
+				  folio_test_writeback(folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			/*
+			 * We stop writing back only if we are not doing
+			 * integrity sync. In case of integrity sync we have to
+			 * keep going until we have written all the pages
+			 * we tagged for writeback prior to entering this loop.
+			 */
+			wbc->nr_to_write -= nr;
+			if (wbc->nr_to_write <= 0 &&
+			    wbc->sync_mode == WB_SYNC_NONE) {
+				done = 1;
+				break;
+			}
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("wbc->nr_to_write %lu, "
+				  "wbc->sync_mode %#x, "
+				  "done_index %llu, "
+				  "done %#x\n",
+				  wbc->nr_to_write,
+				  wbc->sync_mode,
+				  (u64)done_index,
+				  done);
+#endif /* CONFIG_SSDFS_DEBUG */
+		}
+
+		if (ret != -EAGAIN &&
+		    !is_ssdfs_file_inline(ii) &&
+		    is_ssdfs_dirty_batch_not_processed(batch)) {
+			ret = ssdfs_issue_write_request(wbc, &pool, batch,
+						SSDFS_EXTENT_BASED_REQUEST);
+			if (ret < 0) {
+				SSDFS_ERR("ino %lu, nr_to_write %lu, "
+					  "range_start %llu, range_end %llu, "
+					  "writeback_index %llu, "
+					  "wbc->range_cyclic %#x, "
+					  "index %llu, end %llu, "
+					  "done_index %llu\n",
+					  ino, wbc->nr_to_write,
+					  (u64)wbc->range_start,
+					  (u64)wbc->range_end,
+					  (u64)mapping->writeback_index,
+					  wbc->range_cyclic,
+					  (u64)index, (u64)end,
+					  (u64)done_index);
+				goto out_writepages;
+			}
+		}
+
+		index = done_index;
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("index %llu, end %llu, nr_to_write %lu\n",
+			  (u64)index, (u64)end, wbc->nr_to_write);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		folio_batch_reinit(&fvec);
+		cond_resched();
+	};
+
+	if (!ret) {
+		ret = ssdfs_wait_write_pool_requests_end(fsi, &pool);
+		if (unlikely(ret)) {
+			SSDFS_ERR("finish write request failed: "
+				  "err %d\n", ret);
+		}
+	} else
+		ssdfs_clean_failed_request_pool(&pool);
+
+	/*
+	 * If we hit the last page and there is more work to be done: wrap
+	 * back the index back to the start of the file for the next
+	 * time we are called.
+	 */
+	if (wbc->range_cyclic && !done)
+		done_index = 0;
+
+out_writepages:
+	if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))
+		mapping->writeback_index = done_index;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, nr_to_write %lu, "
+		  "range_whole %d, done_index %llu, done %#x, "
+		  "range_start %llu, range_end %llu, "
+		  "writeback_index %llu\n",
+		  ino, wbc->nr_to_write,
+		  range_whole, (u64)done_index, done,
+		  (u64)wbc->range_start,
+		  (u64)wbc->range_end,
+		  (u64)mapping->writeback_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	ssdfs_dirty_folios_batch_free(batch);
+
+	return ret;
+}
+
+static void ssdfs_write_failed(struct address_space *mapping, loff_t to)
+{
+	struct inode *inode = mapping->host;
+
+	if (to > inode->i_size)
+		truncate_pagecache(inode, inode->i_size);
+}
+
+static inline
+struct folio *ssdfs_get_block_folio(struct file *file,
+				    struct address_space *mapping,
+				    pgoff_t index)
+{
+	struct inode *inode = mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct folio *folio = NULL;
+	fgf_t fgp_flags = FGP_WRITEBEGIN;
+	unsigned int nofs_flags;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, index %lu\n",
+		  inode->i_ino, index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	fgp_flags |= fgf_set_order(fsi->pagesize);
+
+	nofs_flags = memalloc_nofs_save();
+	folio = __filemap_get_folio(mapping, index, fgp_flags,
+				    mapping_gfp_mask(mapping));
+	memalloc_nofs_restore(nofs_flags);
+
+	if (!folio) {
+		SSDFS_ERR("fail to grab folio: index %lu\n",
+			  index);
+		return ERR_PTR(-ENOMEM);
+	} else if (IS_ERR(folio)) {
+		SSDFS_ERR("fail to grab folio: index %lu, err %ld\n",
+			  index, PTR_ERR(folio));
+		return folio;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("folio %p, count %d, folio_index %lu, "
+		  "folio_size %zu, page_size %u, "
+		  "fgp_flags %#x, order %u\n",
+		  folio, folio_ref_count(folio),
+		  folio->index, folio_size(folio),
+		  fsi->pagesize, fgp_flags,
+		  FGF_GET_ORDER(fgp_flags));
+
+	SSDFS_DBG("folio->index %ld, "
+		  "folio_test_locked %#x, "
+		  "folio_test_uptodate %#x, "
+		  "folio_test_dirty %#x, "
+		  "folio_test_writeback %#x\n",
+		  folio->index,
+		  folio_test_locked(folio),
+		  folio_test_uptodate(folio),
+		  folio_test_dirty(folio),
+		  folio_test_writeback(folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	ssdfs_account_locked_folio(folio);
+
+	return folio;
+}
+
+static inline
+void ssdfs_folio_test_and_set_if_new(struct inode *inode,
+				     struct folio *folio)
+{
+	pgoff_t last_folio;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!inode || !folio);
+
+	SSDFS_DBG("ino %lu, folio_index %lu\n",
+		  inode->i_ino, folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	last_folio = (i_size_read(inode) - 1) >> PAGE_SHIFT;
+
+	if (i_size_read(inode) == 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("SET NEW FOLIO: "
+			  "file_size %llu, last_folio %lu, "
+			  "folio_index %lu\n",
+			  (u64)i_size_read(inode),
+			  last_folio,
+			  folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		set_folio_new(folio);
+	} else {
+		if (folio->index > last_folio ||
+		    !folio_test_uptodate(folio)) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("SET NEW FOLIO: "
+				  "file_size %llu, last_folio %lu, "
+				  "folio_index %lu\n",
+				  (u64)i_size_read(inode),
+				  last_folio,
+				  folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			set_folio_new(folio);
+		}
+	}
+}
+
+static
+int ssdfs_process_whole_block(struct file *file,
+			      struct address_space *mapping,
+			      struct folio *requested_folio,
+			      loff_t pos, u32 len,
+			      bool is_new_blk)
+{
+	struct inode *inode = mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct folio *cur_folio;
+	struct folio_batch fbatch;
+	pgoff_t index;
+	unsigned start;
+	unsigned end;
+	loff_t logical_offset;
+	u32 processed_bytes = 0;
+	bool need_read_block = false;
+	int i;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!requested_folio);
+
+	SSDFS_DBG("ino %lu, pos %llu, len %u\n",
+		  inode->i_ino, pos, len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	folio_batch_init(&fbatch);
+
+	logical_offset = (loff_t)requested_folio->index << PAGE_SHIFT;
+	logical_offset >>= fsi->log_pagesize;
+	logical_offset <<= fsi->log_pagesize;
+
+	index = logical_offset >> PAGE_SHIFT;
+
+	while (processed_bytes < fsi->pagesize) {
+		if (index == requested_folio->index) {
+			cur_folio = requested_folio;
+		} else {
+			cur_folio = ssdfs_get_block_folio(file, mapping, index);
+			if (unlikely(IS_ERR_OR_NULL(cur_folio))) {
+				err = IS_ERR(cur_folio) ?
+						PTR_ERR(cur_folio) : -ERANGE;
+				SSDFS_ERR("fail to get block's folio: "
+					  "index %lu, err %d\n",
+					  index, err);
+				return err;
+			}
+
+			if (is_new_blk) {
+				ssdfs_folio_test_and_set_if_new(inode,
+								cur_folio);
+			}
+		}
+
+		if (folio_test_uptodate(cur_folio)) {
+			folio_batch_add(&fbatch, cur_folio);
+			goto check_next_folio;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("logical_offset %llu, inode_size %llu\n",
+			  logical_offset, (u64)i_size_read(inode));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		start = offset_in_folio(cur_folio, logical_offset);
+		end = folio_size(cur_folio) - start;
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("start %u, end %u, "
+			  "len %u, processed_bytes %u, "
+			  "folio_size %zu\n",
+			  start, end, len, processed_bytes,
+			  folio_size(cur_folio));
+
+		BUG_ON(processed_bytes > fsi->pagesize);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		if ((logical_offset & PAGE_MASK) >= i_size_read(inode)) {
+			/* Reading beyond i_size is simple: memset to zero */
+			folio_zero_segments(cur_folio, 0, start, end,
+					    folio_size(cur_folio));
+			folio_mark_uptodate(cur_folio);
+			flush_dcache_folio(cur_folio);
+		} else if (len >= (folio_size(cur_folio) + processed_bytes)) {
+			folio_zero_segments(cur_folio, 0, start, end,
+					    folio_size(cur_folio));
+			folio_mark_uptodate(cur_folio);
+			flush_dcache_folio(cur_folio);
+		} else {
+			need_read_block = true;
+		}
+
+		folio_batch_add(&fbatch, cur_folio);
+
+check_next_folio:
+		processed_bytes += folio_size(cur_folio);
+		index += folio_size(cur_folio) >> PAGE_SHIFT;
+	}
+
+	if (need_read_block) {
+		err = ssdfs_read_block_nolock(file, &fbatch,
+						SSDFS_CURRENT_THREAD_READ);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to read folio: "
+				  "index %lu, err %d\n",
+				  index, err);
+			return err;
+		}
+	}
+
+	for (i = 0; i < folio_batch_count(&fbatch); i++) {
+		cur_folio = fbatch.folios[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!cur_folio);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		if (cur_folio != requested_folio)
+			ssdfs_folio_unlock(cur_folio);
+	}
+
+	return 0;
+}
+
+static
+int ssdfs_write_begin_inline_file(struct file *file,
+				  struct address_space *mapping,
+				  loff_t pos, unsigned len,
+				  struct folio **foliop, void **fsdata)
+{
+	struct inode *inode = mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct folio *first_folio;
+	pgoff_t index = pos >> PAGE_SHIFT;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, pos %llu, len %u\n",
+		  inode->i_ino, pos, len);
+
+	if (!can_file_be_inline(inode, i_size_read(inode))) {
+		SSDFS_ERR("not inline file: "
+			  "ino %lu, pos %llu, "
+			  "len %u, file size %llu\n",
+			  inode->i_ino, pos, len,
+			  (u64)i_size_read(inode));
+		return -EINVAL;
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!ii->inline_file) {
+		err = ssdfs_allocate_inline_file_buffer(inode);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to allocate inline buffer\n");
+			return -EAGAIN;
+		}
+
+		/*
+		 * TODO: pre-fetch file's content in buffer
+		 *       (if inode size > 256 bytes)
+		 */
+	}
+
+	atomic_or(SSDFS_INODE_HAS_INLINE_FILE,
+		  &SSDFS_I(inode)->private_flags);
+
+	first_folio = ssdfs_get_block_folio(file, mapping, index);
+	if (!first_folio) {
+		SSDFS_ERR("fail to grab folio: index %lu\n",
+			  index);
+		return -ENOMEM;
+	} else if (IS_ERR(first_folio)) {
+		SSDFS_ERR("fail to grab folio: index %lu, err %ld\n",
+			  index, PTR_ERR(first_folio));
+		return PTR_ERR(first_folio);
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("folio %p, count %d\n",
+		  first_folio, folio_ref_count(first_folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	*foliop = first_folio;
+
+	if ((len == fsi->pagesize) || folio_test_uptodate(first_folio))
+		return 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("pos %llu, inode_size %llu\n",
+		  pos, (u64)i_size_read(inode));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = ssdfs_process_whole_block(file, mapping, first_folio,
+					pos, len, false);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to process thw whole block: "
+			  "ino %lu, pos %llu, len %u, err %d\n",
+			  inode->i_ino, pos, len, err);
+		return err;
+	}
+
+	return 0;
+}
+
+static
+struct folio *ssdfs_write_begin_logical_block(struct file *file,
+					      struct address_space *mapping,
+					      loff_t pos, u32 len)
+{
+	struct inode *inode = mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct folio *first_folio;
+	pgoff_t index = pos >> PAGE_SHIFT;
+	loff_t cur_blk;
+	u64 last_blk = U64_MAX;
+	bool is_new_blk = false;
+#ifdef CONFIG_SSDFS_DEBUG
+	u64 free_blocks = 0;
+#endif /* CONFIG_SSDFS_DEBUG */
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, pos %llu, len %u\n",
+		  inode->i_ino, pos, len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	atomic_and(~SSDFS_INODE_HAS_INLINE_FILE,
+		   &SSDFS_I(inode)->private_flags);
+
+	if (can_file_be_inline(inode, i_size_read(inode))) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("change from inline to regular file\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		last_blk = U64_MAX;
+	} else if (i_size_read(inode) > 0) {
+		last_blk = (i_size_read(inode) - 1) >>
+					fsi->log_pagesize;
+	}
+
+	cur_blk = pos >> fsi->log_pagesize;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("cur_blk %llu, last_blk %llu\n",
+		  (u64)cur_blk, (u64)last_blk);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	first_folio = ssdfs_get_block_folio(file, mapping, index);
+	if (!first_folio) {
+		SSDFS_ERR("fail to grab folio: index %lu\n",
+			  index);
+		return ERR_PTR(-ENOMEM);
+	} else if (IS_ERR(first_folio)) {
+		SSDFS_ERR("fail to grab folio: index %lu, err %ld\n",
+			  index, PTR_ERR(first_folio));
+		return first_folio;
+	}
+
+	if (pos % folio_size(first_folio)) {
+		if (folio_test_uptodate(first_folio))
+			return first_folio;
+		else {
+			SSDFS_ERR("invalid request: "
+				  "pos %llu, pagesize %u, "
+				  "folio_size %zu\n",
+				  pos, fsi->pagesize,
+				  folio_size(first_folio));
+			ssdfs_folio_unlock(first_folio);
+			ssdfs_folio_put(first_folio);
+			return ERR_PTR(-ERANGE);
+		}
+	}
+
+	if (last_blk >= U64_MAX) {
+		is_new_blk = true;
+	} else if (cur_blk > last_blk) {
+		is_new_blk = true;
+	} else if (!folio_test_uptodate(first_folio)) {
+		is_new_blk = !ssdfs_extents_tree_has_logical_block(cur_blk,
+								   inode);
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("cur_blk %llu, is_new_blk %#x\n",
+		  (u64)cur_blk, is_new_blk);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (is_new_blk) {
+		if (!need_add_block(first_folio)) {
+			err = ssdfs_reserve_free_pages(fsi, 1,
+						SSDFS_USER_DATA_PAGES);
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		spin_lock(&fsi->volume_state_lock);
+		free_blocks = fsi->free_pages;
+		spin_unlock(&fsi->volume_state_lock);
+
+		SSDFS_DBG("free_blocks %llu, err %d\n",
+			  free_blocks, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		if (err) {
+			ssdfs_increase_volume_free_pages(fsi, 1);
+
+			ssdfs_folio_unlock(first_folio);
+			ssdfs_folio_put(first_folio);
+
+			ssdfs_write_failed(mapping, pos);
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("folio %p, count %d\n",
+				  first_folio, folio_ref_count(first_folio));
+			SSDFS_DBG("volume hasn't free space\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			return ERR_PTR(err);
+		}
+
+		ssdfs_folio_test_and_set_if_new(inode, first_folio);
+	}
+
+	err = ssdfs_process_whole_block(file, mapping, first_folio,
+					pos, len, is_new_blk);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to process the whole block: "
+			  "ino %lu, pos %llu, len %u, err %d\n",
+			  inode->i_ino, pos, len, err);
+		return ERR_PTR(err);
+	}
+
+	return first_folio;
+}
+
+/*
+ * The ssdfs_write_begin() is called by the generic
+ * buffered write code to ask the filesystem to prepare
+ * to write len bytes at the given offset in the file.
+ */
+static
+int ssdfs_write_begin(const struct kiocb *iocb,
+		      struct address_space *mapping,
+		      loff_t pos, unsigned len,
+		      struct folio **foliop, void **fsdata)
+{
+	struct file *file = iocb->ki_filp;
+	struct inode *inode = mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct folio *first_folio = NULL;
+	loff_t cur_pos, next_pos;
+	loff_t start_blk, end_blk, cur_blk;
+	u32 processed_bytes = 0;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, pos %llu, len %u\n",
+		  inode->i_ino, pos, len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (inode->i_sb->s_flags & SB_RDONLY)
+		return -EROFS;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, large_folios_support %#x\n",
+		  inode->i_ino,
+		  mapping_large_folio_support(mapping));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (!can_file_be_inline(inode, i_size_read(inode))) {
+		/*
+		 * Process as regular file
+		 */
+		goto try_regular_write;
+	} else if (can_file_be_inline(inode, pos + len)) {
+		err = ssdfs_write_begin_inline_file(file, mapping,
+						    pos, len,
+						    foliop, fsdata);
+		if (err == -EAGAIN) {
+			/*
+			 * Process as regular file
+			 */
+			err = 0;
+			goto try_regular_write;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to process inline file: "
+				  "ino %lu, pos %llu, len %u, err %d\n",
+				  inode->i_ino, pos, len, err);
+			goto finish_write_begin;
+		} else
+			goto finish_write_begin;
+	} else {
+try_regular_write:
+		cur_pos = pos;
+		start_blk = pos >> fsi->log_pagesize;
+		end_blk = (pos + len + fsi->pagesize - 1) >> fsi->log_pagesize;
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(start_blk == end_blk);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		for (cur_blk = start_blk; cur_blk < end_blk; cur_blk++) {
+			struct folio *folio;
+			u32 cur_len;
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(processed_bytes > len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			next_pos = (cur_blk + 1) << fsi->log_pagesize;
+			cur_len = min_t(u32, len - processed_bytes,
+						next_pos - cur_pos);
+
+			folio = ssdfs_write_begin_logical_block(file,
+								mapping,
+								cur_pos,
+								cur_len);
+			if (IS_ERR_OR_NULL(folio)) {
+				err = IS_ERR(folio) ? PTR_ERR(folio) : -ERANGE;
+				SSDFS_ERR("fail to process folio: "
+					  "ino %lu, pos %llu, err %d\n",
+					  inode->i_ino, cur_pos, err);
+				goto finish_write_begin;
+			}
+
+			if (!first_folio)
+				first_folio = folio;
+
+			processed_bytes += next_pos - cur_pos;
+			cur_pos = next_pos;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("folio %p, count %d\n",
+			  first_folio,
+			  folio_ref_count(first_folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		*foliop = first_folio;
+	}
+
+finish_write_begin:
+	return err;
+}
+
+/*
+ * After a successful ssdfs_write_begin(), and data copy,
+ * ssdfs_write_end() must be called.
+ */
+static
+int ssdfs_write_end(const struct kiocb *iocb,
+		    struct address_space *mapping,
+		    loff_t pos, unsigned len, unsigned copied,
+		    struct folio *folio, void *fsdata)
+{
+	struct inode *inode = mapping->host;
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	pgoff_t index = folio->index;
+	unsigned start = offset_in_folio(folio, pos);
+	unsigned end = start + copied;
+	loff_t old_size = i_size_read(inode);
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, pos %llu, len %u, copied %u, "
+		  "index %lu, start %u, end %u, old_size %llu, "
+		  "folio_size %zu, need_add_block %#x, "
+		  "folio_test_dirty %#x\n",
+		  inode->i_ino, pos, len, copied,
+		  index, start, end, old_size,
+		  folio_size(folio),
+		  need_add_block(folio),
+		  folio_test_dirty(folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (copied < len) {
+		/*
+		 * VFS copied less data to the folio that it intended and
+		 * declared in its '->write_begin()' call via the @len
+		 * argument. Just tell userspace to retry the entire block.
+		 */
+		if (!folio_test_uptodate(folio)) {
+			copied = 0;
+			goto out;
+		}
+	}
+
+	if (!need_add_block(folio) && !folio_test_dirty(folio)) {
+		u64 folio_offset;
+		u32 offset_inside_folio;
+
+		folio_offset = (u64)folio->index << PAGE_SHIFT;
+		div_u64_rem(folio_offset, fsi->pagesize, &offset_inside_folio);
+
+		if (offset_inside_folio == 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("ACCOUNT UPDATED USER DATA PAGES: "
+				  "ino %lu, pos %llu, len %u, "
+				  "folio_index %lu\n",
+				  inode->i_ino, pos, len,
+				  folio->index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_account_updated_user_data_pages(fsi,
+					SSDFS_MEM_PAGES_PER_LOGICAL_BLOCK(fsi));
+		}
+	}
+
+	if (old_size < (index << PAGE_SHIFT) + end) {
+		i_size_write(inode, (index << PAGE_SHIFT) + end);
+		mark_inode_dirty_sync(inode);
+	}
+
+	flush_dcache_folio(folio);
+
+	folio_mark_uptodate(folio);
+	if (!folio_test_dirty(folio))
+		filemap_dirty_folio(mapping, folio);
+
+out:
+	ssdfs_folio_unlock(folio);
+	ssdfs_folio_put(folio);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("folio %p, count %d, "
+		  "folio_test_dirty %#x\n",
+		  folio, folio_ref_count(folio),
+		  folio_test_dirty(folio));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return err ? err : copied;
+}
+
+/*
+ * The ssdfs_direct_IO() is called by the generic read/write
+ * routines to perform direct_IO - that is IO requests which
+ * bypass the page cache and transfer data directly between
+ * the storage and the application's address space.
+ */
+static ssize_t ssdfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
+{
+	/* TODO: implement */
+	return -ERANGE;
+}
+
+/*
+ * The ssdfs_fsync() is called by the fsync(2) system call.
+ */
+int ssdfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
+{
+	struct inode *inode = file->f_mapping->host;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, start %llu, end %llu, datasync %#x\n",
+		  (unsigned long)inode->i_ino, (unsigned long long)start,
+		  (unsigned long long)end, datasync);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	trace_ssdfs_sync_file_enter(inode);
+
+	err = filemap_write_and_wait_range(inode->i_mapping, start, end);
+	if (err) {
+		trace_ssdfs_sync_file_exit(file, datasync, err);
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("fsync failed: ino %lu, start %llu, "
+			  "end %llu, err %d\n",
+			  (unsigned long)inode->i_ino,
+			  (unsigned long long)start,
+			  (unsigned long long)end,
+			  err);
+#endif /* CONFIG_SSDFS_DEBUG */
+		return err;
+	}
+
+	inode_lock(inode);
+	sync_inode_metadata(inode, 1);
+	blkdev_issue_flush(inode->i_sb->s_bdev);
+	inode_unlock(inode);
+
+	trace_ssdfs_sync_file_exit(file, datasync, err);
+
+	return err;
+}
+
+const struct file_operations ssdfs_file_operations = {
+	.llseek		= generic_file_llseek,
+	.read_iter	= ssdfs_file_read_iter,
+	.write_iter	= generic_file_write_iter,
+	.unlocked_ioctl	= ssdfs_ioctl,
+	.mmap		= generic_file_mmap,
+	.open		= generic_file_open,
+	.fsync		= ssdfs_fsync,
+	.splice_read	= filemap_splice_read,
+	.splice_write	= iter_file_splice_write,
+};
+
+const struct inode_operations ssdfs_file_inode_operations = {
+	.getattr	= ssdfs_getattr,
+	.setattr	= ssdfs_setattr,
+	.listxattr	= ssdfs_listxattr,
+	.get_inode_acl	= ssdfs_get_acl,
+	.set_acl	= ssdfs_set_acl,
+};
+
+const struct inode_operations ssdfs_special_inode_operations = {
+	.setattr	= ssdfs_setattr,
+	.listxattr	= ssdfs_listxattr,
+	.get_inode_acl	= ssdfs_get_acl,
+	.set_acl	= ssdfs_set_acl,
+};
+
+const struct inode_operations ssdfs_symlink_inode_operations = {
+	.get_link	= page_get_link,
+	.getattr	= ssdfs_getattr,
+	.setattr	= ssdfs_setattr,
+	.listxattr	= ssdfs_listxattr,
+};
+
+const struct address_space_operations ssdfs_aops = {
+	.read_folio		= ssdfs_read_block,
+	.readahead		= ssdfs_readahead,
+	.writepages		= ssdfs_writepages,
+	.write_begin		= ssdfs_write_begin,
+	.write_end		= ssdfs_write_end,
+	.migrate_folio		= filemap_migrate_folio,
+	.dirty_folio		= filemap_dirty_folio,
+	.direct_IO		= ssdfs_direct_IO,
+};
-- 
2.34.1