// SPDX-License-Identifier: GPL-2.0-or-later /* * iomap callack functions * * Copyright (c) 2025 LG Electronics Co., Ltd. */ #include #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->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL); if (!iomap->inline_data) { err = -ENOMEM; goto out; } iomap->type = IOMAP_INLINE; 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 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) kfree(iomap->inline_data); return written; } const struct iomap_ops ntfs_read_iomap_ops = { .iomap_begin = ntfs_read_iomap_begin, .iomap_end = ntfs_read_iomap_end, }; /* * 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 const struct iomap_ops ntfs_zero_read_iomap_ops = { .iomap_begin = ntfs_seek_iomap_begin, .iomap_end = ntfs_zero_read_iomap_end, }; const struct iomap_ops ntfs_seek_iomap_ops = { .iomap_begin = ntfs_seek_iomap_begin, .iomap_end = ntfs_read_iomap_end, }; 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; 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->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL); if (!iomap->inline_data) { err = -ENOMEM; goto out; } iomap->type = IOMAP_INLINE; iomap->offset = 0; /* iomap requires there is only one INLINE_DATA extent */ iomap->length = attr_len; out: if (ctx) ntfs_attr_put_search_ctx(ctx); 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); 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); return written; } 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); kfree(iomap->inline_data); 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; } 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, NTFS_IOMAP_FLAGS_MKWRITE); } 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, NTFS_IOMAP_FLAGS_DIO); } 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, 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, };