| // 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->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, |
| }; |