summaryrefslogtreecommitdiffstats
path: root/fs/ntfs/iomap.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ntfs/iomap.c')
-rw-r--r--fs/ntfs/iomap.c831
1 files changed, 831 insertions, 0 deletions
diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
new file mode 100644
index 000000000..d0964ac84
--- /dev/null
+++ b/fs/ntfs/iomap.c
@@ -0,0 +1,831 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * iomap callack functions
+ *
+ * Copyright (c) 2025 LG Electronics Co., Ltd.
+ */
+
+#include <linux/writeback.h>
+
+#include "attrib.h"
+#include "mft.h"
+#include "ntfs.h"
+#include "iomap.h"
+
+static void ntfs_iomap_put_folio_non_resident(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;
+
+ 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));
+ }
+ folio_unlock(folio);
+ folio_put(folio);
+}
+
+/*
+ * iomap_zero_range is called for an area beyond the initialized size,
+ * garbage values can be read, so zeroing out is needed.
+ */
+static void ntfs_iomap_put_folio(struct inode *inode, loff_t pos,
+ unsigned int len, struct folio *folio)
+{
+ if (NInoNonResident(NTFS_I(inode)))
+ return ntfs_iomap_put_folio_non_resident(inode, pos,
+ len, folio);
+ 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_resident(struct inode *inode, loff_t offset, loff_t length,
+ unsigned int flags, struct iomap *iomap)
+{
+ 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;
+
+ if (NInoAttr(ni))
+ base_ni = ni->ext.base_ntfs_ino;
+ else
+ base_ni = 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 {
+ iomap->type = IOMAP_HOLE;
+ iomap->offset = offset;
+ iomap->length = length;
+ }
+ goto out;
+ }
+
+ kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
+
+ iomap->type = IOMAP_INLINE;
+ iomap->inline_data = kattr;
+ iomap->offset = 0;
+ iomap->length = attr_len;
+
+out:
+ if (ctx)
+ ntfs_attr_put_search_ctx(ctx);
+
+ return err;
+}
+
+/*
+ * ntfs_read_iomap_begin_non_resident - map non-resident NTFS file data
+ * @inode: inode to map
+ * @offset: file offset to map
+ * @length: length of mapping
+ * @flags: IOMAP flags
+ * @iomap: iomap structure to fill
+ * @need_unwritten: true if UNWRITTEN extent type is needed
+ *
+ * Map a range of a non-resident NTFS file to an iomap extent.
+ *
+ * NTFS UNWRITTEN extent handling:
+ * ================================
+ * The concept of an unwritten extent in NTFS is slightly different from
+ * that of other filesystems. NTFS conceptually manages only a single
+ * continuous unwritten region, which is strictly defined based on
+ * initialized_size.
+ *
+ * File offset layout:
+ * 0 initialized_size i_size(EOF)
+ * |----------#0----------|----------#1----------|----------#2----------|
+ * | Actual data | Pre-allocated | Pre-allocated |
+ * | (user written) | (within initialized) | (initialized ~ EOF) |
+ * |----------------------|----------------------|----------------------|
+ * MAPPED MAPPED UNWRITTEN (conditionally)
+ *
+ * Region #0: User-written data, initialized and valid.
+ * Region #1: Pre-allocated within initialized_size, must be zero-initialized
+ * by the filesystem before exposure to userspace.
+ * Region #2: Pre-allocated beyond initialized_size, does not need initialization.
+ *
+ * The @need_unwritten parameter controls whether region #2 is mapped as
+ * IOMAP_UNWRITTEN or IOMAP_MAPPED:
+ * - For seek operations (SEEK_DATA/SEEK_HOLE): IOMAP_MAPPED is needed to
+ * prevent iomap_seek_data from incorrectly interpreting pre-allocated
+ * space as a hole. Since NTFS does not support multiple unwritten extents,
+ * all pre-allocated regions should be treated as data, not holes.
+ * - For zero_range operations: IOMAP_MAPPED is needed to be zeroed out.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags, struct iomap *iomap,
+ bool need_unwritten)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ s64 vcn;
+ s64 lcn;
+ struct runlist_element *rl;
+ struct ntfs_volume *vol = ni->vol;
+ loff_t vcn_ofs;
+ loff_t rl_length;
+
+ vcn = ntfs_bytes_to_cluster(vol, offset);
+ vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
+
+ 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 (need_unwritten && offset >= ni->initialized_size)
+ iomap->type = IOMAP_UNWRITTEN;
+ else
+ iomap->type = IOMAP_MAPPED;
+ iomap->addr = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs;
+ }
+
+ rl_length = ntfs_cluster_to_bytes(vol, rl->length - (vcn - rl->vcn));
+
+ 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;
+}
+
+static int __ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+ unsigned int flags, struct iomap *iomap, struct iomap *srcmap,
+ bool need_unwritten)
+{
+ if (NInoNonResident(NTFS_I(inode)))
+ return ntfs_read_iomap_begin_non_resident(inode, offset, length,
+ flags, iomap, need_unwritten);
+ return ntfs_read_iomap_begin_resident(inode, offset, length,
+ flags, iomap);
+}
+
+static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+ unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
+{
+ return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap,
+ srcmap, true);
+}
+
+static DEFINE_IOMAP_ITER_NEXT(ntfs_read_iomap_next, ntfs_read_iomap_begin);
+
+const struct iomap_ops ntfs_read_iomap_ops = {
+ .iomap_next = ntfs_read_iomap_next,
+};
+
+/*
+ * Check that the cached iomap still matches the NTFS runlist before
+ * iomap_zero_range() is called. if the runlist changes while iomap is
+ * iterating a cached iomap, iomap_zero_range() may overwrite folios
+ * that have been already written with valid data.
+ */
+static bool ntfs_iomap_valid(struct inode *inode, const struct iomap *iomap)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ struct runlist_element *rl;
+ s64 vcn, lcn;
+
+ if (!NInoNonResident(ni))
+ return false;
+
+ vcn = iomap->offset >> ni->vol->cluster_size_bits;
+
+ down_read(&ni->runlist.lock);
+ rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn);
+ if (IS_ERR(rl)) {
+ up_read(&ni->runlist.lock);
+ return false;
+ }
+ lcn = ntfs_rl_vcn_to_lcn(rl, vcn);
+ up_read(&ni->runlist.lock);
+ return lcn == LCN_DELALLOC;
+}
+
+static const struct iomap_write_ops ntfs_zero_iomap_folio_ops = {
+ .put_folio = ntfs_iomap_put_folio,
+ .iomap_valid = ntfs_iomap_valid,
+};
+
+static int ntfs_seek_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+ unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
+{
+ return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap,
+ srcmap, false);
+}
+
+static int ntfs_zero_read_iomap_end(struct inode *inode, loff_t pos, loff_t length,
+ ssize_t written, unsigned int flags, struct iomap *iomap)
+{
+ if ((flags & IOMAP_ZERO) && (iomap->flags & IOMAP_F_STALE))
+ return -EPERM;
+ return written;
+}
+
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_zero_read_iomap_next,
+ ntfs_seek_iomap_begin, ntfs_zero_read_iomap_end);
+
+static const struct iomap_ops ntfs_zero_read_iomap_ops = {
+ .iomap_next = ntfs_zero_read_iomap_next,
+};
+
+static DEFINE_IOMAP_ITER_NEXT(ntfs_seek_iomap_next, ntfs_seek_iomap_begin);
+
+const struct iomap_ops ntfs_seek_iomap_ops = {
+ .iomap_next = ntfs_seek_iomap_next,
+};
+
+int ntfs_dio_zero_range(struct inode *inode, loff_t offset, loff_t length)
+{
+ if ((offset | length) & (SECTOR_SIZE - 1))
+ return -EINVAL;
+
+ return blkdev_issue_zeroout(inode->i_sb->s_bdev,
+ offset >> SECTOR_SHIFT,
+ length >> SECTOR_SHIFT,
+ GFP_NOFS,
+ BLKDEV_ZERO_NOUNMAP);
+}
+
+static int ntfs_zero_range(struct inode *inode, loff_t offset, loff_t length)
+{
+ return iomap_zero_range(inode,
+ offset, length,
+ NULL,
+ &ntfs_zero_read_iomap_ops,
+ &ntfs_zero_iomap_folio_ops,
+ NULL);
+}
+
+static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_t offset,
+ loff_t length, struct iomap *iomap)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ struct ntfs_volume *vol = ni->vol;
+ loff_t vcn_ofs, rl_length;
+ struct runlist_element *rl, *rlc;
+ bool is_retry = false;
+ int err = 0;
+ s64 vcn, lcn;
+ s64 max_clu_count =
+ ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size));
+
+ vcn = ntfs_bytes_to_cluster(vol, offset);
+ vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
+
+ 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. */
+ rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn);
+ if (IS_ERR(rl)) {
+ up_write(&ni->runlist.lock);
+ mutex_unlock(&ni->mrec_lock);
+ return -EIO;
+ }
+ 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 = kmalloc(sizeof(struct runlist_element) * 2,
+ GFP_NOFS);
+ 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);
+ kvfree(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 = ntfs_cluster_to_bytes(vol, max_clu_count);
+ 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)) {
+ loff_t z_start, z_end;
+
+ z_start = vcn << vol->cluster_size_bits;
+ z_end = min_t(loff_t, z_start + vol->cluster_size,
+ i_size_read(inode));
+ if (z_end > z_start)
+ err = ntfs_zero_range(inode,
+ z_start,
+ z_end - z_start);
+ }
+ if ((!err || err == -EPERM) &&
+ max_clu_count > 1 &&
+ (iomap->length & vol->cluster_size_mask &&
+ end < ni->initialized_size)) {
+ loff_t z_start, z_end;
+
+ z_start = (vcn + max_clu_count - 1) <<
+ vol->cluster_size_bits;
+ z_end = min_t(loff_t, z_start + vol->cluster_size,
+ i_size_read(inode));
+ if (z_end > z_start)
+ err = ntfs_zero_range(inode,
+ z_start,
+ z_end - z_start);
+ }
+
+ if (err == -EPERM)
+ err = 0;
+ 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 = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs;
+
+ rl_length = ntfs_cluster_to_bytes(vol, max_clu_count);
+ if (length > rl_length - vcn_ofs)
+ iomap->length = rl_length - vcn_ofs;
+ else
+ iomap->length = length;
+ }
+
+ return 0;
+}
+
+#define NTFS_IOMAP_FLAGS_BEGIN BIT(1)
+#define NTFS_IOMAP_FLAGS_DIO BIT(2)
+#define NTFS_IOMAP_FLAGS_MKWRITE BIT(3)
+#define NTFS_IOMAP_FLAGS_WRITEBACK BIT(4)
+
+static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
+ loff_t offset, loff_t length, unsigned int flags,
+ struct iomap *iomap, int ntfs_iomap_flags)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ struct ntfs_volume *vol = ni->vol;
+ loff_t vcn_ofs, rl_length;
+ s64 vcn, start_lcn, lcn_count;
+ bool balloc = false, update_mp;
+ int err;
+ s64 max_clu_count =
+ ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size));
+
+ vcn = ntfs_bytes_to_cluster(vol, offset);
+ vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
+
+ update_mp = ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_DIO | NTFS_IOMAP_FLAGS_MKWRITE) ||
+ 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,
+ ntfs_iomap_flags & NTFS_IOMAP_FLAGS_WRITEBACK);
+ 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 = ntfs_cluster_to_bytes(vol, lcn_count);
+ 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 = ntfs_cluster_to_bytes(vol, start_lcn) + vcn_ofs;
+
+ if (balloc == true) {
+ if (flags & IOMAP_DIRECT ||
+ ntfs_iomap_flags & NTFS_IOMAP_FLAGS_MKWRITE) {
+ loff_t end = offset + length;
+
+ if (vcn_ofs || ((vol->cluster_size > iomap->length) &&
+ end < ni->initialized_size))
+ err = ntfs_dio_zero_range(inode,
+ start_lcn <<
+ vol->cluster_size_bits,
+ vol->cluster_size);
+ if (!err && lcn_count > 1 &&
+ (iomap->length & vol->cluster_size_mask &&
+ end < ni->initialized_size))
+ err = ntfs_dio_zero_range(inode,
+ (start_lcn + lcn_count - 1) <<
+ vol->cluster_size_bits,
+ vol->cluster_size);
+ } 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 (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_MKWRITE &&
+ iomap->offset + iomap->length > ni->initialized_size) {
+ err = ntfs_attr_set_initialized_size(ni, iomap->offset +
+ iomap->length);
+ }
+
+ return err;
+}
+
+static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
+ struct iomap *iomap)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ struct attr_record *a;
+ struct ntfs_attr_search_ctx *ctx;
+ u32 attr_len;
+ int err = 0;
+ char *kattr;
+
+ 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;
+ /* The total length of the attribute value. */
+ attr_len = le32_to_cpu(a->data.resident.value_length);
+ kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset);
+
+ iomap->type = IOMAP_INLINE;
+ iomap->inline_data = kattr;
+ iomap->offset = 0;
+ iomap->length = attr_len;
+
+out:
+ if (ctx)
+ ntfs_attr_put_search_ctx(ctx);
+
+ if (err)
+ mutex_unlock(&ni->mrec_lock);
+
+ return err;
+}
+
+static int ntfs_write_iomap_begin_non_resident(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags,
+ struct iomap *iomap, int ntfs_iomap_flags)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+
+ if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
+ offset + length > ni->initialized_size) {
+ int ret;
+
+ ret = ntfs_extend_initialized_size(inode, offset,
+ offset + length,
+ ntfs_iomap_flags &
+ NTFS_IOMAP_FLAGS_DIO);
+ if (ret < 0)
+ return ret;
+ }
+
+ mutex_lock(&ni->mrec_lock);
+ if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN)
+ return ntfs_write_simple_iomap_begin_non_resident(inode, offset,
+ length, iomap);
+ else
+ return ntfs_write_da_iomap_begin_non_resident(inode,
+ offset, length,
+ flags, iomap,
+ ntfs_iomap_flags);
+}
+
+static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags,
+ struct iomap *iomap, int ntfs_iomap_flags)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ loff_t end = offset + length;
+
+ if (NVolShutdown(ni->vol))
+ return -EIO;
+
+ if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
+ end > ni->data_size) {
+ struct ntfs_volume *vol = ni->vol;
+ int ret;
+
+ mutex_lock(&ni->mrec_lock);
+ if (end > ni->allocated_size &&
+ end < ni->allocated_size + vol->preallocated_size)
+ ret = ntfs_attr_expand(ni, end,
+ ni->allocated_size + vol->preallocated_size);
+ else
+ ret = ntfs_attr_expand(ni, end, 0);
+ mutex_unlock(&ni->mrec_lock);
+ if (ret)
+ return ret;
+ }
+
+ if (!NInoNonResident(ni)) {
+ mutex_lock(&ni->mrec_lock);
+ return ntfs_write_iomap_begin_resident(inode, offset, iomap);
+ }
+ return ntfs_write_iomap_begin_non_resident(inode, offset, length, flags,
+ iomap, ntfs_iomap_flags);
+}
+
+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,
+ NTFS_IOMAP_FLAGS_BEGIN);
+}
+
+static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
+ loff_t length, ssize_t written,
+ unsigned int flags, struct iomap *iomap)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+
+ mark_mft_record_dirty(ni);
+ mutex_unlock(&ni->mrec_lock);
+ return written;
+}
+
+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)
+ return ntfs_write_iomap_end_resident(inode, pos, length,
+ written, flags, iomap);
+ return written;
+}
+
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_write_iomap_next,
+ ntfs_write_iomap_begin, ntfs_write_iomap_end);
+
+const struct iomap_ops ntfs_write_iomap_ops = {
+ .iomap_next = ntfs_write_iomap_next,
+};
+
+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,
+ NTFS_IOMAP_FLAGS_MKWRITE);
+}
+
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_page_mkwrite_iomap_next,
+ ntfs_page_mkwrite_iomap_begin, ntfs_write_iomap_end);
+
+const struct iomap_ops ntfs_page_mkwrite_iomap_ops = {
+ .iomap_next = ntfs_page_mkwrite_iomap_next,
+};
+
+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,
+ NTFS_IOMAP_FLAGS_DIO);
+}
+
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_dio_iomap_next,
+ ntfs_dio_iomap_begin, ntfs_write_iomap_end);
+
+const struct iomap_ops ntfs_dio_iomap_ops = {
+ .iomap_next = ntfs_dio_iomap_next,
+};
+
+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,
+ NTFS_IOMAP_FLAGS_WRITEBACK);
+ 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,
+};