blob: 621645fbbf2ed4cfcf12fce883437fe50bd7360a [file]
// 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,
};