lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20251020021227.5965-1-linkinjeon@kernel.org>
Date: Mon, 20 Oct 2025 11:12:22 +0900
From: Namjae Jeon <linkinjeon@...nel.org>
To: viro@...iv.linux.org.uk,
	brauner@...nel.org,
	hch@...radead.org,
	hch@....de,
	tytso@....edu,
	willy@...radead.org,
	jack@...e.cz,
	djwong@...nel.org,
	josef@...icpanda.com,
	sandeen@...deen.net,
	rgoldwyn@...e.com,
	xiang@...nel.org,
	dsterba@...e.com,
	pali@...nel.org,
	ebiggers@...nel.org,
	neil@...wn.name,
	amir73il@...il.com
Cc: linux-fsdevel@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	iamjoonsoo.kim@....com,
	cheol.lee@....com,
	jay.sim@....com,
	gunho.lee@....com,
	Namjae Jeon <linkinjeon@...nel.org>
Subject: [PATCH 06/11] ntfsplus: add iomap and address space operations

This adds the implementation of iomap and address space operations
for ntfsplus.

Signed-off-by: Namjae Jeon <linkinjeon@...nel.org>
---
 fs/ntfsplus/aops.c       | 631 +++++++++++++++++++++++++++++++++++
 fs/ntfsplus/ntfs_iomap.c | 704 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 1335 insertions(+)
 create mode 100644 fs/ntfsplus/aops.c
 create mode 100644 fs/ntfsplus/ntfs_iomap.c

