269 lines
7.5 KiB
C
269 lines
7.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* iomap callack functions
|
|
*
|
|
* Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
|
|
*/
|
|
|
|
#include <linux/iomap.h>
|
|
#include <linux/pagemap.h>
|
|
|
|
#include "exfat_raw.h"
|
|
#include "exfat_fs.h"
|
|
#include "iomap.h"
|
|
|
|
/*
|
|
* exfat_file_write_dio_end_io - Direct I/O write completion handler
|
|
*
|
|
* Updates i_size if the write extended the file. Called from the dio layer
|
|
* after I/O completion.
|
|
*/
|
|
static int exfat_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
|
|
int error, unsigned int flags)
|
|
{
|
|
struct inode *inode = file_inode(iocb->ki_filp);
|
|
|
|
if (error)
|
|
return error;
|
|
|
|
if (size && i_size_read(inode) < iocb->ki_pos + size) {
|
|
i_size_write(inode, iocb->ki_pos + size);
|
|
mark_inode_dirty(inode);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct iomap_dio_ops exfat_write_dio_ops = {
|
|
.end_io = exfat_file_write_dio_end_io,
|
|
};
|
|
|
|
static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
|
|
unsigned int flags, struct iomap *iomap, bool may_alloc)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
struct exfat_inode_info *ei = EXFAT_I(inode);
|
|
unsigned int cluster, num_clusters;
|
|
loff_t cluster_offset, cluster_length;
|
|
int err;
|
|
bool balloc = false;
|
|
|
|
if (!may_alloc) {
|
|
/* Completely beyond EOF. Treat as hole */
|
|
if (i_size_read(inode) <= offset) {
|
|
iomap->type = IOMAP_HOLE;
|
|
iomap->addr = IOMAP_NULL_ADDR;
|
|
iomap->offset = offset;
|
|
iomap->length = length;
|
|
return 0;
|
|
}
|
|
|
|
/* Clamp length if the requested range goes beyond i_size */
|
|
if (offset + length > i_size_read(inode))
|
|
length = round_up(i_size_read(inode),
|
|
i_blocksize(inode)) - offset;
|
|
}
|
|
|
|
num_clusters = exfat_bytes_to_cluster_round_up(sbi,
|
|
offset + length) - exfat_bytes_to_cluster(sbi, offset);
|
|
|
|
mutex_lock(&sbi->s_lock);
|
|
iomap->bdev = inode->i_sb->s_bdev;
|
|
iomap->offset = offset;
|
|
|
|
err = exfat_map_cluster(inode, exfat_bytes_to_cluster(sbi, offset),
|
|
&cluster, &num_clusters, may_alloc, &balloc);
|
|
if (err)
|
|
goto out;
|
|
|
|
cluster_offset = exfat_cluster_offset(sbi, offset);
|
|
cluster_length = exfat_cluster_to_bytes(sbi, num_clusters);
|
|
|
|
iomap->length = min_t(loff_t, length, cluster_length - cluster_offset);
|
|
iomap->addr = exfat_cluster_to_phys_bytes(sbi, cluster) + cluster_offset;
|
|
iomap->type = IOMAP_MAPPED;
|
|
iomap->flags = IOMAP_F_MERGED;
|
|
|
|
if (may_alloc || flags & IOMAP_ZERO) {
|
|
if (balloc)
|
|
iomap->flags |= IOMAP_F_NEW;
|
|
else if (iomap->offset + iomap->length >= ei->valid_size) {
|
|
/*
|
|
* This is a write that starts at or extends beyond
|
|
* the current valid_size. The region between the old
|
|
* valid_size and the end of this write needs to be
|
|
* zeroed in the page cache to prevent stale data
|
|
* exposure (see IOMAP_F_ZERO_TAIL handling in
|
|
* __iomap_write_begin()).
|
|
*/
|
|
iomap->flags |= IOMAP_F_ZERO_TAIL;
|
|
}
|
|
} else {
|
|
/*
|
|
* valid_size is tracked in byte granularity and
|
|
* marks the exact boundary between valid data and
|
|
* holes (or unwritten space).
|
|
*
|
|
* When IOMAP_REPORT is set (used by lseek(SEEK_HOLE)
|
|
* and SEEK_DATA), we return IOMAP_HOLE. This allows
|
|
* iomap_seek_hole_iter() to directly return the
|
|
* precise byte position.
|
|
*
|
|
* For normal I/O paths (without IOMAP_REPORT) we
|
|
* return IOMAP_UNWRITTEN so the write path can
|
|
* distinguish it from a real hole.
|
|
*/
|
|
if (offset >= ei->valid_size) {
|
|
iomap->type = flags & IOMAP_REPORT ?
|
|
IOMAP_HOLE : IOMAP_UNWRITTEN;
|
|
} else if (offset + iomap->length > ei->valid_size) {
|
|
if (flags & IOMAP_REPORT) {
|
|
/*
|
|
* For SEEK_HOLE/SEEK_DATA, clip the length
|
|
* to the exact byte boundary (valid_size).
|
|
* This ensures the caller gets the precise
|
|
* hole position in byte units.
|
|
*/
|
|
iomap->length = ei->valid_size - iomap->offset;
|
|
} else
|
|
iomap->length = round_up(ei->valid_size,
|
|
i_blocksize(inode)) -
|
|
iomap->offset;
|
|
}
|
|
}
|
|
|
|
iomap->flags |= IOMAP_F_MERGED;
|
|
out:
|
|
mutex_unlock(&sbi->s_lock);
|
|
return err;
|
|
}
|
|
|
|
static int exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
|
|
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
|
|
{
|
|
return __exfat_iomap_begin(inode, offset, length, flags, iomap, false);
|
|
}
|
|
|
|
static int exfat_write_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
|
|
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
|
|
{
|
|
return __exfat_iomap_begin(inode, offset, length, flags, iomap, true);
|
|
}
|
|
|
|
const struct iomap_ops exfat_iomap_ops = {
|
|
.iomap_begin = exfat_iomap_begin,
|
|
};
|
|
|
|
/*
|
|
* exfat_write_iomap_end - Update the state after write
|
|
*
|
|
* Extends ->valid_size to cover the newly written range.
|
|
* Marks the inode dirty if metadata was changed.
|
|
*/
|
|
static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
|
|
ssize_t written, unsigned int flags, struct iomap *iomap)
|
|
{
|
|
struct exfat_inode_info *ei = EXFAT_I(inode);
|
|
bool dirtied = false;
|
|
loff_t end;
|
|
|
|
if (!written)
|
|
return 0;
|
|
|
|
end = pos + written;
|
|
|
|
if (ei->valid_size < end) {
|
|
ei->valid_size = end;
|
|
if (ei->zeroed_size < end)
|
|
ei->zeroed_size = end;
|
|
dirtied = true;
|
|
}
|
|
|
|
if (dirtied || iomap->flags & IOMAP_F_SIZE_CHANGED)
|
|
mark_inode_dirty(inode);
|
|
|
|
return written;
|
|
}
|
|
|
|
const struct iomap_ops exfat_write_iomap_ops = {
|
|
.iomap_begin = exfat_write_iomap_begin,
|
|
.iomap_end = exfat_write_iomap_end,
|
|
};
|
|
|
|
/*
|
|
* exfat_writeback_range - Map folio during writeback
|
|
*
|
|
* Called for each folio during writeback. If the folio falls outside the
|
|
* current iomap, remaps by calling read_iomap_begin.
|
|
*/
|
|
static ssize_t exfat_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 = __exfat_iomap_begin(wpc->inode, offset, len,
|
|
0, &wpc->iomap, false);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
|
|
}
|
|
|
|
const struct iomap_writeback_ops exfat_writeback_ops = {
|
|
.writeback_range = exfat_writeback_range,
|
|
.writeback_submit = iomap_ioend_writeback_submit,
|
|
};
|
|
|
|
/**
|
|
* exfat_iomap_read_end_io - iomap read bio completion handler for exFAT
|
|
* @bio: bio that has completed reading
|
|
*
|
|
* exfat_iomap_begin() rounds up MAPPED extents to the block boundary of
|
|
* valid_size. This ensures that any subsequent blocks are treated as
|
|
* IOMAP_UNWRITTEN, but it also causes the "straddle block" containing
|
|
* valid_size to be read from disk. The disk data beyond valid_size in
|
|
* this block is stale and must be zeroed to prevent data leakage.
|
|
*/
|
|
static void exfat_iomap_read_end_io(struct bio *bio)
|
|
{
|
|
int error = blk_status_to_errno(bio->bi_status);
|
|
struct folio_iter iter;
|
|
|
|
bio_for_each_folio_all(iter, bio) {
|
|
struct folio *folio = iter.folio;
|
|
struct exfat_inode_info *ei = EXFAT_I(folio->mapping->host);
|
|
s64 valid_size;
|
|
loff_t pos = folio_pos(folio);
|
|
|
|
valid_size = ei->valid_size;
|
|
if (pos + iter.offset < valid_size &&
|
|
pos + iter.offset + iter.length > valid_size)
|
|
folio_zero_segment(folio, offset_in_folio(folio, valid_size),
|
|
iter.offset + iter.length);
|
|
|
|
iomap_finish_folio_read(folio, iter.offset, iter.length, error);
|
|
}
|
|
bio_put(bio);
|
|
}
|
|
|
|
static void exfat_iomap_bio_submit_read(const struct iomap_iter *iter,
|
|
struct iomap_read_folio_ctx *ctx)
|
|
{
|
|
iomap_bio_submit_read_endio(iter, ctx, exfat_iomap_read_end_io);
|
|
}
|
|
|
|
const struct iomap_read_ops exfat_iomap_bio_read_ops = {
|
|
.read_folio_range = iomap_bio_read_folio_range,
|
|
.submit_read = exfat_iomap_bio_submit_read,
|
|
};
|
|
|
|
int exfat_iomap_swap_activate(struct swap_info_struct *sis,
|
|
struct file *file, sector_t *span)
|
|
{
|
|
return iomap_swapfile_activate(sis, file, span, &exfat_iomap_ops);
|
|
}
|