diff --git a/fs/ntfsplus/aops.c b/fs/ntfsplus/aops.c
new file mode 100644
index 000000000000..50c804be3bd4
--- /dev/null
+++ b/fs/ntfsplus/aops.c
@@ -0,0 +1,631 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * NTFS kernel address space operations and page cache handling.
+ *
+ * Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc.
+ * Copyright (c) 2002 Richard Russon
+ * Copyright (c) 2025 LG Electronics Co., Ltd.
+ */
+
+#include <linux/writeback.h>
+#include <linux/mpage.h>
+#include <linux/uio.h>
+
+#include "aops.h"
+#include "attrib.h"
+#include "mft.h"
+#include "ntfs.h"
+#include "misc.h"
+#include "ntfs_iomap.h"
+
+static s64 ntfs_convert_page_index_into_lcn(struct ntfs_volume *vol, struct ntfs_inode *ni,
+		unsigned long page_index)
+{
+	sector_t iblock;
+	s64 vcn;
+	s64 lcn;
+	unsigned char blocksize_bits = vol->sb->s_blocksize_bits;
+
+	iblock = (s64)page_index << (PAGE_SHIFT - blocksize_bits);
+	vcn = (s64)iblock << blocksize_bits >> vol->cluster_size_bits;
+
+	down_read(&ni->runlist.lock);
+	lcn = ntfs_attr_vcn_to_lcn_nolock(ni, vcn, false);
+	up_read(&ni->runlist.lock);
+
+	return lcn;
+}
+
+struct bio *ntfs_setup_bio(struct ntfs_volume *vol, unsigned int opf, s64 lcn,
+		unsigned int pg_ofs)
+{
+	struct bio *bio;
+
+	bio = bio_alloc(vol->sb->s_bdev, 1, opf, GFP_NOIO);
+	if (!bio)
+		return NULL;
+	bio->bi_iter.bi_sector = ((lcn << vol->cluster_size_bits) + pg_ofs) >>
+		vol->sb->s_blocksize_bits;
+
+	return bio;
+}
+
+/**
+ * ntfs_read_folio - fill a @folio of a @file with data from the device
+ * @file:	open file to which the folio @folio belongs or NULL
+ * @folio:	page cache folio to fill with data
+ *
+ * For non-resident attributes, ntfs_read_folio() fills the @folio of the open
+ * file @file by calling the ntfs version of the generic block_read_full_folio()
+ * function, which in turn creates and reads in the buffers associated with
+ * the folio asynchronously.
+ *
+ * For resident attributes, OTOH, ntfs_read_folio() fills @folio by copying the
+ * data from the mft record (which at this stage is most likely in memory) and
+ * fills the remainder with zeroes. Thus, in this case, I/O is synchronous, as
+ * even if the mft record is not cached at this point in time, we need to wait
+ * for it to be read in before we can do the copy.
+ *
+ * Return 0 on success and -errno on error.
+ */
+static int ntfs_read_folio(struct file *file, struct folio *folio)
+{
+	loff_t i_size;
+	struct inode *vi;
+	struct ntfs_inode *ni;
+
+	vi = folio->mapping->host;
+	i_size = i_size_read(vi);
+	/* Is the page fully outside i_size? (truncate in progress) */
+	if (unlikely(folio->index >= (i_size + PAGE_SIZE - 1) >>
+			PAGE_SHIFT)) {
+		folio_zero_segment(folio, 0, PAGE_SIZE);
+		ntfs_debug("Read outside i_size - truncated?");
+		folio_mark_uptodate(folio);
+		folio_unlock(folio);
+		return 0;
+	}
+	/*
+	 * This can potentially happen because we clear PageUptodate() during
+	 * ntfs_writepage() of MstProtected() attributes.
+	 */
+	if (folio_test_uptodate(folio)) {
+		folio_unlock(folio);
+		return 0;
+	}
+	ni = NTFS_I(vi);
+
+	/*
+	 * Only $DATA attributes can be encrypted and only unnamed $DATA
+	 * attributes can be compressed.  Index root can have the flags set but
+	 * this means to create compressed/encrypted files, not that the
+	 * attribute is compressed/encrypted.  Note we need to check for
+	 * AT_INDEX_ALLOCATION since this is the type of both directory and
+	 * index inodes.
+	 */
+	if (ni->type != AT_INDEX_ALLOCATION) {
+		/* If attribute is encrypted, deny access, just like NT4. */
+		if (NInoEncrypted(ni)) {
+			BUG_ON(ni->type != AT_DATA);
+			folio_unlock(folio);
+			return -EACCES;
+		}
+		/* Compressed data streams are handled in compress.c. */
+		if (NInoNonResident(ni) && NInoCompressed(ni)) {
+			BUG_ON(ni->type != AT_DATA);
+			BUG_ON(ni->name_len);
+			return ntfs_read_compressed_block(folio);
+		}
+	}
+
+	return iomap_read_folio(folio, &ntfs_read_iomap_ops);
+}
+
+static int ntfs_write_mft_block(struct ntfs_inode *ni, struct folio *folio,
+		struct writeback_control *wbc)
+{
+	struct inode *vi = VFS_I(ni);
+	struct ntfs_volume *vol = ni->vol;
+	u8 *kaddr;
+	struct ntfs_inode *locked_nis[PAGE_SIZE / NTFS_BLOCK_SIZE];
+	int nr_locked_nis = 0, err = 0, mft_ofs, prev_mft_ofs;
+	struct bio *bio = NULL;
+	unsigned long mft_no;
+	struct ntfs_inode *tni;
+	s64 lcn;
+	unsigned int lcn_folio_off = 0;
+	s64 vcn = (s64)folio->index << PAGE_SHIFT >> vol->cluster_size_bits;
+	s64 end_vcn = ni->allocated_size >> vol->cluster_size_bits;
+	unsigned int folio_sz;
+	struct runlist_element *rl;
+
+	ntfs_debug("Entering for inode 0x%lx, attribute type 0x%x, folio index 0x%lx.",
+			vi->i_ino, ni->type, folio->index);
+	BUG_ON(!NInoNonResident(ni));
+	BUG_ON(!NInoMstProtected(ni));
+
+	/*
+	 * NOTE: ntfs_write_mft_block() would be called for $MFTMirr if a page
+	 * in its page cache were to be marked dirty.  However this should
+	 * never happen with the current driver and considering we do not
+	 * handle this case here we do want to BUG(), at least for now.
+	 */
+
+	BUG_ON(!((S_ISREG(vi->i_mode) && !vi->i_ino) || S_ISDIR(vi->i_mode) ||
+		(NInoAttr(ni) && ni->type == AT_INDEX_ALLOCATION)));
+
+	lcn = ntfs_convert_page_index_into_lcn(vol, ni, folio->index);
+	if (lcn <= LCN_HOLE) {
+		folio_start_writeback(folio);
+		folio_unlock(folio);
+		folio_end_writeback(folio);
+		return -EIO;
+	}
+
+	if (vol->cluster_size_bits > PAGE_SHIFT) {
+		lcn_folio_off = folio->index << PAGE_SHIFT;
+		lcn_folio_off &= vol->cluster_size_mask;
+	}
+
+	/* Map folio so we can access its contents. */
+	kaddr = kmap_local_folio(folio, 0);
+	/* Clear the page uptodate flag whilst the mst fixups are applied. */
+	folio_clear_uptodate(folio);
+
+	for (mft_ofs = 0; mft_ofs < PAGE_SIZE && vcn < end_vcn;
+	     mft_ofs += vol->mft_record_size) {
+		/* Get the mft record number. */
+		mft_no = (((s64)folio->index << PAGE_SHIFT) + mft_ofs) >>
+			vol->mft_record_size_bits;
+		/* Check whether to write this mft record. */
+		tni = NULL;
+		if (ntfs_may_write_mft_record(vol, mft_no,
+					(struct mft_record *)(kaddr + mft_ofs), &tni)) {
+			unsigned int mft_record_off = 0;
+			s64 vcn_off = vcn;
+
+			/*
+			 * The record should be written.  If a locked ntfs
+			 * inode was returned, add it to the array of locked
+			 * ntfs inodes.
+			 */
+			if (tni)
+				locked_nis[nr_locked_nis++] = tni;
+
+			if (bio && (mft_ofs != prev_mft_ofs + vol->mft_record_size)) {
+flush_bio:
+				flush_dcache_folio(folio);
+				submit_bio_wait(bio);
+				bio_put(bio);
+				bio = NULL;
+			}
+
+			if (vol->cluster_size == NTFS_BLOCK_SIZE) {
+				down_write(&ni->runlist.lock);
+				rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
+				up_write(&ni->runlist.lock);
+				if (IS_ERR(rl) || lcn < 0) {
+					err = -EIO;
+					goto unm_done;
+				}
+			}
+
+			if (!bio) {
+				unsigned int off = lcn_folio_off;
+
+				if (vol->cluster_size != NTFS_BLOCK_SIZE)
+					off += mft_ofs;
+
+				bio = ntfs_setup_bio(vol, REQ_OP_WRITE, lcn, off);
+				if (!bio) {
+					err = -ENOMEM;
+					goto unm_done;
+				}
+			}
+
+			if (vol->cluster_size == NTFS_BLOCK_SIZE && rl->length == 1)
+				folio_sz = NTFS_BLOCK_SIZE;
+			else
+				folio_sz = vol->mft_record_size;
+			if (!bio_add_folio(bio, folio, folio_sz,
+					   mft_ofs + mft_record_off)) {
+				err = -EIO;
+				bio_put(bio);
+				goto unm_done;
+			}
+			prev_mft_ofs = mft_ofs;
+			mft_record_off += folio_sz;
+
+			if (mft_record_off != vol->mft_record_size) {
+				vcn_off++;
+				goto flush_bio;
+			}
+
+			if (mft_no < vol->mftmirr_size)
+				ntfs_sync_mft_mirror(vol, mft_no,
+						(struct mft_record *)(kaddr + mft_ofs));
+		}
+
+		vcn += vol->mft_record_size >> vol->cluster_size_bits;
+	}
+
+	if (bio) {
+		flush_dcache_folio(folio);
+		submit_bio_wait(bio);
+		bio_put(bio);
+	}
+	flush_dcache_folio(folio);
+unm_done:
+	folio_mark_uptodate(folio);
+	kunmap_local(kaddr);
+
+	folio_start_writeback(folio);
+	folio_unlock(folio);
+	folio_end_writeback(folio);
+
+	/* Unlock any locked inodes. */
+	while (nr_locked_nis-- > 0) {
+		struct ntfs_inode *base_tni;
+
+		tni = locked_nis[nr_locked_nis];
+		mutex_unlock(&tni->mrec_lock);
+
+		/* Get the base inode. */
+		mutex_lock(&tni->extent_lock);
+		if (tni->nr_extents >= 0)
+			base_tni = tni;
+		else {
+			base_tni = tni->ext.base_ntfs_ino;
+			BUG_ON(!base_tni);
+		}
+		mutex_unlock(&tni->extent_lock);
+		ntfs_debug("Unlocking %s inode 0x%lx.",
+				tni == base_tni ? "base" : "extent",
+				tni->mft_no);
+		atomic_dec(&tni->count);
+		iput(VFS_I(base_tni));
+	}
+
+	if (unlikely(err && err != -ENOMEM))
+		NVolSetErrors(vol);
+	if (likely(!err))
+		ntfs_debug("Done.");
+	return err;
+}
+
+/**
+ * ntfs_bmap - map logical file block to physical device block
+ * @mapping:	address space mapping to which the block to be mapped belongs
+ * @block:	logical block to map to its physical device block
+ *
+ * For regular, non-resident files (i.e. not compressed and not encrypted), map
+ * the logical @block belonging to the file described by the address space
+ * mapping @mapping to its physical device block.
+ *
+ * The size of the block is equal to the @s_blocksize field of the super block
+ * of the mounted file system which is guaranteed to be smaller than or equal
+ * to the cluster size thus the block is guaranteed to fit entirely inside the
+ * cluster which means we do not need to care how many contiguous bytes are
+ * available after the beginning of the block.
+ *
+ * Return the physical device block if the mapping succeeded or 0 if the block
+ * is sparse or there was an error.
+ *
+ * Note: This is a problem if someone tries to run bmap() on $Boot system file
+ * as that really is in block zero but there is nothing we can do.  bmap() is
+ * just broken in that respect (just like it cannot distinguish sparse from
+ * not available or error).
+ */
+static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
+{
+	s64 ofs, size;
+	loff_t i_size;
+	s64 lcn;
+	unsigned long blocksize, flags;
+	struct ntfs_inode *ni = NTFS_I(mapping->host);
+	struct ntfs_volume *vol = ni->vol;
+	unsigned int delta;
+	unsigned char blocksize_bits, cluster_size_shift;
+
+	ntfs_debug("Entering for mft_no 0x%lx, logical block 0x%llx.",
+			ni->mft_no, (unsigned long long)block);
+	if (ni->type != AT_DATA || !NInoNonResident(ni) || NInoEncrypted(ni)) {
+		ntfs_error(vol->sb, "BMAP does not make sense for %s attributes, returning 0.",
+				(ni->type != AT_DATA) ? "non-data" :
+				(!NInoNonResident(ni) ? "resident" :
+				"encrypted"));
+		return 0;
+	}
+	/* None of these can happen. */
+	BUG_ON(NInoCompressed(ni));
+	BUG_ON(NInoMstProtected(ni));
+	blocksize = vol->sb->s_blocksize;
+	blocksize_bits = vol->sb->s_blocksize_bits;
+	ofs = (s64)block << blocksize_bits;
+	read_lock_irqsave(&ni->size_lock, flags);
+	size = ni->initialized_size;
+	i_size = i_size_read(VFS_I(ni));
+	read_unlock_irqrestore(&ni->size_lock, flags);
+	/*
+	 * If the offset is outside the initialized size or the block straddles
+	 * the initialized size then pretend it is a hole unless the
+	 * initialized size equals the file size.
+	 */
+	if (unlikely(ofs >= size || (ofs + blocksize > size && size < i_size)))
+		goto hole;
+	cluster_size_shift = vol->cluster_size_bits;
+	down_read(&ni->runlist.lock);
+	lcn = ntfs_attr_vcn_to_lcn_nolock(ni, ofs >> cluster_size_shift, false);
+	up_read(&ni->runlist.lock);
+	if (unlikely(lcn < LCN_HOLE)) {
+		/*
+		 * Step down to an integer to avoid gcc doing a long long
+		 * comparision in the switch when we know @lcn is between
+		 * LCN_HOLE and LCN_EIO (i.e. -1 to -5).
+		 *
+		 * Otherwise older gcc (at least on some architectures) will
+		 * try to use __cmpdi2() which is of course not available in
+		 * the kernel.
+		 */
+		switch ((int)lcn) {
+		case LCN_ENOENT:
+			/*
+			 * If the offset is out of bounds then pretend it is a
+			 * hole.
+			 */
+			goto hole;
+		case LCN_ENOMEM:
+			ntfs_error(vol->sb,
+				"Not enough memory to complete mapping for inode 0x%lx. Returning 0.",
+				ni->mft_no);
+			break;
+		default:
+			ntfs_error(vol->sb,
+				"Failed to complete mapping for inode 0x%lx.  Run chkdsk. Returning 0.",
+				ni->mft_no);
+			break;
+		}
+		return 0;
+	}
+	if (lcn < 0) {
+		/* It is a hole. */
+hole:
+		ntfs_debug("Done (returning hole).");
+		return 0;
+	}
+	/*
+	 * The block is really allocated and fullfils all our criteria.
+	 * Convert the cluster to units of block size and return the result.
+	 */
+	delta = ofs & vol->cluster_size_mask;
+	if (unlikely(sizeof(block) < sizeof(lcn))) {
+		block = lcn = ((lcn << cluster_size_shift) + delta) >>
+				blocksize_bits;
+		/* If the block number was truncated return 0. */
+		if (unlikely(block != lcn)) {
+			ntfs_error(vol->sb,
+				"Physical block 0x%llx is too large to be returned, returning 0.",
+				(long long)lcn);
+			return 0;
+		}
+	} else
+		block = ((lcn << cluster_size_shift) + delta) >>
+				blocksize_bits;
+	ntfs_debug("Done (returning block 0x%llx).", (unsigned long long)lcn);
+	return block;
+}
+
+static void ntfs_readahead(struct readahead_control *rac)
+{
+	struct address_space *mapping = rac->mapping;
+	struct inode *inode = mapping->host;
+	struct ntfs_inode *ni = NTFS_I(inode);
+
+	if (!NInoNonResident(ni) || NInoCompressed(ni)) {
+		/* No readahead for resident and compressed. */
+		return;
+	}
+
+	if (NInoMstProtected(ni) &&
+	    (ni->mft_no == FILE_MFT || ni->mft_no == FILE_MFTMirr))
+		return;
+
+	iomap_readahead(rac, &ntfs_read_iomap_ops);
+}
+
+static int ntfs_mft_writepage(struct folio *folio, struct writeback_control *wbc)
+{
+	struct address_space *mapping = folio->mapping;
+	struct inode *vi = mapping->host;
+	struct ntfs_inode *ni = NTFS_I(vi);
+	loff_t i_size;
+	int ret;
+
+	i_size = i_size_read(vi);
+
+	/* We have to zero every time due to mmap-at-end-of-file. */
+	if (folio->index >= (i_size >> PAGE_SHIFT)) {
+		/* The page straddles i_size. */
+		unsigned int ofs = i_size & ~PAGE_MASK;
+
+		folio_zero_segment(folio, ofs, PAGE_SIZE);
+	}
+
+	ret = ntfs_write_mft_block(ni, folio, wbc);
+	mapping_set_error(mapping, ret);
+	return ret;
+}
+
+static int ntfs_writepages(struct address_space *mapping,
+		struct writeback_control *wbc)
+{
+	struct inode *inode = mapping->host;
+	struct ntfs_inode *ni = NTFS_I(inode);
+	struct iomap_writepage_ctx wpc = {
+		.inode		= mapping->host,
+		.wbc		= wbc,
+		.ops		= &ntfs_writeback_ops,
+	};
+
+	if (NVolShutdown(ni->vol))
+		return -EIO;
+
+	if (!NInoNonResident(ni))
+		return 0;
+
+	if (NInoMstProtected(ni) && ni->mft_no == FILE_MFT) {
+		struct folio *folio = NULL;
+		int error;
+
+		while ((folio = writeback_iter(mapping, wbc, folio, &error)))
+			error = ntfs_mft_writepage(folio, wbc);
+		return error;
+	}
+
+	/* If file is encrypted, deny access, just like NT4. */
+	if (NInoEncrypted(ni)) {
+		ntfs_debug("Denying write access to encrypted file.");
+		return -EACCES;
+	}
+
+	return iomap_writepages(&wpc);
+}
+
+static int ntfs_swap_activate(struct swap_info_struct *sis,
+		struct file *swap_file, sector_t *span)
+{
+	return iomap_swapfile_activate(sis, swap_file, span,
+			&ntfs_read_iomap_ops);
+}
+
+/**
+ * ntfs_normal_aops - address space operations for normal inodes and attributes
+ *
+ * Note these are not used for compressed or mst protected inodes and
+ * attributes.
+ */
+const struct address_space_operations ntfs_normal_aops = {
+	.read_folio		= ntfs_read_folio,
+	.readahead		= ntfs_readahead,
+	.writepages		= ntfs_writepages,
+	.direct_IO		= noop_direct_IO,
+	.dirty_folio		= iomap_dirty_folio,
+	.bmap			= ntfs_bmap,
+	.migrate_folio		= filemap_migrate_folio,
+	.is_partially_uptodate	= iomap_is_partially_uptodate,
+	.error_remove_folio	= generic_error_remove_folio,
+	.release_folio		= iomap_release_folio,
+	.invalidate_folio	= iomap_invalidate_folio,
+	.swap_activate          = ntfs_swap_activate,
+};
+
+/**
+ * ntfs_compressed_aops - address space operations for compressed inodes
+ */
+const struct address_space_operations ntfs_compressed_aops = {
+	.read_folio		= ntfs_read_folio,
+	.direct_IO		= noop_direct_IO,
+	.writepages		= ntfs_writepages,
+	.dirty_folio		= iomap_dirty_folio,
+	.migrate_folio		= filemap_migrate_folio,
+	.is_partially_uptodate	= iomap_is_partially_uptodate,
+	.error_remove_folio	= generic_error_remove_folio,
+	.release_folio		= iomap_release_folio,
+	.invalidate_folio	= iomap_invalidate_folio,
+};
+
+/**
+ * ntfs_mst_aops - general address space operations for mst protecteed inodes
+ *		   and attributes
+ */
+const struct address_space_operations ntfs_mst_aops = {
+	.read_folio		= ntfs_read_folio,	/* Fill page with data. */
+	.readahead		= ntfs_readahead,
+	.writepages		= ntfs_writepages,	/* Write dirty page to disk. */
+	.dirty_folio		= iomap_dirty_folio,
+	.migrate_folio		= filemap_migrate_folio,
+	.is_partially_uptodate	= iomap_is_partially_uptodate,
+	.error_remove_folio	= generic_error_remove_folio,
+	.release_folio		= iomap_release_folio,
+	.invalidate_folio	= iomap_invalidate_folio,
+};
+
+void mark_ntfs_record_dirty(struct folio *folio)
+{
+	iomap_dirty_folio(folio->mapping, folio);
+}
+
+int ntfs_dev_read(struct super_block *sb, void *buf, loff_t start, loff_t size)
+{
+	pgoff_t idx, idx_end;
+	loff_t offset, end = start + size;
+	u32 from, to, buf_off = 0;
+	struct folio *folio;
+	char *kaddr;
+
+	idx = start >> PAGE_SHIFT;
+	idx_end = end >> PAGE_SHIFT;
+	from = start & ~PAGE_MASK;
+
+	if (idx == idx_end)
+		idx_end++;
+
+	for (; idx < idx_end; idx++, from = 0) {
+		folio = ntfs_read_mapping_folio(sb->s_bdev->bd_mapping, idx);
+		if (IS_ERR(folio)) {
+			ntfs_error(sb, "Unable to read %ld page", idx);
+			return PTR_ERR(folio);
+		}
+
+		kaddr = kmap_local_folio(folio, 0);
+		offset = (loff_t)idx << PAGE_SHIFT;
+		to = min_t(u32, end - offset, PAGE_SIZE);
+
+		memcpy(buf + buf_off, kaddr + from, to);
+		buf_off += to;
+		kunmap_local(kaddr);
+		folio_put(folio);
+	}
+
+	return 0;
+}
+
+int ntfs_dev_write(struct super_block *sb, void *buf, loff_t start,
+			loff_t size, bool wait)
+{
+	pgoff_t idx, idx_end;
+	loff_t offset, end = start + size;
+	u32 from, to, buf_off = 0;
+	struct folio *folio;
+	char *kaddr;
+
+	idx = start >> PAGE_SHIFT;
+	idx_end = end >> PAGE_SHIFT;
+	from = start & ~PAGE_MASK;
+
+	if (idx == idx_end)
+		idx_end++;
+
+	for (; idx < idx_end; idx++, from = 0) {
+		folio = ntfs_read_mapping_folio(sb->s_bdev->bd_mapping, idx);
+		if (IS_ERR(folio)) {
+			ntfs_error(sb, "Unable to read %ld page", idx);
+			return PTR_ERR(folio);
+		}
+
+		kaddr = kmap_local_folio(folio, 0);
+		offset = (loff_t)idx << PAGE_SHIFT;
+		to = min_t(u32, end - offset, PAGE_SIZE);
+
+		memcpy(kaddr + from, buf + buf_off, to);
+		buf_off += to;
+		kunmap_local(kaddr);
+		folio_mark_uptodate(folio);
+		folio_mark_dirty(folio);
+		if (wait)
+			folio_wait_stable(folio);
+		folio_put(folio);
+	}
+
+	return 0;
+}
diff --git a/fs/ntfsplus/ntfs_iomap.c b/fs/ntfsplus/ntfs_iomap.c
new file mode 100644
index 000000000000..a6d2c9e01ca6
--- /dev/null
+++ b/fs/ntfsplus/ntfs_iomap.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * iomap callack functions
+ *
+ * Copyright (c) 2025 LG Electronics Co., Ltd.
+ */
+
+#include <linux/writeback.h>
+#include <linux/mpage.h>
+#include <linux/uio.h>
+
+#include "aops.h"
+#include "attrib.h"
+#include "mft.h"
+#include "ntfs.h"
+#include "misc.h"
+#include "ntfs_iomap.h"
+
+static void ntfs_iomap_put_folio(struct inode *inode, loff_t pos,
+		unsigned int len, struct folio *folio)
+{
+	struct ntfs_inode *ni = NTFS_I(inode);
+	unsigned long sector_size = 1UL << inode->i_blkbits;
+	loff_t start_down, end_up, init;
+
+	if (!NInoNonResident(ni))
+		goto out;
+
+	start_down = round_down(pos, sector_size);
+	end_up = (pos + len - 1) | (sector_size - 1);
+	init = ni->initialized_size;
+
+	if (init >= start_down && init <= end_up) {
+		if (init < pos) {
+			loff_t offset = offset_in_folio(folio, pos + len);
+
+			if (offset == 0)
+				offset = folio_size(folio);
+			folio_zero_segments(folio,
+					    offset_in_folio(folio, init),
+					    offset_in_folio(folio, pos),
+					    offset,
+					    folio_size(folio));
+
+		} else  {
+			loff_t offset = max_t(loff_t, pos + len, init);
+
+			offset = offset_in_folio(folio, offset);
+			if (offset == 0)
+				offset = folio_size(folio);
+			folio_zero_segment(folio,
+					   offset,
+					   folio_size(folio));
+		}
+	} else if (init <= pos) {
+		loff_t offset = 0, offset2 = offset_in_folio(folio, pos + len);
+
+		if ((init >> folio_shift(folio)) == (pos >> folio_shift(folio)))
+			offset = offset_in_folio(folio, init);
+		if (offset2 == 0)
+			offset2 = folio_size(folio);
+		folio_zero_segments(folio,
+				    offset,
+				    offset_in_folio(folio, pos),
+				    offset2,
+				    folio_size(folio));
+	}
+
+out:
+	folio_unlock(folio);
+	folio_put(folio);
+}
+
+const struct iomap_write_ops ntfs_iomap_folio_ops = {
+	.put_folio = ntfs_iomap_put_folio,
+};
+
+static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+		unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
+{
+	struct ntfs_inode *base_ni, *ni = NTFS_I(inode);
+	struct ntfs_attr_search_ctx *ctx;
+	loff_t i_size;
+	u32 attr_len;
+	int err = 0;
+	char *kattr;
+	struct page *ipage;
+
+	if (NInoNonResident(ni)) {
+		s64 vcn;
+		s64 lcn;
+		struct runlist_element *rl;
+		struct ntfs_volume *vol = ni->vol;
+		loff_t vcn_ofs;
+		loff_t rl_length;
+
+		vcn = offset >> vol->cluster_size_bits;
+		vcn_ofs = offset & vol->cluster_size_mask;
+
+		down_write(&ni->runlist.lock);
+		rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn);
+		if (IS_ERR(rl)) {
+			up_write(&ni->runlist.lock);
+			return PTR_ERR(rl);
+		}
+
+		if (flags & IOMAP_REPORT) {
+			if (lcn < LCN_HOLE) {
+				up_write(&ni->runlist.lock);
+				return -ENOENT;
+			}
+		} else if (lcn < LCN_ENOENT) {
+			up_write(&ni->runlist.lock);
+			return -EINVAL;
+		}
+
+		iomap->bdev = inode->i_sb->s_bdev;
+		iomap->offset = offset;
+
+		if (lcn <= LCN_DELALLOC) {
+			if (lcn == LCN_DELALLOC)
+				iomap->type = IOMAP_DELALLOC;
+			else
+				iomap->type = IOMAP_HOLE;
+			iomap->addr = IOMAP_NULL_ADDR;
+		} else {
+			if (!(flags & IOMAP_ZERO) && offset >= ni->initialized_size)
+				iomap->type = IOMAP_UNWRITTEN;
+			else
+				iomap->type = IOMAP_MAPPED;
+			iomap->addr = (lcn << vol->cluster_size_bits) + vcn_ofs;
+		}
+
+		rl_length = (rl->length - (vcn - rl->vcn)) << ni->vol->cluster_size_bits;
+
+		if (rl_length == 0 && rl->lcn > LCN_DELALLOC) {
+			ntfs_error(inode->i_sb,
+				   "runlist(vcn : %lld, length : %lld, lcn : %lld) is corrupted\n",
+				   rl->vcn, rl->length, rl->lcn);
+			up_write(&ni->runlist.lock);
+			return -EIO;
+		}
+
+		if (rl_length && length > rl_length - vcn_ofs)
+			iomap->length = rl_length - vcn_ofs;
+		else
+			iomap->length = length;
+		up_write(&ni->runlist.lock);
+
+		if (!(flags & IOMAP_ZERO) &&
+		    iomap->type == IOMAP_MAPPED &&
+		    iomap->offset < ni->initialized_size &&
+		    iomap->offset + iomap->length > ni->initialized_size) {
+			iomap->length = round_up(ni->initialized_size, 1 << inode->i_blkbits) -
+				iomap->offset;
+		}
+		iomap->flags |= IOMAP_F_MERGED;
+		return 0;
+	}
+
+	if (NInoAttr(ni))
+		base_ni = ni->ext.base_ntfs_ino;
+	else
+		base_ni = ni;
+	BUG_ON(NInoNonResident(ni));
+
+	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
+	if (!ctx) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
+			CASE_SENSITIVE, 0, NULL, 0, ctx);
+	if (unlikely(err))
+		goto out;
+
+	attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
+	if (unlikely(attr_len > ni->initialized_size))
+		attr_len = ni->initialized_size;
+	i_size = i_size_read(inode);
+
+	if (unlikely(attr_len > i_size)) {
+		/* Race with shrinking truncate. */
+		attr_len = i_size;
+	}
+
+	if (offset >= attr_len) {
+		if (flags & IOMAP_REPORT)
+			err = -ENOENT;
+		else
+			err = -EFAULT;
+		goto out;
+	}
+
+	kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
+
+	ipage = alloc_page(__GFP_NOWARN | __GFP_IO | __GFP_ZERO);
+	if (!ipage) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(page_address(ipage), kattr, attr_len);
+	iomap->type = IOMAP_INLINE;
+	iomap->inline_data = page_address(ipage);
+	iomap->offset = 0;
+	iomap->length = min_t(loff_t, attr_len, PAGE_SIZE);
+	iomap->private = ipage;
+
+out:
+	if (ctx)
+		ntfs_attr_put_search_ctx(ctx);
+	return err;
+}
+
+static int ntfs_read_iomap_end(struct inode *inode, loff_t pos, loff_t length,
+		ssize_t written, unsigned int flags, struct iomap *iomap)
+{
+	if (iomap->type == IOMAP_INLINE) {
+		struct page *ipage = iomap->private;
+
+		put_page(ipage);
+	}
+	return written;
+}
+
+const struct iomap_ops ntfs_read_iomap_ops = {
+	.iomap_begin = ntfs_read_iomap_begin,
+	.iomap_end = ntfs_read_iomap_end,
+};
+
+static int ntfs_buffered_zeroed_clusters(struct inode *vi, s64 vcn)
+{
+	struct ntfs_inode *ni = NTFS_I(vi);
+	struct ntfs_volume *vol = ni->vol;
+	struct address_space *mapping = vi->i_mapping;
+	struct folio *folio;
+	pgoff_t idx, idx_end;
+	u32 from, to;
+
+	idx = (vcn << vol->cluster_size_bits) >> PAGE_SHIFT;
+	idx_end = ((vcn + 1) << vol->cluster_size_bits) >> PAGE_SHIFT;
+	from = (vcn << vol->cluster_size_bits) & ~PAGE_MASK;
+	if (idx == idx_end)
+		idx_end++;
+
+	to = min_t(u32, vol->cluster_size, PAGE_SIZE);
+	for (; idx < idx_end; idx++, from = 0) {
+		if (to != PAGE_SIZE) {
+			folio = ntfs_read_mapping_folio(mapping, idx);
+			if (IS_ERR(folio))
+				return PTR_ERR(folio);
+			folio_lock(folio);
+		} else {
+			folio = __filemap_get_folio(mapping, idx,
+					FGP_WRITEBEGIN | FGP_NOFS, mapping_gfp_mask(mapping));
+			if (IS_ERR(folio))
+				return PTR_ERR(folio);
+		}
+
+		if (folio_test_uptodate(folio) ||
+		    iomap_is_partially_uptodate(folio, from, to))
+			goto next_folio;
+
+		folio_zero_segment(folio, from, from + to);
+		folio_mark_uptodate(folio);
+
+next_folio:
+		iomap_dirty_folio(mapping, folio);
+		folio_unlock(folio);
+		folio_put(folio);
+		balance_dirty_pages_ratelimited(mapping);
+		cond_resched();
+	}
+
+	return 0;
+}
+
+int ntfs_zeroed_clusters(struct inode *vi, s64 lcn, s64 num)
+{
+	struct ntfs_inode *ni = NTFS_I(vi);
+	struct ntfs_volume *vol = ni->vol;
+	u32 to;
+	struct bio *bio = NULL;
+	s64 err = 0, zero_len = num << vol->cluster_size_bits;
+	s64 loc = lcn << vol->cluster_size_bits, curr = 0;
+
+	while (zero_len > 0) {
+setup_bio:
+		if (!bio) {
+			bio = bio_alloc(vol->sb->s_bdev,
+					bio_max_segs(DIV_ROUND_UP(zero_len, PAGE_SIZE)),
+					REQ_OP_WRITE | REQ_SYNC | REQ_IDLE, GFP_NOIO);
+			if (!bio)
+				return -ENOMEM;
+			bio->bi_iter.bi_sector = (loc + curr) >> vol->sb->s_blocksize_bits;
+		}
+
+		to = min_t(u32, zero_len, PAGE_SIZE);
+		if (!bio_add_page(bio, ZERO_PAGE(0), to, 0)) {
+			err = submit_bio_wait(bio);
+			bio_put(bio);
+			bio = NULL;
+			if (err)
+				break;
+			goto setup_bio;
+		}
+		zero_len -= to;
+		curr += to;
+	}
+
+	if (bio) {
+		err = submit_bio_wait(bio);
+		bio_put(bio);
+	}
+
+	return err;
+}
+
+static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
+				    loff_t length, unsigned int flags,
+				    struct iomap *iomap, bool da, bool mapped)
+{
+	struct ntfs_inode *ni = NTFS_I(inode);
+	struct ntfs_volume *vol = ni->vol;
+	struct attr_record *a;
+	struct ntfs_attr_search_ctx *ctx;
+	u32 attr_len;
+	int err = 0;
+	char *kattr;
+	struct page *ipage;
+
+	if (NVolShutdown(vol))
+		return -EIO;
+
+	mutex_lock(&ni->mrec_lock);
+	if (NInoNonResident(ni)) {
+		s64 vcn;
+		loff_t vcn_ofs;
+		loff_t rl_length;
+		s64 max_clu_count =
+			round_up(length, vol->cluster_size) >> vol->cluster_size_bits;
+
+		vcn = offset >> vol->cluster_size_bits;
+		vcn_ofs = offset & vol->cluster_size_mask;
+
+		if (da) {
+			bool balloc = false;
+			s64 start_lcn, lcn_count;
+			bool update_mp;
+
+			update_mp = (flags & IOMAP_DIRECT) || mapped ||
+				NInoAttr(ni) || ni->mft_no < FILE_first_user;
+			down_write(&ni->runlist.lock);
+			err = ntfs_attr_map_cluster(ni, vcn, &start_lcn, &lcn_count,
+					max_clu_count, &balloc, update_mp,
+					!(flags & IOMAP_DIRECT) && !mapped);
+			up_write(&ni->runlist.lock);
+			mutex_unlock(&ni->mrec_lock);
+			if (err) {
+				ni->i_dealloc_clusters = 0;
+				return err;
+			}
+
+			iomap->bdev = inode->i_sb->s_bdev;
+			iomap->offset = offset;
+
+			rl_length = lcn_count << ni->vol->cluster_size_bits;
+			if (length > rl_length - vcn_ofs)
+				iomap->length = rl_length - vcn_ofs;
+			else
+				iomap->length = length;
+
+			if (start_lcn == LCN_HOLE)
+				iomap->type = IOMAP_HOLE;
+			else
+				iomap->type = IOMAP_MAPPED;
+			if (balloc == true)
+				iomap->flags = IOMAP_F_NEW;
+
+			iomap->addr = (start_lcn << vol->cluster_size_bits) + vcn_ofs;
+
+			if (balloc == true) {
+				if (flags & IOMAP_DIRECT || mapped == true) {
+					loff_t end = offset + length;
+
+					if (vcn_ofs || ((vol->cluster_size > iomap->length) &&
+							end < ni->initialized_size))
+						err = ntfs_zeroed_clusters(inode,
+								start_lcn, 1);
+					if (!err && lcn_count > 1 &&
+					    (iomap->length & vol->cluster_size_mask &&
+					     end < ni->initialized_size))
+						err = ntfs_zeroed_clusters(inode,
+								start_lcn + (lcn_count - 1), 1);
+				} else {
+					if (lcn_count > ni->i_dealloc_clusters)
+						ni->i_dealloc_clusters = 0;
+					else
+						ni->i_dealloc_clusters -= lcn_count;
+				}
+				if (err < 0)
+					return err;
+			}
+
+			if (mapped && iomap->offset + iomap->length >
+			    ni->initialized_size) {
+				err = ntfs_attr_set_initialized_size(ni, iomap->offset +
+								     iomap->length);
+				if (err)
+					return err;
+			}
+		} else {
+			struct runlist_element *rl, *rlc;
+			s64 lcn;
+			bool is_retry = false;
+
+			down_read(&ni->runlist.lock);
+			rl = ni->runlist.rl;
+			if (!rl) {
+				up_read(&ni->runlist.lock);
+				err = ntfs_map_runlist(ni, vcn);
+				if (err) {
+					mutex_unlock(&ni->mrec_lock);
+					return -ENOENT;
+				}
+				down_read(&ni->runlist.lock);
+				rl = ni->runlist.rl;
+			}
+			up_read(&ni->runlist.lock);
+
+			down_write(&ni->runlist.lock);
+remap_rl:
+			/* Seek to element containing target vcn. */
+			while (rl->length && rl[1].vcn <= vcn)
+				rl++;
+			lcn = ntfs_rl_vcn_to_lcn(rl, vcn);
+
+			if (lcn <= LCN_RL_NOT_MAPPED && is_retry == false) {
+				is_retry = true;
+				if (!ntfs_map_runlist_nolock(ni, vcn, NULL)) {
+					rl = ni->runlist.rl;
+					goto remap_rl;
+				}
+			}
+
+			max_clu_count = min(max_clu_count, rl->length - (vcn - rl->vcn));
+			if (max_clu_count == 0) {
+				ntfs_error(inode->i_sb,
+					   "runlist(vcn : %lld, length : %lld) is corrupted\n",
+					   rl->vcn, rl->length);
+				up_write(&ni->runlist.lock);
+				mutex_unlock(&ni->mrec_lock);
+				return -EIO;
+			}
+
+			iomap->bdev = inode->i_sb->s_bdev;
+			iomap->offset = offset;
+
+			if (lcn <= LCN_DELALLOC) {
+				if (lcn < LCN_DELALLOC) {
+					max_clu_count =
+						ntfs_available_clusters_count(vol, max_clu_count);
+					if (max_clu_count < 0) {
+						err = max_clu_count;
+						up_write(&ni->runlist.lock);
+						mutex_unlock(&ni->mrec_lock);
+						return err;
+					}
+				}
+
+				iomap->type = IOMAP_DELALLOC;
+				iomap->addr = IOMAP_NULL_ADDR;
+
+				if (lcn <= LCN_HOLE) {
+					size_t new_rl_count;
+
+					rlc = ntfs_malloc_nofs(sizeof(struct runlist_element) * 2);
+					if (!rlc) {
+						up_write(&ni->runlist.lock);
+						mutex_unlock(&ni->mrec_lock);
+						return -ENOMEM;
+					}
+
+					rlc->vcn = vcn;
+					rlc->lcn = LCN_DELALLOC;
+					rlc->length = max_clu_count;
+
+					rlc[1].vcn = vcn + max_clu_count;
+					rlc[1].lcn = LCN_RL_NOT_MAPPED;
+					rlc[1].length = 0;
+
+					rl = ntfs_runlists_merge(&ni->runlist, rlc, 0,
+							&new_rl_count);
+					if (IS_ERR(rl)) {
+						ntfs_error(vol->sb, "Failed to merge runlists");
+						up_write(&ni->runlist.lock);
+						mutex_unlock(&ni->mrec_lock);
+						ntfs_free(rlc);
+						return PTR_ERR(rl);
+					}
+
+					ni->runlist.rl = rl;
+					ni->runlist.count = new_rl_count;
+					ni->i_dealloc_clusters += max_clu_count;
+				}
+				up_write(&ni->runlist.lock);
+				mutex_unlock(&ni->mrec_lock);
+
+				if (lcn < LCN_DELALLOC)
+					ntfs_hold_dirty_clusters(vol, max_clu_count);
+
+				rl_length = max_clu_count << ni->vol->cluster_size_bits;
+				if (length > rl_length - vcn_ofs)
+					iomap->length = rl_length - vcn_ofs;
+				else
+					iomap->length = length;
+
+				iomap->flags = IOMAP_F_NEW;
+				if (lcn <= LCN_HOLE) {
+					loff_t end = offset + length;
+
+					if (vcn_ofs || ((vol->cluster_size > iomap->length) &&
+							end < ni->initialized_size))
+						err = ntfs_buffered_zeroed_clusters(inode, vcn);
+					if (!err && max_clu_count > 1 &&
+					    (iomap->length & vol->cluster_size_mask &&
+					     end < ni->initialized_size))
+						err = ntfs_buffered_zeroed_clusters(inode,
+								vcn + (max_clu_count - 1));
+					if (err) {
+						ntfs_release_dirty_clusters(vol, max_clu_count);
+						return err;
+					}
+				}
+			} else {
+				up_write(&ni->runlist.lock);
+				mutex_unlock(&ni->mrec_lock);
+
+				iomap->type = IOMAP_MAPPED;
+				iomap->addr = (lcn << vol->cluster_size_bits) + vcn_ofs;
+
+				rl_length = max_clu_count << ni->vol->cluster_size_bits;
+				if (length > rl_length - vcn_ofs)
+					iomap->length = rl_length - vcn_ofs;
+				else
+					iomap->length = length;
+			}
+		}
+
+		return 0;
+	}
+
+	ctx = ntfs_attr_get_search_ctx(ni, NULL);
+	if (!ctx) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
+			CASE_SENSITIVE, 0, NULL, 0, ctx);
+	if (err) {
+		if (err == -ENOENT)
+			err = -EIO;
+		goto out;
+	}
+
+	a = ctx->attr;
+	BUG_ON(a->non_resident);
+	/* The total length of the attribute value. */
+	attr_len = le32_to_cpu(a->data.resident.value_length);
+
+	BUG_ON(offset > attr_len);
+	kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset);
+
+	ipage = alloc_page(__GFP_NOWARN | __GFP_IO | __GFP_ZERO);
+	if (!ipage) {
+		err = -ENOMEM;
+		goto out;
+	}
+	memcpy(page_address(ipage), kattr, attr_len);
+
+	iomap->type = IOMAP_INLINE;
+	iomap->inline_data = page_address(ipage);
+	iomap->offset = 0;
+	/* iomap requires there is only one INLINE_DATA extent */
+	iomap->length = attr_len;
+	iomap->private = ipage;
+
+out:
+	if (ctx)
+		ntfs_attr_put_search_ctx(ctx);
+	mutex_unlock(&ni->mrec_lock);
+
+	return err;
+}
+
+static int ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
+				  loff_t length, unsigned int flags,
+				  struct iomap *iomap, struct iomap *srcmap)
+{
+	return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap,
+			false, false);
+}
+
+static int ntfs_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
+		ssize_t written, unsigned int flags, struct iomap *iomap)
+{
+	if (iomap->type == IOMAP_INLINE) {
+		struct page *ipage = iomap->private;
+		struct ntfs_inode *ni = NTFS_I(inode);
+		struct ntfs_attr_search_ctx *ctx;
+		u32 attr_len;
+		int err;
+		char *kattr;
+
+		mutex_lock(&ni->mrec_lock);
+		ctx = ntfs_attr_get_search_ctx(ni, NULL);
+		if (!ctx) {
+			written = -ENOMEM;
+			mutex_unlock(&ni->mrec_lock);
+			goto out;
+		}
+
+		err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
+				CASE_SENSITIVE, 0, NULL, 0, ctx);
+		if (err) {
+			if (err == -ENOENT)
+				err = -EIO;
+			written = err;
+			goto err_out;
+		}
+
+		/* The total length of the attribute value. */
+		attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
+		if (pos >= attr_len || pos + written > attr_len)
+			goto err_out;
+
+		kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
+		memcpy(kattr + pos, iomap_inline_data(iomap, pos), written);
+		mark_mft_record_dirty(ctx->ntfs_ino);
+err_out:
+		ntfs_attr_put_search_ctx(ctx);
+		put_page(ipage);
+		mutex_unlock(&ni->mrec_lock);
+	}
+
+out:
+	return written;
+}
+
+const struct iomap_ops ntfs_write_iomap_ops = {
+	.iomap_begin		= ntfs_write_iomap_begin,
+	.iomap_end		= ntfs_write_iomap_end,
+};
+
+static int ntfs_page_mkwrite_iomap_begin(struct inode *inode, loff_t offset,
+				  loff_t length, unsigned int flags,
+				  struct iomap *iomap, struct iomap *srcmap)
+{
+	return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap,
+			true, true);
+}
+
+const struct iomap_ops ntfs_page_mkwrite_iomap_ops = {
+	.iomap_begin		= ntfs_page_mkwrite_iomap_begin,
+	.iomap_end		= ntfs_write_iomap_end,
+};
+
+static int ntfs_dio_iomap_begin(struct inode *inode, loff_t offset,
+				  loff_t length, unsigned int flags,
+				  struct iomap *iomap, struct iomap *srcmap)
+{
+	return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap,
+			true, false);
+}
+
+const struct iomap_ops ntfs_dio_iomap_ops = {
+	.iomap_begin		= ntfs_dio_iomap_begin,
+	.iomap_end		= ntfs_write_iomap_end,
+};
+
+static ssize_t ntfs_writeback_range(struct iomap_writepage_ctx *wpc,
+		struct folio *folio, u64 offset, unsigned int len, u64 end_pos)
+{
+	if (offset < wpc->iomap.offset ||
+	    offset >= wpc->iomap.offset + wpc->iomap.length) {
+		int error;
+
+		error = __ntfs_write_iomap_begin(wpc->inode, offset,
+				NTFS_I(wpc->inode)->allocated_size - offset,
+				IOMAP_WRITE, &wpc->iomap, true, false);
+		if (error)
+			return error;
+	}
+
+	return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
+}
+
+const struct iomap_writeback_ops ntfs_writeback_ops = {
+	.writeback_range	= ntfs_writeback_range,
+	.writeback_submit	= iomap_ioend_writeback_submit,
+};
-- 
2.34.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